-
Notifications
You must be signed in to change notification settings - Fork 40
Stabilize normalized form submission context #1359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8a5ba9f
219c8ba
e97b5e3
2ca9fbe
0abd65a
4180973
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,15 +7,41 @@ | |
| // Ensure PUM exists globally | ||
| window.PUM = window.PUM || {}; | ||
| window.PUM.integrations = window.PUM.integrations || {}; | ||
| const PUM = window.PUM; | ||
| const pumVars = window.pum_vars; | ||
|
|
||
| function filterNull( x ) { | ||
| return x; | ||
| } | ||
|
|
||
| function generateSubmissionId() { | ||
| if ( window.crypto && 'function' === typeof window.crypto.randomUUID ) { | ||
| return window.crypto.randomUUID(); | ||
| } | ||
|
|
||
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( | ||
| /[xy]/g, | ||
| ( character ) => { | ||
| const random = Math.floor( Math.random() * 16 ); | ||
| const value = 'x' === character ? random : ( random % 4 ) + 8; | ||
|
|
||
| return value.toString( 16 ); | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| function validSubmissionId( submissionId ) { | ||
| return ( | ||
| ( 'string' === typeof submissionId || | ||
| 'number' === typeof submissionId ) && | ||
| '' !== String( submissionId ) | ||
| ); | ||
| } | ||
|
|
||
| $.extend( window.PUM.integrations, { | ||
| init: function () { | ||
| if ( 'undefined' !== typeof pum_vars.form_submission ) { | ||
| var submission = pum_vars.form_submission; | ||
| init() { | ||
| if ( pumVars && 'undefined' !== typeof pumVars.form_submission ) { | ||
| const submission = pumVars.form_submission; | ||
|
|
||
| // Declare these are not AJAX submissions. | ||
| submission.ajax = false; | ||
|
|
@@ -39,26 +65,69 @@ | |
| * @param {Object} form JavaScript DOM node or jQuery object for the form submitted | ||
| * @param {Object} args { | ||
| * @type {string} formProvider Such as gravityforms or ninjaforms | ||
| * @type {string|int} formId Usually an integer ID number such as 1 | ||
| * @type {int} formInstanceId Not all form plugins support this. | ||
| * @type {string|number} formId Usually an integer ID number such as 1 | ||
| * @type {number} formInstanceId Not all form plugins support this. | ||
| * @type {string|number} submissionId Stable submission or provider entry ID. | ||
| * @type {number} sourcePostId Optional post/page ID where the form was submitted. | ||
| * @type {string} sourceUrl URL where the form was submitted. | ||
| * @type {Object} context Extension-owned submission context. | ||
| * } | ||
| */ | ||
| formSubmission: function ( form, args ) { | ||
| var $popup = PUM.getPopup( form ); | ||
| formSubmission( form, args ) { | ||
| const $popup = PUM.getPopup( form ); | ||
|
|
||
| args = $.extend( | ||
| { | ||
| popup: $popup, | ||
| formProvider: null, | ||
| formId: null, | ||
| formInstanceId: null, | ||
| submissionId: null, | ||
| sourcePostId: null, | ||
| sourceUrl: window.location.href, | ||
| context: {}, | ||
| formKey: null, | ||
| ajax: true, // Allows detecting submissions that may have already been counted. | ||
| tracked: false, | ||
| }, | ||
| args | ||
| ); | ||
|
|
||
| args.submissionId = validSubmissionId( args.submissionId ) | ||
| ? args.submissionId | ||
| : generateSubmissionId(); | ||
| args.context = | ||
| args.context && | ||
| 'object' === typeof args.context && | ||
| ! Array.isArray( args.context ) | ||
| ? args.context | ||
| : {}; | ||
| const canonicalSubmissionId = args.submissionId; | ||
|
Comment on lines
+96
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Normalize source metadata before and after the filter.
Also applies to: 121-129 🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * Filters normalized form submission arguments before success handlers run. | ||
| * | ||
| * Extensions can append context without coupling to individual providers. | ||
| * | ||
| * @param {Object} args Normalized submission arguments. | ||
| * @param {Object} form Submitted form element or jQuery object. | ||
| */ | ||
| args = window.PUM.hooks.applyFilters( | ||
| 'pum.integration.form.submissionArgs', | ||
| args, | ||
| form | ||
| ); | ||
|
|
||
| args.submissionId = validSubmissionId( args.submissionId ) | ||
| ? args.submissionId | ||
| : canonicalSubmissionId; | ||
| args.context = | ||
| args.context && | ||
| 'object' === typeof args.context && | ||
| ! Array.isArray( args.context ) | ||
| ? args.context | ||
| : {}; | ||
|
|
||
| // Generate unique formKey identifier. | ||
| args.formKey = | ||
| args.formKey || | ||
|
|
@@ -83,10 +152,14 @@ | |
| * @param {Object} form JavaScript DOM node or jQuery object for the form submitted | ||
| * @param {Object} args { | ||
| * @type {string} formProvider Such as gravityforms or ninjaforms | ||
| * @type {string|int} formId Usually an integer ID number such as 1 | ||
| * @type {int} formInstanceId Not all form plugins support this. | ||
| * @type {string|number} formId Usually an integer ID number such as 1 | ||
| * @type {number} formInstanceId Not all form plugins support this. | ||
| * @type {string|number} submissionId Stable submission or provider entry ID. | ||
| * @type {number} sourcePostId Optional post/page ID where the form was submitted. | ||
| * @type {string} sourceUrl URL where the form was submitted. | ||
| * @type {Object} context Extension-owned submission context. | ||
| * @type {string} formKey Concatenation of provider, ID & Instance ID. | ||
| * @type {int} popupId The ID of the popup the form was in. | ||
| * @type {number} popupId The ID of the popup the form was in. | ||
| * @type {Object} popup Usable jQuery object for the popup. | ||
| * } | ||
| */ | ||
|
|
@@ -96,14 +169,14 @@ | |
| args | ||
| ); | ||
| }, | ||
| checkFormKeyMatches: function ( | ||
| checkFormKeyMatches( | ||
| formIdentifier, | ||
| formInstanceId, | ||
| submittedFormArgs | ||
| ) { | ||
| formInstanceId = '' === formInstanceId ? formInstanceId : false; | ||
| // Check if the submitted form matches trigger requirements. | ||
| var checks = [ | ||
| const checks = [ | ||
| // Any supported form. | ||
| formIdentifier === 'any', | ||
|
|
||
|
|
@@ -138,28 +211,28 @@ | |
| * @since 1.9.0 | ||
| * | ||
| * @param {boolean} matchFound A boolean determining whether a match was found. | ||
| * @param {Object} args { | ||
| * @param {Object} args { | ||
| * @type {string} formIdentifier gravityforms_any or ninjaforms_1 | ||
| * @type {int} formInstanceId Not all form plugins support this. | ||
| * @type {number} formInstanceId Not all form plugins support this. | ||
| * @type {Object} submittedFormArgs{ | ||
| * @type {string} formProvider Such as gravityforms or ninjaforms | ||
| * @type {string|int} formId Usually an integer ID number such as 1 | ||
| * @type {int} formInstanceId Not all form plugins support this. | ||
| * @type {string|number} formId Usually an integer ID number such as 1 | ||
| * @type {number} formInstanceId Not all form plugins support this. | ||
| * @type {string} formKey Concatenation of provider, ID & Instance ID. | ||
| * @type {int} popupId The ID of the popup the form was in. | ||
| * @type {number} popupId The ID of the popup the form was in. | ||
| * @type {Object} popup Usable jQuery object for the popup. | ||
| * } | ||
| * } | ||
| * | ||
| * @returns {boolean} | ||
| * @return {boolean} | ||
| */ | ||
| return window.PUM.hooks.applyFilters( | ||
| 'pum.integration.checkFormKeyMatches', | ||
| matchFound, | ||
| { | ||
| formIdentifier: formIdentifier, | ||
| formInstanceId: formInstanceId, | ||
| submittedFormArgs: submittedFormArgs, | ||
| formIdentifier, | ||
| formInstanceId, | ||
| submittedFormArgs, | ||
| } | ||
| ); | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Normalized form submission context | ||
|
|
||
| Popup Maker form integrations report successful submissions through | ||
| `pum_integrated_form_submission()` in PHP and | ||
| `PUM.integrations.formSubmission()` in JavaScript. The normalized envelope is | ||
| provider-independent, and extensions can attach their own namespaced data to | ||
| `context` without changing each provider integration. | ||
|
|
||
| ## Contract | ||
|
|
||
| PHP integrations use snake-cased keys: | ||
|
|
||
| ```php | ||
| pum_integrated_form_submission( [ | ||
| 'form_provider' => 'example', | ||
| 'form_id' => 12, | ||
| 'submission_id' => 'entry-456', | ||
| 'source_post_id' => 78, | ||
| 'source_url' => 'https://example.com/guide/', | ||
| 'context' => [ | ||
| 'my_extension' => [ | ||
| 'campaign_id' => 90, | ||
| ], | ||
| ], | ||
| ] ); | ||
| ``` | ||
|
|
||
| JavaScript integrations receive the camel-cased equivalents. When a provider | ||
| does not supply `submission_id` / `submissionId`, Popup Maker generates a UUID | ||
| for that one normalized event. Provider-native submission IDs are the reliable | ||
| correlation key when matching independently observed server and frontend | ||
| callbacks; independently generated IDs are not a cross-transport deduplication | ||
| mechanism. | ||
|
|
||
| `source_url` defaults to the sanitized request referrer in PHP and the current | ||
| page URL in JavaScript. PHP resolves `source_post_id` from that URL when | ||
| possible. Both values remain nullable because referrers may be unavailable and | ||
| not every URL represents a WordPress post. | ||
|
|
||
| ## Extension context | ||
|
|
||
| PHP extensions can use the existing | ||
| `pum_integrated_form_submission_args` filter. Frontend extensions use the | ||
| `pum.integration.form.submissionArgs` filter, which runs after defaults and | ||
| popup resolution but before the form key, conversion event, and normalized | ||
| success action are produced. | ||
|
|
||
| ```js | ||
| PUM.hooks.addFilter( | ||
| 'pum.integration.form.submissionArgs', | ||
| ( args ) => ( { | ||
| ...args, | ||
| context: { | ||
| ...args.context, | ||
| myExtension: { campaignId: 90 }, | ||
| }, | ||
| } ) | ||
| ); | ||
| ``` | ||
|
|
||
| Context and source values are descriptive metadata, not proof of identity or | ||
| authorization. Consumers must validate untrusted values before privileged | ||
| operations and apply their own privacy and retention policies before storing | ||
| submission data. |
Uh oh!
There was an error while loading. Please reload this page.