Changeset 3433818
- Timestamp:
- 01/06/2026 05:21:52 PM (6 weeks ago)
- Location:
- contact-form-7-mailchimp-extension
- Files:
-
- 22 edited
-
tags/0.9.68/assets/js/chimpmatic-lite.js (modified) (5 diffs)
-
tags/0.9.68/assets/js/chimpmatic.js (modified) (9 diffs)
-
tags/0.9.68/lib/activate.php (modified) (1 diff)
-
tags/0.9.68/lib/class-cmatic-api-panel.php (modified) (3 diffs)
-
tags/0.9.68/lib/class-cmatic-audiences.php (modified) (2 diffs)
-
tags/0.9.68/lib/class-cmatic-plugin-links.php (modified) (2 diffs)
-
tags/0.9.68/lib/class-cmatic-pursuit.php (modified) (2 diffs)
-
tags/0.9.68/lib/enqueue.php (modified) (1 diff)
-
tags/0.9.68/lib/handler.php (modified) (12 diffs)
-
tags/0.9.68/lib/rest-api.php (modified) (13 diffs)
-
tags/0.9.68/lib/tools.php (modified) (3 diffs)
-
trunk/assets/js/chimpmatic-lite.js (modified) (5 diffs)
-
trunk/assets/js/chimpmatic.js (modified) (9 diffs)
-
trunk/lib/activate.php (modified) (1 diff)
-
trunk/lib/class-cmatic-api-panel.php (modified) (3 diffs)
-
trunk/lib/class-cmatic-audiences.php (modified) (2 diffs)
-
trunk/lib/class-cmatic-plugin-links.php (modified) (2 diffs)
-
trunk/lib/class-cmatic-pursuit.php (modified) (2 diffs)
-
trunk/lib/enqueue.php (modified) (1 diff)
-
trunk/lib/handler.php (modified) (12 diffs)
-
trunk/lib/rest-api.php (modified) (13 diffs)
-
trunk/lib/tools.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
contact-form-7-mailchimp-extension/tags/0.9.68/assets/js/chimpmatic-lite.js
r3433098 r3433818 311 311 } 312 312 313 /** 314 * Securely get the API key - fetches from REST endpoint if masked. 315 * This prevents sending masked values (with bullets) to the server. 316 * CVE-2025-68989 fix: API key no longer stored in data-real-key attribute. 317 */ 318 async function getSecureApiKey(apiKeyInput, formId) { 319 const isMasked = apiKeyInput.dataset.isMasked === '1'; 320 const hasKey = apiKeyInput.dataset.hasKey === '1'; 321 const inputValue = apiKeyInput.value.trim(); 322 323 // If not masked, the input contains the real key (user just typed/pasted it). 324 if (!isMasked) { 325 return inputValue; 326 } 327 328 // If masked but no key exists in DB, return empty. 329 if (!hasKey) { 330 return ''; 331 } 332 333 // Masked with existing key - fetch the real key from secure endpoint. 334 try { 335 const response = await fetch( 336 `${chimpmaticLite.restUrl}api-key/${formId}`, 337 { 338 method: 'GET', 339 headers: { 340 'X-WP-Nonce': chimpmaticLite.restNonce, 341 'Content-Type': 'application/json' 342 } 343 } 344 ); 345 346 if (!response.ok) { 347 console.error('ChimpMatic: Failed to fetch API key'); 348 return ''; 349 } 350 351 const data = await response.json(); 352 return data.api_key || ''; 353 } catch (err) { 354 console.error('ChimpMatic: Error fetching API key', err); 355 return ''; 356 } 357 } 358 313 359 // Fetch Mailchimp Lists Button 314 360 const fetchListsButton = document.getElementById('chm_activalist'); … … 323 369 324 370 const formId = getFormId(); 325 const isMasked = apiKeyInput.dataset.isMasked === '1'; 326 const apiKey = (isMasked && apiKeyInput.dataset.realKey) ? apiKeyInput.dataset.realKey : apiKeyInput.value.trim(); 371 const apiKey = await getSecureApiKey(apiKeyInput, formId); 327 372 328 373 if (!apiKey) { … … 1304 1349 }); 1305 1350 1306 // API Key Eye Toggle 1351 // API Key Eye Toggle (CVE-2025-68989 fix: fetch key via secure REST endpoint) 1307 1352 document.addEventListener('DOMContentLoaded', function() { 1308 1353 const eye = document.querySelector('.cmatic-eye'); … … 1310 1355 if (!eye || !input) return; 1311 1356 1312 eye.addEventListener('click', function(e) { 1357 // Cache for the fetched API key (only fetched once per page load). 1358 let cachedRealKey = null; 1359 1360 eye.addEventListener('click', async function(e) { 1313 1361 e.preventDefault(); 1314 1362 const icon = this.querySelector('.dashicons'); 1315 1363 const isMasked = input.dataset.isMasked === '1'; 1316 1317 if (isMasked) { 1318 input.value = input.dataset.realKey; 1364 const hasKey = input.dataset.hasKey === '1'; 1365 1366 if (isMasked && hasKey) { 1367 // Show real key - fetch from secure endpoint if not cached. 1368 if (!cachedRealKey) { 1369 const formId = typeof chimpmaticLite !== 'undefined' ? chimpmaticLite.formId : 0; 1370 if (!formId) { 1371 console.warn('ChimpMatic: No form ID available'); 1372 return; 1373 } 1374 1375 try { 1376 eye.style.opacity = '0.5'; 1377 const response = await fetch( 1378 `${chimpmaticLite.restUrl}api-key/${formId}`, 1379 { 1380 method: 'GET', 1381 headers: { 1382 'X-WP-Nonce': chimpmaticLite.restNonce, 1383 'Content-Type': 'application/json' 1384 } 1385 } 1386 ); 1387 1388 if (!response.ok) { 1389 throw new Error('Failed to fetch API key'); 1390 } 1391 1392 const data = await response.json(); 1393 cachedRealKey = data.api_key || ''; 1394 } catch (err) { 1395 console.error('ChimpMatic: Error fetching API key', err); 1396 eye.style.opacity = '1'; 1397 return; 1398 } 1399 eye.style.opacity = '1'; 1400 } 1401 1402 input.value = cachedRealKey; 1319 1403 input.dataset.isMasked = '0'; 1320 1404 icon.classList.remove('dashicons-visibility'); 1321 1405 icon.classList.add('dashicons-hidden'); 1322 1406 } else { 1407 // Hide key - show masked version. 1323 1408 input.value = input.dataset.maskedKey; 1324 1409 input.dataset.isMasked = '1'; … … 1330 1415 const form = input.closest('form'); 1331 1416 if (form) { 1332 form.addEventListener('submit', function() {1417 form.addEventListener('submit', async function(e) { 1333 1418 const isMasked = input.dataset.isMasked === '1'; 1334 if (isMasked && input.dataset.realKey) { 1335 input.value = input.dataset.realKey; 1419 const hasKey = input.dataset.hasKey === '1'; 1420 1421 // If masked and has existing key, restore real key for submission. 1422 if (isMasked && hasKey) { 1423 if (cachedRealKey) { 1424 input.value = cachedRealKey; 1425 } else { 1426 // Need to fetch synchronously for form submit. 1427 const formId = typeof chimpmaticLite !== 'undefined' ? chimpmaticLite.formId : 0; 1428 if (formId) { 1429 e.preventDefault(); 1430 try { 1431 const response = await fetch( 1432 `${chimpmaticLite.restUrl}api-key/${formId}`, 1433 { 1434 method: 'GET', 1435 headers: { 1436 'X-WP-Nonce': chimpmaticLite.restNonce, 1437 'Content-Type': 'application/json' 1438 } 1439 } 1440 ); 1441 1442 if (response.ok) { 1443 const data = await response.json(); 1444 cachedRealKey = data.api_key || ''; 1445 input.value = cachedRealKey; 1446 } 1447 } catch (err) { 1448 console.error('ChimpMatic: Error fetching API key for submit', err); 1449 } 1450 // Re-submit the form. 1451 form.submit(); 1452 } 1453 } 1336 1454 } 1337 1455 }); -
contact-form-7-mailchimp-extension/tags/0.9.68/assets/js/chimpmatic.js
r3433098 r3433818 97 97 } 98 98 99 function getApiKey() { 99 /** 100 * Securely get the API key - fetches from REST endpoint if masked. 101 * CVE-2025-68989 fix: API key no longer stored in data-real-key attribute. 102 * @returns {Promise<string>} The API key. 103 */ 104 async function getApiKey() { 100 105 const apiInput = document.getElementById('cmatic-api'); 101 106 if (!apiInput) return ''; 107 102 108 const isMasked = apiInput.dataset.isMasked === '1'; 103 return (isMasked && apiInput.dataset.realKey) 104 ? apiInput.dataset.realKey 105 : apiInput.value; 109 const hasKey = apiInput.dataset.hasKey === '1'; 110 const inputValue = apiInput.value.trim(); 111 112 // If not masked, the input contains the real key (user just typed/pasted it). 113 if (!isMasked) { 114 return inputValue; 115 } 116 117 // If masked but no key exists in DB, return empty. 118 if (!hasKey) { 119 return ''; 120 } 121 122 // Masked with existing key - fetch the real key from secure endpoint. 123 const formId = getFormId(); 124 if (!formId) return ''; 125 126 try { 127 // Use Lite endpoint (works for both Lite and PRO). 128 const restUrl = typeof chimpmaticLite !== 'undefined' 129 ? chimpmaticLite.restUrl 130 : getRestUrl().replace('chimpmatic/v1/', 'chimpmatic-lite/v1/'); 131 const nonce = typeof chimpmaticLite !== 'undefined' 132 ? chimpmaticLite.restNonce 133 : (typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : ''); 134 135 const response = await fetch( 136 `${restUrl}api-key/${formId}`, 137 { 138 method: 'GET', 139 headers: { 140 'X-WP-Nonce': nonce, 141 'Content-Type': 'application/json' 142 } 143 } 144 ); 145 146 if (!response.ok) { 147 console.error('ChimpMatic: Failed to fetch API key'); 148 return ''; 149 } 150 151 const data = await response.json(); 152 return data.api_key || ''; 153 } catch (err) { 154 console.error('ChimpMatic: Error fetching API key', err); 155 return ''; 156 } 106 157 } 107 158 … … 335 386 336 387 // Fetch Your Fields and Groups 337 document.addEventListener('click', function(event) {388 document.addEventListener('click', async function(event) { 338 389 if (event.target && (event.target.id === 'chm_selgetcampos' || event.target.id === 'mce_fetch_fields')) { 339 390 event.preventDefault(); … … 346 397 chm_idformxx: getFormId(), 347 398 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 348 chimpapi: getApiKey()399 chimpapi: await getApiKey() 349 400 }; 350 401 … … 409 460 410 461 // Load Groups 411 document.addEventListener('click', function(event) {462 document.addEventListener('click', async function(event) { 412 463 if (event.target && event.target.id === 'chm_activagroups') { 413 464 event.preventDefault(); … … 417 468 chm_idformxx: getFormId(), 418 469 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 419 chimpapi: getApiKey()470 chimpapi: await getApiKey() 420 471 }; 421 472 … … 444 495 445 496 // Export Users 446 document.addEventListener('click', function(event) {497 document.addEventListener('click', async function(event) { 447 498 if (event.target && event.target.id === 'chm_userexport') { 448 499 event.preventDefault(); … … 463 514 tool_key: document.getElementById('wpcf7-mailchimp-tool_key')?.value || '', 464 515 chm_idformxx: getFormId(), 465 chimpapi: getApiKey(),516 chimpapi: await getApiKey(), 466 517 cadseluser: valuesChecked 467 518 }; … … 481 532 482 533 // Get Interest (Groups - Arbitrary) 483 document.addEventListener('change', function(event) {534 document.addEventListener('change', async function(event) { 484 535 if (event.target && event.target.classList.contains('chimp-gg-arbirary')) { 485 536 event.preventDefault(); … … 494 545 chm_idformxx: getFormId(), 495 546 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 496 chimpapi: getApiKey(),547 chimpapi: await getApiKey(), 497 548 indtag: itag, 498 549 ggid: ggKeyInput ? ggKeyInput.value : '' -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/activate.php
r3433098 r3433818 201 201 if ( $file === 'contact-form-7-mailchimp-extension/chimpmatic-lite.php' ) { 202 202 203 $links[] = '<a href="' . MCE_URL. '" target="_blank" title="Chimpmatic Lite Documentation">Chimpmatic Documentation</a>';203 $links[] = '<a href="' . esc_url( Cmatic_Pursuit::docs( '', 'plugin_row_meta' ) ) . '" target="_blank" title="Chimpmatic Lite Documentation">Chimpmatic Documentation</a>'; 204 204 } 205 205 return $links; -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-api-panel.php
r3433098 r3433818 9 9 10 10 class Cmatic_Api_Panel { 11 12 /** @var string Help URL for API key instructions. */13 const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';14 11 15 12 /** Mask an API key for display. */ … … 33 30 $btn_class = 'button'; 34 31 35 $help_url = class_exists( 'Pursuit' ) 36 ? Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'api_panel_help' ) 37 : self::HELP_URL; 32 $help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'api_panel_help' ); 38 33 39 34 ?> … … 48 43 placeholder="<?php echo esc_attr__( 'Enter Your Mailchimp API key Here', 'chimpmatic-lite' ); ?>" 49 44 value="<?php echo esc_attr( $is_masked ? $masked_key : $api_key ); ?>" 50 data-real-key="<?php echo esc_attr( $api_key ); ?>"51 45 data-masked-key="<?php echo esc_attr( $masked_key ); ?>" 52 46 data-is-masked="<?php echo $is_masked ? '1' : '0'; ?>" 47 data-has-key="<?php echo ! empty( $api_key ) ? '1' : '0'; ?>" 53 48 /> 54 49 <button type="button" class="cmatic-eye" title="<?php echo esc_attr__( 'Show/Hide', 'chimpmatic-lite' ); ?>"> -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-audiences.php
r3433098 r3433818 10 10 class Cmatic_Audiences { 11 11 12 /** @var string Help URL for list ID instructions. */13 const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';14 15 12 /** Render the audiences panel. */ 16 13 public static function render( string $apivalid, ?array $listdata, array $cf7_mch ): void { … … 18 15 $count = isset( $listdata['lists'] ) && is_array( $listdata['lists'] ) ? count( $listdata['lists'] ) : 0; 19 16 20 $help_url = class_exists( 'Pursuit' ) 21 ? Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'audiences_help' ) 22 : self::HELP_URL; 17 $help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'audiences_help' ); 23 18 24 19 $disclosure_class = ( '1' === $apivalid ) ? 'chmp-active' : 'chmp-inactive'; -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-plugin-links.php
r3433098 r3433818 10 10 class Cmatic_Plugin_Links { 11 11 12 /** @var string Panel key for CF7 tab. */13 12 const PANEL_KEY = 'Chimpmatic'; 14 15 /** @var string Documentation URL. */16 const DOCS_URL = 'https://chimpmatic.com/help';17 13 18 14 /** Get the settings URL for the plugin. */ … … 52 48 } 53 49 54 /** Get the documentation link HTML. */55 50 public static function get_docs_link() { 56 51 return sprintf( 57 52 '<a href="%s" target="_blank" title="%s">%s</a>', 58 esc_url( self::DOCS_URL),53 esc_url( Cmatic_Pursuit::docs( '', 'plugins_page' ) ), 59 54 esc_attr__( 'Chimpmatic Documentation', 'chimpmatic-lite' ), 60 55 esc_html__( 'Docs', 'chimpmatic-lite' ) -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-pursuit.php
r3433098 r3433818 19 19 'promo' => 'https://chimpmatic.com/almost-there', 20 20 'home' => 'https://chimpmatic.com', 21 'author' => 'https://renzojohnson.com', 21 22 ); 22 23 … … 70 71 } 71 72 73 public static function author( string $content = '' ): string { 74 return self::url( self::BASE_URLS['author'], 'plugin', $content, 'author' ); 75 } 76 72 77 public static function adminbar( string $destination, string $content = '' ): string { 73 78 $base = self::BASE_URLS[ $destination ] ?? self::BASE_URLS['home']; -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/enqueue.php
r3433098 r3433818 138 138 'nonce' => wp_create_nonce( 'wp_rest' ), 139 139 'pluginUrl' => SPARTAN_MCE_PLUGIN_URL, 140 'formId' => $form_id, 140 141 'mergeFields' => $merge_fields, 141 142 'loggingEnabled' => $logging_enabled, -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/handler.php
r3433098 r3433818 1 1 <?php 2 /**3 * Form submission handler.4 *5 * @package ChimpMatic_Lite6 */7 2 8 3 defined( 'ABSPATH' ) || exit; … … 21 16 function cmatic_add_editor_panel( $panels ) { 22 17 if ( defined( 'CMATIC_VERSION' ) ) { 18 return $panels; 19 } 20 21 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; 22 if ( ! current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { 23 23 return $panels; 24 24 } … … 60 60 } 61 61 62 // CF7 verified nonce earlier; re-verification fails for new forms (nonce used post_ID=-1, form now has real ID).63 64 // Send form_saved signal (blocking - must complete before save continues).65 62 if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) { 66 63 $payload = \Cmatic\Metrics\Core\Collector::collect( 'form_saved' ); … … 87 84 if ( isset( $posted_data[ $field ] ) ) { 88 85 $sanitized_value = trim( sanitize_text_field( $posted_data[ $field ] ) ); 89 // Only save non-empty values (skip empty/whitespace "Choose.." placeholder).90 86 if ( '' !== $sanitized_value ) { 91 87 $sanitized_settings[ $field ] = $sanitized_value; … … 143 139 } 144 140 145 // Check global debug setting (from cmatic option structure)146 141 $logfile_enabled = (bool) mce_get_cmatic( 'debug', false ); 147 142 $logger = new Cmatic_File_Logger( 'api-events', $logfile_enabled ); … … 250 245 $response_data = cmatic_call_api_put( $api_key, $url, $info ); 251 246 252 // Check for API errors in response.253 247 $api_response = isset( $response_data[0] ) ? $response_data[0] : array(); 254 248 255 // Log only the Mailchimp API response body, not the full HTTP response object.256 249 $logger->log( 'INFO', 'Mailchimp API Response.', $api_response ); 257 250 258 // Check for WP_Error from cmatic_call_api_put (network failure).259 251 if ( false === $response_data[0] ) { 260 252 $logger->log( 'ERROR', 'Network request failed.', array( 'response' => $response_data[1] ) ); 261 253 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', '', $email ) ); 262 254 } elseif ( empty( $api_response ) ) { 263 // Empty response - likely invalid API key or server error.264 255 $logger->log( 'ERROR', 'Empty API response received.' ); 265 256 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'api_error', 'Empty response from Mailchimp API.', $email ) ); … … 269 260 $php_logger->log( 'ERROR', 'Mailchimp API Error received.', $error ); 270 261 } 271 // Set feedback for API error.272 262 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 273 263 } elseif ( isset( $api_response['status'] ) && is_int( $api_response['status'] ) && $api_response['status'] >= 400 ) { 274 // HTTP error status in response body (e.g., 401 Unauthorized, 404 Not Found).275 264 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 276 265 } elseif ( isset( $api_response['title'] ) && stripos( $api_response['title'], 'error' ) !== false ) { 277 // Response contains error title (some Mailchimp errors have title but no status code).278 266 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 279 267 } else { 280 268 mce_save_contador(); 281 269 282 // Set success feedback with merge fields sent.283 270 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::success( $email, $status, $merge_vars, $api_response ) ); 284 271 285 /**286 * Fires after successful Mailchimp subscription.287 *288 * @param int $form_id The CF7 form ID.289 * @param string $email The subscriber email address.290 */291 272 do_action( 'cmatic_subscription_success', $form_id, $email ); 292 273 } … … 303 284 return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted; 304 285 } 305 return $matches[0]; // Return the tag itself if no value found286 return $matches[0]; 306 287 } 307 288 return $subject; … … 322 303 <label for="wpcf7-mailchimp-list"> 323 304 <?php 324 /* translators: %d: number of Mailchimp audiences */325 305 printf( esc_html__( 'Total Mailchimp Audiences: %d', 'chimpmatic-lite' ), esc_html( $count ) ); 326 306 ?> … … 356 336 $saved_value = isset( $cf7_mch[ $field_name ] ) ? trim( sanitize_text_field( $cf7_mch[ $field_name ] ) ) : ''; 357 337 358 // Auto-select matching form field based on merge tag (fuzzy match).359 338 if ( '' === $saved_value && ! empty( $merge_tag ) ) { 360 339 $merge_tag_lower = strtolower( $merge_tag ); 361 340 foreach ( $form_tags as $tag ) { 362 // Match by basetype for email, or by name containing the merge tag.363 341 if ( 'email' === $filter && ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ) ) { 364 342 $saved_value = '[' . $tag['name'] . ']'; … … 384 362 continue; 385 363 } 386 // Filter by field type when $filter is specified (fuzzy match for email).387 364 if ( 'email' === $filter ) { 388 365 $is_email_field = ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ); … … 637 614 } 638 615 639 // Use Cmatic_Pursuit::promo() which handles WP Engine's utm_* stripping via u_* prefix.640 616 $pursuit_addy = add_query_arg( 641 617 array( -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/rest-api.php
r3433098 r3433818 1 1 <?php 2 /**3 * REST API endpoints.4 *5 * @package ChimpMatic_Lite6 */7 8 2 defined( 'ABSPATH' ) || exit; 9 3 … … 16 10 'methods' => 'POST', 17 11 'callback' => 'cmatic_rest_get_lists', 18 'permission_callback' => 'cmatic_rest_ permission_check',12 'permission_callback' => 'cmatic_rest_api_key_permission_check', 19 13 'args' => array( 20 14 'form_id' => array( … … 139 133 'methods' => 'POST', 140 134 'callback' => 'cmatic_rest_get_merge_fields', 141 'permission_callback' => 'cmatic_rest_ permission_check',135 'permission_callback' => 'cmatic_rest_api_key_permission_check', 142 136 'args' => array( 143 137 'form_id' => array( … … 188 182 'methods' => 'POST', 189 183 'callback' => 'cmatic_rest_save_form_field', 190 'permission_callback' => 'cmatic_rest_ permission_check',184 'permission_callback' => 'cmatic_rest_api_key_permission_check', 191 185 'args' => array( 192 186 'form_id' => array( … … 207 201 ) 208 202 ); 203 204 register_rest_route( 205 'chimpmatic-lite/v1', 206 '/api-key/(?P<form_id>\d+)', 207 array( 208 'methods' => 'GET', 209 'callback' => 'cmatic_rest_get_api_key', 210 'permission_callback' => 'cmatic_rest_api_key_permission_check', 211 'args' => array( 212 'form_id' => array( 213 'required' => true, 214 'type' => 'integer', 215 'sanitize_callback' => 'absint', 216 'validate_callback' => function ( $param ) { 217 return is_numeric( $param ) && $param > 0; 218 }, 219 ), 220 ), 221 ) 222 ); 209 223 } 210 224 add_action( 'rest_api_init', 'cmatic_register_rest_routes' ); … … 231 245 } 232 246 247 function cmatic_rest_api_key_permission_check( $request ) { 248 $form_id = $request->get_param( 'form_id' ); 249 250 if ( ! current_user_can( 'wpcf7_edit_contact_form', $form_id ) ) { 251 return new WP_Error( 252 'rest_forbidden', 253 esc_html__( 'You do not have permission to access the API key.', 'chimpmatic-lite' ), 254 array( 'status' => 403 ) 255 ); 256 } 257 258 $nonce = $request->get_header( 'X-WP-Nonce' ); 259 if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { 260 return new WP_Error( 261 'rest_cookie_invalid_nonce', 262 esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ), 263 array( 'status' => 403 ) 264 ); 265 } 266 267 return true; 268 } 269 233 270 234 271 function cmatic_rest_get_lists( $request ) { … … 236 273 $api_key = $request->get_param( 'api_key' ); 237 274 238 // Track API setup funnel: Sync attempted239 275 if ( ! mce_get_cmatic( 'api.sync_attempted' ) ) { 240 // First time user clicks "Sync Audiences"241 276 mce_update_cmatic( 'api.sync_attempted', time() ); 242 277 } 243 // Increment sync attempts counter244 278 $current_count = (int) mce_get_cmatic( 'api.sync_attempts_count', 0 ); 245 279 mce_update_cmatic( 'api.sync_attempts_count', $current_count + 1 ); … … 248 282 $cf7_mch = get_option( $option_name, array() ); 249 283 250 // Defensive: ensure $cf7_mch is array (corrupt options can return empty string).251 284 if ( ! is_array( $cf7_mch ) ) { 252 285 $cf7_mch = array(); 253 286 } 254 287 255 // Use global debug logger setting (not per-form)256 288 $logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false ); 257 289 … … 331 363 array( 332 364 'api' => $api_key, 333 'merge_fields' => $merge_fields, // Save formatted merge fields365 'merge_fields' => $merge_fields, 334 366 ) 335 367 ); 336 368 update_option( $option_name, $settings_to_save ); 337 369 338 // Save lisdata globally for Signals (single source of truth, always freshest).339 370 if ( ! empty( $lists_result['lisdata'] ) ) { 340 371 mce_update_cmatic( 'lisdata', $lists_result['lisdata'] ); … … 436 467 'enabled' => $enabled, 437 468 'message' => $enabled 438 /* translators: %s: setting name */439 469 ? sprintf( __( '%s enabled.', 'chimpmatic-lite' ), $label ) 440 /* translators: %s: setting name */441 470 : sprintf( __( '%s disabled.', 'chimpmatic-lite' ), $label ), 442 471 ) … … 478 507 'enabled' => $enabled, 479 508 'message' => $enabled 480 /* translators: %s: tag name */481 509 ? sprintf( __( 'Tag [%s] enabled.', 'chimpmatic-lite' ), $tag ) 482 /* translators: %s: tag name */483 510 : sprintf( __( 'Tag [%s] disabled.', 'chimpmatic-lite' ), $tag ), 484 511 ) … … 548 575 return new WP_Error( 549 576 'invalid_field', 550 /* translators: %s: field name */551 577 sprintf( __( 'Field "%s" is not allowed.', 'chimpmatic-lite' ), $field ), 552 578 array( 'status' => 400 ) … … 984 1010 ); 985 1011 } 1012 1013 function cmatic_rest_get_api_key( $request ) { 1014 $form_id = $request->get_param( 'form_id' ); 1015 $option_name = 'cf7_mch_' . $form_id; 1016 $cf7_mch = get_option( $option_name, array() ); 1017 1018 if ( ! is_array( $cf7_mch ) ) { 1019 $cf7_mch = array(); 1020 } 1021 1022 $api_key = isset( $cf7_mch['api'] ) ? $cf7_mch['api'] : ''; 1023 1024 return rest_ensure_response( 1025 array( 1026 'success' => true, 1027 'api_key' => $api_key, 1028 ) 1029 ); 1030 } -
contact-form-7-mailchimp-extension/tags/0.9.68/lib/tools.php
r3433098 r3433818 20 20 function mce_author() { 21 21 $author_pre = 'Contact form 7 Mailchimp extension by '; 22 $author_name = 'Renzo Johnson';23 $author_url = '//renzojohnson.com';24 22 $author_title = 'Renzo Johnson - Web Developer'; 23 $author_url = Cmatic_Pursuit::author( 'backlink' ); 25 24 26 25 $mce_author = '<p style="display: none !important">'; 27 26 $mce_author .= $author_pre; 28 $mce_author .= '<a href="' . $author_url. '" ';29 $mce_author .= 'title="' . $author_title. '" ';27 $mce_author .= '<a href="' . esc_url( $author_url ) . '" '; 28 $mce_author .= 'title="' . esc_attr( $author_title ) . '" '; 30 29 $mce_author .= 'target="_blank">'; 31 $mce_author .= '' . $author_title . '';30 $mce_author .= esc_html( $author_title ); 32 31 $mce_author .= '</a>'; 33 32 $mce_author .= '</p>' . "\n"; … … 65 64 66 65 function mce_init_constants() { 67 define( 'MCE_URL', '//chimpmatic.com/help' );68 define( 'MC_URL', '//chimpmatic.com/help' );69 define( 'MCE_AUTH', '//renzojohnson.com' );70 66 define( 'MCE_AUTH_COMM', '<!-- Chimpmatic -->' ); 71 67 define( 'MCE_NAME', 'MailChimp Contact Form 7 Extension' ); 72 68 define( 'MCE_SETT', admin_url( 'admin.php?page=wpcf7' ) ); 73 69 define( 'MCE_DON', 'https://www.paypal.me/renzojohnson' ); 74 define( 'CHIMPL_URL', '//chimpmatic.com' );75 define( 'CHIMPHELP_URL', '//chimpmatic.com/help' );76 70 } 77 71 add_action( 'init', 'mce_init_constants' ); … … 172 166 function mce_set_welcomebanner() { 173 167 $default_panel = '<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> 174 <p class="about-description">Would you like to <a class="button-primary" href="http ://bit.ly/cafe4renzo" target="_blank">buy me a coffee?</a> or <a class="button-primary" href="http://bit.ly/cafe4renzo" target="_blank">Donate with Paypal</a></p>';168 <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>'; 175 169 176 170 $banner = get_site_option( 'mce_conten_panel_welcome', $default_panel ); -
contact-form-7-mailchimp-extension/trunk/assets/js/chimpmatic-lite.js
r3433098 r3433818 311 311 } 312 312 313 /** 314 * Securely get the API key - fetches from REST endpoint if masked. 315 * This prevents sending masked values (with bullets) to the server. 316 * CVE-2025-68989 fix: API key no longer stored in data-real-key attribute. 317 */ 318 async function getSecureApiKey(apiKeyInput, formId) { 319 const isMasked = apiKeyInput.dataset.isMasked === '1'; 320 const hasKey = apiKeyInput.dataset.hasKey === '1'; 321 const inputValue = apiKeyInput.value.trim(); 322 323 // If not masked, the input contains the real key (user just typed/pasted it). 324 if (!isMasked) { 325 return inputValue; 326 } 327 328 // If masked but no key exists in DB, return empty. 329 if (!hasKey) { 330 return ''; 331 } 332 333 // Masked with existing key - fetch the real key from secure endpoint. 334 try { 335 const response = await fetch( 336 `${chimpmaticLite.restUrl}api-key/${formId}`, 337 { 338 method: 'GET', 339 headers: { 340 'X-WP-Nonce': chimpmaticLite.restNonce, 341 'Content-Type': 'application/json' 342 } 343 } 344 ); 345 346 if (!response.ok) { 347 console.error('ChimpMatic: Failed to fetch API key'); 348 return ''; 349 } 350 351 const data = await response.json(); 352 return data.api_key || ''; 353 } catch (err) { 354 console.error('ChimpMatic: Error fetching API key', err); 355 return ''; 356 } 357 } 358 313 359 // Fetch Mailchimp Lists Button 314 360 const fetchListsButton = document.getElementById('chm_activalist'); … … 323 369 324 370 const formId = getFormId(); 325 const isMasked = apiKeyInput.dataset.isMasked === '1'; 326 const apiKey = (isMasked && apiKeyInput.dataset.realKey) ? apiKeyInput.dataset.realKey : apiKeyInput.value.trim(); 371 const apiKey = await getSecureApiKey(apiKeyInput, formId); 327 372 328 373 if (!apiKey) { … … 1304 1349 }); 1305 1350 1306 // API Key Eye Toggle 1351 // API Key Eye Toggle (CVE-2025-68989 fix: fetch key via secure REST endpoint) 1307 1352 document.addEventListener('DOMContentLoaded', function() { 1308 1353 const eye = document.querySelector('.cmatic-eye'); … … 1310 1355 if (!eye || !input) return; 1311 1356 1312 eye.addEventListener('click', function(e) { 1357 // Cache for the fetched API key (only fetched once per page load). 1358 let cachedRealKey = null; 1359 1360 eye.addEventListener('click', async function(e) { 1313 1361 e.preventDefault(); 1314 1362 const icon = this.querySelector('.dashicons'); 1315 1363 const isMasked = input.dataset.isMasked === '1'; 1316 1317 if (isMasked) { 1318 input.value = input.dataset.realKey; 1364 const hasKey = input.dataset.hasKey === '1'; 1365 1366 if (isMasked && hasKey) { 1367 // Show real key - fetch from secure endpoint if not cached. 1368 if (!cachedRealKey) { 1369 const formId = typeof chimpmaticLite !== 'undefined' ? chimpmaticLite.formId : 0; 1370 if (!formId) { 1371 console.warn('ChimpMatic: No form ID available'); 1372 return; 1373 } 1374 1375 try { 1376 eye.style.opacity = '0.5'; 1377 const response = await fetch( 1378 `${chimpmaticLite.restUrl}api-key/${formId}`, 1379 { 1380 method: 'GET', 1381 headers: { 1382 'X-WP-Nonce': chimpmaticLite.restNonce, 1383 'Content-Type': 'application/json' 1384 } 1385 } 1386 ); 1387 1388 if (!response.ok) { 1389 throw new Error('Failed to fetch API key'); 1390 } 1391 1392 const data = await response.json(); 1393 cachedRealKey = data.api_key || ''; 1394 } catch (err) { 1395 console.error('ChimpMatic: Error fetching API key', err); 1396 eye.style.opacity = '1'; 1397 return; 1398 } 1399 eye.style.opacity = '1'; 1400 } 1401 1402 input.value = cachedRealKey; 1319 1403 input.dataset.isMasked = '0'; 1320 1404 icon.classList.remove('dashicons-visibility'); 1321 1405 icon.classList.add('dashicons-hidden'); 1322 1406 } else { 1407 // Hide key - show masked version. 1323 1408 input.value = input.dataset.maskedKey; 1324 1409 input.dataset.isMasked = '1'; … … 1330 1415 const form = input.closest('form'); 1331 1416 if (form) { 1332 form.addEventListener('submit', function() {1417 form.addEventListener('submit', async function(e) { 1333 1418 const isMasked = input.dataset.isMasked === '1'; 1334 if (isMasked && input.dataset.realKey) { 1335 input.value = input.dataset.realKey; 1419 const hasKey = input.dataset.hasKey === '1'; 1420 1421 // If masked and has existing key, restore real key for submission. 1422 if (isMasked && hasKey) { 1423 if (cachedRealKey) { 1424 input.value = cachedRealKey; 1425 } else { 1426 // Need to fetch synchronously for form submit. 1427 const formId = typeof chimpmaticLite !== 'undefined' ? chimpmaticLite.formId : 0; 1428 if (formId) { 1429 e.preventDefault(); 1430 try { 1431 const response = await fetch( 1432 `${chimpmaticLite.restUrl}api-key/${formId}`, 1433 { 1434 method: 'GET', 1435 headers: { 1436 'X-WP-Nonce': chimpmaticLite.restNonce, 1437 'Content-Type': 'application/json' 1438 } 1439 } 1440 ); 1441 1442 if (response.ok) { 1443 const data = await response.json(); 1444 cachedRealKey = data.api_key || ''; 1445 input.value = cachedRealKey; 1446 } 1447 } catch (err) { 1448 console.error('ChimpMatic: Error fetching API key for submit', err); 1449 } 1450 // Re-submit the form. 1451 form.submit(); 1452 } 1453 } 1336 1454 } 1337 1455 }); -
contact-form-7-mailchimp-extension/trunk/assets/js/chimpmatic.js
r3431951 r3433818 97 97 } 98 98 99 function getApiKey() { 99 /** 100 * Securely get the API key - fetches from REST endpoint if masked. 101 * CVE-2025-68989 fix: API key no longer stored in data-real-key attribute. 102 * @returns {Promise<string>} The API key. 103 */ 104 async function getApiKey() { 100 105 const apiInput = document.getElementById('cmatic-api'); 101 106 if (!apiInput) return ''; 107 102 108 const isMasked = apiInput.dataset.isMasked === '1'; 103 return (isMasked && apiInput.dataset.realKey) 104 ? apiInput.dataset.realKey 105 : apiInput.value; 109 const hasKey = apiInput.dataset.hasKey === '1'; 110 const inputValue = apiInput.value.trim(); 111 112 // If not masked, the input contains the real key (user just typed/pasted it). 113 if (!isMasked) { 114 return inputValue; 115 } 116 117 // If masked but no key exists in DB, return empty. 118 if (!hasKey) { 119 return ''; 120 } 121 122 // Masked with existing key - fetch the real key from secure endpoint. 123 const formId = getFormId(); 124 if (!formId) return ''; 125 126 try { 127 // Use Lite endpoint (works for both Lite and PRO). 128 const restUrl = typeof chimpmaticLite !== 'undefined' 129 ? chimpmaticLite.restUrl 130 : getRestUrl().replace('chimpmatic/v1/', 'chimpmatic-lite/v1/'); 131 const nonce = typeof chimpmaticLite !== 'undefined' 132 ? chimpmaticLite.restNonce 133 : (typeof wpApiSettings !== 'undefined' ? wpApiSettings.nonce : ''); 134 135 const response = await fetch( 136 `${restUrl}api-key/${formId}`, 137 { 138 method: 'GET', 139 headers: { 140 'X-WP-Nonce': nonce, 141 'Content-Type': 'application/json' 142 } 143 } 144 ); 145 146 if (!response.ok) { 147 console.error('ChimpMatic: Failed to fetch API key'); 148 return ''; 149 } 150 151 const data = await response.json(); 152 return data.api_key || ''; 153 } catch (err) { 154 console.error('ChimpMatic: Error fetching API key', err); 155 return ''; 156 } 106 157 } 107 158 … … 335 386 336 387 // Fetch Your Fields and Groups 337 document.addEventListener('click', function(event) {388 document.addEventListener('click', async function(event) { 338 389 if (event.target && (event.target.id === 'chm_selgetcampos' || event.target.id === 'mce_fetch_fields')) { 339 390 event.preventDefault(); … … 346 397 chm_idformxx: getFormId(), 347 398 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 348 chimpapi: getApiKey()399 chimpapi: await getApiKey() 349 400 }; 350 401 … … 409 460 410 461 // Load Groups 411 document.addEventListener('click', function(event) {462 document.addEventListener('click', async function(event) { 412 463 if (event.target && event.target.id === 'chm_activagroups') { 413 464 event.preventDefault(); … … 417 468 chm_idformxx: getFormId(), 418 469 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 419 chimpapi: getApiKey()470 chimpapi: await getApiKey() 420 471 }; 421 472 … … 444 495 445 496 // Export Users 446 document.addEventListener('click', function(event) {497 document.addEventListener('click', async function(event) { 447 498 if (event.target && event.target.id === 'chm_userexport') { 448 499 event.preventDefault(); … … 463 514 tool_key: document.getElementById('wpcf7-mailchimp-tool_key')?.value || '', 464 515 chm_idformxx: getFormId(), 465 chimpapi: getApiKey(),516 chimpapi: await getApiKey(), 466 517 cadseluser: valuesChecked 467 518 }; … … 481 532 482 533 // Get Interest (Groups - Arbitrary) 483 document.addEventListener('change', function(event) {534 document.addEventListener('change', async function(event) { 484 535 if (event.target && event.target.classList.contains('chimp-gg-arbirary')) { 485 536 event.preventDefault(); … … 494 545 chm_idformxx: getFormId(), 495 546 chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '', 496 chimpapi: getApiKey(),547 chimpapi: await getApiKey(), 497 548 indtag: itag, 498 549 ggid: ggKeyInput ? ggKeyInput.value : '' -
contact-form-7-mailchimp-extension/trunk/lib/activate.php
r3433098 r3433818 201 201 if ( $file === 'contact-form-7-mailchimp-extension/chimpmatic-lite.php' ) { 202 202 203 $links[] = '<a href="' . MCE_URL. '" target="_blank" title="Chimpmatic Lite Documentation">Chimpmatic Documentation</a>';203 $links[] = '<a href="' . esc_url( Cmatic_Pursuit::docs( '', 'plugin_row_meta' ) ) . '" target="_blank" title="Chimpmatic Lite Documentation">Chimpmatic Documentation</a>'; 204 204 } 205 205 return $links; -
contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-api-panel.php
r3433098 r3433818 9 9 10 10 class Cmatic_Api_Panel { 11 12 /** @var string Help URL for API key instructions. */13 const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';14 11 15 12 /** Mask an API key for display. */ … … 33 30 $btn_class = 'button'; 34 31 35 $help_url = class_exists( 'Pursuit' ) 36 ? Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'api_panel_help' ) 37 : self::HELP_URL; 32 $help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'api_panel_help' ); 38 33 39 34 ?> … … 48 43 placeholder="<?php echo esc_attr__( 'Enter Your Mailchimp API key Here', 'chimpmatic-lite' ); ?>" 49 44 value="<?php echo esc_attr( $is_masked ? $masked_key : $api_key ); ?>" 50 data-real-key="<?php echo esc_attr( $api_key ); ?>"51 45 data-masked-key="<?php echo esc_attr( $masked_key ); ?>" 52 46 data-is-masked="<?php echo $is_masked ? '1' : '0'; ?>" 47 data-has-key="<?php echo ! empty( $api_key ) ? '1' : '0'; ?>" 53 48 /> 54 49 <button type="button" class="cmatic-eye" title="<?php echo esc_attr__( 'Show/Hide', 'chimpmatic-lite' ); ?>"> -
contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-audiences.php
r3433098 r3433818 10 10 class Cmatic_Audiences { 11 11 12 /** @var string Help URL for list ID instructions. */13 const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';14 15 12 /** Render the audiences panel. */ 16 13 public static function render( string $apivalid, ?array $listdata, array $cf7_mch ): void { … … 18 15 $count = isset( $listdata['lists'] ) && is_array( $listdata['lists'] ) ? count( $listdata['lists'] ) : 0; 19 16 20 $help_url = class_exists( 'Pursuit' ) 21 ? Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'audiences_help' ) 22 : self::HELP_URL; 17 $help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'audiences_help' ); 23 18 24 19 $disclosure_class = ( '1' === $apivalid ) ? 'chmp-active' : 'chmp-inactive'; -
contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-plugin-links.php
r3433098 r3433818 10 10 class Cmatic_Plugin_Links { 11 11 12 /** @var string Panel key for CF7 tab. */13 12 const PANEL_KEY = 'Chimpmatic'; 14 15 /** @var string Documentation URL. */16 const DOCS_URL = 'https://chimpmatic.com/help';17 13 18 14 /** Get the settings URL for the plugin. */ … … 52 48 } 53 49 54 /** Get the documentation link HTML. */55 50 public static function get_docs_link() { 56 51 return sprintf( 57 52 '<a href="%s" target="_blank" title="%s">%s</a>', 58 esc_url( self::DOCS_URL),53 esc_url( Cmatic_Pursuit::docs( '', 'plugins_page' ) ), 59 54 esc_attr__( 'Chimpmatic Documentation', 'chimpmatic-lite' ), 60 55 esc_html__( 'Docs', 'chimpmatic-lite' ) -
contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-pursuit.php
r3433106 r3433818 19 19 'promo' => 'https://chimpmatic.com/almost-there', 20 20 'home' => 'https://chimpmatic.com', 21 'author' => 'https://renzojohnson.com', 21 22 ); 22 23 … … 70 71 } 71 72 73 public static function author( string $content = '' ): string { 74 return self::url( self::BASE_URLS['author'], 'plugin', $content, 'author' ); 75 } 76 72 77 public static function adminbar( string $destination, string $content = '' ): string { 73 78 $base = self::BASE_URLS[ $destination ] ?? self::BASE_URLS['home']; -
contact-form-7-mailchimp-extension/trunk/lib/enqueue.php
r3433098 r3433818 138 138 'nonce' => wp_create_nonce( 'wp_rest' ), 139 139 'pluginUrl' => SPARTAN_MCE_PLUGIN_URL, 140 'formId' => $form_id, 140 141 'mergeFields' => $merge_fields, 141 142 'loggingEnabled' => $logging_enabled, -
contact-form-7-mailchimp-extension/trunk/lib/handler.php
r3433098 r3433818 1 1 <?php 2 /**3 * Form submission handler.4 *5 * @package ChimpMatic_Lite6 */7 2 8 3 defined( 'ABSPATH' ) || exit; … … 21 16 function cmatic_add_editor_panel( $panels ) { 22 17 if ( defined( 'CMATIC_VERSION' ) ) { 18 return $panels; 19 } 20 21 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; 22 if ( ! current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { 23 23 return $panels; 24 24 } … … 60 60 } 61 61 62 // CF7 verified nonce earlier; re-verification fails for new forms (nonce used post_ID=-1, form now has real ID).63 64 // Send form_saved signal (blocking - must complete before save continues).65 62 if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) { 66 63 $payload = \Cmatic\Metrics\Core\Collector::collect( 'form_saved' ); … … 87 84 if ( isset( $posted_data[ $field ] ) ) { 88 85 $sanitized_value = trim( sanitize_text_field( $posted_data[ $field ] ) ); 89 // Only save non-empty values (skip empty/whitespace "Choose.." placeholder).90 86 if ( '' !== $sanitized_value ) { 91 87 $sanitized_settings[ $field ] = $sanitized_value; … … 143 139 } 144 140 145 // Check global debug setting (from cmatic option structure)146 141 $logfile_enabled = (bool) mce_get_cmatic( 'debug', false ); 147 142 $logger = new Cmatic_File_Logger( 'api-events', $logfile_enabled ); … … 250 245 $response_data = cmatic_call_api_put( $api_key, $url, $info ); 251 246 252 // Check for API errors in response.253 247 $api_response = isset( $response_data[0] ) ? $response_data[0] : array(); 254 248 255 // Log only the Mailchimp API response body, not the full HTTP response object.256 249 $logger->log( 'INFO', 'Mailchimp API Response.', $api_response ); 257 250 258 // Check for WP_Error from cmatic_call_api_put (network failure).259 251 if ( false === $response_data[0] ) { 260 252 $logger->log( 'ERROR', 'Network request failed.', array( 'response' => $response_data[1] ) ); 261 253 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', '', $email ) ); 262 254 } elseif ( empty( $api_response ) ) { 263 // Empty response - likely invalid API key or server error.264 255 $logger->log( 'ERROR', 'Empty API response received.' ); 265 256 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'api_error', 'Empty response from Mailchimp API.', $email ) ); … … 269 260 $php_logger->log( 'ERROR', 'Mailchimp API Error received.', $error ); 270 261 } 271 // Set feedback for API error.272 262 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 273 263 } elseif ( isset( $api_response['status'] ) && is_int( $api_response['status'] ) && $api_response['status'] >= 400 ) { 274 // HTTP error status in response body (e.g., 401 Unauthorized, 404 Not Found).275 264 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 276 265 } elseif ( isset( $api_response['title'] ) && stripos( $api_response['title'], 'error' ) !== false ) { 277 // Response contains error title (some Mailchimp errors have title but no status code).278 266 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) ); 279 267 } else { 280 268 mce_save_contador(); 281 269 282 // Set success feedback with merge fields sent.283 270 Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::success( $email, $status, $merge_vars, $api_response ) ); 284 271 285 /**286 * Fires after successful Mailchimp subscription.287 *288 * @param int $form_id The CF7 form ID.289 * @param string $email The subscriber email address.290 */291 272 do_action( 'cmatic_subscription_success', $form_id, $email ); 292 273 } … … 303 284 return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted; 304 285 } 305 return $matches[0]; // Return the tag itself if no value found286 return $matches[0]; 306 287 } 307 288 return $subject; … … 322 303 <label for="wpcf7-mailchimp-list"> 323 304 <?php 324 /* translators: %d: number of Mailchimp audiences */325 305 printf( esc_html__( 'Total Mailchimp Audiences: %d', 'chimpmatic-lite' ), esc_html( $count ) ); 326 306 ?> … … 356 336 $saved_value = isset( $cf7_mch[ $field_name ] ) ? trim( sanitize_text_field( $cf7_mch[ $field_name ] ) ) : ''; 357 337 358 // Auto-select matching form field based on merge tag (fuzzy match).359 338 if ( '' === $saved_value && ! empty( $merge_tag ) ) { 360 339 $merge_tag_lower = strtolower( $merge_tag ); 361 340 foreach ( $form_tags as $tag ) { 362 // Match by basetype for email, or by name containing the merge tag.363 341 if ( 'email' === $filter && ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ) ) { 364 342 $saved_value = '[' . $tag['name'] . ']'; … … 384 362 continue; 385 363 } 386 // Filter by field type when $filter is specified (fuzzy match for email).387 364 if ( 'email' === $filter ) { 388 365 $is_email_field = ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ); … … 637 614 } 638 615 639 // Use Cmatic_Pursuit::promo() which handles WP Engine's utm_* stripping via u_* prefix.640 616 $pursuit_addy = add_query_arg( 641 617 array( -
contact-form-7-mailchimp-extension/trunk/lib/rest-api.php
r3433098 r3433818 1 1 <?php 2 /**3 * REST API endpoints.4 *5 * @package ChimpMatic_Lite6 */7 8 2 defined( 'ABSPATH' ) || exit; 9 3 … … 16 10 'methods' => 'POST', 17 11 'callback' => 'cmatic_rest_get_lists', 18 'permission_callback' => 'cmatic_rest_ permission_check',12 'permission_callback' => 'cmatic_rest_api_key_permission_check', 19 13 'args' => array( 20 14 'form_id' => array( … … 139 133 'methods' => 'POST', 140 134 'callback' => 'cmatic_rest_get_merge_fields', 141 'permission_callback' => 'cmatic_rest_ permission_check',135 'permission_callback' => 'cmatic_rest_api_key_permission_check', 142 136 'args' => array( 143 137 'form_id' => array( … … 188 182 'methods' => 'POST', 189 183 'callback' => 'cmatic_rest_save_form_field', 190 'permission_callback' => 'cmatic_rest_ permission_check',184 'permission_callback' => 'cmatic_rest_api_key_permission_check', 191 185 'args' => array( 192 186 'form_id' => array( … … 207 201 ) 208 202 ); 203 204 register_rest_route( 205 'chimpmatic-lite/v1', 206 '/api-key/(?P<form_id>\d+)', 207 array( 208 'methods' => 'GET', 209 'callback' => 'cmatic_rest_get_api_key', 210 'permission_callback' => 'cmatic_rest_api_key_permission_check', 211 'args' => array( 212 'form_id' => array( 213 'required' => true, 214 'type' => 'integer', 215 'sanitize_callback' => 'absint', 216 'validate_callback' => function ( $param ) { 217 return is_numeric( $param ) && $param > 0; 218 }, 219 ), 220 ), 221 ) 222 ); 209 223 } 210 224 add_action( 'rest_api_init', 'cmatic_register_rest_routes' ); … … 231 245 } 232 246 247 function cmatic_rest_api_key_permission_check( $request ) { 248 $form_id = $request->get_param( 'form_id' ); 249 250 if ( ! current_user_can( 'wpcf7_edit_contact_form', $form_id ) ) { 251 return new WP_Error( 252 'rest_forbidden', 253 esc_html__( 'You do not have permission to access the API key.', 'chimpmatic-lite' ), 254 array( 'status' => 403 ) 255 ); 256 } 257 258 $nonce = $request->get_header( 'X-WP-Nonce' ); 259 if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { 260 return new WP_Error( 261 'rest_cookie_invalid_nonce', 262 esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ), 263 array( 'status' => 403 ) 264 ); 265 } 266 267 return true; 268 } 269 233 270 234 271 function cmatic_rest_get_lists( $request ) { … … 236 273 $api_key = $request->get_param( 'api_key' ); 237 274 238 // Track API setup funnel: Sync attempted239 275 if ( ! mce_get_cmatic( 'api.sync_attempted' ) ) { 240 // First time user clicks "Sync Audiences"241 276 mce_update_cmatic( 'api.sync_attempted', time() ); 242 277 } 243 // Increment sync attempts counter244 278 $current_count = (int) mce_get_cmatic( 'api.sync_attempts_count', 0 ); 245 279 mce_update_cmatic( 'api.sync_attempts_count', $current_count + 1 ); … … 248 282 $cf7_mch = get_option( $option_name, array() ); 249 283 250 // Defensive: ensure $cf7_mch is array (corrupt options can return empty string).251 284 if ( ! is_array( $cf7_mch ) ) { 252 285 $cf7_mch = array(); 253 286 } 254 287 255 // Use global debug logger setting (not per-form)256 288 $logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false ); 257 289 … … 331 363 array( 332 364 'api' => $api_key, 333 'merge_fields' => $merge_fields, // Save formatted merge fields365 'merge_fields' => $merge_fields, 334 366 ) 335 367 ); 336 368 update_option( $option_name, $settings_to_save ); 337 369 338 // Save lisdata globally for Signals (single source of truth, always freshest).339 370 if ( ! empty( $lists_result['lisdata'] ) ) { 340 371 mce_update_cmatic( 'lisdata', $lists_result['lisdata'] ); … … 436 467 'enabled' => $enabled, 437 468 'message' => $enabled 438 /* translators: %s: setting name */439 469 ? sprintf( __( '%s enabled.', 'chimpmatic-lite' ), $label ) 440 /* translators: %s: setting name */441 470 : sprintf( __( '%s disabled.', 'chimpmatic-lite' ), $label ), 442 471 ) … … 478 507 'enabled' => $enabled, 479 508 'message' => $enabled 480 /* translators: %s: tag name */481 509 ? sprintf( __( 'Tag [%s] enabled.', 'chimpmatic-lite' ), $tag ) 482 /* translators: %s: tag name */483 510 : sprintf( __( 'Tag [%s] disabled.', 'chimpmatic-lite' ), $tag ), 484 511 ) … … 548 575 return new WP_Error( 549 576 'invalid_field', 550 /* translators: %s: field name */551 577 sprintf( __( 'Field "%s" is not allowed.', 'chimpmatic-lite' ), $field ), 552 578 array( 'status' => 400 ) … … 984 1010 ); 985 1011 } 1012 1013 function cmatic_rest_get_api_key( $request ) { 1014 $form_id = $request->get_param( 'form_id' ); 1015 $option_name = 'cf7_mch_' . $form_id; 1016 $cf7_mch = get_option( $option_name, array() ); 1017 1018 if ( ! is_array( $cf7_mch ) ) { 1019 $cf7_mch = array(); 1020 } 1021 1022 $api_key = isset( $cf7_mch['api'] ) ? $cf7_mch['api'] : ''; 1023 1024 return rest_ensure_response( 1025 array( 1026 'success' => true, 1027 'api_key' => $api_key, 1028 ) 1029 ); 1030 } -
contact-form-7-mailchimp-extension/trunk/lib/tools.php
r3433098 r3433818 20 20 function mce_author() { 21 21 $author_pre = 'Contact form 7 Mailchimp extension by '; 22 $author_name = 'Renzo Johnson';23 $author_url = '//renzojohnson.com';24 22 $author_title = 'Renzo Johnson - Web Developer'; 23 $author_url = Cmatic_Pursuit::author( 'backlink' ); 25 24 26 25 $mce_author = '<p style="display: none !important">'; 27 26 $mce_author .= $author_pre; 28 $mce_author .= '<a href="' . $author_url. '" ';29 $mce_author .= 'title="' . $author_title. '" ';27 $mce_author .= '<a href="' . esc_url( $author_url ) . '" '; 28 $mce_author .= 'title="' . esc_attr( $author_title ) . '" '; 30 29 $mce_author .= 'target="_blank">'; 31 $mce_author .= '' . $author_title . '';30 $mce_author .= esc_html( $author_title ); 32 31 $mce_author .= '</a>'; 33 32 $mce_author .= '</p>' . "\n"; … … 65 64 66 65 function mce_init_constants() { 67 define( 'MCE_URL', '//chimpmatic.com/help' );68 define( 'MC_URL', '//chimpmatic.com/help' );69 define( 'MCE_AUTH', '//renzojohnson.com' );70 66 define( 'MCE_AUTH_COMM', '<!-- Chimpmatic -->' ); 71 67 define( 'MCE_NAME', 'MailChimp Contact Form 7 Extension' ); 72 68 define( 'MCE_SETT', admin_url( 'admin.php?page=wpcf7' ) ); 73 69 define( 'MCE_DON', 'https://www.paypal.me/renzojohnson' ); 74 define( 'CHIMPL_URL', '//chimpmatic.com' );75 define( 'CHIMPHELP_URL', '//chimpmatic.com/help' );76 70 } 77 71 add_action( 'init', 'mce_init_constants' ); … … 172 166 function mce_set_welcomebanner() { 173 167 $default_panel = '<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> 174 <p class="about-description">Would you like to <a class="button-primary" href="http ://bit.ly/cafe4renzo" target="_blank">buy me a coffee?</a> or <a class="button-primary" href="http://bit.ly/cafe4renzo" target="_blank">Donate with Paypal</a></p>';168 <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>'; 175 169 176 170 $banner = get_site_option( 'mce_conten_panel_welcome', $default_panel );
Note: See TracChangeset
for help on using the changeset viewer.