Plugin Directory

Changeset 3458122


Ignore:
Timestamp:
02/10/2026 02:29:36 PM (7 weeks ago)
Author:
maxwellfp
Message:

update to v0.3.0

Location:
firstpromoter/trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • firstpromoter/trunk/assets/css/admin.css

    r3399029 r3458122  
    620620}
    621621
     622/* Custom Forms Repeater */
     623.fp-custom-forms-repeater {
     624  margin-top: 12px;
     625}
     626
     627.fp-custom-form-row {
     628  display: flex;
     629  align-items: center;
     630  gap: 10px;
     631  padding: 12px;
     632  margin-bottom: 8px;
     633  background: #f9fafb;
     634  border: 1px solid #e5e7eb;
     635  border-radius: 8px;
     636}
     637
     638.fp-custom-form-fields {
     639  flex: 1;
     640  display: flex;
     641  gap: 10px;
     642}
     643
     644.fp-custom-form-fields .fp-input.regular-text {
     645  flex: 1;
     646  margin-bottom: 0;
     647}
     648
     649.fp-remove-form-row {
     650  background: none;
     651  border: 1px solid #d1d5db;
     652  border-radius: 6px;
     653  width: 32px;
     654  height: 32px;
     655  font-size: 18px;
     656  line-height: 1;
     657  cursor: pointer;
     658  color: #6b7280;
     659  display: flex;
     660  align-items: center;
     661  justify-content: center;
     662  flex-shrink: 0;
     663  padding: 0;
     664}
     665
     666.fp-remove-form-row:hover {
     667  color: #ef4444;
     668  border-color: #ef4444;
     669  background: #fef2f2;
     670}
     671
     672.fp-add-form-row {
     673  background: none;
     674  border: 2px dashed #d1d5db;
     675  border-radius: 8px;
     676  padding: 10px 20px;
     677  color: #6b7280;
     678  font-size: 14px;
     679  cursor: pointer;
     680  width: 100%;
     681  margin-top: 4px;
     682}
     683
     684.fp-add-form-row:hover {
     685  border-color: #2271b1;
     686  color: #2271b1;
     687  background: rgba(34, 113, 177, 0.04);
     688}
     689
     690@media (max-width: 768px) {
     691  .fp-custom-form-fields {
     692    flex-direction: column;
     693  }
     694}
     695
    622696/* Button disabled state */
    623697.button-primary:disabled,
  • firstpromoter/trunk/assets/js/admin.js

    r3399029 r3458122  
    1313      this.setupTooltips();
    1414      this.setupFieldHighlight();
     15      this.setupCustomFormsRepeater();
    1516    },
    1617
     
    223224      var originalText = $btn.text();
    224225      $btn.text('Copied!').addClass('copied');
    225      
     226
    226227      setTimeout(function() {
    227228        $btn.text(originalText).removeClass('copied');
    228229      }, 2000);
     230    },
     231
     232    // Custom Forms repeater add/remove rows
     233    setupCustomFormsRepeater: function() {
     234      var $container = $('.fp-custom-form-rows');
     235      if (!$container.length) return;
     236
     237      // Toggle multiple forms mode
     238      $('.fp-multiple-forms-checkbox').on('change', function() {
     239        var $repeater = $(this).closest('.fp-custom-forms-repeater');
     240        var $addBtn = $repeater.find('.fp-add-form-row');
     241        var $removeBtns = $repeater.find('.fp-remove-form-row');
     242
     243        if ($(this).is(':checked')) {
     244          $addBtn.show();
     245          $removeBtns.show();
     246        } else {
     247          $addBtn.hide();
     248          // Remove all rows except the first, clear extra fields
     249          var $rows = $container.find('.fp-custom-form-row');
     250          $rows.each(function(i) {
     251            if (i > 0) {
     252              $(this).remove();
     253            }
     254          });
     255          // Re-index the remaining row
     256          $container.find('.fp-custom-form-row').each(function(idx) {
     257            $(this).find('input[type="text"]').each(function() {
     258              var name = $(this).attr('name');
     259              $(this).attr('name', name.replace(/\[custom_forms\]\[\d+\]/, '[custom_forms][' + idx + ']'));
     260            });
     261          });
     262          $removeBtns.hide();
     263        }
     264      });
     265
     266      // Add row
     267      $(document).on('click', '.fp-add-form-row', function() {
     268        var $rows = $container.find('.fp-custom-form-row');
     269        var newIndex = $rows.length;
     270        var $row = $(
     271          '<div class="fp-custom-form-row" style="display:none;">' +
     272            '<div class="fp-custom-form-fields">' +
     273              '<input type="text" name="firstpromoter_integration_settings[custom_forms][' + newIndex + '][email_selector]" value="" placeholder="Email input selector, e.g. #signup-email" class="fp-input regular-text">' +
     274              '<input type="text" name="firstpromoter_integration_settings[custom_forms][' + newIndex + '][submit_selector]" value="" placeholder="Submit selector, e.g. #signup-btn or form.my-form" class="fp-input regular-text">' +
     275            '</div>' +
     276            '<button type="button" class="fp-remove-form-row" title="Remove row">&times;</button>' +
     277          '</div>'
     278        );
     279        $container.append($row);
     280        $row.fadeIn(200);
     281      });
     282
     283      // Remove row
     284      $(document).on('click', '.fp-remove-form-row', function() {
     285        var $rows = $container.find('.fp-custom-form-row');
     286        var $row = $(this).closest('.fp-custom-form-row');
     287
     288        if ($rows.length <= 1) {
     289          $row.find('input').val('');
     290          return;
     291        }
     292
     293        $row.fadeOut(200, function() {
     294          $(this).remove();
     295          // Re-index remaining rows
     296          $container.find('.fp-custom-form-row').each(function(i) {
     297            $(this).find('input').each(function() {
     298              var name = $(this).attr('name');
     299              $(this).attr('name', name.replace(/\[custom_forms\]\[\d+\]/, '[custom_forms][' + i + ']'));
     300            });
     301          });
     302          // If only 1 row left, uncheck multi toggle and hide remove buttons
     303          if ($container.find('.fp-custom-form-row').length <= 1) {
     304            var $repeater = $container.closest('.fp-custom-forms-repeater');
     305            $repeater.find('.fp-multiple-forms-checkbox').prop('checked', false);
     306            $repeater.find('.fp-add-form-row').slideUp(200);
     307            $repeater.find('.fp-remove-form-row').hide();
     308          }
     309        });
     310      });
    229311    }
    230312  };
  • firstpromoter/trunk/firstpromoter.php

    r3404859 r3458122  
    33Plugin Name: FirstPromoter
    44Description: FirstPromoter tracking scripts with WooCommerce, OptimizePress, Contact Form 7 & MemberPress
    5 Version: 0.2.1
     5Version: 0.3.0
    66Author: FirstPromoter
    77Author URI: https://firstpromoter.com
     
    1919}
    2020
    21 //composer autoload
    22 require 'vendor/autoload.php';
    23 
    2421// Define plugin constants
    25 define('FIRSTPROMOTER_VERSION', '0.2.1');
     22define('FIRSTPROMOTER_VERSION', '0.3.0');
    2623define('FIRSTPROMOTER_PLUGIN_PATH', plugin_dir_path(__FILE__));
    2724define('FIRSTPROMOTER_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    3431require_once FIRSTPROMOTER_PLUGIN_PATH . 'integrations/class-fp-integration-contactform7.php';
    3532require_once FIRSTPROMOTER_PLUGIN_PATH . 'integrations/class-fp-integration-memberpress.php';
     33require_once FIRSTPROMOTER_PLUGIN_PATH . 'integrations/class-fp-integration-customforms.php';
    3634
    3735class FirstPromoter_Core
     
    5452
    5553        // Check for URL parameters before anything else
    56         add_action('init', [$this, 'check_url_parameters'], 1);
     54        add_action('init', array($this, 'check_url_parameters'), 1);
    5755
    5856        // Initialize settings page with available integrations
     
    6058
    6159        // Add base tracking script - this should run on all pages
    62         add_action('wp_enqueue_scripts', [$this, 'add_base_tracking_script'], 5);
     60        add_action('wp_enqueue_scripts', array($this, 'add_base_tracking_script'), 5);
     61
     62        // Register AJAX handler once (not per-integration)
     63        add_action('wp_ajax_nopriv_firstpromoter_track_api_fallback', array($this, 'handle_api_fallback'));
     64        add_action('wp_ajax_firstpromoter_track_api_fallback', array($this, 'handle_api_fallback'));
    6365
    6466        // Initialize the enabled integrations
    6567        $this->load_integrations();
     68    }
     69
     70    /**
     71     * Handle AJAX API fallback - delegates to the first available integration
     72     */
     73    public function handle_api_fallback()
     74    {
     75        // Create a minimal integration instance to handle the fallback
     76        // We need an integration with access to the base API methods
     77        $integration_settings = get_option('firstpromoter_integration_settings');
     78
     79        // Try WooCommerce first, then any available integration
     80        if (class_exists('FirstPromoter_Integration_WooCommerce')) {
     81            $handler = new FirstPromoter_Integration_WooCommerce($integration_settings);
     82        } elseif (class_exists('FirstPromoter_Integration_MemberPress')) {
     83            $handler = new FirstPromoter_Integration_MemberPress($integration_settings);
     84        } elseif (class_exists('FirstPromoter_Integration_ContactForm7')) {
     85            $handler = new FirstPromoter_Integration_ContactForm7($integration_settings);
     86        } elseif (class_exists('FirstPromoter_Integration_CustomForms')) {
     87            $handler = new FirstPromoter_Integration_CustomForms($integration_settings);
     88        } else {
     89            wp_die();
     90            return;
     91        }
     92
     93        $handler->handle_api_fallback();
    6694    }
    6795
     
    136164
    137165        // Prepare tracking configuration
    138         $hasCrossDomains = isset($base_settings['cross_domain']) && $base_settings['cross_domain'];
     166        $cross_domain_value = isset($base_settings['cross_domain']) ? $base_settings['cross_domain'] : '';
     167        $hasCrossDomains = ! empty($cross_domain_value);
    139168        $cross_domains = $hasCrossDomains ? wp_json_encode(
    140             array_map(
    141                 'trim',
    142                 explode(',', isset($base_settings['cross_domain']) ? $base_settings['cross_domain'] : '')
    143             )
     169            array_map('trim', explode(',', $cross_domain_value))
    144170        ) : '';
    145171        $force_new_cookies_on_refresh = isset($base_settings['force_new_cookies_on_refresh']) && $base_settings['force_new_cookies_on_refresh'];
     
    181207        $integration_settings = get_option('firstpromoter_integration_settings');
    182208
    183         // Load WooCommerce integration if WooCommerce is active and integration is enabled
     209        $integrations = array(
     210            'woocommerce'  => array('key' => 'woo_enabled',  'class' => 'FirstPromoter_Integration_WooCommerce'),
     211            'optimizepress' => array('key' => 'op_enabled',   'class' => 'FirstPromoter_Integration_OptimizePress'),
     212            'contactform7'  => array('key' => 'cf7_enabled',  'class' => 'FirstPromoter_Integration_ContactForm7'),
     213            'memberpress'   => array('key' => 'mepr_enabled', 'class' => 'FirstPromoter_Integration_MemberPress'),
     214        );
     215
     216        foreach ($integrations as $slug => $config) {
     217            if (
     218                isset($integration_settings[$config['key']]) &&
     219                $integration_settings[$config['key']] &&
     220                isset($this->available_integrations[$slug]) &&
     221                $this->available_integrations[$slug]
     222            ) {
     223                new $config['class']($integration_settings);
     224            }
     225        }
     226
     227        // Custom Forms integration - always available (no plugin dependency)
     228        if (defined('WP_DEBUG') && WP_DEBUG) {
     229            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
     230            error_log('FirstPromoter: Custom Forms Enabled Setting: ' . (isset($integration_settings['custom_forms_enabled']) ? $integration_settings['custom_forms_enabled'] : 'NOT SET'));
     231        }
     232
    184233        if (
    185             isset($integration_settings['woo_enabled']) &&
    186             $integration_settings['woo_enabled'] &&
    187             isset($this->available_integrations['woocommerce']) &&
    188             $this->available_integrations['woocommerce']
     234            isset($integration_settings['custom_forms_enabled']) &&
     235            $integration_settings['custom_forms_enabled']
    189236        ) {
    190             new FirstPromoter_Integration_WooCommerce($integration_settings);
    191         }
    192 
    193         // Load OptimizePress integration if OptimizePress is active and integration is enabled
    194         if (
    195             isset($integration_settings['op_enabled']) &&
    196             $integration_settings['op_enabled'] &&
    197             isset($this->available_integrations['optimizepress']) &&
    198             $this->available_integrations['optimizepress']
    199         ) {
    200             new FirstPromoter_Integration_OptimizePress($integration_settings);
    201         }
    202 
    203         // Load Contact Form 7 integration if Contact Form 7 is active and integration is enabled
    204         if (
    205             isset($integration_settings['cf7_enabled']) &&
    206             $integration_settings['cf7_enabled'] &&
    207             isset($this->available_integrations['contactform7']) &&
    208             $this->available_integrations['contactform7']
    209         ) {
    210             new FirstPromoter_Integration_ContactForm7($integration_settings);
    211         }
    212 
    213         // Load MemberPress integration if MemberPress is active and integration is enabled
    214         if (
    215             isset($integration_settings['mepr_enabled']) &&
    216             $integration_settings['mepr_enabled'] &&
    217             isset($this->available_integrations['memberpress']) &&
    218             $this->available_integrations['memberpress']
    219         ) {
    220             new FirstPromoter_Integration_MemberPress($integration_settings);
    221         }
    222 
    223         // Allow other integrations to be added
     237            if (defined('WP_DEBUG') && WP_DEBUG) {
     238                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
     239                error_log('FirstPromoter: Custom Forms Integration Initializing...');
     240            }
     241            new FirstPromoter_Integration_CustomForms($integration_settings);
     242        }
     243
    224244        do_action('firstpromoter_load_integrations', $integration_settings, $this->available_integrations);
    225245    }
     
    238258        }
    239259
    240         $param_names = ['fpr', 'via', '_fprom'];
     260        $param_names = array('fpr', 'via', '_fprom');
    241261        $ref_id = null;
    242262
     
    251271
    252272        if (!empty($ref_id) && empty($_COOKIE['_fprom_ref'])) {
    253             // Set cookies with appropriate expiration (30 days)
    254273            $expiry = time() + (30 * DAY_IN_SECONDS);
    255             $path = '/';
    256             $domain = '';
    257274            $secure = is_ssl();
    258             $httponly = true;
    259 
    260             setcookie('_fprom_ref_fallback', $ref_id, $expiry, $path, $domain, $secure, $httponly);
     275
     276            if (PHP_VERSION_ID >= 70300) {
     277                setcookie('_fprom_ref_fallback', $ref_id, array(
     278                    'expires'  => $expiry,
     279                    'path'     => '/',
     280                    'domain'   => '',
     281                    'secure'   => $secure,
     282                    'httponly'  => true,
     283                    'samesite' => 'Lax',
     284                ));
     285            } else {
     286                setcookie('_fprom_ref_fallback', $ref_id, $expiry, '/; SameSite=Lax', '', $secure, true);
     287            }
    261288        }
    262289    }
  • firstpromoter/trunk/includes/class-fp-helpers.php

    r3399288 r3458122  
    1414    {
    1515        // Set default field parameters
    16         $field = wp_parse_args($field, [
     16        $field = wp_parse_args($field, array(
    1717            'type' => 'text',
    1818            'id' => '',
     
    2323            'rows' => 4,
    2424            'placeholder' => ''
    25         ]);
     25        ));
    2626
    2727        // Escape attributes
  • firstpromoter/trunk/includes/class-fp-settings-page.php

    r3402820 r3458122  
    55    private $base_settings;
    66    private $integration_settings;
    7     private $availableIntegrations = [];
     7    private $availableIntegrations = array();
    88
    99    public function __construct()
    1010    {
    1111        include_once plugin_dir_path(__FILE__) . 'class-fp-helpers.php';
    12         add_action('admin_menu', [$this, 'add_settings_page']);
    13         add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
    14         add_action('admin_init', [$this, 'register_settings']);
    15         add_action('admin_notices', [$this, 'show_api_key_notices']);
     12        add_action('admin_menu', array($this, 'add_settings_page'));
     13        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
     14        add_action('admin_init', array($this, 'register_settings'));
     15        add_action('admin_notices', array($this, 'show_api_key_notices'));
    1616
    1717        $this->base_settings        = get_option('firstpromoter_base_settings');
     
    9696            ];
    9797        }
     98
     99        // Custom Form Tracking - always available (no plugin dependency)
     100        $this->availableIntegrations['CustomForms'] = [
     101            'title'    => 'Custom Form Tracking',
     102            'settings' => [
     103                'enabled_key' => 'custom_forms_enabled',
     104            ],
     105            'options'       => [],
     106            'custom_content' => true,
     107            'description'   => 'Track email submissions from any form on your site using CSS selectors. Works with any form builder, custom HTML forms, or dynamically loaded popups.',
     108        ];
    98109    }
    99110
     
    107118            'manage_options',
    108119            'first-promoter',
    109             [$this, 'render_settings_page'],
     120            array($this, 'render_settings_page'),
    110121            'data:image/svg+xml;base64,' . $base64
    111122        );
     
    121132            'first-promoter-admin',
    122133            plugins_url('assets/css/admin.css', dirname(__FILE__)),
    123             [],
    124             '1.0.0'
     134            array(),
     135            FIRSTPROMOTER_VERSION
    125136        );
    126137
     
    128139            'first-promoter-admin',
    129140            plugins_url('assets/js/admin.js', dirname(__FILE__)),
    130             ['jquery'],
    131             '1.0.0',
     141            array('jquery'),
     142            FIRSTPROMOTER_VERSION,
    132143            true
    133144        );
     
    177188                                        id="firstpromoter_cid"
    178189                                        name="firstpromoter_base_settings[cid]"
    179                                         value="<?php echo esc_attr($this->base_settings['cid'] ?? ''); ?>"
     190                                        value="<?php echo esc_attr((isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '')); ?>"
    180191                                        class="fp-input regular-text"
    181192                                        required>
     
    192203                                        id="firstpromoter_api_key"
    193204                                        name="firstpromoter_base_settings[api_key]"
    194                                         value="<?php echo esc_attr($this->base_settings['api_key'] ?? ''); ?>"
     205                                        value="<?php echo esc_attr((isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '')); ?>"
    195206                                        class="fp-input regular-text"
    196207                                        required>
     
    209220                                        name="firstpromoter_base_settings[cross_domain]"
    210221                                        placeholder="mywebsite.com,myotherwebsite.com"
    211                                         value="<?php echo esc_attr($this->base_settings['cross_domain'] ?? ''); ?>"
     222                                        value="<?php echo esc_attr((isset($this->base_settings['cross_domain']) ? $this->base_settings['cross_domain'] : '')); ?>"
    212223                                        class="fp-input regular-text">
    213224                                    <p class="description">This is useful when you are tracking accross multiple domains. For example mywebsite.com and myotherwebsite.com. <br> Please enter the domains separated with a comma.<br> This is not needed if you are just using subdomains.</p>
     
    290301                                        </div>
    291302
    292                                         <div class="fp-integration-option-details">
     303                                        <?php $is_enabled = isset($this->integration_settings[$integration['settings']['enabled_key']]) && $this->integration_settings[$integration['settings']['enabled_key']]; ?>
     304                                        <div class="fp-integration-option-details"<?php if (!$is_enabled) echo ' style="display:none"'; ?>>
    293305                                            <?php if (isset($integration['description']) && !empty($integration['description'])): ?>
    294306                                                <p class="description" style="margin: 12px 0 8px 0; color: #666;">
     
    299311                                                <?php
    300312                                                foreach ($integration["options"] as $option) {
    301                                                     $value = $this->integration_settings[$option['id']] ?? '';
     313                                                    $value = isset($this->integration_settings[$option['id']]) ? $this->integration_settings[$option['id']] : '';
    302314                                                    FirstPromoter_Helpers::firstpromoter_render_field($option, $value);
    303315                                                }
    304316                                                ?>
    305317                                            </div>
     318
     319                                            <?php if (!empty($integration['custom_content'])): ?>
     320                                                <?php
     321                                                $saved_forms = isset($this->integration_settings['custom_forms']) ? $this->integration_settings['custom_forms'] : array();
     322                                                if (empty($saved_forms)) {
     323                                                    $saved_forms = array(array('email_selector' => '', 'submit_selector' => ''));
     324                                                }
     325                                                $has_multiple = count($saved_forms) > 1;
     326                                                ?>
     327                                                <div class="fp-custom-forms-repeater">
     328                                                    <p class="firstpromoter_label" style="margin-top: 12px;">Form Selectors</p>
     329                                                    <div class="fp-custom-form-rows">
     330                                                        <?php foreach ($saved_forms as $idx => $form_row): ?>
     331                                                            <div class="fp-custom-form-row"<?php if ($idx > 0 && !$has_multiple) echo ' style="display:none"'; ?>>
     332                                                                <div class="fp-custom-form-fields">
     333                                                                    <input type="text"
     334                                                                        name="firstpromoter_integration_settings[custom_forms][<?php echo (int) $idx; ?>][email_selector]"
     335                                                                        value="<?php echo esc_attr(isset($form_row['email_selector']) ? $form_row['email_selector'] : ''); ?>"
     336                                                                        placeholder="Email input selector, e.g. #signup-email"
     337                                                                        class="fp-input regular-text">
     338                                                                    <input type="text"
     339                                                                        name="firstpromoter_integration_settings[custom_forms][<?php echo (int) $idx; ?>][submit_selector]"
     340                                                                        value="<?php echo esc_attr(isset($form_row['submit_selector']) ? $form_row['submit_selector'] : ''); ?>"
     341                                                                        placeholder="Submit selector, e.g. #signup-btn or form.my-form"
     342                                                                        class="fp-input regular-text">
     343                                                                </div>
     344                                                                <button type="button" class="fp-remove-form-row" title="Remove row"<?php if (!$has_multiple) echo ' style="display:none"'; ?>>&times;</button>
     345                                                            </div>
     346                                                        <?php endforeach; ?>
     347                                                    </div>
     348                                                    <div class="fp-multiple-forms-toggle" style="margin-top: 8px;">
     349                                                        <label style="display: flex; align-items: center; gap: 8px; font-size: 14px; color: #374151;">
     350                                                            <input type="checkbox" class="fp-multiple-forms-checkbox" <?php checked($has_multiple); ?>>
     351                                                            I have multiple forms to track
     352                                                        </label>
     353                                                    </div>
     354                                                    <button type="button" class="fp-add-form-row"<?php if (!$has_multiple) echo ' style="display:none"'; ?>>+ Add Another Form</button>
     355                                                    <p class="description" style="margin-top: 10px;">
     356                                                        Enter a CSS selector for the email input and either a form element or submit button.<br>
     357                                                        Examples: <code>#signup-email</code> / <code>#signup-btn</code> &mdash; <code>.popup-email</code> / <code>form.popup-form</code>
     358                                                    </p>
     359                                                </div>
     360                                            <?php endif; ?>
    306361                                        </div>
    307362                                    </div>
     
    322377    public function sanitize_integration_settings($input)
    323378    {
    324         $sanitized = [];
     379        $sanitized = array();
    325380
    326381        // Sanitize toggle checkboxes
    327         $toggle_keys = ['woo_enabled', 'op_enabled', 'cf7_enabled', 'mepr_enabled'];
     382        $toggle_keys = ['woo_enabled', 'op_enabled', 'cf7_enabled', 'mepr_enabled', 'custom_forms_enabled'];
    328383        foreach ($toggle_keys as $key) {
    329384            $sanitized[$key] = isset($input[$key]) ? 1 : 0;
     
    342397        }
    343398
     399        // Sanitize custom form selectors
     400        $sanitized['custom_forms'] = array();
     401        if (isset($input['custom_forms']) && is_array($input['custom_forms'])) {
     402            foreach ($input['custom_forms'] as $row) {
     403                $email_sel  = isset($row['email_selector']) ? sanitize_text_field($row['email_selector']) : '';
     404                $submit_sel = isset($row['submit_selector']) ? sanitize_text_field($row['submit_selector']) : '';
     405                if ($email_sel !== '' || $submit_sel !== '') {
     406                    $sanitized['custom_forms'][] = array(
     407                        'email_selector'  => $email_sel,
     408                        'submit_selector' => $submit_sel,
     409                    );
     410                }
     411            }
     412        }
     413
    344414        // Validate API key requirements
    345415        $base_settings = get_option('firstpromoter_base_settings');
     
    347417
    348418        if (empty($api_key)) {
    349             $api_required_features = [];
     419            $api_required_features = array();
    350420
    351421            // Check if Contact Form 7 is enabled
     
    383453    public function sanitize_base_settings($input)
    384454    {
    385         $sanitized = [];
     455        $sanitized = array();
    386456
    387457        // Sanitize CID (required)
    388         $sanitized['cid'] = sanitize_text_field($input['cid'] ?? '');
     458        $sanitized['cid'] = sanitize_text_field(isset($input['cid']) ? $input['cid'] : '');
    389459
    390460        // Sanitize API key (sensitive data)
    391         $sanitized['api_key'] = sanitize_text_field($input['api_key'] ?? '');
     461        $sanitized['api_key'] = sanitize_text_field(isset($input['api_key']) ? $input['api_key'] : '');
    392462
    393463        // Sanitize cross domain
    394         $sanitized['cross_domain'] = sanitize_text_field($input['cross_domain'] ?? '');
     464        $sanitized['cross_domain'] = sanitize_text_field(isset($input['cross_domain']) ? $input['cross_domain'] : '');
    395465
    396466        // Sanitize checkbox
     
    408478            'firstpromoter_base_settings',
    409479            'firstpromoter_base_settings',
    410             [$this, 'sanitize_base_settings']
     480            array($this, 'sanitize_base_settings')
    411481        );
    412482
     
    415485            'firstpromoter_integration_settings',
    416486            'firstpromoter_integration_settings',
    417             [$this, 'sanitize_integration_settings']
     487            array($this, 'sanitize_integration_settings')
    418488        );
    419489    }
     
    433503
    434504        if (empty($api_key)) {
    435             $requires_api = [];
     505            $requires_api = array();
    436506
    437507            // Check Contact Form 7
  • firstpromoter/trunk/integrations/class-fp-integration-base.php

    r3404859 r3458122  
    1616        $this->integration_settings = $integration_settings;
    1717        $this->base_settings        = get_option('firstpromoter_base_settings');
    18         add_action('wp_ajax_nopriv_firstpromoter_track_api_fallback', [$this, 'handle_api_fallback']);
    19         add_action('wp_ajax_firstpromoter_track_api_fallback', [$this, 'handle_api_fallback']);
    2018        $this->init();
    2119    }
    2220
    2321    abstract protected function init();
     22
     23    /**
     24     * Log a debug message if WP_DEBUG is enabled
     25     *
     26     * @param string $message Message to log
     27     */
     28    protected function log($message)
     29    {
     30        if (defined('WP_DEBUG') && WP_DEBUG) {
     31            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
     32            error_log('FirstPromoter: ' . $message);
     33        }
     34    }
    2435
    2536    protected function track_referral($email, $userId = null)
     
    3546            }
    3647
    37             $data = [
     48            $data = array(
    3849                'email' => $email,
    39             ];
     50            );
    4051
    4152            if (! empty($userId)) {
     
    4556            // Check if wp_enqueue_scripts has already fired
    4657            if (did_action('wp_enqueue_scripts')) {
    47                 // Too late for wp_enqueue_scripts, use wp_footer instead
    4858                add_action('wp_footer', function () use ($data) {
    4959                    $this->add_referral_tracking_scripts($data);
    5060                }, 20);
    5161            } else {
    52                 // Normal case - wp_enqueue_scripts hasn't fired yet
    5362                add_action('wp_enqueue_scripts', function () use ($data) {
    5463                    $this->add_referral_tracking_scripts($data);
     
    6978
    7079            if ($has_fallback_cookie && ! $has_main_ref_cookie) {
    71                 // Fallback cookie exists but main cookie doesn't - ad blocker likely blocking cookies
    72                 // Make direct server-side API call
    73                 if (defined('WP_DEBUG') && WP_DEBUG) {
    74                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    75                     error_log('FirstPromoter: Fallback cookie detected without main cookies. Making direct API call for signup tracking.');
    76                 }
     80                $this->log('Fallback cookie detected without main cookies. Making direct API call for signup tracking.');
    7781                $this->track_signup_via_api($email, $userId);
    7882                return;
     
    8589
    8690            // No cookies at all - add JavaScript fallback that will check again client-side
    87             // Check if wp_enqueue_scripts has already fired for fallback too
    88             if (did_action('wp_enqueue_scripts')) {
    89                 add_action('wp_footer', function () use ($email, $userId) {
    90                     wp_enqueue_script('firstpromoter-ajax-fallback', '', [], FIRSTPROMOTER_VERSION, true);
    91 
    92                     // Localize script for AJAX URL and nonce
    93                     wp_localize_script('firstpromoter-ajax-fallback', 'firstpromoter_ajax', [
    94                         'ajax_url' => admin_url('admin-ajax.php'),
    95                         'nonce'    => wp_create_nonce('firstpromoter_ajax'),
    96                     ]);
    97 
    98                     $inline_script = '
    99                         (function() {
    100                             setTimeout(function() {
    101                                 if (window.fprom_loaded) {
    102                                     return;
    103                                 }
    104                                 var xhr = new XMLHttpRequest();
    105                                 xhr.open("POST", firstpromoter_ajax.ajax_url, true);
    106                                 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    107                                 xhr.send("action=firstpromoter_track_api_fallback&email=' . esc_js($email) . '&uid=' . esc_js($userId) . '&nonce=" + firstpromoter_ajax.nonce);
    108                             }, 100);
    109                         })();
    110                     ';
    111 
    112                     wp_add_inline_script('firstpromoter-ajax-fallback', $inline_script);
    113                 }, 30);
    114             } else {
    115                 add_action('wp_enqueue_scripts', function () use ($email, $userId) {
    116                     wp_enqueue_script('firstpromoter-ajax-fallback', '', [], FIRSTPROMOTER_VERSION, true);
    117 
    118                     // Localize script for AJAX URL and nonce
    119                     wp_localize_script('firstpromoter-ajax-fallback', 'firstpromoter_ajax', [
    120                         'ajax_url' => admin_url('admin-ajax.php'),
    121                         'nonce'    => wp_create_nonce('firstpromoter_ajax'),
    122                     ]);
    123 
    124                     $inline_script = '
    125                         (function() {
    126                             setTimeout(function() {
    127                                 if (window.fprom_loaded) {
    128                                     return;
    129                                 }
    130                                 var xhr = new XMLHttpRequest();
    131                                 xhr.open("POST", firstpromoter_ajax.ajax_url, true);
    132                                 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    133                                 xhr.send("action=firstpromoter_track_api_fallback&email=' . esc_js($email) . '&uid=' . esc_js($userId) . '&nonce=" + firstpromoter_ajax.nonce);
    134                             }, 100);
    135                         })();
    136                     ';
    137 
    138                     wp_add_inline_script('firstpromoter-ajax-fallback', $inline_script);
    139                 }, 30);
    140             }
    141         } catch (\Exception $e) {
    142             if (defined('WP_DEBUG') && WP_DEBUG) {
    143                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    144                 error_log('FirstPromoter track_referral error: ' . $e->getMessage());
    145             }
    146             // Silently fail to ensure application functionality is not affected
     91            $this->enqueue_ajax_fallback($email, $userId);
     92        } catch (\Exception $e) {
     93            $this->log('track_referral error: ' . $e->getMessage());
     94        }
     95    }
     96
     97    /**
     98     * Enqueue the AJAX fallback script for ad-blocker bypass
     99     *
     100     * @param string $email
     101     * @param mixed $userId
     102     */
     103    private function enqueue_ajax_fallback($email, $userId)
     104    {
     105        $callback = function () use ($email, $userId) {
     106            wp_enqueue_script('firstpromoter-ajax-fallback', '', array(), FIRSTPROMOTER_VERSION, true);
     107
     108            wp_localize_script('firstpromoter-ajax-fallback', 'firstpromoter_ajax', array(
     109                'ajax_url' => admin_url('admin-ajax.php'),
     110                'nonce'    => wp_create_nonce('firstpromoter_ajax'),
     111            ));
     112
     113            $inline_script = '
     114                (function() {
     115                    setTimeout(function() {
     116                        if (window.fprom_loaded) {
     117                            return;
     118                        }
     119                        var xhr = new XMLHttpRequest();
     120                        xhr.open("POST", firstpromoter_ajax.ajax_url, true);
     121                        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
     122                        xhr.send("action=firstpromoter_track_api_fallback&email=' . esc_js($email) . '&uid=' . esc_js($userId) . '&nonce=" + firstpromoter_ajax.nonce);
     123                    }, 100);
     124                })();
     125            ';
     126
     127            wp_add_inline_script('firstpromoter-ajax-fallback', $inline_script);
     128        };
     129
     130        if (did_action('wp_enqueue_scripts')) {
     131            add_action('wp_footer', $callback, 30);
     132        } else {
     133            add_action('wp_enqueue_scripts', $callback, 30);
    147134        }
    148135    }
     
    166153            // Check if we're being called from wp_footer (after wp_enqueue_scripts)
    167154            if (doing_action('wp_footer')) {
    168                 // Output script directly since we're already in the footer
    169155                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $json_data is already safely encoded via wp_json_encode()
    170156                echo '<script type="text/javascript">fpr("referral", ' . $json_data . ');</script>' . "\n";
    171157            } else {
    172                 // Normal enqueue method
    173                 wp_enqueue_script('firstpromoter-referral-tracking', '', [], FIRSTPROMOTER_VERSION, true);
     158                wp_enqueue_script('firstpromoter-referral-tracking', '', array(), FIRSTPROMOTER_VERSION, true);
    174159                $inline_script = 'fpr("referral", ' . $json_data . ');';
    175160                wp_add_inline_script('firstpromoter-referral-tracking', $inline_script);
    176161            }
    177162        } catch (\Exception $e) {
    178             if (defined('WP_DEBUG') && WP_DEBUG) {
    179                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    180                 error_log('FirstPromoter add_referral_tracking_scripts error: ' . $e->getMessage());
    181             }
    182             // Silently fail to ensure application functionality is not affected
    183         }
     163            $this->log('add_referral_tracking_scripts error: ' . $e->getMessage());
     164        }
     165    }
     166
     167    /**
     168     * Make a POST request to the FirstPromoter API
     169     *
     170     * @param string $endpoint API endpoint path (e.g. "/track/sale")
     171     * @param array $data Request body data
     172     * @return array|false Decoded response body on success, false on failure
     173     */
     174    protected function api_post($endpoint, $data)
     175    {
     176        $apiKey    = isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '';
     177        $accountId = isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '';
     178
     179        if (empty($apiKey) || empty($accountId)) {
     180            $this->log('API key or Account ID missing for v2 API');
     181            return false;
     182        }
     183
     184        $url = $this->base_api_v2_url . $endpoint;
     185
     186        $this->log('Sending POST to ' . $url . ' with data: ' . wp_json_encode($data));
     187
     188        $response = wp_remote_post($url, array(
     189            'body'    => wp_json_encode($data),
     190            'headers' => array(
     191                'Authorization' => 'Bearer ' . $apiKey,
     192                'Account-ID'    => $accountId,
     193                'Content-Type'  => 'application/json',
     194            ),
     195            'timeout' => 30,
     196        ));
     197
     198        if (is_wp_error($response)) {
     199            $this->log('API request error: ' . $response->get_error_message());
     200            return false;
     201        }
     202
     203        $status_code = wp_remote_retrieve_response_code($response);
     204        $body        = wp_remote_retrieve_body($response);
     205
     206        $this->log('API response status: ' . $status_code);
     207
     208        if ($status_code < 200 || $status_code >= 300) {
     209            $this->log('API returned error status ' . $status_code . ': ' . $body);
     210            return false;
     211        }
     212
     213        return array(
     214            'status_code' => $status_code,
     215            'body'        => $body,
     216        );
    184217    }
    185218
     
    187220    {
    188221        try {
    189             if (defined('WP_DEBUG') && WP_DEBUG) {
    190                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    191                 error_log('FirstPromoter: track_sale_via_api called with email: ' . $email . ', uid: ' . ($uid ?: 'none') . ', event_id: ' . $event_id);
    192             }
    193 
    194             $apiKey    = isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '';
    195             $accountId = isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '';
    196 
    197             if (defined('WP_DEBUG') && WP_DEBUG) {
    198                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    199                 error_log('FirstPromoter: API Key present: ' . (! empty($apiKey) ? 'YES' : 'NO') . ', Account ID present: ' . (! empty($accountId) ? 'YES' : 'NO'));
    200             }
    201 
    202             if (empty($apiKey) || empty($accountId)) {
    203                 if (defined('WP_DEBUG') && WP_DEBUG) {
    204                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    205                     error_log('FirstPromoter: API key or Account ID missing for v2 API');
    206                 }
    207                 return false;
    208             }
    209 
    210             $data = array_filter([
     222            $this->log('track_sale_via_api called with email: ' . $email . ', uid: ' . ($uid ? $uid : 'none') . ', event_id: ' . $event_id);
     223
     224            $data = array_filter(array(
    211225                'email'      => $email,
    212226                'uid'        => isset($uid) && $uid != 0 ? (string) $uid : null,
     
    217231                'tid'        => ! empty($tid) ? $tid : null,
    218232                'ref_id'     => ! empty($ref_id) ? $ref_id : null,
    219             ], function ($value) {
     233            ), function ($value) {
    220234                return $value !== null;
    221235            });
    222236
    223             $baseUrl = $this->base_api_v2_url . "/track/sale";
    224 
    225             if (defined('WP_DEBUG') && WP_DEBUG) {
    226                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    227                 error_log('FirstPromoter: Sending POST to ' . $baseUrl . ' with data: ' . wp_json_encode($data));
    228             }
    229 
    230             $client   = new \GuzzleHttp\Client();
    231             $response = $client->request('POST', $baseUrl, [
    232                 'json'        => $data,
    233                 'headers'     => [
    234                     'Authorization' => 'Bearer ' . $apiKey,
    235                     'Account-ID'    => $accountId,
    236                     'Content-Type'  => 'application/json',
    237                 ],
    238                 'timeout'     => 30,
    239                 'http_errors' => true,
    240             ]);
    241 
    242             if (defined('WP_DEBUG') && WP_DEBUG) {
    243                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    244                 error_log('FirstPromoter: API request successful. Status code: ' . $response->getStatusCode());
    245                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    246                 error_log('FirstPromoter: API response body: ' . $response->getBody()->getContents());
    247                 $response->getBody()->rewind(); // Reset stream for later use
    248             }
    249 
    250             return $response;
    251         } catch (\GuzzleHttp\Exception\RequestException $e) {
    252             if (defined('WP_DEBUG') && WP_DEBUG) {
    253                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    254                 error_log('FirstPromoter sale tracking RequestException: ' . $e->getMessage());
    255                 if ($e->hasResponse()) {
    256                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    257                     error_log('FirstPromoter API error response: ' . $e->getResponse()->getBody()->getContents());
    258                 }
    259             }
    260             return $e;
    261         } catch (\Exception $e) {
    262             if (defined('WP_DEBUG') && WP_DEBUG) {
    263                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    264                 error_log('FirstPromoter sale tracking Exception: ' . $e->getMessage());
    265             }
    266             return $e;
     237            return $this->api_post('/track/sale', $data);
     238        } catch (\Exception $e) {
     239            $this->log('sale tracking error: ' . $e->getMessage());
     240            return false;
    267241        }
    268242    }
     
    271245    {
    272246        try {
    273             if (defined('WP_DEBUG') && WP_DEBUG) {
    274                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    275                 error_log('FirstPromoter: track_signup_via_api called with email: ' . $email . ', uid: ' . ($uid ?: 'none'));
    276             }
    277 
    278             $apiKey    = isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '';
    279             $accountId = isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '';
    280 
    281             if (defined('WP_DEBUG') && WP_DEBUG) {
    282                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    283                 error_log('FirstPromoter: API Key present: ' . (! empty($apiKey) ? 'YES' : 'NO') . ', Account ID present: ' . (! empty($accountId) ? 'YES' : 'NO'));
    284             }
    285 
    286             if (empty($apiKey) || empty($accountId)) {
    287                 if (defined('WP_DEBUG') && WP_DEBUG) {
    288                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    289                     error_log('FirstPromoter: API key or Account ID missing for v2 API');
    290                 }
    291                 return false;
    292             }
     247            $this->log('track_signup_via_api called with email: ' . $email . ', uid: ' . ($uid ? $uid : 'none'));
    293248
    294249            $tid    = isset($_COOKIE["_fprom_tid"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_tid"])) : null;
    295250            $ref_id = isset($_COOKIE["_fprom_ref"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_ref"])) : (isset($_COOKIE["_fprom_ref_fallback"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_ref_fallback"])) : null);
    296251
    297             if (defined('WP_DEBUG') && WP_DEBUG) {
    298                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    299                 error_log('FirstPromoter: Cookies - tid: ' . ($tid ?: 'none') . ', ref_id: ' . ($ref_id ?: 'none'));
    300             }
    301 
    302             $data = array_filter([
     252            $this->log('Cookies - tid: ' . ($tid ? $tid : 'none') . ', ref_id: ' . ($ref_id ? $ref_id : 'none'));
     253
     254            $data = array_filter(array(
    303255                'email'  => $email,
    304256                'uid'    => isset($uid) && $uid != 0 ? (string) $uid : null,
    305257                'tid'    => $tid,
    306258                'ref_id' => $ref_id,
    307             ], function ($value) {
     259            ), function ($value) {
    308260                return $value !== null;
    309261            });
    310262
    311             $baseUrl = $this->base_api_v2_url . "/track/signup";
    312 
    313             if (defined('WP_DEBUG') && WP_DEBUG) {
    314                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    315                 error_log('FirstPromoter: Sending POST to ' . $baseUrl . ' with data: ' . wp_json_encode($data));
    316             }
    317 
    318             $client   = new \GuzzleHttp\Client();
    319             $response = $client->request('POST', $baseUrl, [
    320                 'json'        => $data,
    321                 'headers'     => [
    322                     'Authorization' => 'Bearer ' . $apiKey,
    323                     'Account-ID'    => $accountId,
    324                     'Content-Type'  => 'application/json',
    325                 ],
    326                 'timeout'     => 30,
    327                 'http_errors' => true,
    328             ]);
    329 
    330             if (defined('WP_DEBUG') && WP_DEBUG) {
    331                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    332                 error_log('FirstPromoter: Signup API request successful. Status code: ' . $response->getStatusCode());
    333                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    334                 error_log('FirstPromoter: Signup API response body: ' . $response->getBody()->getContents());
    335                 $response->getBody()->rewind(); // Reset stream for later use
    336             }
    337 
    338             return $response;
    339         } catch (\GuzzleHttp\Exception\RequestException $e) {
    340             if (defined('WP_DEBUG') && WP_DEBUG) {
    341                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    342                 error_log('FirstPromoter signup tracking RequestException: ' . $e->getMessage());
    343                 if ($e->hasResponse()) {
    344                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    345                     error_log('FirstPromoter Signup API error response: ' . $e->getResponse()->getBody()->getContents());
    346                 }
    347             }
    348             return $e;
    349         } catch (\Exception $e) {
    350             if (defined('WP_DEBUG') && WP_DEBUG) {
    351                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    352                 error_log('FirstPromoter signup tracking Exception: ' . $e->getMessage());
    353             }
    354             return $e;
     263            return $this->api_post('/track/signup', $data);
     264        } catch (\Exception $e) {
     265            $this->log('signup tracking error: ' . $e->getMessage());
     266            return false;
    355267        }
    356268    }
     
    359271    {
    360272        try {
    361             // Check nonce for security
    362273            if (! wp_verify_nonce(isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '', 'firstpromoter_ajax')) {
    363274                wp_die('Security check failed', 'Security Error', 403);
     
    371282            }
    372283        } catch (\Exception $e) {
    373             if (defined('WP_DEBUG') && WP_DEBUG) {
    374                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    375                 error_log('FirstPromoter handle_api_fallback error: ' . $e->getMessage());
    376             }
    377             // Silently fail to ensure application functionality is not affected
    378         }
    379 
    380         wp_die(); // Required to terminate properly
     284            $this->log('handle_api_fallback error: ' . $e->getMessage());
     285        }
     286
     287        wp_die();
    381288    }
    382289
     
    391298     * @param string $sale_event_id Original sale event ID
    392299     * @param bool $skip_email_notification Whether to skip email notifications
    393      * @return mixed Response or error
     300     * @return array|false Response data on success, false on failure
    394301     */
    395302    protected function track_refund_via_api($email, $uid, $event_id, $amount, $quantity = '', $sale_event_id = '', $skip_email_notification = true)
    396303    {
    397304        try {
    398             $apiKey    = isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '';
    399             $accountId = isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '';
    400 
    401             if (empty($apiKey) || empty($accountId)) {
    402                 if (defined('WP_DEBUG') && WP_DEBUG) {
    403                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    404                     error_log('FirstPromoter: API key or Account ID missing for v2 API');
    405                 }
    406                 return false;
    407             }
    408 
    409             $data = array_filter([
     305            $data = array_filter(array(
    410306                'email'                   => $email,
    411307                'uid'                     => isset($uid) && $uid != 0 ? (string) $uid : null,
     
    415311                'sale_event_id'           => ! empty($sale_event_id) ? $sale_event_id : null,
    416312                'skip_email_notification' => $skip_email_notification,
    417             ], function ($value) {
     313            ), function ($value) {
    418314                return $value !== null;
    419315            });
    420316
    421             $baseUrl = $this->base_api_v2_url . "/track/refund";
    422 
    423             $client   = new \GuzzleHttp\Client();
    424             $response = $client->request('POST', $baseUrl, [
    425                 'json'        => $data,
    426                 'headers'     => [
    427                     'Authorization' => 'Bearer ' . $apiKey,
    428                     'Account-ID'    => $accountId,
    429                     'Content-Type'  => 'application/json',
    430                 ],
    431                 'timeout'     => 30,
    432                 'http_errors' => true,
    433             ]);
    434 
    435             return $response;
    436         } catch (\GuzzleHttp\Exception\RequestException $e) {
    437             if (defined('WP_DEBUG') && WP_DEBUG) {
    438                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    439                 error_log('FirstPromoter refund tracking error: ' . $e->getMessage());
    440             }
    441             return $e;
    442         } catch (\Exception $e) {
    443             if (defined('WP_DEBUG') && WP_DEBUG) {
    444                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    445                 error_log('FirstPromoter refund tracking error: ' . $e->getMessage());
    446             }
    447             return $e;
     317            return $this->api_post('/track/refund', $data);
     318        } catch (\Exception $e) {
     319            $this->log('refund tracking error: ' . $e->getMessage());
     320            return false;
    448321        }
    449322    }
     
    454327     * @param string $email Customer email
    455328     * @param string $uid Customer unique identifier
    456      * @return mixed Response or error
     329     * @return array|false Response data on success, false on failure
    457330     */
    458331    protected function track_cancellation_via_api($email, $uid)
    459332    {
    460333        try {
    461             $apiKey    = isset($this->base_settings['api_key']) ? $this->base_settings['api_key'] : '';
    462             $accountId = isset($this->base_settings['cid']) ? $this->base_settings['cid'] : '';
    463 
    464             if (empty($apiKey) || empty($accountId)) {
    465                 if (defined('WP_DEBUG') && WP_DEBUG) {
    466                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    467                     error_log('FirstPromoter: API key or Account ID missing for v2 API');
    468                 }
    469                 return false;
    470             }
    471 
    472             $data = array_filter([
     334            $data = array_filter(array(
    473335                'email' => $email,
    474336                'uid'   => isset($uid) && $uid != 0 ? (string) $uid : null,
    475             ], function ($value) {
     337            ), function ($value) {
    476338                return $value !== null;
    477339            });
    478340
    479             $baseUrl = $this->base_api_v2_url . "/track/cancellation";
    480 
    481             $client   = new \GuzzleHttp\Client();
    482             $response = $client->request('POST', $baseUrl, [
    483                 'json'        => $data,
    484                 'headers'     => [
    485                     'Authorization' => 'Bearer ' . $apiKey,
    486                     'Account-ID'    => $accountId,
    487                     'Content-Type'  => 'application/json',
    488                 ],
    489                 'timeout'     => 30,
    490                 'http_errors' => true,
    491             ]);
    492 
    493             return $response;
    494         } catch (\GuzzleHttp\Exception\RequestException $e) {
    495             if (defined('WP_DEBUG') && WP_DEBUG) {
    496                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    497                 error_log('FirstPromoter cancellation tracking error: ' . $e->getMessage());
    498             }
    499             return $e;
    500         } catch (\Exception $e) {
    501             if (defined('WP_DEBUG') && WP_DEBUG) {
    502                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    503                 error_log('FirstPromoter cancellation tracking error: ' . $e->getMessage());
    504             }
    505             return $e;
     341            return $this->api_post('/track/cancellation', $data);
     342        } catch (\Exception $e) {
     343            $this->log('cancellation tracking error: ' . $e->getMessage());
     344            return false;
    506345        }
    507346    }
  • firstpromoter/trunk/integrations/class-fp-integration-contactform7.php

    r3402820 r3458122  
    1212    {
    1313        if (isset($this->integration_settings['cf7_enabled']) && $this->integration_settings['cf7_enabled']) {
    14             // Hook to capture form data before it's sent
    15             add_filter('wpcf7_posted_data', [$this, 'save_contact_form_data']);
    16 
    17             // Hook to track referral after form is successfully submitted
    18             add_action('wpcf7_mail_sent', [$this, 'track_contact_form_submission']);
     14            add_filter('wpcf7_posted_data', array($this, 'save_contact_form_data'));
     15            add_action('wpcf7_mail_sent', array($this, 'track_contact_form_submission'));
    1916        }
    2017    }
     
    6764
    6865        if (empty($email)) {
    69             if (defined('WP_DEBUG') && WP_DEBUG) {
    70                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    71                 error_log('FirstPromoter CF7: No email found in form submission for form ID: ' . $contact_form->id());
    72             }
     66            $this->log('CF7: No email found in form submission for form ID: ' . $contact_form->id());
    7367            return;
    7468        }
    7569
    76         if (defined('WP_DEBUG') && WP_DEBUG) {
    77             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    78             error_log('FirstPromoter CF7: Processing form submission for email: ' . $email . ' from form ID: ' . $contact_form->id());
    79         }
     70        $this->log('CF7: Processing form submission for email: ' . $email . ' from form ID: ' . $contact_form->id());
    8071
    8172        // CF7 submissions are AJAX-based, so we need to use server-side API tracking
    82         // Get cookies for referral attribution
    8373        $ref_id = isset($_COOKIE["_fprom_ref"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_ref"])) : (isset($_COOKIE["_fprom_ref_fallback"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_ref_fallback"])) : null);
    8474        $tid = isset($_COOKIE["_fprom_tid"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_tid"])) : null;
    8575
    86         if (defined('WP_DEBUG') && WP_DEBUG) {
    87             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    88             error_log('FirstPromoter CF7: Cookies - ref_id: ' . ($ref_id ?: 'none') . ', tid: ' . ($tid ?: 'none'));
    89         }
     76        $this->log('CF7: Cookies - ref_id: ' . ($ref_id ? $ref_id : 'none') . ', tid: ' . ($tid ? $tid : 'none'));
    9077
    9178        // Only track if we have referral cookies (otherwise there's no attribution)
    9279        if (!empty($ref_id) || !empty($tid)) {
    93             // Track the signup via API
    9480            $response = $this->track_signup_via_api($email, null);
    95 
    96             if (defined('WP_DEBUG') && WP_DEBUG) {
    97                 if ($response && is_object($response) && method_exists($response, 'getStatusCode')) {
    98                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    99                     error_log('FirstPromoter CF7: Signup tracked successfully via API. Status: ' . $response->getStatusCode());
    100                 } else {
    101                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    102                     error_log('FirstPromoter CF7: API tracking failed or returned error');
    103                 }
    104             }
     81            $this->log('CF7: Signup tracking result: ' . ($response ? 'success' : 'failed'));
    10582        } else {
    106             if (defined('WP_DEBUG') && WP_DEBUG) {
    107                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    108                 error_log('FirstPromoter CF7: No referral cookies found - skipping API tracking (no attribution possible)');
    109             }
     83            $this->log('CF7: No referral cookies found - skipping API tracking (no attribution possible)');
    11084        }
    11185    }
  • firstpromoter/trunk/integrations/class-fp-integration-memberpress.php

    r3402820 r3458122  
    1111        if (isset($this->integration_settings['mepr_enabled']) && $this->integration_settings['mepr_enabled']) {
    1212            // Track new member signups
    13             add_action('mepr-signup', [$this, 'track_member_signup']);
     13            add_action('mepr-signup', array($this, 'track_member_signup'));
    1414
    1515            // Track subscription/transaction payments (for sales tracking)
    1616            if (isset($this->integration_settings['mepr_track_sales']) && $this->integration_settings['mepr_track_sales']) {
    17                 add_action('mepr-event-transaction-completed', [$this, 'track_transaction_sale'], 10, 1);
     17                add_action('mepr-event-transaction-completed', array($this, 'track_transaction_sale'), 10, 1);
    1818            }
    1919
    2020            // Track recurring payments
    2121            if (isset($this->integration_settings['mepr_track_recurring']) && $this->integration_settings['mepr_track_recurring']) {
    22                 add_action('mepr-event-recurring-transaction-completed', [$this, 'track_recurring_payment'], 10, 1);
     22                add_action('mepr-event-recurring-transaction-completed', array($this, 'track_recurring_payment'), 10, 1);
    2323            }
    2424        }
     
    3636        }
    3737
    38         // Get user information
    3938        $user = get_user_by('id', $txn->user_id);
    4039        if (!$user) {
     
    4645
    4746        if (empty($email)) {
    48             if (defined('WP_DEBUG') && WP_DEBUG) {
    49                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    50                 error_log('FirstPromoter MemberPress: No email found for user ID: ' . $user_id);
    51             }
     47            $this->log('MemberPress: No email found for user ID: ' . $user_id);
    5248            return;
    5349        }
    5450
    55         // Track the referral (no prefix needed for JavaScript tracking)
    5651        $this->track_referral($email, $user_id);
    57 
    58         if (defined('WP_DEBUG') && WP_DEBUG) {
    59             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    60             error_log('FirstPromoter MemberPress: Tracked signup for email: ' . $email . ', user ID: ' . $user_id);
    61         }
     52        $this->log('MemberPress: Tracked signup for email: ' . $email . ', user ID: ' . $user_id);
    6253    }
    6354
     
    7869        }
    7970
    80         // Get user information
    8171        $user = get_user_by('id', $txn->user_id);
    8272        if (!$user) {
     
    8676        $email = $user->user_email;
    8777        $user_id = $txn->user_id;
    88         $amount = $txn->amount * 100; // Convert to cents
     78        $amount = $txn->amount * 100;
    8979
    90         // Skip if amount is 0 (free trial or already processed)
    9180        if ($amount == 0) {
    92             if (defined('WP_DEBUG') && WP_DEBUG) {
    93                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    94                 error_log('FirstPromoter MemberPress: Skipping transaction with $0 amount. Transaction ID: ' . $txn->id);
    95             }
     81            $this->log('MemberPress: Skipping transaction with $0 amount. Transaction ID: ' . $txn->id);
    9682            return;
    9783        }
     
    10086        $tracked = get_post_meta($txn->id, 'firstpromoter_sale_tracked', true);
    10187        if ($tracked) {
    102             if (defined('WP_DEBUG') && WP_DEBUG) {
    103                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    104                 error_log('FirstPromoter MemberPress: Sale already tracked for transaction ID: ' . $txn->id);
    105             }
     88            $this->log('MemberPress: Sale already tracked for transaction ID: ' . $txn->id);
    10689            return;
    10790        }
    10891
    109         // Get product information
    11092        $product = $txn->product();
    11193        $plan = $product ? $product->post_title : '';
    11294
    113         // Create unique event ID
    11495        $event_id = 'mepr-' . $txn->id . '-' . time();
    11596
     
    11899        $tid = isset($_COOKIE["_fprom_tid"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_tid"])) : null;
    119100
    120         if (defined('WP_DEBUG') && WP_DEBUG) {
    121             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    122             error_log('FirstPromoter MemberPress: Tracking sale - Email: ' . $email . ', Amount: ' . $amount . ', Event ID: ' . $event_id);
    123         }
     101        $this->log('MemberPress: Tracking sale - Email: ' . $email . ', Amount: ' . $amount . ', Event ID: ' . $event_id);
    124102
    125         // Track the sale via API (prefix user_id for API tracking to avoid conflicts)
    126103        $prefixed_user_id = 'mepr-' . $user_id;
    127104        $response = $this->track_sale_via_api($email, $prefixed_user_id, $event_id, $amount, $plan, '', $tid, $ref_id);
    128105
    129         // Check if response is successful
    130         if ($response && is_object($response) && method_exists($response, 'getStatusCode')) {
    131             if ($response->getStatusCode() == 200) {
    132                 update_post_meta($txn->id, 'firstpromoter_sale_tracked', true);
    133                 update_post_meta($txn->id, 'firstpromoter_event_id', $event_id);
    134 
    135                 if (defined('WP_DEBUG') && WP_DEBUG) {
    136                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    137                     error_log('FirstPromoter MemberPress: Sale successfully tracked for transaction ID: ' . $txn->id);
    138                 }
    139             }
     106        if ($response && is_array($response) && isset($response['status_code']) && $response['status_code'] == 200) {
     107            update_post_meta($txn->id, 'firstpromoter_sale_tracked', true);
     108            update_post_meta($txn->id, 'firstpromoter_event_id', $event_id);
     109            $this->log('MemberPress: Sale successfully tracked for transaction ID: ' . $txn->id);
    140110        }
    141111    }
     
    157127        }
    158128
    159         // Get user information
    160129        $user = get_user_by('id', $txn->user_id);
    161130        if (!$user) {
     
    165134        $email = $user->user_email;
    166135        $user_id = $txn->user_id;
    167         $amount = $txn->amount * 100; // Convert to cents
     136        $amount = $txn->amount * 100;
    168137
    169         // Skip if amount is 0
    170138        if ($amount == 0) {
    171             if (defined('WP_DEBUG') && WP_DEBUG) {
    172                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    173                 error_log('FirstPromoter MemberPress: Skipping recurring payment with $0 amount. Transaction ID: ' . $txn->id);
    174             }
     139            $this->log('MemberPress: Skipping recurring payment with $0 amount. Transaction ID: ' . $txn->id);
    175140            return;
    176141        }
     
    179144        $tracked = get_post_meta($txn->id, 'firstpromoter_recurring_tracked', true);
    180145        if ($tracked) {
    181             if (defined('WP_DEBUG') && WP_DEBUG) {
    182                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    183                 error_log('FirstPromoter MemberPress: Recurring payment already tracked for transaction ID: ' . $txn->id);
    184             }
     146            $this->log('MemberPress: Recurring payment already tracked for transaction ID: ' . $txn->id);
    185147            return;
    186148        }
    187149
    188         // Get product information
    189150        $product = $txn->product();
    190151        $plan = $product ? $product->post_title : '';
    191152
    192         // Create unique event ID for recurring payment
    193153        $event_id = 'mepr-recurring-' . $txn->id . '-' . time();
    194154
     
    197157        $tid = isset($_COOKIE["_fprom_tid"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_tid"])) : null;
    198158
    199         if (defined('WP_DEBUG') && WP_DEBUG) {
    200             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    201             error_log('FirstPromoter MemberPress: Tracking recurring payment - Email: ' . $email . ', Amount: ' . $amount . ', Event ID: ' . $event_id);
    202         }
     159        $this->log('MemberPress: Tracking recurring payment - Email: ' . $email . ', Amount: ' . $amount . ', Event ID: ' . $event_id);
    203160
    204         // Track the recurring payment as a new sale (prefix user_id for API tracking to avoid conflicts)
    205161        $prefixed_user_id = 'mepr-' . $user_id;
    206162        $response = $this->track_sale_via_api($email, $prefixed_user_id, $event_id, $amount, $plan, '', $tid, $ref_id);
    207163
    208         // Check if response is successful
    209         if ($response && is_object($response) && method_exists($response, 'getStatusCode')) {
    210             if ($response->getStatusCode() == 200) {
    211                 update_post_meta($txn->id, 'firstpromoter_recurring_tracked', true);
    212                 update_post_meta($txn->id, 'firstpromoter_recurring_event_id', $event_id);
    213 
    214                 if (defined('WP_DEBUG') && WP_DEBUG) {
    215                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    216                     error_log('FirstPromoter MemberPress: Recurring payment successfully tracked for transaction ID: ' . $txn->id);
    217                 }
    218             }
     164        if ($response && is_array($response) && isset($response['status_code']) && $response['status_code'] == 200) {
     165            update_post_meta($txn->id, 'firstpromoter_recurring_tracked', true);
     166            update_post_meta($txn->id, 'firstpromoter_recurring_event_id', $event_id);
     167            $this->log('MemberPress: Recurring payment successfully tracked for transaction ID: ' . $txn->id);
    219168        }
    220169    }
  • firstpromoter/trunk/integrations/class-fp-integration-optimizepress.php

    r3399029 r3458122  
    11<?php
    22
    3     if (! defined('ABSPATH')) {
    4         exit;
     3if (! defined('ABSPATH')) {
     4    exit;
     5}
     6
     7class FirstPromoter_Integration_OptimizePress extends FirstPromoter_Integration_Base
     8{
     9    protected function init()
     10    {
     11        add_action('wp_enqueue_scripts', array($this, 'firstpromoter_track_op'), 30);
    512    }
    613
    7     class FirstPromoter_Integration_OptimizePress extends FirstPromoter_Integration_Base
     14    public function firstpromoter_track_op()
    815    {
    9         protected function init()
    10         {
    11             add_action('wp_enqueue_scripts', [$this, 'firstpromoter_track_op'], 30);
    12         }
     16        if ($this->integration_settings['op_enabled']) {
     17            wp_enqueue_script('firstpromoter-optimizepress', '', array(), FIRSTPROMOTER_VERSION, true);
    1318
    14         public function firstpromoter_track_op()
    15         {
    16             if ($this->integration_settings['op_enabled']) {
    17                 wp_enqueue_script('firstpromoter-optimizepress', '', array(), FIRSTPROMOTER_VERSION, true);
    18                
    19                 $inline_script = '
    20                     function initializeFPR() {
    21                         document.querySelectorAll("input[type=email]").forEach(function(emailInput) {
    22                             ["mousedown", "touchstart"].forEach(function(event) {
    23                                 emailInput.closest("form")
    24                                     .querySelector("[data-op3-element-type=button]")
    25                                     .addEventListener(event, function() {
    26                                         fpr("referral", {
    27                                             email: emailInput.value
    28                                         })
    29                                     });
    30                             })
     19            $inline_script = '
     20                function initializeFPR() {
     21                    document.querySelectorAll("input[type=email]").forEach(function(emailInput) {
     22                        var form = emailInput.closest("form");
     23                        if (!form) return;
     24                        var button = form.querySelector("[data-op3-element-type=button]");
     25                        if (!button) return;
     26                        ["mousedown", "touchstart"].forEach(function(event) {
     27                            button.addEventListener(event, function() {
     28                                fpr("referral", {
     29                                    email: emailInput.value
     30                                })
     31                            });
    3132                        })
    32                     }
    33                     if (window.attachEvent) {
    34                         window.attachEvent("onload", initializeFPR);
    35                     } else {
    36                         window.addEventListener("load", initializeFPR, false);
    37                     }
    38                 ';
    39                
    40                 wp_add_inline_script('firstpromoter-optimizepress', $inline_script);
    41             }
     33                    })
     34                }
     35                window.addEventListener("load", initializeFPR, false);
     36            ';
     37
     38            wp_add_inline_script('firstpromoter-optimizepress', $inline_script);
    4239        }
    4340    }
     41}
  • firstpromoter/trunk/integrations/class-fp-integration-woocommerce.php

    r3404859 r3458122  
    1010    {
    1111        if (isset($this->integration_settings['woo_signup']) && $this->integration_settings['woo_signup']) {
    12             add_filter('woocommerce_registration_redirect', [$this, 'firstpromoter_add_query_to_url']);
     12            add_filter('woocommerce_registration_redirect', array($this, 'firstpromoter_add_query_to_url'));
    1313            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Parameter added by WooCommerce redirect, nonce not applicable
    1414            if (isset($_GET['_new_reg']) && sanitize_text_field(wp_unslash($_GET['_new_reg'])) === 'true') {
    15                 add_action('template_redirect', [$this, 'firstpromoter_track_new_account']);
     15                add_action('template_redirect', array($this, 'firstpromoter_track_new_account'));
    1616            }
    1717        }
     
    1919        // Register checkout email capture hook if enabled
    2020        if (isset($this->integration_settings['woo_checkout']) && $this->integration_settings['woo_checkout']) {
    21             add_action('woocommerce_thankyou', [$this, 'track_checkout']);
    22         }
    23 
    24         // Register payment complete hook if either email capture OR sale tracking is enabled
    25         $email_capture_enabled = isset($this->integration_settings['woo_checkout']) && $this->integration_settings['woo_checkout'];
     21            add_action('woocommerce_thankyou', array($this, 'track_checkout'));
     22        }
     23
     24        // Register payment complete hook for sale tracking only
    2625        $sale_tracking_enabled = isset($this->integration_settings['woo_send_sale_to_firstpromoter_on_checkout']) && $this->integration_settings['woo_send_sale_to_firstpromoter_on_checkout'];
    2726
    28         if ($email_capture_enabled || $sale_tracking_enabled) {
    29             add_action('woocommerce_payment_complete', [$this, 'order_payment_successful'], 10, 1);
    30         }
    31 
    32         // Add hooks for refunds and cancellations if sale tracking is enabled
    3327        if ($sale_tracking_enabled) {
    34             add_action('woocommerce_order_refunded', [$this, 'track_refund'], 10, 2);
    35             add_action('woocommerce_order_status_cancelled', [$this, 'track_order_cancellation'], 10, 1);
     28            add_action('woocommerce_payment_complete', array($this, 'order_payment_successful'), 10, 1);
     29            add_action('woocommerce_order_refunded', array($this, 'track_refund'), 10, 2);
     30            add_action('woocommerce_order_status_cancelled', array($this, 'track_order_cancellation'), 10, 1);
    3631        }
    3732    }
     
    4035    {
    4136        if (wp_get_referer()) {
    42             $redirect = add_query_arg(['_new_reg' => 'true'], esc_url(wp_get_referer()));
     37            $redirect = add_query_arg(array('_new_reg' => 'true'), esc_url(wp_get_referer()));
    4338        } else {
    44             $redirect = add_query_arg(['_new_reg' => 'true'], esc_url(get_permalink(wc_get_page_id('myaccount'))));
     39            $redirect = add_query_arg(array('_new_reg' => 'true'), esc_url(get_permalink(wc_get_page_id('myaccount'))));
    4540        }
    4641        return $redirect;
    4742    }
    4843
    49     public function firstpromoter_track_new_account($customer_id)
     44    public function firstpromoter_track_new_account()
    5045    {
    5146        $customer_id = get_current_user_id();
     
    5550        if ($email) {
    5651            $this->track_referral($email, $customer_id);
    57         }
    58     }
    59 
    60     public function track_registration()
    61     {
    62         // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Parameter added by WooCommerce redirect, nonce not applicable
    63         if (isset($_GET['_new_reg']) && sanitize_text_field(wp_unslash($_GET['_new_reg'])) === 'true') {
    64             add_action('wp_footer', [$this, 'track']);
    6552        }
    6653    }
     
    8471            $order->save();
    8572        } catch (\Exception $e) {
    86             if (defined('WP_DEBUG') && WP_DEBUG) {
    87                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    88                 error_log('FirstPromoter WooCommerce track_checkout error: ' . $e->getMessage());
    89             }
    90             // Silently fail to ensure checkout process is not affected
     73            $this->log('WooCommerce track_checkout error: ' . $e->getMessage());
    9174        }
    9275    }
     
    9578    {
    9679        try {
    97             if (defined('WP_DEBUG') && WP_DEBUG) {
    98                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    99                 error_log('FirstPromoter: order_payment_successful hook fired for order #' . $order_id);
    100             }
     80            $this->log('order_payment_successful hook fired for order #' . $order_id);
    10181
    10282            $order = wc_get_order($order_id);
    10383            if (! $order) {
    104                 if (defined('WP_DEBUG') && WP_DEBUG) {
    105                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    106                     error_log('FirstPromoter: Order not found for order #' . $order_id);
    107                 }
    108                 return;
    109             }
    110 
    111             $email      = $order->get_billing_email();
    112             $customerId = $order->get_customer_id();
     84                $this->log('Order not found for order #' . $order_id);
     85                return;
     86            }
     87
    11388            $order_status = $order->get_status();
    11489
    115             if (defined('WP_DEBUG') && WP_DEBUG) {
    116                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    117                 error_log('FirstPromoter: Order #' . $order_id . ' status: ' . $order_status . ', email: ' . $email . ', customerId: ' . $customerId);
    118             }
    119 
    120             // Track referral for both processing and completed orders
    121             $valid_statuses = ['processing', 'completed'];
    122             if (in_array($order_status, $valid_statuses) && ($email || $customerId)) {
    123                 if (defined('WP_DEBUG') && WP_DEBUG) {
    124                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    125                     error_log('FirstPromoter: Tracking referral for order #' . $order_id);
    126                 }
    127                 $this->track_referral($email, $customerId);
    128             }
    129 
    130             $sale_tracking_enabled = isset($this->integration_settings['woo_send_sale_to_firstpromoter_on_checkout']) && $this->integration_settings['woo_send_sale_to_firstpromoter_on_checkout'];
    131 
    132             if (defined('WP_DEBUG') && WP_DEBUG) {
    133                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    134                 error_log('FirstPromoter: Sale tracking enabled: ' . ($sale_tracking_enabled ? 'YES' : 'NO'));
    135             }
    136 
    137             if ($sale_tracking_enabled) {
    138                 if (defined('WP_DEBUG') && WP_DEBUG) {
    139                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    140                     error_log('FirstPromoter: Calling track_sale for order #' . $order_id);
    141                 }
    142                 $this->track_sale($order_id);
    143             }
    144         } catch (\Exception $e) {
    145             // Log the error but don't break the payment process
    146             if (defined('WP_DEBUG') && WP_DEBUG) {
    147                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    148                 error_log('FirstPromoter: Critical error in order_payment_successful for order #' . $order_id . ': ' . $e->getMessage());
    149             }
    150             // Silently fail to ensure WooCommerce payment completion is not affected
     90            // Only track for valid order statuses
     91            $valid_statuses = array('processing', 'completed');
     92            if (! in_array($order_status, $valid_statuses)) {
     93                $this->log('Order #' . $order_id . ' has invalid status (' . $order_status . '). Not tracking sale.');
     94                return;
     95            }
     96
     97            $this->log('Calling track_sale for order #' . $order_id);
     98            $this->track_sale($order_id);
     99        } catch (\Exception $e) {
     100            $this->log('Critical error in order_payment_successful for order #' . $order_id . ': ' . $e->getMessage());
    151101        }
    152102    }
     
    155105    {
    156106        try {
    157             if (defined('WP_DEBUG') && WP_DEBUG) {
    158                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    159                 error_log('FirstPromoter: track_sale called for order #' . $order_id);
    160             }
     107            $this->log('track_sale called for order #' . $order_id);
    161108
    162109            $order = wc_get_order($order_id);
    163110            if (!$order) {
    164                 if (defined('WP_DEBUG') && WP_DEBUG) {
    165                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    166                     error_log('FirstPromoter: Order not found for order #' . $order_id);
    167                 }
    168                 return;
    169             }
    170 
    171             $order_data = $order->get_data();
    172             $email      = $order->get_billing_email();
    173             $customerId = $order->get_customer_id() != 0 ? $order->get_customer_id() : null;
     111                $this->log('Order not found for order #' . $order_id);
     112                return;
     113            }
     114
     115            $order_data   = $order->get_data();
     116            $email        = $order->get_billing_email();
     117            $customerId   = $order->get_customer_id() != 0 ? $order->get_customer_id() : null;
    174118            $order_status = $order->get_status();
    175119
    176             if (defined('WP_DEBUG') && WP_DEBUG) {
    177                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    178                 error_log('FirstPromoter: Order #' . $order_id . ' - Status: ' . $order_status . ', Email: ' . $email . ', Customer ID: ' . ($customerId ?: 'none'));
    179             }
    180 
    181             // Check if order is in a valid status for tracking (processing or completed)
    182             $valid_statuses = ['processing', 'completed'];
    183             if (!in_array($order_status, $valid_statuses)) {
    184                 if (defined('WP_DEBUG') && WP_DEBUG) {
    185                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    186                     error_log('FirstPromoter: Order #' . $order_id . ' has invalid status (' . $order_status . '). Not tracking sale.');
    187                 }
    188                 return;
    189             }
     120            $this->log('Order #' . $order_id . ' - Status: ' . $order_status . ', Email: ' . $email . ', Customer ID: ' . ($customerId ? $customerId : 'none'));
    190121
    191122            if (!$email && !$customerId) {
    192                 if (defined('WP_DEBUG') && WP_DEBUG) {
    193                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    194                     error_log('FirstPromoter: Order #' . $order_id . ' has no email or customer ID. Not tracking sale.');
    195                 }
     123                $this->log('Order #' . $order_id . ' has no email or customer ID. Not tracking sale.');
    196124                return;
    197125            }
     
    199127            // Check if sale was already tracked to prevent duplicates
    200128            if ($order->get_meta('firstpromoter_sale_tracked_on_fp')) {
    201                 if (defined('WP_DEBUG') && WP_DEBUG) {
    202                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    203                     error_log('FirstPromoter: Sale already tracked for order #' . $order_id);
    204                 }
     129                $this->log('Sale already tracked for order #' . $order_id);
    205130                return;
    206131            }
     
    214139            $items = $order->get_items();
    215140
    216             $product_names = [];
     141            $product_names = array();
    217142            foreach ($items as $item) {
    218143                $product_names[] = $item->get_name();
     
    224149            $tid = isset($_COOKIE["_fprom_tid"]) ? sanitize_text_field(wp_unslash($_COOKIE["_fprom_tid"])) : null;
    225150
    226             if (defined('WP_DEBUG') && WP_DEBUG) {
    227                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    228                 error_log('FirstPromoter: Order #' . $order_id . ' - Cookies - ref_id: ' . ($ref_id ?: 'none') . ', tid: ' . ($tid ?: 'none'));
    229                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    230                 error_log('FirstPromoter: Order #' . $order_id . ' - Sale data - event_id: ' . $event_id . ', amount: ' . $order_total . ', plan: ' . $plan);
    231             }
    232 
    233             if (defined('WP_DEBUG') && WP_DEBUG) {
    234                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    235                 error_log('FirstPromoter: Calling track_sale_via_api for order #' . $order_id);
    236             }
     151            $this->log('Order #' . $order_id . ' - Sale data - event_id: ' . $event_id . ', amount: ' . $order_total . ', plan: ' . $plan);
    237152
    238153            $response = $this->track_sale_via_api($email, $customerId, $event_id, $order_total, $plan, $coupon_code, $tid, $ref_id);
    239154
    240             // Check if response is an actual response object (not an exception)
    241             if ($response && is_object($response) && method_exists($response, 'getStatusCode')) {
    242                 if (defined('WP_DEBUG') && WP_DEBUG) {
    243                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    244                     error_log('FirstPromoter: API response status code: ' . $response->getStatusCode());
    245                 }
    246 
    247                 if ($response->getStatusCode() == 200) {
    248                     $order->update_meta_data('firstpromoter_event_id', $event_id);
    249                     $order->update_meta_data('firstpromoter_sale_tracked_on_fp', true);
    250                     $order->save();
    251 
    252                     if (defined('WP_DEBUG') && WP_DEBUG) {
    253                         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    254                         error_log('FirstPromoter: Sale successfully tracked for order #' . $order_id);
    255                     }
    256                 } else {
    257                     if (defined('WP_DEBUG') && WP_DEBUG) {
    258                         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    259                         error_log('FirstPromoter: API returned non-200 status code: ' . $response->getStatusCode() . ' for order #' . $order_id);
    260                     }
    261                 }
    262             } elseif ($response && $response instanceof \Exception) {
    263                 // Response is actually an exception
    264                 if (defined('WP_DEBUG') && WP_DEBUG) {
    265                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    266                     error_log('FirstPromoter: API call failed with exception: ' . $response->getMessage());
    267                 }
     155            if ($response && is_array($response) && isset($response['status_code']) && $response['status_code'] == 200) {
     156                $order->update_meta_data('firstpromoter_event_id', $event_id);
     157                $order->update_meta_data('firstpromoter_sale_tracked_on_fp', true);
     158                $order->save();
     159
     160                $this->log('Sale successfully tracked for order #' . $order_id);
    268161            } else {
    269                 if (defined('WP_DEBUG') && WP_DEBUG) {
    270                     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    271                     error_log('FirstPromoter: API call returned false/null for order #' . $order_id);
    272                 }
    273             }
    274         } catch (\Exception $e) {
    275             if (defined('WP_DEBUG') && WP_DEBUG) {
    276                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    277                 error_log('FirstPromoter Integration WooCommerce Error: ' . $e->getMessage());
    278                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    279                 error_log('FirstPromoter Integration WooCommerce Stack trace: ' . $e->getTraceAsString());
    280             }
    281             // Silently fail to ensure order processing is not affected
     162                $this->log('API call failed for order #' . $order_id);
     163            }
     164        } catch (\Exception $e) {
     165            $this->log('WooCommerce track_sale error: ' . $e->getMessage());
    282166        }
    283167    }
     
    285169    /**
    286170     * Track WooCommerce refund
    287      * 
     171     *
    288172     * @param int $order_id Order ID
    289173     * @param int $refund_id Refund ID
     
    304188            if ($email || $customerId) {
    305189                $refund_event_id = $refund_id . "-" . time();
    306                 $refund_amount = abs($refund->get_amount()) * 100; // Convert to cents
     190                $refund_amount = abs($refund->get_amount()) * 100;
    307191                $original_event_id = $order->get_meta('firstpromoter_event_id');
    308192
     
    312196                    $refund_event_id,
    313197                    $refund_amount,
    314                     '', // quantity - WooCommerce doesn't provide this easily
     198                    '',
    315199                    $original_event_id,
    316                     true // skip email notification
     200                    true
    317201                );
    318202
    319                 if ($response && method_exists($response, 'getStatusCode') && $response->getStatusCode() == 200) {
     203                if ($response && is_array($response) && isset($response['status_code']) && $response['status_code'] == 200) {
    320204                    $refund->update_meta_data('firstpromoter_refund_tracked', true);
    321205                    $refund->update_meta_data('firstpromoter_refund_event_id', $refund_event_id);
     
    324208            }
    325209        } catch (\Exception $e) {
    326             if (defined('WP_DEBUG') && WP_DEBUG) {
    327                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    328                 error_log('FirstPromoter WooCommerce refund tracking error: ' . $e->getMessage());
    329             }
    330             // Silently fail to ensure refund processing is not affected
     210            $this->log('WooCommerce refund tracking error: ' . $e->getMessage());
    331211        }
    332212    }
     
    334214    /**
    335215     * Track WooCommerce order cancellation
    336      * 
     216     *
    337217     * @param int $order_id Order ID
    338218     */
     
    352232                $response = $this->track_cancellation_via_api($email, $customerId);
    353233
    354                 if ($response && method_exists($response, 'getStatusCode') && $response->getStatusCode() == 200) {
     234                if ($response && is_array($response) && isset($response['status_code']) && $response['status_code'] == 200) {
    355235                    $order->update_meta_data('firstpromoter_cancellation_tracked', true);
    356236                    $order->save();
     
    358238            }
    359239        } catch (\Exception $e) {
    360             if (defined('WP_DEBUG') && WP_DEBUG) {
    361                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Only logs in debug mode
    362                 error_log('FirstPromoter WooCommerce cancellation tracking error: ' . $e->getMessage());
    363             }
    364             // Silently fail to ensure order cancellation is not affected
     240            $this->log('WooCommerce cancellation tracking error: ' . $e->getMessage());
    365241        }
    366242    }
  • firstpromoter/trunk/readme.txt

    r3404859 r3458122  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 0.2.1
    7 Requires PHP: 7.2
     6Stable tag: 0.3.0
     7Requires PHP: 5.6
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    151151== Changelog ==
    152152
     153= 0.3.0 =
     154* New: Custom Form Tracking integration — track email submissions from any form using CSS selectors
     155* Supports multiple form selector pairs (email input + submit element)
     156* MutationObserver support for dynamically loaded forms (popups, AJAX content)
     157* Server-side AJAX fallback for ad-blocker bypass when force tracking is enabled
     158* Admin repeater UI for managing form selectors
     159* Removed Guzzle dependency - replaced with WordPress native wp_remote_post() for all API calls
     160* Lowered PHP requirement from 7.2 to 5.6 for broader hosting compatibility
     161* Fixed: AJAX fallback handler now registers once instead of per-integration
     162* Fixed: WooCommerce double-tracking on checkout (track_checkout and order_payment_successful both calling track_referral)
     163* Fixed: OptimizePress JS crash when button element not found in form
     164* Fixed: Removed IE-only window.attachEvent from OptimizePress integration
     165* Fixed: setcookie now includes SameSite attribute for modern browser compatibility
     166* Improved: Centralized API request handling via api_post() helper method
     167* Improved: Consolidated debug logging via log() helper to reduce boilerplate
     168* Improved: All API methods now return false on failure instead of exception objects
     169* Removed dead track_registration() method from WooCommerce integration
     170* Removed redundant cross-domain setting check
     171
    153172= 0.2.1 =
    154173* Fixed: Comprehensive error handling to prevent API failures from breaking payments
     
    183202== Upgrade Notice ==
    184203
     204= 0.3.0 =
     205Removed Guzzle dependency, now uses WordPress native HTTP functions. PHP requirement lowered to 5.6. Multiple bug fixes for WooCommerce double-tracking and OptimizePress JS errors.
     206
    185207= 0.2.1 =
    186208Critical bug fix: Ensure all FirstPromoter API calls Fail gracefully.
Note: See TracChangeset for help on using the changeset viewer.