Changeset 3397405
- Timestamp:
- 11/17/2025 04:44:47 PM (4 months ago)
- Location:
- form-attribution-tracking/trunk
- Files:
-
- 9 edited
-
assets/js/referral-source.js (modified) (3 diffs)
-
attribution-tracking.php (modified) (2 diffs)
-
readme.md (modified) (1 diff)
-
src/Abstracts/AbstractFormIntegration.php (modified) (1 diff)
-
src/Integrations/FluentFormsIntegration.php (modified) (14 diffs)
-
src/Integrations/FormidableFormsIntegration.php (modified) (7 diffs)
-
src/Integrations/GravityFormsIntegration.php (modified) (8 diffs)
-
src/Plugin.php (modified) (2 diffs)
-
src/Views/admin-page.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
form-attribution-tracking/trunk/assets/js/referral-source.js
r3397093 r3397405 105 105 106 106 /** 107 * Get the referral source with intelligent detection and persistence 108 */ 109 function getReferralSource() { 110 log('Getting referral source...'); 111 112 // 1. Check UTM parameters first (highest priority) 113 const utmSource = getUrlParameter('utm_source'); 114 if (utmSource) { 115 log('UTM source found', utmSource); 116 // If we have UTM, store it as the original source and return it 117 setCookie(CONFIG.cookieName, utmSource, CONFIG.cookieDays); 118 return utmSource; 119 } 120 121 // 2. Check UTM medium and campaign for additional context 122 const utmMedium = getUrlParameter('utm_medium'); 123 const utmCampaign = getUrlParameter('utm_campaign'); 124 if (utmMedium || utmCampaign) { 125 const combinedUtm = [utmMedium, utmCampaign].filter(Boolean).join(' / '); 126 log('UTM medium/campaign found', combinedUtm); 127 setCookie(CONFIG.cookieName, combinedUtm, CONFIG.cookieDays); 128 return combinedUtm; 129 } 130 131 // 3. Check if we already have a stored original referrer source 132 const storedSource = getCookie(CONFIG.cookieName); 133 if (storedSource) { 134 log('Stored source found', storedSource); 135 return storedSource; 136 } 137 138 // 4. Parse document.referrer for new visitors 139 const referrer = document.referrer; 140 log('Document referrer', referrer); 141 142 // If no referrer, it's direct traffic 143 if (!referrer) { 144 log('No referrer - direct traffic'); 145 setCookie(CONFIG.cookieName, 'direct', CONFIG.cookieDays); 146 return 'direct'; 147 } 148 149 try { 150 const referrerUrl = new URL(referrer); 151 const hostname = referrerUrl.hostname.toLowerCase().replace('www.', ''); 152 153 // Skip if referrer is same as current site (internal navigation) 154 const currentHostname = window.location.hostname.toLowerCase().replace('www.', ''); 155 if (hostname === currentHostname) { 156 log('Internal referrer detected'); 157 // This is internal navigation - check if we have a stored source 158 const existing = getCookie(CONFIG.cookieName); 159 if (existing) { 160 return existing; 161 } else { 162 // No stored source and internal referrer = direct traffic 163 setCookie(CONFIG.cookieName, 'direct', CONFIG.cookieDays); 164 return 'direct'; 107 * Get the attribution data with intelligent detection and persistence 108 * Returns an object with all attribution fields or null 109 */ 110 function getAttributionData() { 111 log('Getting attribution data...'); 112 113 // 1. Check if we already have stored attribution data (first-touch attribution) 114 const storedData = getCookie(CONFIG.cookieName); 115 if (storedData) { 116 try { 117 const parsed = JSON.parse(storedData); 118 if (parsed && typeof parsed === 'object') { 119 log('Stored attribution data found', parsed); 120 return parsed; 121 } 122 } catch (e) { 123 // If cookie is old format (plain string), we'll create new attribution data below 124 log('Old format cookie detected, will create new attribution data', storedData); 125 } 126 } 127 128 // 2. Create new attribution data for first-time visitors 129 const attribution = { 130 utm_source: getUrlParameter('utm_source') || null, 131 utm_medium: getUrlParameter('utm_medium') || null, 132 utm_campaign: getUrlParameter('utm_campaign') || null, 133 utm_term: getUrlParameter('utm_term') || null, 134 utm_content: getUrlParameter('utm_content') || null, 135 gclid: getUrlParameter('gclid') || null, 136 landing_page: window.location.href, 137 timestamp: new Date().toISOString() 138 }; 139 140 // 3. Determine the source if no UTM parameters 141 if (!attribution.utm_source) { 142 const referrer = document.referrer; 143 log('Document referrer', referrer); 144 145 if (!referrer) { 146 attribution.utm_source = 'direct'; 147 } else { 148 try { 149 const referrerUrl = new URL(referrer); 150 const hostname = referrerUrl.hostname.toLowerCase().replace('www.', ''); 151 const currentHostname = window.location.hostname.toLowerCase().replace('www.', ''); 152 153 if (hostname === currentHostname) { 154 // Internal referrer - this is direct traffic 155 attribution.utm_source = 'direct'; 156 } else { 157 // External referrer - parse and categorize it 158 attribution.utm_source = parseReferrerSource(hostname); 159 if (!attribution.utm_medium) { 160 attribution.utm_medium = 'referral'; 161 } 162 } 163 } catch (e) { 164 log('Error parsing referrer', e); 165 attribution.utm_source = 'direct'; 165 166 } 166 167 } 167 168 // This is an external referrer - parse and categorize it 169 const parsedSource = parseReferrerSource(hostname); 170 log('External referrer parsed', { hostname, parsedSource }); 171 setCookie(CONFIG.cookieName, parsedSource, CONFIG.cookieDays); 172 return parsedSource; 173 174 } catch (e) { 175 // If URL parsing fails, return direct 176 log('Error parsing referrer', e); 177 setCookie(CONFIG.cookieName, 'direct', CONFIG.cookieDays); 178 return 'direct'; 179 } 168 } 169 170 // 4. Store the attribution data as JSON 171 const jsonData = JSON.stringify(attribution); 172 setCookie(CONFIG.cookieName, jsonData, CONFIG.cookieDays); 173 log('New attribution data created and stored', attribution); 174 175 return attribution; 176 } 177 178 /** 179 * Backward compatibility: Get just the referral source 180 */ 181 function getReferralSource() { 182 const attribution = getAttributionData(); 183 return attribution ? attribution.utm_source : 'direct'; 180 184 } 181 185 … … 189 193 190 194 /** 191 * Populate form fields with referral source195 * Populate form fields with attribution data 192 196 */ 193 197 function populateFormFields() { 194 const referralSource = getReferralSource();195 if (! referralSource) {196 log('No referral sourceto populate');198 const attribution = getAttributionData(); 199 if (!attribution) { 200 log('No attribution data to populate'); 197 201 return; 198 202 } 199 203 200 log('Populating form fields with referral source', referralSource); 201 202 // Find all possible referral source fields 203 const selectors = [ 204 // Direct name match (Fluent Forms) 205 `input[name="${CONFIG.fieldName}"]`, 206 207 // ID-based selectors (Formidable Forms) 208 `input[id*="${CONFIG.fieldName}"]`, 209 `input[id="field_${CONFIG.fieldName}"]`, 210 211 // aria-describedby selectors (Formidable Forms) 212 `input[aria-describedby*="${CONFIG.fieldName}"]`, 213 214 // Generic selectors for various patterns 215 'input[name*="referral"]', 216 'input[name*="source"]', 217 'input[id*="referral"]', 218 'input[id*="source"]', 219 'input[class*="referral"]', 220 'input[class*="source"]', 221 222 // Formidable-specific patterns 223 'input[name^="item_meta["][id*="referral"]', 224 'input[name^="item_meta["][id*="source"]' 225 ]; 204 log('Populating form fields with attribution data', attribution); 205 206 // Map of attribution fields to their values 207 const attributionFields = { 208 'attribution_source': attribution.utm_source || '', 209 'attribution_medium': attribution.utm_medium || '', 210 'attribution_campaign': attribution.utm_campaign || '', 211 'attribution_term': attribution.utm_term || '', 212 'attribution_content': attribution.utm_content || '', 213 'attribution_gclid': attribution.gclid || '', 214 'attribution_landing_page': attribution.landing_page || '', 215 'attribution_timestamp': attribution.timestamp || '' 216 }; 226 217 227 218 let fieldsPopulated = 0; 228 219 229 selectors.forEach(selector => { 230 const fields = document.querySelectorAll(selector); 231 log(`Selector "${selector}" found ${fields.length} fields`); 232 233 fields.forEach(field => { 234 if (field.type === 'hidden' || field.type === 'text') { 235 if (!field.value || field.value === '') { 236 field.value = referralSource; 237 fieldsPopulated++; 238 log('Field populated', { 239 selector, 240 id: field.id, 241 name: field.name, 242 type: field.type, 243 value: referralSource 244 }); 245 } else { 246 log('Field skipped (already has value)', { 247 selector, 248 id: field.id, 249 name: field.name, 250 existingValue: field.value 251 }); 220 // Populate each attribution field 221 Object.entries(attributionFields).forEach(([fieldName, value]) => { 222 // Define selectors for each field type 223 const selectors = [ 224 // Direct name match (Fluent Forms) 225 `input[name="${fieldName}"]`, 226 227 // ID-based selectors (Formidable Forms) 228 `input[id*="${fieldName}"]`, 229 `input[id="field_${fieldName}"]`, 230 231 // aria-describedby selectors (Formidable Forms) 232 `input[aria-describedby*="${fieldName}"]`, 233 234 // Formidable-specific patterns 235 `input[name^="item_meta["][id*="${fieldName}"]`, 236 237 // Gravity Forms patterns 238 `input[class*="${fieldName}"]` 239 ]; 240 241 selectors.forEach(selector => { 242 const fields = document.querySelectorAll(selector); 243 244 fields.forEach(field => { 245 if (field.type === 'hidden' || field.type === 'text') { 246 if (!field.value || field.value === '') { 247 field.value = value; 248 fieldsPopulated++; 249 log('Field populated', { 250 selector, 251 fieldName, 252 id: field.id, 253 name: field.name, 254 type: field.type, 255 value: value 256 }); 257 } 252 258 } 253 } else { 254 log('Field skipped (wrong type)', { 255 selector, 256 id: field.id, 257 name: field.name, 258 type: field.type 259 }); 260 } 259 }); 261 260 }); 262 261 }); 263 262 264 263 log(`Total fields populated: ${fieldsPopulated}`); 265 264 266 265 // Trigger custom event for other scripts 267 266 if (fieldsPopulated > 0) { 267 window.dispatchEvent(new CustomEvent('attributionDataPopulated', { 268 detail: { attribution: attribution, fieldsCount: fieldsPopulated } 269 })); 270 } 271 272 // Also trigger legacy event for backward compatibility 273 if (attribution.utm_source) { 268 274 window.dispatchEvent(new CustomEvent('referralSourcePopulated', { 269 detail: { source: referralSource, fieldsCount: fieldsPopulated }275 detail: { source: attribution.utm_source, fieldsCount: fieldsPopulated } 270 276 })); 271 277 } … … 323 329 // Expose functions globally for backward compatibility and manual usage 324 330 window.getReferralSource = getReferralSource; 331 window.getAttributionData = getAttributionData; 325 332 window.populateFormFields = populateFormFields; 326 333 window.FormAttributionTracking = { 327 334 getReferralSource, 335 getAttributionData, 328 336 populateFormFields, 329 337 setCookie, -
form-attribution-tracking/trunk/attribution-tracking.php
r3397396 r3397405 4 4 * Plugin URI: https://wordpress.org/plugins/form-attribution-tracking/ 5 5 * Description: Automatically adds referral source fields to Formidable, Fluent and Gravity Forms 6 * Version: 1.1. 16 * Version: 1.1.2 7 7 * Author: Ryan Howard 8 8 * Author URI: https://www.ryanhoward.dev … … 23 23 24 24 // Define plugin constants 25 define('ATTRIBUTION_TRACKING_VERSION', '1.1. 1');25 define('ATTRIBUTION_TRACKING_VERSION', '1.1.2'); 26 26 define('ATTRIBUTION_TRACKING_PLUGIN_FILE', __FILE__); 27 27 define('ATTRIBUTION_TRACKING_PLUGIN_DIR', plugin_dir_path(__FILE__)); -
form-attribution-tracking/trunk/readme.md
r3397396 r3397405 6 6 Tested up to: 6.8 7 7 Requires PHP: 8.0 8 Stable tag: 1.1. 18 Stable tag: 1.1.2 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html -
form-attribution-tracking/trunk/src/Abstracts/AbstractFormIntegration.php
r3397093 r3397405 13 13 { 14 14 /** 15 * The field name used for referral source15 * Attribution field definitions (field_name => label) 16 16 */ 17 protected const FIELD_NAME = 'referral_source'; 17 protected const ATTRIBUTION_FIELDS = [ 18 'attribution_source' => 'Attribution Source', 19 'attribution_medium' => 'Attribution Medium', 20 'attribution_campaign' => 'Attribution Campaign', 21 'attribution_term' => 'Attribution Keyword', 22 'attribution_content' => 'Attribution Content', 23 'attribution_gclid' => 'Google Click ID', 24 'attribution_landing_page' => 'Landing Page', 25 'attribution_timestamp' => 'First Click Time', 26 ]; 18 27 19 28 /** 20 * The field label used for referral source29 * Legacy field name for backward compatibility 21 30 */ 22 protected const FIELD_LABEL = 'Referral Source'; 31 protected const FIELD_NAME = 'attribution_source'; 32 33 /** 34 * Legacy field label for backward compatibility 35 */ 36 protected const FIELD_LABEL = 'Attribution Source'; 23 37 24 38 /** 25 39 * The field description 26 40 */ 27 protected const FIELD_DESCRIPTION = 'Automatically populated referral source field';41 protected const FIELD_DESCRIPTION = 'Automatically populated attribution tracking fields'; 28 42 29 43 /** -
form-attribution-tracking/trunk/src/Integrations/FluentFormsIntegration.php
r3397112 r3397405 91 91 { 92 92 $field_name = $this->getReferralSourceFieldName(); 93 93 94 94 // Only populate if field is empty (JavaScript failed) 95 95 if (!isset($data[$field_name]) || empty($data[$field_name])) { … … 121 121 122 122 /** 123 * Add referral source fieldto a specific form124 * 123 * Add attribution fields to a specific form 124 * 125 125 * @param int $formId The form ID 126 126 * @return bool Success status … … 132 132 } 133 133 134 // Check if field already exists134 // Check if fields already exist 135 135 if ($this->hasReferralSourceField($formId)) { 136 $this->log(' Referral source field already exists', ['form_id' => $formId]);136 $this->log('Attribution fields already exist', ['form_id' => $formId]); 137 137 return true; 138 138 } … … 152 152 } 153 153 154 // Create referral source field 155 $referralField = [ 156 'index' => count($formFields['fields']), 157 'element' => 'input_hidden', 158 'attributes' => [ 159 'type' => 'hidden', 160 'name' => static::FIELD_NAME, 161 'value' => '', 162 'class' => 'referral-source-field', 163 ], 164 'settings' => [ 165 'admin_field_label' => static::FIELD_LABEL, 166 'help_message' => static::FIELD_DESCRIPTION, 167 ], 168 'editor_options' => [ 169 'title' => static::FIELD_LABEL, 170 'icon_class' => 'ff-edit-hidden-field', 171 'template' => 'inputHidden', 172 ], 173 'uniqElKey' => 'el_' . time() . '_' . wp_rand(1000, 9999), 174 ]; 175 176 // Add the field to form fields 177 $formFields['fields'][] = $referralField; 154 $baseIndex = count($formFields['fields']); 155 $createdFields = []; 156 157 // Create all attribution fields 158 foreach (static::ATTRIBUTION_FIELDS as $fieldKey => $fieldLabel) { 159 $attributionField = [ 160 'index' => $baseIndex++, 161 'element' => 'input_hidden', 162 'attributes' => [ 163 'type' => 'hidden', 164 'name' => $fieldKey, 165 'value' => '', 166 'class' => 'attribution-field attribution-' . str_replace('_', '-', $fieldKey), 167 ], 168 'settings' => [ 169 'admin_field_label' => $fieldLabel, 170 'help_message' => static::FIELD_DESCRIPTION, 171 ], 172 'editor_options' => [ 173 'title' => $fieldLabel, 174 'icon_class' => 'ff-edit-hidden-field', 175 'template' => 'inputHidden', 176 ], 177 'uniqElKey' => 'el_' . time() . '_' . wp_rand(1000, 9999), 178 ]; 179 180 // Add the field to form fields 181 $formFields['fields'][] = $attributionField; 182 $createdFields[] = ['key' => $fieldKey, 'label' => $fieldLabel]; 183 184 // Small delay to ensure unique timestamps 185 usleep(1000); 186 } 178 187 179 188 // Update the form … … 181 190 $form->save(); 182 191 183 $this->log('Referral source field created successfully', [ 184 'form_id' => $formId, 185 'field_key' => $referralField['uniqElKey'] 192 $this->log('Attribution fields created successfully', [ 193 'form_id' => $formId, 194 'fields_created' => count($createdFields), 195 'fields' => $createdFields 186 196 ]); 187 197 … … 189 199 190 200 } catch (\Exception $e) { 191 $this->log('Exception while creating referral source field', [201 $this->log('Exception while creating attribution fields', [ 192 202 'form_id' => $formId, 193 203 'error' => $e->getMessage() … … 198 208 199 209 /** 200 * Remove referral source fieldfrom a specific form201 * 210 * Remove all attribution fields from a specific form 211 * 202 212 * @param int $formId The form ID 203 213 * @return bool Success status … … 212 222 $form = \FluentForm\App\Models\Form::find($formId); 213 223 if (!$form) { 214 return true; // Form doesn't exist, consider field removed224 return true; // Form doesn't exist, consider fields removed 215 225 } 216 226 … … 218 228 $formFields = json_decode($form->form_fields, true); 219 229 if (!$formFields || !isset($formFields['fields'])) { 220 return true; // No fields structure, consider field removed 221 } 222 223 // Find and remove referral source field 224 $fieldFound = false; 230 return true; // No fields structure, consider fields removed 231 } 232 233 // Get list of attribution field names 234 $attributionFieldNames = array_keys(static::ATTRIBUTION_FIELDS); 235 236 // Find and remove all attribution fields 237 $removedCount = 0; 225 238 $newFields = []; 226 239 227 240 foreach ($formFields['fields'] as $field) { 228 if (isset($field['attributes']['name']) && 229 $field['attributes']['name'] === static::FIELD_NAME&&241 if (isset($field['attributes']['name']) && 242 in_array($field['attributes']['name'], $attributionFieldNames, true) && 230 243 $field['element'] === 'input_hidden') { 231 $ fieldFound = true;244 $removedCount++; 232 245 continue; // Skip this field (remove it) 233 246 } … … 235 248 } 236 249 237 if ( !$fieldFound) {238 return true; // Field didn't exist, consider it removed250 if ($removedCount === 0) { 251 return true; // No fields to remove 239 252 } 240 253 … … 244 257 $form->save(); 245 258 246 $this->log('Referral source field removed successfully', ['form_id' => $formId]); 259 $this->log('Attribution fields removed successfully', [ 260 'form_id' => $formId, 261 'fields_removed' => $removedCount 262 ]); 247 263 return true; 248 264 249 265 } catch (\Exception $e) { 250 $this->log('Exception while removing referral source field', [266 $this->log('Exception while removing attribution fields', [ 251 267 'form_id' => $formId, 252 268 'error' => $e->getMessage() … … 257 273 258 274 /** 259 * Check if a form already has a referral source field260 * 275 * Check if a form already has attribution fields 276 * 261 277 * @param int $formId The form ID 262 278 * @return bool … … 280 296 } 281 297 282 // Look for referralsource field298 // Look for the main attribution_source field 283 299 foreach ($formFields['fields'] as $field) { 284 if (isset($field['attributes']['name']) && 285 $field['attributes']['name'] === static::FIELD_NAME&&300 if (isset($field['attributes']['name']) && 301 $field['attributes']['name'] === 'attribution_source' && 286 302 $field['element'] === 'input_hidden') { 287 303 return true; … … 292 308 293 309 } catch (\Exception $e) { 294 $this->log('Exception while checking for referralsource field', [310 $this->log('Exception while checking for attribution source field', [ 295 311 'form_id' => $formId, 296 312 'error' => $e->getMessage() -
form-attribution-tracking/trunk/src/Integrations/FormidableFormsIntegration.php
r3397093 r3397405 114 114 115 115 /** 116 * Add referral source fieldto a specific form117 * 116 * Add attribution fields to a specific form 117 * 118 118 * @param int $formId The form ID 119 119 * @return bool Success status … … 125 125 } 126 126 127 // Check if field already exists127 // Check if fields already exist 128 128 if ($this->hasReferralSourceField($formId)) { 129 $this->log(' Referral source field already exists', ['form_id' => $formId]);129 $this->log('Attribution fields already exist', ['form_id' => $formId]); 130 130 return true; 131 131 } 132 132 133 133 try { 134 $field_values = [ 135 'form_id' => $formId, 136 'type' => 'hidden', 137 'name' => static::FIELD_LABEL, 138 'description' => static::FIELD_DESCRIPTION, 139 'field_key' => static::FIELD_NAME, 140 'default_value' => '', 141 'field_order' => 999, // Put it at the end 142 'required' => 0, 143 'field_options' => [ 144 'size' => '', 145 'max' => '', 146 'label_position' => '', 147 'invalid' => '', 148 'classes' => 'referral-source-field', 149 'description_loc' => '', 150 ] 151 ]; 152 153 // Use Formidable's filter to customize field before creation 154 $field_values = apply_filters('frm_before_field_created', $field_values); 155 156 // Create the field 157 $field_id = \FrmField::create($field_values); 158 159 if ($field_id) { 160 $this->log('Referral source field created successfully', [ 134 $fieldOrder = 999; // Start at the end 135 $createdFields = []; 136 137 // Create all attribution fields 138 foreach (static::ATTRIBUTION_FIELDS as $fieldKey => $fieldLabel) { 139 $field_values = [ 161 140 'form_id' => $formId, 162 'field_id' => $field_id 141 'type' => 'hidden', 142 'name' => $fieldLabel, 143 'description' => static::FIELD_DESCRIPTION, 144 'field_key' => $fieldKey, 145 'default_value' => '', 146 'field_order' => $fieldOrder++, 147 'required' => 0, 148 'field_options' => [ 149 'size' => '', 150 'max' => '', 151 'label_position' => '', 152 'invalid' => '', 153 'classes' => 'attribution-field attribution-' . str_replace('_', '-', $fieldKey), 154 'description_loc' => '', 155 ] 156 ]; 157 158 // Use Formidable's filter to customize field before creation 159 $field_values = apply_filters('frm_before_field_created', $field_values); 160 161 // Create the field 162 $field_id = \FrmField::create($field_values); 163 164 if ($field_id) { 165 $createdFields[] = ['key' => $fieldKey, 'id' => $field_id]; 166 } else { 167 $this->log('Failed to create attribution field', [ 168 'form_id' => $formId, 169 'field_key' => $fieldKey 170 ]); 171 } 172 } 173 174 if (count($createdFields) > 0) { 175 $this->log('Attribution fields created successfully', [ 176 'form_id' => $formId, 177 'fields_created' => count($createdFields), 178 'fields' => $createdFields 163 179 ]); 164 180 return true; 165 181 } 166 182 167 $this->log('Failed to create referral source field', ['form_id' => $formId]);183 $this->log('Failed to create any attribution fields', ['form_id' => $formId]); 168 184 return false; 169 185 170 186 } catch (\Exception $e) { 171 $this->log('Exception while creating referral source field', [187 $this->log('Exception while creating attribution fields', [ 172 188 'form_id' => $formId, 173 189 'error' => $e->getMessage() … … 178 194 179 195 /** 180 * Remove referral source fieldfrom a specific form181 * 196 * Remove all attribution fields from a specific form 197 * 182 198 * @param int $formId The form ID 183 199 * @return bool Success status … … 190 206 191 207 try { 192 $field = $this->getReferralSourceField($formId); 193 194 if (!$field) { 195 return true; // Field doesn't exist, consider it removed 196 } 197 198 $result = \FrmField::destroy($field->id); 199 200 if ($result) { 201 $this->log('Referral source field removed successfully', [ 208 $removedCount = 0; 209 210 // Remove all attribution fields 211 foreach (array_keys(static::ATTRIBUTION_FIELDS) as $fieldKey) { 212 $fields = \FrmField::getAll([ 213 'fi.form_id' => $formId, 214 'fi.type' => 'hidden', 215 'fi.field_key like' => $fieldKey, 216 ], 'id ASC'); 217 218 foreach ($fields as $field) { 219 $result = \FrmField::destroy($field->id); 220 if ($result) { 221 $removedCount++; 222 $this->log('Attribution field removed', [ 223 'form_id' => $formId, 224 'field_id' => $field->id, 225 'field_key' => $fieldKey 226 ]); 227 } 228 } 229 } 230 231 if ($removedCount > 0) { 232 $this->log('Attribution fields removed successfully', [ 202 233 'form_id' => $formId, 203 'field _id' => $field->id234 'fields_removed' => $removedCount 204 235 ]); 205 return true; 206 } 207 208 return false; 236 } 237 238 return true; 209 239 210 240 } catch (\Exception $e) { 211 $this->log('Exception while removing referral source field', [241 $this->log('Exception while removing attribution fields', [ 212 242 'form_id' => $formId, 213 243 'error' => $e->getMessage() … … 261 291 262 292 /** 263 * Get the referralsource field for a specific form264 * 293 * Get the main attribution source field for a specific form 294 * 265 295 * @param int $formId 266 296 * @return object|null … … 273 303 274 304 try { 275 // Use getAll to search by multiple criteria 276 $fields = \FrmField::getAll( [ 277 'fi.form_id' => $formId, 278 'fi.type' => 'hidden', 279 'fi.field_key like' => static::FIELD_NAME, 280 ], 281 'id ASC' 282 ); 283 284 $this->log('Searching for referral source field', [ 305 // Look for the main attribution_source field 306 $fields = \FrmField::getAll([ 307 'fi.form_id' => $formId, 308 'fi.type' => 'hidden', 309 'fi.field_key like' => 'attribution_source', 310 ], 'id ASC'); 311 312 $this->log('Searching for attribution source field', [ 285 313 'form_id' => $formId, 286 'field_key' => static::FIELD_NAME,314 'field_key' => 'attribution_source', 287 315 'fields_found' => count($fields) 288 316 ]); … … 292 320 293 321 } catch (\Exception $e) { 294 $this->log('Exception while getting referralsource field', [322 $this->log('Exception while getting attribution source field', [ 295 323 'form_id' => $formId, 296 324 'error' => $e->getMessage() -
form-attribution-tracking/trunk/src/Integrations/GravityFormsIntegration.php
r3397093 r3397405 50 50 51 51 if ($current !== '') { 52 return; // J S уже заполнил52 return; // JavaScript already populated the field 53 53 } 54 54 … … 65 65 66 66 /** 67 * ✅ Добавление поля без GFAPI::add_field() (чистый, рабочий способ) 67 * Add attribution fields to a specific form 68 * 69 * @param int $formId The form ID 70 * @return bool Success status 68 71 */ 69 72 public function addReferralSourceField(int $formId): bool … … 74 77 75 78 if ($this->hasReferralSourceField($formId)) { 76 $this->log(' Referral source field already exists', ['form_id' => $formId]);79 $this->log('Attribution fields already exist', ['form_id' => $formId]); 77 80 return true; 78 81 } … … 88 91 } 89 92 90 $fieldArr = [ 91 'type' => 'hidden', 92 'label' => static::FIELD_LABEL, 93 'adminLabel' => static::FIELD_LABEL, 94 'description' => static::FIELD_DESCRIPTION, 95 'inputName' => static::FIELD_NAME, 96 'visibility' => 'hidden', 97 'allowsPrepopulate' => true, 98 'defaultValue' => '', 99 ]; 100 101 $fieldObj = \GF_Fields::create($fieldArr); 102 $form['fields'][] = $fieldObj; 93 $createdFields = []; 94 95 // Create all attribution fields 96 foreach (static::ATTRIBUTION_FIELDS as $fieldKey => $fieldLabel) { 97 $fieldArr = [ 98 'type' => 'hidden', 99 'label' => $fieldLabel, 100 'adminLabel' => $fieldLabel, 101 'description' => static::FIELD_DESCRIPTION, 102 'inputName' => $fieldKey, 103 'visibility' => 'hidden', 104 'allowsPrepopulate' => true, 105 'defaultValue' => '', 106 'cssClass' => 'attribution-field attribution-' . str_replace('_', '-', $fieldKey), 107 ]; 108 109 $fieldObj = \GF_Fields::create($fieldArr); 110 $form['fields'][] = $fieldObj; 111 $createdFields[] = ['key' => $fieldKey, 'label' => $fieldLabel]; 112 } 103 113 104 114 $result = \GFAPI::update_form($form); 105 115 if (is_wp_error($result)) { 106 $this->log('Failed to update form when adding field', [116 $this->log('Failed to update form when adding attribution fields', [ 107 117 'form_id' => $formId, 108 'error' => $result->get_error_message(),118 'error' => $result->get_error_message(), 109 119 ]); 110 120 return false; 111 121 } 112 122 113 $this->log('Referral source field created successfully', ['form_id' => $formId]); 123 $this->log('Attribution fields created successfully', [ 124 'form_id' => $formId, 125 'fields_created' => count($createdFields), 126 'fields' => $createdFields 127 ]); 114 128 115 129 return true; … … 127 141 } 128 142 143 // Get list of attribution field names 144 $attributionFieldNames = array_keys(static::ATTRIBUTION_FIELDS); 145 129 146 $newFields = []; 130 $removed = false;147 $removedCount = 0; 131 148 132 149 foreach ($form['fields'] as $field) { 133 150 if ($field instanceof \GF_Field && 134 151 $field->type === 'hidden' && 135 $field->inputName === static::FIELD_NAME) {136 $removed = true;152 in_array($field->inputName, $attributionFieldNames, true)) { 153 $removedCount++; 137 154 continue; 138 155 } … … 140 157 } 141 158 142 if ( !$removed) {159 if ($removedCount === 0) { 143 160 return true; 144 161 } … … 148 165 149 166 if (is_wp_error($res)) { 150 $this->log('Failed to remove referral field', [167 $this->log('Failed to remove attribution fields', [ 151 168 'form_id' => $formId, 152 'error' => $res->get_error_message(),169 'error' => $res->get_error_message(), 153 170 ]); 154 171 return false; 155 172 } 156 173 157 $this->log('Referral source field removed', ['form_id' => $formId]); 174 $this->log('Attribution fields removed successfully', [ 175 'form_id' => $formId, 176 'fields_removed' => $removedCount 177 ]); 158 178 return true; 159 179 } … … 200 220 } 201 221 222 // Look for the main attribution_source field 202 223 foreach ($form['fields'] as $field) { 203 224 if ($field instanceof \GF_Field && 204 225 $field->type === 'hidden' && 205 $field->inputName === static::FIELD_NAME) {226 $field->inputName === 'attribution_source') { 206 227 return $field; 207 228 } -
form-attribution-tracking/trunk/src/Plugin.php
r3397093 r3397405 250 250 'attribution_tracking_general', 251 251 ['option_name' => 'auto_add_to_new_forms', 'description' => __('Automatically add referral source field to new forms', 'form-attribution-tracking')] 252 );253 254 add_settings_field(255 'field_name',256 __('Field Name', 'form-attribution-tracking'),257 [$this, 'renderTextField'],258 'form-attribution-tracking',259 'attribution_tracking_general',260 ['option_name' => 'field_name', 'description' => __('Name for the referral source field', 'form-attribution-tracking')]261 252 ); 262 253 … … 709 700 global $wpdb; 710 701 711 $field_name = $this->getOption('field_name', 'referral_source');702 $field_name = 'attribution'; 712 703 $like = $wpdb->esc_like($field_name) . '%'; 713 704 -
form-attribution-tracking/trunk/src/Views/admin-page.php
r3397093 r3397405 46 46 </th> 47 47 <td> 48 <input type="checkbox" 49 id="auto_add_to_new_forms" 50 name="auto_add_to_new_forms" 51 value="1" 48 <input type="checkbox" 49 id="auto_add_to_new_forms" 50 name="auto_add_to_new_forms" 51 value="1" 52 52 <?php checked($plugin->getOption('auto_add_to_new_forms', true)); ?> /> 53 53 <p class="description"> 54 <?php esc_html_e('Automatically add referral source field to newly created forms', 'form-attribution-tracking'); ?>54 <?php esc_html_e('Automatically add full Google Ads attribution tracking fields to newly created forms (captures UTM parameters, GCLID, landing page, and timestamp)', 'form-attribution-tracking'); ?> 55 55 </p> 56 56 </td> 57 57 </tr> 58 58 59 <tr>60 <th scope="row">61 <label for="field_name">62 <?php esc_html_e('Field Name', 'form-attribution-tracking'); ?>63 </label>64 </th>65 <td>66 <input type="text"67 id="field_name"68 name="field_name"69 value="<?php echo esc_attr($plugin->getOption('field_name', 'referral_source')); ?>"70 class="regular-text" />71 <p class="description">72 <?php esc_html_e('Name for the referral source field (used in forms and database)', 'form-attribution-tracking'); ?>73 </p>74 </td>75 </tr>76 59 77 60 <tr> … … 82 65 </th> 83 66 <td> 84 <input type="number" 85 id="cookie_duration" 86 name="cookie_duration" 87 value="<?php echo esc_attr($plugin->getOption('cookie_duration', 30)); ?>" 88 min="1" 67 <input type="number" 68 id="cookie_duration" 69 name="cookie_duration" 70 value="<?php echo esc_attr($plugin->getOption('cookie_duration', 30)); ?>" 71 min="1" 89 72 max="365" /> 90 73 <p class="description"> 91 <?php esc_html_e('How long to store referral sourcein visitor cookies (1-365 days)', 'form-attribution-tracking'); ?>74 <?php esc_html_e('How long to store first-touch attribution data in visitor cookies (1-365 days)', 'form-attribution-tracking'); ?> 92 75 </p> 93 76 </td> … … 135 118 <h2><?php esc_html_e('Available Form Plugins', 'form-attribution-tracking'); ?></h2> 136 119 <p class="description"> 137 <?php esc_html_e('Manage referral source fields across all forms in your detected form plugins.', 'form-attribution-tracking'); ?>120 <?php esc_html_e('Manage Google Ads attribution tracking fields across all forms in your detected form plugins. Each form will receive 8 hidden fields to capture complete attribution data.', 'form-attribution-tracking'); ?> 138 121 </p> 139 122
Note: See TracChangeset
for help on using the changeset viewer.