Plugin Directory

Changeset 3433818


Ignore:
Timestamp:
01/06/2026 05:21:52 PM (6 weeks ago)
Author:
rnzo
Message:

Update strongly recommended - improved security and UI

Location:
contact-form-7-mailchimp-extension
Files:
22 edited

Legend:

Unmodified
Added
Removed
  • contact-form-7-mailchimp-extension/tags/0.9.68/assets/js/chimpmatic-lite.js

    r3433098 r3433818  
    311311    }
    312312
     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
    313359    // Fetch Mailchimp Lists Button
    314360    const fetchListsButton = document.getElementById('chm_activalist');
     
    323369
    324370            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);
    327372
    328373            if (!apiKey) {
     
    13041349});
    13051350
    1306 // API Key Eye Toggle
     1351// API Key Eye Toggle (CVE-2025-68989 fix: fetch key via secure REST endpoint)
    13071352document.addEventListener('DOMContentLoaded', function() {
    13081353    const eye = document.querySelector('.cmatic-eye');
     
    13101355    if (!eye || !input) return;
    13111356
    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) {
    13131361        e.preventDefault();
    13141362        const icon = this.querySelector('.dashicons');
    13151363        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;
    13191403            input.dataset.isMasked = '0';
    13201404            icon.classList.remove('dashicons-visibility');
    13211405            icon.classList.add('dashicons-hidden');
    13221406        } else {
     1407            // Hide key - show masked version.
    13231408            input.value = input.dataset.maskedKey;
    13241409            input.dataset.isMasked = '1';
     
    13301415    const form = input.closest('form');
    13311416    if (form) {
    1332         form.addEventListener('submit', function() {
     1417        form.addEventListener('submit', async function(e) {
    13331418            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                }
    13361454            }
    13371455        });
  • contact-form-7-mailchimp-extension/tags/0.9.68/assets/js/chimpmatic.js

    r3433098 r3433818  
    9797}
    9898
    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 */
     104async function getApiKey() {
    100105    const apiInput = document.getElementById('cmatic-api');
    101106    if (!apiInput) return '';
     107
    102108    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    }
    106157}
    107158
     
    335386
    336387// Fetch Your Fields and Groups
    337 document.addEventListener('click', function(event) {
     388document.addEventListener('click', async function(event) {
    338389    if (event.target && (event.target.id === 'chm_selgetcampos' || event.target.id === 'mce_fetch_fields')) {
    339390        event.preventDefault();
     
    346397            chm_idformxx: getFormId(),
    347398            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    348             chimpapi: getApiKey()
     399            chimpapi: await getApiKey()
    349400        };
    350401
     
    409460
    410461// Load Groups
    411 document.addEventListener('click', function(event) {
     462document.addEventListener('click', async function(event) {
    412463    if (event.target && event.target.id === 'chm_activagroups') {
    413464        event.preventDefault();
     
    417468            chm_idformxx: getFormId(),
    418469            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    419             chimpapi: getApiKey()
     470            chimpapi: await getApiKey()
    420471        };
    421472
     
    444495
    445496// Export Users
    446 document.addEventListener('click', function(event) {
     497document.addEventListener('click', async function(event) {
    447498    if (event.target && event.target.id === 'chm_userexport') {
    448499        event.preventDefault();
     
    463514            tool_key: document.getElementById('wpcf7-mailchimp-tool_key')?.value || '',
    464515            chm_idformxx: getFormId(),
    465             chimpapi: getApiKey(),
     516            chimpapi: await getApiKey(),
    466517            cadseluser: valuesChecked
    467518        };
     
    481532
    482533// Get Interest (Groups - Arbitrary)
    483 document.addEventListener('change', function(event) {
     534document.addEventListener('change', async function(event) {
    484535    if (event.target && event.target.classList.contains('chimp-gg-arbirary')) {
    485536        event.preventDefault();
     
    494545            chm_idformxx: getFormId(),
    495546            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    496             chimpapi: getApiKey(),
     547            chimpapi: await getApiKey(),
    497548            indtag: itag,
    498549            ggid: ggKeyInput ? ggKeyInput.value : ''
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/activate.php

    r3433098 r3433818  
    201201        if ( $file === 'contact-form-7-mailchimp-extension/chimpmatic-lite.php' ) {
    202202
    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>';
    204204        }
    205205        return $links;
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-api-panel.php

    r3433098 r3433818  
    99
    1010class 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';
    1411
    1512    /** Mask an API key for display. */
     
    3330        $btn_class = 'button';
    3431
    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' );
    3833
    3934        ?>
     
    4843                placeholder="<?php echo esc_attr__( 'Enter Your Mailchimp API key Here', 'chimpmatic-lite' ); ?>"
    4944                value="<?php echo esc_attr( $is_masked ? $masked_key : $api_key ); ?>"
    50                 data-real-key="<?php echo esc_attr( $api_key ); ?>"
    5145                data-masked-key="<?php echo esc_attr( $masked_key ); ?>"
    5246                data-is-masked="<?php echo $is_masked ? '1' : '0'; ?>"
     47                data-has-key="<?php echo ! empty( $api_key ) ? '1' : '0'; ?>"
    5348            />
    5449            <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  
    1010class Cmatic_Audiences {
    1111
    12     /** @var string Help URL for list ID instructions. */
    13     const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';
    14 
    1512    /** Render the audiences panel. */
    1613    public static function render( string $apivalid, ?array $listdata, array $cf7_mch ): void {
     
    1815        $count = isset( $listdata['lists'] ) && is_array( $listdata['lists'] ) ? count( $listdata['lists'] ) : 0;
    1916
    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' );
    2318
    2419        $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  
    1010class Cmatic_Plugin_Links {
    1111
    12     /** @var string Panel key for CF7 tab. */
    1312    const PANEL_KEY = 'Chimpmatic';
    14 
    15     /** @var string Documentation URL. */
    16     const DOCS_URL = 'https://chimpmatic.com/help';
    1713
    1814    /** Get the settings URL for the plugin. */
     
    5248    }
    5349
    54     /** Get the documentation link HTML. */
    5550    public static function get_docs_link() {
    5651        return sprintf(
    5752            '<a href="%s" target="_blank" title="%s">%s</a>',
    58             esc_url( self::DOCS_URL ),
     53            esc_url( Cmatic_Pursuit::docs( '', 'plugins_page' ) ),
    5954            esc_attr__( 'Chimpmatic Documentation', 'chimpmatic-lite' ),
    6055            esc_html__( 'Docs', 'chimpmatic-lite' )
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/class-cmatic-pursuit.php

    r3433098 r3433818  
    1919        'promo'   => 'https://chimpmatic.com/almost-there',
    2020        'home'    => 'https://chimpmatic.com',
     21        'author'  => 'https://renzojohnson.com',
    2122    );
    2223
     
    7071    }
    7172
     73    public static function author( string $content = '' ): string {
     74        return self::url( self::BASE_URLS['author'], 'plugin', $content, 'author' );
     75    }
     76
    7277    public static function adminbar( string $destination, string $content = '' ): string {
    7378        $base = self::BASE_URLS[ $destination ] ?? self::BASE_URLS['home'];
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/enqueue.php

    r3433098 r3433818  
    138138            'nonce'           => wp_create_nonce( 'wp_rest' ),
    139139            'pluginUrl'       => SPARTAN_MCE_PLUGIN_URL,
     140            'formId'          => $form_id,
    140141            'mergeFields'     => $merge_fields,
    141142            'loggingEnabled'  => $logging_enabled,
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/handler.php

    r3433098 r3433818  
    11<?php
    2 /**
    3  * Form submission handler.
    4  *
    5  * @package ChimpMatic_Lite
    6  */
    72
    83defined( 'ABSPATH' ) || exit;
     
    2116function cmatic_add_editor_panel( $panels ) {
    2217    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 ) ) {
    2323        return $panels;
    2424    }
     
    6060    }
    6161
    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).
    6562    if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) {
    6663        $payload = \Cmatic\Metrics\Core\Collector::collect( 'form_saved' );
     
    8784        if ( isset( $posted_data[ $field ] ) ) {
    8885            $sanitized_value = trim( sanitize_text_field( $posted_data[ $field ] ) );
    89             // Only save non-empty values (skip empty/whitespace "Choose.." placeholder).
    9086            if ( '' !== $sanitized_value ) {
    9187                $sanitized_settings[ $field ] = $sanitized_value;
     
    143139    }
    144140
    145     // Check global debug setting (from cmatic option structure)
    146141    $logfile_enabled = (bool) mce_get_cmatic( 'debug', false );
    147142    $logger          = new Cmatic_File_Logger( 'api-events', $logfile_enabled );
     
    250245        $response_data = cmatic_call_api_put( $api_key, $url, $info );
    251246
    252         // Check for API errors in response.
    253247        $api_response = isset( $response_data[0] ) ? $response_data[0] : array();
    254248
    255         // Log only the Mailchimp API response body, not the full HTTP response object.
    256249        $logger->log( 'INFO', 'Mailchimp API Response.', $api_response );
    257250
    258         // Check for WP_Error from cmatic_call_api_put (network failure).
    259251        if ( false === $response_data[0] ) {
    260252            $logger->log( 'ERROR', 'Network request failed.', array( 'response' => $response_data[1] ) );
    261253            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', '', $email ) );
    262254        } elseif ( empty( $api_response ) ) {
    263             // Empty response - likely invalid API key or server error.
    264255            $logger->log( 'ERROR', 'Empty API response received.' );
    265256            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'api_error', 'Empty response from Mailchimp API.', $email ) );
     
    269260                $php_logger->log( 'ERROR', 'Mailchimp API Error received.', $error );
    270261            }
    271             // Set feedback for API error.
    272262            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    273263        } 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).
    275264            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    276265        } 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).
    278266            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    279267        } else {
    280268            mce_save_contador();
    281269
    282             // Set success feedback with merge fields sent.
    283270            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::success( $email, $status, $merge_vars, $api_response ) );
    284271
    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              */
    291272            do_action( 'cmatic_subscription_success', $form_id, $email );
    292273        }
     
    303284            return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted;
    304285        }
    305         return $matches[0]; // Return the tag itself if no value found
     286        return $matches[0];
    306287    }
    307288    return $subject;
     
    322303        <label for="wpcf7-mailchimp-list">
    323304            <?php
    324             /* translators: %d: number of Mailchimp audiences */
    325305            printf( esc_html__( 'Total Mailchimp Audiences: %d', 'chimpmatic-lite' ), esc_html( $count ) );
    326306            ?>
     
    356336    $saved_value = isset( $cf7_mch[ $field_name ] ) ? trim( sanitize_text_field( $cf7_mch[ $field_name ] ) ) : '';
    357337
    358     // Auto-select matching form field based on merge tag (fuzzy match).
    359338    if ( '' === $saved_value && ! empty( $merge_tag ) ) {
    360339        $merge_tag_lower = strtolower( $merge_tag );
    361340        foreach ( $form_tags as $tag ) {
    362             // Match by basetype for email, or by name containing the merge tag.
    363341            if ( 'email' === $filter && ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ) ) {
    364342                $saved_value = '[' . $tag['name'] . ']';
     
    384362                continue;
    385363            }
    386             // Filter by field type when $filter is specified (fuzzy match for email).
    387364            if ( 'email' === $filter ) {
    388365                $is_email_field = ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) );
     
    637614    }
    638615
    639     // Use Cmatic_Pursuit::promo() which handles WP Engine's utm_* stripping via u_* prefix.
    640616    $pursuit_addy = add_query_arg(
    641617        array(
  • contact-form-7-mailchimp-extension/tags/0.9.68/lib/rest-api.php

    r3433098 r3433818  
    11<?php
    2 /**
    3  * REST API endpoints.
    4  *
    5  * @package ChimpMatic_Lite
    6  */
    7 
    82defined( 'ABSPATH' ) || exit;
    93
     
    1610            'methods'             => 'POST',
    1711            'callback'            => 'cmatic_rest_get_lists',
    18             'permission_callback' => 'cmatic_rest_permission_check',
     12            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    1913            'args'                => array(
    2014                'form_id' => array(
     
    139133            'methods'             => 'POST',
    140134            'callback'            => 'cmatic_rest_get_merge_fields',
    141             'permission_callback' => 'cmatic_rest_permission_check',
     135            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    142136            'args'                => array(
    143137                'form_id' => array(
     
    188182            'methods'             => 'POST',
    189183            'callback'            => 'cmatic_rest_save_form_field',
    190             'permission_callback' => 'cmatic_rest_permission_check',
     184            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    191185            'args'                => array(
    192186                'form_id' => array(
     
    207201        )
    208202    );
     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    );
    209223}
    210224add_action( 'rest_api_init', 'cmatic_register_rest_routes' );
     
    231245}
    232246
     247function 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
    233270
    234271function cmatic_rest_get_lists( $request ) {
     
    236273    $api_key = $request->get_param( 'api_key' );
    237274
    238     // Track API setup funnel: Sync attempted
    239275    if ( ! mce_get_cmatic( 'api.sync_attempted' ) ) {
    240         // First time user clicks "Sync Audiences"
    241276        mce_update_cmatic( 'api.sync_attempted', time() );
    242277    }
    243     // Increment sync attempts counter
    244278    $current_count = (int) mce_get_cmatic( 'api.sync_attempts_count', 0 );
    245279    mce_update_cmatic( 'api.sync_attempts_count', $current_count + 1 );
     
    248282    $cf7_mch     = get_option( $option_name, array() );
    249283
    250     // Defensive: ensure $cf7_mch is array (corrupt options can return empty string).
    251284    if ( ! is_array( $cf7_mch ) ) {
    252285        $cf7_mch = array();
    253286    }
    254287
    255     // Use global debug logger setting (not per-form)
    256288    $logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false );
    257289
     
    331363            array(
    332364                'api'          => $api_key,
    333                 'merge_fields' => $merge_fields,  // Save formatted merge fields
     365                'merge_fields' => $merge_fields,
    334366            )
    335367        );
    336368        update_option( $option_name, $settings_to_save );
    337369
    338         // Save lisdata globally for Signals (single source of truth, always freshest).
    339370        if ( ! empty( $lists_result['lisdata'] ) ) {
    340371            mce_update_cmatic( 'lisdata', $lists_result['lisdata'] );
     
    436467            'enabled' => $enabled,
    437468            'message' => $enabled
    438                 /* translators: %s: setting name */
    439469                ? sprintf( __( '%s enabled.', 'chimpmatic-lite' ), $label )
    440                 /* translators: %s: setting name */
    441470                : sprintf( __( '%s disabled.', 'chimpmatic-lite' ), $label ),
    442471        )
     
    478507            'enabled' => $enabled,
    479508            'message' => $enabled
    480                 /* translators: %s: tag name */
    481509                ? sprintf( __( 'Tag [%s] enabled.', 'chimpmatic-lite' ), $tag )
    482                 /* translators: %s: tag name */
    483510                : sprintf( __( 'Tag [%s] disabled.', 'chimpmatic-lite' ), $tag ),
    484511        )
     
    548575        return new WP_Error(
    549576            'invalid_field',
    550             /* translators: %s: field name */
    551577            sprintf( __( 'Field "%s" is not allowed.', 'chimpmatic-lite' ), $field ),
    552578            array( 'status' => 400 )
     
    9841010    );
    9851011}
     1012
     1013function 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  
    2020function mce_author() {
    2121    $author_pre   = 'Contact form 7 Mailchimp extension by ';
    22     $author_name  = 'Renzo Johnson';
    23     $author_url   = '//renzojohnson.com';
    2422    $author_title = 'Renzo Johnson - Web Developer';
     23    $author_url   = Cmatic_Pursuit::author( 'backlink' );
    2524
    2625    $mce_author  = '<p style="display: none !important">';
    2726    $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 ) . '" ';
    3029    $mce_author .= 'target="_blank">';
    31     $mce_author .= '' . $author_title . '';
     30    $mce_author .= esc_html( $author_title );
    3231    $mce_author .= '</a>';
    3332    $mce_author .= '</p>' . "\n";
     
    6564
    6665function mce_init_constants() {
    67     define( 'MCE_URL', '//chimpmatic.com/help' );
    68     define( 'MC_URL', '//chimpmatic.com/help' );
    69     define( 'MCE_AUTH', '//renzojohnson.com' );
    7066    define( 'MCE_AUTH_COMM', '<!-- Chimpmatic -->' );
    7167    define( 'MCE_NAME', 'MailChimp Contact Form 7 Extension' );
    7268    define( 'MCE_SETT', admin_url( 'admin.php?page=wpcf7' ) );
    7369    define( 'MCE_DON', 'https://www.paypal.me/renzojohnson' );
    74     define( 'CHIMPL_URL', '//chimpmatic.com' );
    75     define( 'CHIMPHELP_URL', '//chimpmatic.com/help' );
    7670}
    7771add_action( 'init', 'mce_init_constants' );
     
    172166function mce_set_welcomebanner() {
    173167    $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>';
    175169
    176170    $banner = get_site_option( 'mce_conten_panel_welcome', $default_panel );
  • contact-form-7-mailchimp-extension/trunk/assets/js/chimpmatic-lite.js

    r3433098 r3433818  
    311311    }
    312312
     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
    313359    // Fetch Mailchimp Lists Button
    314360    const fetchListsButton = document.getElementById('chm_activalist');
     
    323369
    324370            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);
    327372
    328373            if (!apiKey) {
     
    13041349});
    13051350
    1306 // API Key Eye Toggle
     1351// API Key Eye Toggle (CVE-2025-68989 fix: fetch key via secure REST endpoint)
    13071352document.addEventListener('DOMContentLoaded', function() {
    13081353    const eye = document.querySelector('.cmatic-eye');
     
    13101355    if (!eye || !input) return;
    13111356
    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) {
    13131361        e.preventDefault();
    13141362        const icon = this.querySelector('.dashicons');
    13151363        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;
    13191403            input.dataset.isMasked = '0';
    13201404            icon.classList.remove('dashicons-visibility');
    13211405            icon.classList.add('dashicons-hidden');
    13221406        } else {
     1407            // Hide key - show masked version.
    13231408            input.value = input.dataset.maskedKey;
    13241409            input.dataset.isMasked = '1';
     
    13301415    const form = input.closest('form');
    13311416    if (form) {
    1332         form.addEventListener('submit', function() {
     1417        form.addEventListener('submit', async function(e) {
    13331418            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                }
    13361454            }
    13371455        });
  • contact-form-7-mailchimp-extension/trunk/assets/js/chimpmatic.js

    r3431951 r3433818  
    9797}
    9898
    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 */
     104async function getApiKey() {
    100105    const apiInput = document.getElementById('cmatic-api');
    101106    if (!apiInput) return '';
     107
    102108    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    }
    106157}
    107158
     
    335386
    336387// Fetch Your Fields and Groups
    337 document.addEventListener('click', function(event) {
     388document.addEventListener('click', async function(event) {
    338389    if (event.target && (event.target.id === 'chm_selgetcampos' || event.target.id === 'mce_fetch_fields')) {
    339390        event.preventDefault();
     
    346397            chm_idformxx: getFormId(),
    347398            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    348             chimpapi: getApiKey()
     399            chimpapi: await getApiKey()
    349400        };
    350401
     
    409460
    410461// Load Groups
    411 document.addEventListener('click', function(event) {
     462document.addEventListener('click', async function(event) {
    412463    if (event.target && event.target.id === 'chm_activagroups') {
    413464        event.preventDefault();
     
    417468            chm_idformxx: getFormId(),
    418469            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    419             chimpapi: getApiKey()
     470            chimpapi: await getApiKey()
    420471        };
    421472
     
    444495
    445496// Export Users
    446 document.addEventListener('click', function(event) {
     497document.addEventListener('click', async function(event) {
    447498    if (event.target && event.target.id === 'chm_userexport') {
    448499        event.preventDefault();
     
    463514            tool_key: document.getElementById('wpcf7-mailchimp-tool_key')?.value || '',
    464515            chm_idformxx: getFormId(),
    465             chimpapi: getApiKey(),
     516            chimpapi: await getApiKey(),
    466517            cadseluser: valuesChecked
    467518        };
     
    481532
    482533// Get Interest (Groups - Arbitrary)
    483 document.addEventListener('change', function(event) {
     534document.addEventListener('change', async function(event) {
    484535    if (event.target && event.target.classList.contains('chimp-gg-arbirary')) {
    485536        event.preventDefault();
     
    494545            chm_idformxx: getFormId(),
    495546            chm_listid: document.getElementById('wpcf7-mailchimp-list')?.value || '',
    496             chimpapi: getApiKey(),
     547            chimpapi: await getApiKey(),
    497548            indtag: itag,
    498549            ggid: ggKeyInput ? ggKeyInput.value : ''
  • contact-form-7-mailchimp-extension/trunk/lib/activate.php

    r3433098 r3433818  
    201201        if ( $file === 'contact-form-7-mailchimp-extension/chimpmatic-lite.php' ) {
    202202
    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>';
    204204        }
    205205        return $links;
  • contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-api-panel.php

    r3433098 r3433818  
    99
    1010class 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';
    1411
    1512    /** Mask an API key for display. */
     
    3330        $btn_class = 'button';
    3431
    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' );
    3833
    3934        ?>
     
    4843                placeholder="<?php echo esc_attr__( 'Enter Your Mailchimp API key Here', 'chimpmatic-lite' ); ?>"
    4944                value="<?php echo esc_attr( $is_masked ? $masked_key : $api_key ); ?>"
    50                 data-real-key="<?php echo esc_attr( $api_key ); ?>"
    5145                data-masked-key="<?php echo esc_attr( $masked_key ); ?>"
    5246                data-is-masked="<?php echo $is_masked ? '1' : '0'; ?>"
     47                data-has-key="<?php echo ! empty( $api_key ) ? '1' : '0'; ?>"
    5348            />
    5449            <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  
    1010class Cmatic_Audiences {
    1111
    12     /** @var string Help URL for list ID instructions. */
    13     const HELP_URL = '//chimpmatic.com/help/how-to-get-your-mailchimp-api-key';
    14 
    1512    /** Render the audiences panel. */
    1613    public static function render( string $apivalid, ?array $listdata, array $cf7_mch ): void {
     
    1815        $count = isset( $listdata['lists'] ) && is_array( $listdata['lists'] ) ? count( $listdata['lists'] ) : 0;
    1916
    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' );
    2318
    2419        $disclosure_class = ( '1' === $apivalid ) ? 'chmp-active' : 'chmp-inactive';
  • contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-plugin-links.php

    r3433098 r3433818  
    1010class Cmatic_Plugin_Links {
    1111
    12     /** @var string Panel key for CF7 tab. */
    1312    const PANEL_KEY = 'Chimpmatic';
    14 
    15     /** @var string Documentation URL. */
    16     const DOCS_URL = 'https://chimpmatic.com/help';
    1713
    1814    /** Get the settings URL for the plugin. */
     
    5248    }
    5349
    54     /** Get the documentation link HTML. */
    5550    public static function get_docs_link() {
    5651        return sprintf(
    5752            '<a href="%s" target="_blank" title="%s">%s</a>',
    58             esc_url( self::DOCS_URL ),
     53            esc_url( Cmatic_Pursuit::docs( '', 'plugins_page' ) ),
    5954            esc_attr__( 'Chimpmatic Documentation', 'chimpmatic-lite' ),
    6055            esc_html__( 'Docs', 'chimpmatic-lite' )
  • contact-form-7-mailchimp-extension/trunk/lib/class-cmatic-pursuit.php

    r3433106 r3433818  
    1919        'promo'   => 'https://chimpmatic.com/almost-there',
    2020        'home'    => 'https://chimpmatic.com',
     21        'author'  => 'https://renzojohnson.com',
    2122    );
    2223
     
    7071    }
    7172
     73    public static function author( string $content = '' ): string {
     74        return self::url( self::BASE_URLS['author'], 'plugin', $content, 'author' );
     75    }
     76
    7277    public static function adminbar( string $destination, string $content = '' ): string {
    7378        $base = self::BASE_URLS[ $destination ] ?? self::BASE_URLS['home'];
  • contact-form-7-mailchimp-extension/trunk/lib/enqueue.php

    r3433098 r3433818  
    138138            'nonce'           => wp_create_nonce( 'wp_rest' ),
    139139            'pluginUrl'       => SPARTAN_MCE_PLUGIN_URL,
     140            'formId'          => $form_id,
    140141            'mergeFields'     => $merge_fields,
    141142            'loggingEnabled'  => $logging_enabled,
  • contact-form-7-mailchimp-extension/trunk/lib/handler.php

    r3433098 r3433818  
    11<?php
    2 /**
    3  * Form submission handler.
    4  *
    5  * @package ChimpMatic_Lite
    6  */
    72
    83defined( 'ABSPATH' ) || exit;
     
    2116function cmatic_add_editor_panel( $panels ) {
    2217    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 ) ) {
    2323        return $panels;
    2424    }
     
    6060    }
    6161
    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).
    6562    if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) {
    6663        $payload = \Cmatic\Metrics\Core\Collector::collect( 'form_saved' );
     
    8784        if ( isset( $posted_data[ $field ] ) ) {
    8885            $sanitized_value = trim( sanitize_text_field( $posted_data[ $field ] ) );
    89             // Only save non-empty values (skip empty/whitespace "Choose.." placeholder).
    9086            if ( '' !== $sanitized_value ) {
    9187                $sanitized_settings[ $field ] = $sanitized_value;
     
    143139    }
    144140
    145     // Check global debug setting (from cmatic option structure)
    146141    $logfile_enabled = (bool) mce_get_cmatic( 'debug', false );
    147142    $logger          = new Cmatic_File_Logger( 'api-events', $logfile_enabled );
     
    250245        $response_data = cmatic_call_api_put( $api_key, $url, $info );
    251246
    252         // Check for API errors in response.
    253247        $api_response = isset( $response_data[0] ) ? $response_data[0] : array();
    254248
    255         // Log only the Mailchimp API response body, not the full HTTP response object.
    256249        $logger->log( 'INFO', 'Mailchimp API Response.', $api_response );
    257250
    258         // Check for WP_Error from cmatic_call_api_put (network failure).
    259251        if ( false === $response_data[0] ) {
    260252            $logger->log( 'ERROR', 'Network request failed.', array( 'response' => $response_data[1] ) );
    261253            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', '', $email ) );
    262254        } elseif ( empty( $api_response ) ) {
    263             // Empty response - likely invalid API key or server error.
    264255            $logger->log( 'ERROR', 'Empty API response received.' );
    265256            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'api_error', 'Empty response from Mailchimp API.', $email ) );
     
    269260                $php_logger->log( 'ERROR', 'Mailchimp API Error received.', $error );
    270261            }
    271             // Set feedback for API error.
    272262            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    273263        } 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).
    275264            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    276265        } 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).
    278266            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_response, $email ) );
    279267        } else {
    280268            mce_save_contador();
    281269
    282             // Set success feedback with merge fields sent.
    283270            Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::success( $email, $status, $merge_vars, $api_response ) );
    284271
    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              */
    291272            do_action( 'cmatic_subscription_success', $form_id, $email );
    292273        }
     
    303284            return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted;
    304285        }
    305         return $matches[0]; // Return the tag itself if no value found
     286        return $matches[0];
    306287    }
    307288    return $subject;
     
    322303        <label for="wpcf7-mailchimp-list">
    323304            <?php
    324             /* translators: %d: number of Mailchimp audiences */
    325305            printf( esc_html__( 'Total Mailchimp Audiences: %d', 'chimpmatic-lite' ), esc_html( $count ) );
    326306            ?>
     
    356336    $saved_value = isset( $cf7_mch[ $field_name ] ) ? trim( sanitize_text_field( $cf7_mch[ $field_name ] ) ) : '';
    357337
    358     // Auto-select matching form field based on merge tag (fuzzy match).
    359338    if ( '' === $saved_value && ! empty( $merge_tag ) ) {
    360339        $merge_tag_lower = strtolower( $merge_tag );
    361340        foreach ( $form_tags as $tag ) {
    362             // Match by basetype for email, or by name containing the merge tag.
    363341            if ( 'email' === $filter && ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ) ) {
    364342                $saved_value = '[' . $tag['name'] . ']';
     
    384362                continue;
    385363            }
    386             // Filter by field type when $filter is specified (fuzzy match for email).
    387364            if ( 'email' === $filter ) {
    388365                $is_email_field = ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) );
     
    637614    }
    638615
    639     // Use Cmatic_Pursuit::promo() which handles WP Engine's utm_* stripping via u_* prefix.
    640616    $pursuit_addy = add_query_arg(
    641617        array(
  • contact-form-7-mailchimp-extension/trunk/lib/rest-api.php

    r3433098 r3433818  
    11<?php
    2 /**
    3  * REST API endpoints.
    4  *
    5  * @package ChimpMatic_Lite
    6  */
    7 
    82defined( 'ABSPATH' ) || exit;
    93
     
    1610            'methods'             => 'POST',
    1711            'callback'            => 'cmatic_rest_get_lists',
    18             'permission_callback' => 'cmatic_rest_permission_check',
     12            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    1913            'args'                => array(
    2014                'form_id' => array(
     
    139133            'methods'             => 'POST',
    140134            'callback'            => 'cmatic_rest_get_merge_fields',
    141             'permission_callback' => 'cmatic_rest_permission_check',
     135            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    142136            'args'                => array(
    143137                'form_id' => array(
     
    188182            'methods'             => 'POST',
    189183            'callback'            => 'cmatic_rest_save_form_field',
    190             'permission_callback' => 'cmatic_rest_permission_check',
     184            'permission_callback' => 'cmatic_rest_api_key_permission_check',
    191185            'args'                => array(
    192186                'form_id' => array(
     
    207201        )
    208202    );
     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    );
    209223}
    210224add_action( 'rest_api_init', 'cmatic_register_rest_routes' );
     
    231245}
    232246
     247function 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
    233270
    234271function cmatic_rest_get_lists( $request ) {
     
    236273    $api_key = $request->get_param( 'api_key' );
    237274
    238     // Track API setup funnel: Sync attempted
    239275    if ( ! mce_get_cmatic( 'api.sync_attempted' ) ) {
    240         // First time user clicks "Sync Audiences"
    241276        mce_update_cmatic( 'api.sync_attempted', time() );
    242277    }
    243     // Increment sync attempts counter
    244278    $current_count = (int) mce_get_cmatic( 'api.sync_attempts_count', 0 );
    245279    mce_update_cmatic( 'api.sync_attempts_count', $current_count + 1 );
     
    248282    $cf7_mch     = get_option( $option_name, array() );
    249283
    250     // Defensive: ensure $cf7_mch is array (corrupt options can return empty string).
    251284    if ( ! is_array( $cf7_mch ) ) {
    252285        $cf7_mch = array();
    253286    }
    254287
    255     // Use global debug logger setting (not per-form)
    256288    $logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false );
    257289
     
    331363            array(
    332364                'api'          => $api_key,
    333                 'merge_fields' => $merge_fields,  // Save formatted merge fields
     365                'merge_fields' => $merge_fields,
    334366            )
    335367        );
    336368        update_option( $option_name, $settings_to_save );
    337369
    338         // Save lisdata globally for Signals (single source of truth, always freshest).
    339370        if ( ! empty( $lists_result['lisdata'] ) ) {
    340371            mce_update_cmatic( 'lisdata', $lists_result['lisdata'] );
     
    436467            'enabled' => $enabled,
    437468            'message' => $enabled
    438                 /* translators: %s: setting name */
    439469                ? sprintf( __( '%s enabled.', 'chimpmatic-lite' ), $label )
    440                 /* translators: %s: setting name */
    441470                : sprintf( __( '%s disabled.', 'chimpmatic-lite' ), $label ),
    442471        )
     
    478507            'enabled' => $enabled,
    479508            'message' => $enabled
    480                 /* translators: %s: tag name */
    481509                ? sprintf( __( 'Tag [%s] enabled.', 'chimpmatic-lite' ), $tag )
    482                 /* translators: %s: tag name */
    483510                : sprintf( __( 'Tag [%s] disabled.', 'chimpmatic-lite' ), $tag ),
    484511        )
     
    548575        return new WP_Error(
    549576            'invalid_field',
    550             /* translators: %s: field name */
    551577            sprintf( __( 'Field "%s" is not allowed.', 'chimpmatic-lite' ), $field ),
    552578            array( 'status' => 400 )
     
    9841010    );
    9851011}
     1012
     1013function 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  
    2020function mce_author() {
    2121    $author_pre   = 'Contact form 7 Mailchimp extension by ';
    22     $author_name  = 'Renzo Johnson';
    23     $author_url   = '//renzojohnson.com';
    2422    $author_title = 'Renzo Johnson - Web Developer';
     23    $author_url   = Cmatic_Pursuit::author( 'backlink' );
    2524
    2625    $mce_author  = '<p style="display: none !important">';
    2726    $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 ) . '" ';
    3029    $mce_author .= 'target="_blank">';
    31     $mce_author .= '' . $author_title . '';
     30    $mce_author .= esc_html( $author_title );
    3231    $mce_author .= '</a>';
    3332    $mce_author .= '</p>' . "\n";
     
    6564
    6665function mce_init_constants() {
    67     define( 'MCE_URL', '//chimpmatic.com/help' );
    68     define( 'MC_URL', '//chimpmatic.com/help' );
    69     define( 'MCE_AUTH', '//renzojohnson.com' );
    7066    define( 'MCE_AUTH_COMM', '<!-- Chimpmatic -->' );
    7167    define( 'MCE_NAME', 'MailChimp Contact Form 7 Extension' );
    7268    define( 'MCE_SETT', admin_url( 'admin.php?page=wpcf7' ) );
    7369    define( 'MCE_DON', 'https://www.paypal.me/renzojohnson' );
    74     define( 'CHIMPL_URL', '//chimpmatic.com' );
    75     define( 'CHIMPHELP_URL', '//chimpmatic.com/help' );
    7670}
    7771add_action( 'init', 'mce_init_constants' );
     
    172166function mce_set_welcomebanner() {
    173167    $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>';
    175169
    176170    $banner = get_site_option( 'mce_conten_panel_welcome', $default_panel );
Note: See TracChangeset for help on using the changeset viewer.