Skip to content

New duplicate check#2133

Merged
Crabcyborg merged 20 commits into
masterfrom
prototype_extra_duplicate_check
Nov 29, 2024
Merged

New duplicate check#2133
Crabcyborg merged 20 commits into
masterfrom
prototype_extra_duplicate_check

Conversation

@Crabcyborg
Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg commented Nov 21, 2024

Related ticket https://secure.helpscout.net/conversation/2765614867/215349/

The issue
Reopening a page on an iPhone re-submits the POST request. Our duplicate check defaults to 60 seconds, but it's likely someone will open a page again hours later, maybe even days later.

The solution
I'm adding a unique string that gets added right before the form is submitted.

If this string is detected, it is checked in the duplicate check, and saved to entry meta under field 0, serialized as an array like a:1:{s:9:"unique_id";s:9:"a34d2ypgn";}.

When this exact item meta row is detected, during the duplicate check, it will return true immediately if there is a match.

The refresh events don't re-trigger the JavaScript, so all of these requests will send the same unique string.

Pre-release
formidable-6.16.3b.zip

To opt out In case the database query causes performance issues

add_filter( 'frm_extend_duplicate_entry_time_on_unique_id_match', '__return_false' );

This has been confirmed by the customer

@Crabcyborg Crabcyborg marked this pull request as ready for review November 21, 2024 17:53
@Crabcyborg Crabcyborg added this to the 6.16.3 milestone Nov 21, 2024
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 21, 2024

Walkthrough

The pull request introduces enhancements to the FrmEntry and FrmEntryMeta classes, as well as the frmFrontFormJS JavaScript module. A new method, maybe_check_for_unique_id_match, is added to the FrmEntry class to improve duplicate entry detection based on a unique ID. The FrmEntryMeta class's update_entry_metas method is modified to store this unique ID with entry metadata. Additionally, the frmFrontFormJS module now includes a hidden input field for the unique ID in the form submission process, enhancing data management without altering existing functionality.

Changes

File Path Change Summary
classes/models/FrmEntry.php Added method maybe_check_for_unique_id_match for checking unique IDs in duplicate entry detection. Updated is_duplicate method to utilize the new method and skip processing for field IDs that are zero. Added docblock for the new method.
classes/models/FrmEntryMeta.php Enhanced update_entry_metas method to associate a unique identifier with the entry metadata if present. No other changes made.
js/formidable.js Introduced a hidden input field unique_id in the submitFormNow function, populated with a random string for form submissions.

Possibly related PRs


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (3)
classes/models/FrmEntryMeta.php (1)

128-132: Consider additional validation for the unique ID.

The implementation correctly stores the unique ID in entry metadata with proper sanitization. However, consider adding validation for the unique ID format to ensure it matches the expected pattern.

 $unique_id = FrmAppHelper::get_post_param( 'unique_id', '', 'sanitize_key' );
-if ( $unique_id ) {
+if ( $unique_id && preg_match('/^[a-z0-9]{9}$/', $unique_id) ) {
     self::add_entry_meta( $entry_id, 0, '', compact( 'unique_id' ) );
 }
js/formidable.js (1)

1743-1748: Consider extracting unique ID generation to a separate function.

The unique ID generation logic could be moved to a dedicated function for better maintainability and reusability.

Consider refactoring like this:

+			function generateUniqueId() {
+				return Array.from(crypto.getRandomValues(new Uint8Array(8)))
+					.map(b => b.toString(16).padStart(2, '0'))
+					.join('')
+					.substring(0, 9);
+			}
+
 			const uniqueIDInput = document.createElement('input');
 			uniqueIDInput.type = 'hidden';
 			uniqueIDInput.name = 'unique_id';
-			uniqueIDInput.value = Math.random().toString(36).substring(2, 11);
+			uniqueIDInput.value = generateUniqueId();
 			object.appendChild(uniqueIDInput);
classes/models/FrmEntry.php (1)

132-132: Update the @since tag in the docblock

The @since x.x tag should be updated with the appropriate version number to accurately document when this method was introduced.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2965180 and fa25a29.

📒 Files selected for processing (3)
  • classes/models/FrmEntry.php (3 hunks)
  • classes/models/FrmEntryMeta.php (1 hunks)
  • js/formidable.js (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPStan
classes/models/FrmEntry.php

[failure] 155-155:
Only numeric types are allowed in -, int|false given on the left side.

🔇 Additional comments (4)
classes/models/FrmEntryMeta.php (1)

128-132: Verify JavaScript integration for unique ID generation.

Let's verify that the JavaScript code correctly generates and sends the unique ID in the expected format.

✅ Verification successful

Let me try a different search pattern to find the JavaScript code related to form submission and unique ID handling.


Based on the search results, I can now provide a final response as we have found the relevant JavaScript implementation for unique ID generation and form submission handling.

The unique ID generation and form submission integration is properly implemented

The JavaScript code in js/formidable.js correctly handles the unique ID generation:

  • A hidden input field is created with name="unique_id"
  • The value is generated using Math.random().toString(36).substring(2, 11) which creates a random alphanumeric string
  • The field is properly appended to the form before submission
  • The form submission handling includes both AJAX and regular form submission paths
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the JavaScript implementation of unique ID generation
# Expected: Find the unique ID generation in the form submission code

# Search for unique ID generation in JavaScript files
echo "Searching for unique ID generation in JavaScript files..."
rg -A 5 "unique_id.*=" --type js

# Search for form submission handling
echo "Searching for form submission handling..."
ast-grep --pattern 'submitFormNow($$$) {
  $$$
}'

Length of output: 304


Script:

#!/bin/bash
# Search for unique ID related code with broader patterns
echo "Searching for unique ID related code..."
rg -i "unique.*id" --type js -A 5

# Search for form submission with different pattern
echo "Searching for form submission code..."
rg "submit.*form" --type js -A 5

# Look for AJAX form submission
echo "Searching for AJAX form submission..."
rg "ajax.*submit|submit.*ajax" --type js -A 5

Length of output: 43687

js/formidable.js (1)

1743-1748: Verify the unique ID implementation in the backend.

The unique ID is being added to prevent duplicate form submissions, but we should verify that:

  1. The backend properly handles and stores this unique ID
  2. The duplicate check mechanism correctly uses this value
  3. The one-month extension of the duplicate check window is properly implemented
✅ Verification successful

Based on the shell script results, I can now generate the final response as the code shows clear evidence of how the unique ID is handled.

The unique ID implementation is properly handled in the backend

The verification confirms that:

  1. The unique ID is properly stored in frm_item_metas table with field_id=0 (see FrmEntryMeta.php)
  2. The duplicate check mechanism uses this value by checking entries within a time window (see FrmEntry::maybe_extend_duplicate_entry_time)
  3. The one-month extension is correctly implemented using MONTH_IN_SECONDS when a unique ID match is found
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the backend properly handles the unique ID

# Check for unique_id field handling in PHP files
rg -A 5 "unique_id" --type php

# Check for duplicate check related code
rg -A 10 "duplicate.*check|check.*duplicate" --type php

Length of output: 11179

classes/models/FrmEntry.php (2)

54-55: Enhancing duplicate entry time logic

The addition of maybe_extend_duplicate_entry_time effectively adjusts the duplicate entry time based on the presence of a unique ID, improving the accuracy of duplicate entry detection.


85-87: Skip processing for field IDs equal to zero

By adding a check to skip field_id equal to zero, the code correctly avoids processing invalid or special fields during the duplicate check, ensuring only relevant data is considered.

Comment thread js/formidable.js
Comment thread classes/models/FrmEntry.php
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
classes/models/FrmEntry.php (1)

131-167: Consider adding error logging for debugging.

The implementation looks solid, but consider adding debug logging when:

  1. A unique ID is found but malformed
  2. Database errors occur during the lookup

This will help track any issues in production.

Apply this diff to add logging:

 private static function maybe_extend_duplicate_entry_time( $duplicate_entry_time, $created_at ) {
     if ( 60 !== $duplicate_entry_time ) {
         // Only check if the time has not been filtered.
         return $duplicate_entry_time;
     }

     $unique_id = FrmAppHelper::get_post_param( 'unique_id', '', 'sanitize_key' );
     if ( ! $unique_id ) {
         // Only continue if a unique ID was generated on form submit.
         return $duplicate_entry_time;
     }

     $timestamp = strtotime( $created_at );
     if ( false === $timestamp ) {
         $timestamp = time();
+        if ( defined('WP_DEBUG') && WP_DEBUG ) {
+            error_log( sprintf( 'FrmEntry: Invalid created_at timestamp for unique_id %s', $unique_id ) );
+        }
     }

     $unique_id_match = FrmDb::get_var(
         'frm_item_metas',
         array(
             'field_id'     => 0,
             'meta_value'   => serialize( compact( 'unique_id' ) ),
             'created_at >' => gmdate( 'Y-m-d H:i:s', $timestamp - MONTH_IN_SECONDS ),
         ),
         'id'
     );
+    if ( $unique_id_match === false && defined('WP_DEBUG') && WP_DEBUG ) {
+        error_log( sprintf( 'FrmEntry: Database error while checking unique_id %s', $unique_id ) );
+    }

     // Extend the check to a month when unique ID is detected.
     return $unique_id_match ? MONTH_IN_SECONDS : $duplicate_entry_time;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fa25a29 and 03daeb9.

📒 Files selected for processing (1)
  • classes/models/FrmEntry.php (3 hunks)
🔇 Additional comments (3)
classes/models/FrmEntry.php (3)

54-55: LGTM: New method call is properly integrated.

The placement of maybe_extend_duplicate_entry_time is correct as it allows modifying the duplicate check window before constructing the database query conditions.


85-87: LGTM: Properly excludes metadata field from duplicate check.

The condition correctly skips field ID 0, which is used for storing the unique ID metadata, preventing it from affecting the duplicate entry comparison logic.


131-167: Verify the implementation works as expected.

Let's verify two critical aspects:

  1. The unique ID is stored in the correct format
  2. The duplicate check window is properly extended
✅ Verification successful

Implementation verified and working as expected

The verification shows the implementation is correct and complete:

  1. The unique ID is properly:
    • Generated client-side with Math.random() and stored as a hidden input
    • Sanitized server-side using sanitize_key
    • Stored in serialized format in frm_item_metas table with field_id=0
  2. The duplicate check window correctly:
    • Defaults to 60 seconds
    • Extends to MONTH_IN_SECONDS when a matching unique ID is found
    • Uses proper timestamp comparison with created_at > condition
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify unique ID storage format and duplicate check window extension

# Check if any entries have the unique ID stored in the expected format
echo "Checking unique ID format in database..."
rg -A 5 "a:1:\{s:9:\"unique_id\";" 

# Check if the duplicate check window is being extended
echo "Checking duplicate check window extension..."
rg -A 10 "MONTH_IN_SECONDS" 

# Check for any existing error logs related to this feature
echo "Checking for any existing error logs..."
rg "FrmEntry: (Invalid|Database error)" 

Length of output: 2661


Script:

#!/bin/bash
# Let's verify the unique ID implementation more thoroughly

# Check how unique_id is generated and used
echo "Checking unique_id usage..."
rg -B 3 -A 3 "unique_id"

# Check for serialized data handling in frm_item_metas table
echo "Checking serialized data handling..."
ast-grep --pattern 'serialize($_)'

# Check for duplicate entry time window implementations
echo "Checking duplicate entry time implementations..."
rg -B 3 -A 3 "duplicate_entry_time"

# Check for any related tests
echo "Checking related tests..."
fd "test" -e php -x rg -l "unique_id|duplicate_entry"

Length of output: 6140

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
js/formidable.js (1)

1597-1607: Secure implementation of unique key generation

The implementation correctly uses the Web Crypto API (crypto.getRandomValues()) for cryptographically secure random number generation, which is perfect for preventing duplicate form submissions.

Add return description to JSDoc

The JSDoc comment is missing a description for the @return tag.

Apply this diff to improve the documentation:

 /**
  * @since x.x
  *
- * @return {string}
+ * @return {string} A 9-character cryptographically secure random string
  */
🧰 Tools
🪛 eslint (1.23.1)

[error] 1600-1600: Missing JSDoc @return description.

(jsdoc/require-returns-description)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 03daeb9 and 811bb00.

📒 Files selected for processing (1)
  • js/formidable.js (2 hunks)
🧰 Additional context used
🪛 eslint (1.23.1)
js/formidable.js

[error] 1600-1600: Missing JSDoc @return description.

(jsdoc/require-returns-description)

🔇 Additional comments (1)
js/formidable.js (1)

1755-1761: LGTM! Clean implementation of unique ID field

The implementation correctly adds a hidden input field with a unique ID right before form submission, which will work seamlessly with both AJAX and regular form submissions. This change effectively addresses the issue of duplicate form submissions on iPhones by providing a consistent unique identifier.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
classes/models/FrmEntry.php (1)

167-175: Consider adding an index hint for the query

The query looks for entries with matching unique ID within last month. Consider adding an index hint to optimize this frequently used query.

 $unique_id_match = FrmDb::get_var(
-			'frm_item_metas',
+			'frm_item_metas USE INDEX (field_id)',
 			array(
 				'field_id'     => 0,
 				'meta_value'   => serialize( compact( 'unique_id' ) ),
 				'created_at >' => gmdate( 'Y-m-d H:i:s', $timestamp - MONTH_IN_SECONDS ),
 			),
 			'id'
 		);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 236ea79 and c24fb98.

📒 Files selected for processing (1)
  • classes/models/FrmEntry.php (3 hunks)
🔇 Additional comments (4)
classes/models/FrmEntry.php (4)

54-55: LGTM: Method call placement is correct

The maybe_extend_duplicate_entry_time is called at the appropriate point in the flow, before the duplicate entry check logic.


85-87: LGTM: Correctly skips unique ID from duplicate comparison

The check to skip field_id=0 is essential as it prevents the stored unique ID from affecting the duplicate entry detection logic.


162-165: LGTM: Proper handling of invalid timestamp

The code properly handles potential false return from strtotime() by falling back to current time.


131-179: Verify the impact of extending duplicate window to 1 month

The implementation correctly extends duplicate detection window to 1 month when unique ID matches. However, we should verify:

  1. Impact on database size from storing unique IDs
  2. Performance impact of longer duplicate check window
✅ Verification successful

Extending duplicate window to 1 month is safe with proper controls in place

The implementation has several safeguards that make this change safe:

  1. The extended window only applies when:
    • The default 60-second window hasn't been filtered (if ( 60 !== $duplicate_entry_time ))
    • A unique ID is present in the submission
    • The frm_extend_duplicate_entry_time_on_unique_id_match filter returns true
  2. Database impact is minimized because:
    • Unique IDs are only stored temporarily (auto-cleaned after 1 month)
    • Only matches from the last month are queried (created_at > condition)
    • Users can opt-out via filter if performance issues arise
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check average number of entries per month to assess impact
rg -A 5 "created_at.*MONTH" . --type php | grep -v "test"

# Check current table size
mysql -e "SELECT table_name, table_rows, data_length, index_length 
FROM information_schema.tables 
WHERE table_name = 'frm_item_metas';"

Length of output: 638


Script:

#!/bin/bash
# Let's check for other uses of unique_id and duplicate entry time checks
rg -B 2 -A 5 "duplicate.*entry.*time" . --type php

# Check for any other places where we store data in frm_item_metas with field_id = 0
ast-grep --pattern 'FrmDb::get_var($$$"frm_item_metas"$$$)'

# Look for any performance related filters or configurations
rg "frm_extend_duplicate_entry_time" . --type php

Length of output: 4971

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
classes/models/FrmEntry.php (1)

141-177: LGTM! Well-implemented unique ID check with proper error handling.

The implementation includes:

  • Configurable opt-out filter for performance optimization
  • Proper sanitization of user input
  • Robust error handling for invalid timestamps
  • Clear month-long window for duplicate detection

Consider adding unit tests to verify:

  1. The opt-out filter functionality
  2. Timestamp error handling
  3. Different unique ID scenarios
🧰 Tools
🪛 GitHub Check: PHPStan

[failure] 141-141:
PHPDoc tag @param references unknown parameter: $duplicate_entry_time

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c24fb98 and 4332fbc.

📒 Files selected for processing (2)
  • classes/models/FrmEntry.php (3 hunks)
  • js/formidable.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/formidable.js
🧰 Additional context used
🪛 GitHub Check: PHPStan
classes/models/FrmEntry.php

[failure] 141-141:
PHPDoc tag @param references unknown parameter: $duplicate_entry_time

🔇 Additional comments (3)
classes/models/FrmEntry.php (3)

54-57: LGTM! Early duplicate detection implementation looks good.

The placement of the unique ID check before other duplicate checks is optimal for performance, as it allows for early returns when duplicates are found.


88-90: LGTM! Proper handling of special metadata fields.

The condition correctly skips processing for field ID 0, which is used for storing metadata like the unique ID.


Line range hint 54-177: Verify integration with form submission process.

The implementation looks solid, but let's verify the complete flow of the unique ID through the system.

✅ Verification successful

The verification results show a complete integration flow:

  1. In js/formidable.js, a unique ID is generated and added to the form submission via a hidden input field named 'unique_id'.
  2. The unique ID is processed during form submission as shown in the reviewed code.
  3. The entry meta updates are handled through FrmEntryMeta::update_entry_metas() which is properly integrated in both create and update flows in FrmEntry.php.

Let's verify one final aspect - the unique ID generation and form submission initialization:


Integration verified successfully

The implementation shows a complete, working flow for handling duplicate submissions:

  1. Client-side: A unique ID is properly generated via getUniqueKey() and injected into form submissions through a hidden input field.
  2. Server-side: The unique ID is checked against entries from the last month using maybe_check_for_unique_id_match().
  3. Integration points: The unique ID check is properly integrated into the form submission flow through FrmEntryMeta::update_entry_metas().
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration points for unique ID handling

# Check for unique ID field in JavaScript form submission code
echo "Checking for unique ID handling in JS..."
rg -A 5 "unique_id" --type js

# Check for unique ID storage in entry metadata
echo "Checking for unique ID storage in entry meta..."
rg -A 5 "field_id\s*=>\s*0" --type php

# Check for form submission handling
echo "Checking form submission handlers..."
rg -A 5 "submitFormNow|update_entry_metas" --type php

Length of output: 3791


Script:

#!/bin/bash
# Check unique ID generation and form submission initialization
echo "Checking unique ID generation..."
rg -B 5 "getUniqueKey" --type js

echo "Checking form submission initialization..."
rg -B 5 "frm_ajax_submit|addUniqueIdToForm" --type js

Length of output: 4723

🧰 Tools
🪛 GitHub Check: PHPStan

[failure] 141-141:
PHPDoc tag @param references unknown parameter: $duplicate_entry_time

Comment thread classes/models/FrmEntry.php
@Crabcyborg Crabcyborg removed the request for review from lauramekaj1 November 25, 2024 21:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
classes/models/FrmEntry.php (2)

133-138: Fix incorrect PHPDoc return type.

The PHPDoc states @return int but the method returns a boolean value.

Apply this diff to fix the documentation:

 /**
  * @since x.x
  *
  * @param string $created_at
  * @return bool
  */

163-171: Consider using JSON instead of PHP serialization.

Using PHP's serialize() for data storage can be problematic if the data structure changes or if you need to interact with this data from other systems.

Consider using json_encode() instead:

-				'meta_value'   => serialize( compact( 'unique_id' ) ),
+				'meta_value'   => json_encode( compact( 'unique_id' ) ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4332fbc and ff8fa1f.

📒 Files selected for processing (1)
  • classes/models/FrmEntry.php (3 hunks)
🔇 Additional comments (4)
classes/models/FrmEntry.php (4)

54-56: LGTM! Early duplicate check implementation looks good.

The new unique ID check is well-placed as an early return condition before proceeding with other duplicate checks.


87-89: LGTM! Good defensive programming.

Skipping field_id=0 prevents processing system metadata used for storing the unique ID, which is the correct approach.


158-161: LGTM! Good timestamp validation.

The code properly handles invalid timestamps by falling back to the current time.


168-168: Verify MONTH_IN_SECONDS constant availability.

The code uses the MONTH_IN_SECONDS constant. Let's verify it's available.

✅ Verification successful

MONTH_IN_SECONDS constant is defined in stubs.php

The constant is properly defined in the project's stubs.php file, which is commonly used to define WordPress-related constants for development purposes. The constant is defined as 30 * DAY_IN_SECONDS, following WordPress core's definition pattern.

Let me verify if DAY_IN_SECONDS is also defined:


Let me check if HOUR_IN_SECONDS is also defined to complete the verification chain:


Let me verify the final constant MINUTE_IN_SECONDS to complete the entire chain:


MONTH_IN_SECONDS constant is properly defined and available

The constant is correctly defined in stubs.php with a complete chain of time-based constants:

  • MINUTE_IN_SECONDS = 60
  • HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS
  • DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS
  • MONTH_IN_SECONDS = 30 * DAY_IN_SECONDS

The usage in FrmEntry.php is safe and follows WordPress conventions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if MONTH_IN_SECONDS is defined in WordPress core or in this codebase

# Check WordPress core constant
rg "define.*MONTH_IN_SECONDS" 

# Check if it's defined in this codebase
ast-grep --pattern 'define($_, "MONTH_IN_SECONDS")'

Length of output: 147


Script:

#!/bin/bash
# Check if DAY_IN_SECONDS is defined
rg "define.*DAY_IN_SECONDS"

Length of output: 274


Script:

#!/bin/bash
# Check if HOUR_IN_SECONDS is defined
rg "define.*HOUR_IN_SECONDS"

Length of output: 155


Script:

#!/bin/bash
# Check if MINUTE_IN_SECONDS is defined
rg "define.*MINUTE_IN_SECONDS"

Length of output: 142

@Achilles28
Copy link
Copy Markdown

@Crabcyborg The customer has replied and confirmed that the resubmission issue in Safari has been fixed with the latest patch.

Customer tested under the following cuircumstances:

  • I submitted a form with a confirmation screen via Safari on an iPhone.
  • I immediately tried to refresh the confirmation page and got the resubmit warning. When I clicked OK, I got the "already submitted" message (properly displayed).
  • I closed Safari and opened it and got the "already submitted" message (partially displayed).
  • I closed Safari and waited 5 minutes, then opened it and got the "already submitted" message (partially displayed).
  • I closed Safari and waited overnight, then opened it and got the "already submitted" message (partially displayed).

The partially displayed in the tests refer to how the message was displayed which is illustrated here: https://cln.sh/vHdqjNZ6

@Crabcyborg
Copy link
Copy Markdown
Contributor Author

Thank you @Achilles28!

@Crabcyborg Crabcyborg changed the title New duplicate check prototype New duplicate check Nov 27, 2024
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
classes/models/FrmEntry.php (3)

133-138: Fix incorrect PHPDoc return type.

The method returns a boolean but the PHPDoc indicates it returns an int.

Apply this diff to fix the documentation:

 /**
  * @since x.x
  *
  * @param string $created_at
- * @return int
+ * @return bool
  */

163-171: Consider adding an index for performance optimization.

The query on frm_item_metas table filtering by field_id and meta_value could benefit from an index, especially as the table grows.

Consider adding a composite index on (field_id, meta_value) to optimize the duplicate check query:

ALTER TABLE `frm_item_metas` ADD INDEX `idx_field_meta` (`field_id`, `meta_value`(191));

152-156: Consider additional validation for unique_id.

While sanitize_key provides basic sanitization, consider adding length validation and more specific error handling for invalid unique IDs.

Consider applying this diff:

 $unique_id = FrmAppHelper::get_post_param( 'unique_id', '', 'sanitize_key' );
-if ( ! $unique_id ) {
+if ( ! $unique_id || strlen($unique_id) !== 9 ) {
     // Only continue if a unique ID was generated on form submit.
     return false;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5b7ef28 and 1154b99.

📒 Files selected for processing (1)
  • classes/models/FrmEntry.php (3 hunks)
🔇 Additional comments (3)
classes/models/FrmEntry.php (3)

54-56: LGTM! Early duplicate check optimization.

The early check for unique ID matches is well-placed and helps optimize performance by avoiding unnecessary duplicate checks when an exact match is found.


87-89: LGTM! Proper handling of metadata fields.

Correctly skips field ID 0 during duplicate checks, maintaining clean separation between metadata (like unique_id) and actual form data.


158-161: LGTM! Proper timestamp fallback.

Good defensive programming by providing a fallback to current time if the timestamp parsing fails.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
classes/models/FrmEntryMeta.php (2)

128-133: Consider documenting the metadata structure.

Since this implementation stores system data (unique ID) in entry metadata with field_id=0, it would be helpful to document this convention in a code comment or documentation. This helps maintain the code and prevents accidental conflicts with future system metadata.

Example addition to the comments:

 // This unique ID is inserted with JS on form submit.
 // It is used to check for duplicate entries.
+// The unique ID is stored in entry metadata with field_id=0 to distinguish it from regular field data.

128-133: Consider adding error logging for debugging.

While the implementation is solid, adding debug logging when the unique ID is missing or invalid could help troubleshoot issues in production.

 $unique_id = FrmAppHelper::get_post_param( 'unique_id', '', 'sanitize_key' );
+if ( defined( 'WP_DEBUG' ) && WP_DEBUG && empty( $unique_id ) ) {
+    error_log( 'Formidable Forms: Missing unique_id in form submission' );
+}
 if ( $unique_id && FrmEntry::should_check_for_unique_id_match() ) {
classes/models/FrmEntry.php (2)

133-138: Fix return type hint in PHPDoc.

The method only returns boolean values, so the return type hint should be updated.

-	 * @return false|int
+	 * @return bool

Line range hint 54-166: Excellent implementation for preventing duplicate form submissions.

The solution effectively addresses the iPhone resubmission issue by:

  1. Using a unique ID to track submissions
  2. Storing the ID in entry metadata
  3. Providing configurable duplicate checking
  4. Including proper validation and error handling

This aligns perfectly with the PR objectives and has been confirmed working by users.

Consider adding unit tests to verify:

  • Unique ID generation and storage
  • Duplicate detection with various time windows
  • Filter hook functionality
    Would you like me to help create these tests?
🧰 Tools
🪛 GitHub Check: PHPStan

[failure] 139-139:
Method FrmEntry::maybe_check_for_unique_id_match() never returns int so it can be removed from the return type.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1154b99 and faa76aa.

📒 Files selected for processing (2)
  • classes/models/FrmEntry.php (3 hunks)
  • classes/models/FrmEntryMeta.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPStan
classes/models/FrmEntry.php

[failure] 139-139:
Method FrmEntry::maybe_check_for_unique_id_match() never returns int so it can be removed from the return type.

🔇 Additional comments (6)
classes/models/FrmEntryMeta.php (2)

128-133: LGTM! The implementation properly handles the unique ID storage.

The code correctly:

  • Sanitizes the unique ID input using sanitize_key
  • Stores it in entry metadata with appropriate conditions
  • Uses clear and descriptive comments

128-133: Verify the integration with JavaScript changes.

The comment indicates that the unique ID is inserted via JavaScript. Let's verify this integration to ensure the complete solution works as expected.

✅ Verification successful

Based on the search results, I can see that the JavaScript integration for the unique ID is properly implemented. The code shows:

  1. The unique ID is added during form submission in js/formidable.js:
// Add a unique ID, used for duplicate checks.
const uniqueIDInput = document.createElement('input');
uniqueIDInput.type = 'hidden';
uniqueIDInput.name = 'unique_id';
uniqueIDInput.value = getUniqueKey();
object.appendChild(uniqueIDInput);
  1. The form submission handling is properly set up with event listeners and the unique ID is added before the form is submitted.

Integration with JavaScript is properly implemented.

The JavaScript code adds a hidden input field with the unique ID during form submission, which matches with the PHP code that processes this unique ID. The integration is complete and working as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for the JavaScript implementation that generates and inserts the unique ID.
# Expected: Find the JavaScript code that handles the unique ID insertion.

# Search for unique ID related code in JavaScript files
rg -t js "unique_id" 

# Search for form submission handling code
ast-grep --pattern 'function $_(form) {
  $$$
  unique_id
  $$$
}'

Length of output: 144


Script:

#!/bin/bash
# Let's get more context around the JavaScript implementation
rg -t js -B 5 -A 5 "unique_id"

# Also search for form submission related code that might handle this
rg -t js "beforeSubmit|submit" -B 5 -A 5

Length of output: 127757

classes/models/FrmEntry.php (4)

139-166: LGTM! Well-structured implementation of unique ID checking.

The method effectively:

  • Validates the unique ID presence
  • Handles invalid timestamps gracefully
  • Uses a reasonable time window (1 month) for duplicate checks
  • Properly serializes the meta value
🧰 Tools
🪛 GitHub Check: PHPStan

[failure] 139-139:
Method FrmEntry::maybe_check_for_unique_id_match() never returns int so it can be removed from the return type.


171-181: LGTM! Well-designed control method with extensibility.

The method provides a clean way to toggle unique ID checking with a filter hook for customization.


54-56: LGTM! Proper integration of unique ID checking.

The early return when a unique ID match is found is correctly placed and efficiently prevents unnecessary processing.


87-89: LGTM! Proper handling of system metadata.

The check correctly skips processing of system metadata (field_id = 0) which is important for the unique ID implementation.

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 29, 2024

Codecov Report

Attention: Patch coverage is 41.37931% with 17 lines in your changes missing coverage. Please review.

Project coverage is 27.17%. Comparing base (f894f70) to head (27f129e).
Report is 65 commits behind head on master.

Files with missing lines Patch % Lines
classes/models/FrmEntry.php 38.46% 16 Missing ⚠️
classes/models/FrmEntryMeta.php 66.66% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2133      +/-   ##
============================================
+ Coverage     27.16%   27.17%   +0.01%     
- Complexity     8187     8200      +13     
============================================
  Files           127      127              
  Lines         27183    27202      +19     
============================================
+ Hits           7384     7392       +8     
- Misses        19799    19810      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
classes/models/FrmEntry.php (1)

155-163: Consider making the time window configurable.

The 1-month window (MONTH_IN_SECONDS) for checking duplicate entries is currently hardcoded. Consider making this configurable through a filter hook, similar to how frm_check_for_unique_id_match works.

Example filter usage:

$time_window = apply_filters( 'frm_unique_id_time_window', MONTH_IN_SECONDS );
'created_at >' => gmdate( 'Y-m-d H:i:s', $timestamp - $time_window ),
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 155-163: classes/models/FrmEntry.php#L155-L163
Added lines #L155 - L163 were not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between faa76aa and 27f129e.

📒 Files selected for processing (1)
  • classes/models/FrmEntry.php (3 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
classes/models/FrmEntry.php

[warning] 55-55: classes/models/FrmEntry.php#L55
Added line #L55 was not covered by tests


[warning] 88-88: classes/models/FrmEntry.php#L88
Added line #L88 was not covered by tests


[warning] 141-141: classes/models/FrmEntry.php#L141
Added line #L141 was not covered by tests


[warning] 150-152: classes/models/FrmEntry.php#L150-L152
Added lines #L150 - L152 were not covered by tests


[warning] 155-163: classes/models/FrmEntry.php#L155-L163
Added lines #L155 - L163 were not covered by tests


[warning] 165-165: classes/models/FrmEntry.php#L165
Added line #L165 was not covered by tests

🔇 Additional comments (4)
classes/models/FrmEntry.php (4)

54-56: LGTM! Early return for unique ID match is well-placed.

The addition of the unique ID check before other duplicate checks is an efficient approach that aligns with the PR objectives.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 55-55: classes/models/FrmEntry.php#L55
Added line #L55 was not covered by tests


87-89: LGTM! Skip processing for field_id=0 is a good optimization.

The check prevents unnecessary processing of metadata entries with field_id=0, which are used for storing the unique_id.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 88-88: classes/models/FrmEntry.php#L88
Added line #L88 was not covered by tests


168-182: LGTM! Well-documented filter hook for performance control.

The method provides a clean way to disable unique ID checks if needed, with clear documentation and appropriate use of WordPress filter patterns.


133-166: 🛠️ Refactor suggestion

Add unit tests and consider improving error handling.

The new method looks good but has a few areas for improvement:

  1. Add unit tests to cover this critical duplicate detection logic.
  2. Consider adding debug logging for when unique ID checks fail.
  3. Using serialize() for DB storage can be problematic if the serialization format changes.
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 141-141: classes/models/FrmEntry.php#L141
Added line #L141 was not covered by tests


[warning] 150-152: classes/models/FrmEntry.php#L150-L152
Added lines #L150 - L152 were not covered by tests


[warning] 155-163: classes/models/FrmEntry.php#L155-L163
Added lines #L155 - L163 were not covered by tests


[warning] 165-165: classes/models/FrmEntry.php#L165
Added line #L165 was not covered by tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants