Skip to content

Conversation

@saifsultanc
Copy link
Contributor

Context

⛑️ Ticket(s): https://secure.helpscout.net/conversation/2921885802/82733

Summary

File uploaded to an ACF Image field on the user profile, when user profile is updated with GFUR is removed.

https://www.loom.com/share/029fcc517e50428d81800577d1106661?sid=ac7c7d82-17ae-4161-bda6-f4c3478d5e50

@coderabbitai
Copy link

coderabbitai bot commented Apr 30, 2025

Walkthrough

The update_user_image_field method in the GPML_ACF_User_Image_Field class was modified to handle cases where the ACF image field value is empty but uploaded files exist either in the $_POST data under 'gform_uploaded_files' or in the $_FILES superglobal. It extracts the uploaded filename, queries the WordPress database for the corresponding attachment ID, and sets the field value before checking if the value is empty and whether to remove it.

Changes

File(s) Change Summary
gp-media-library/gpml-acf-user-image-field.php Added protected method get_attachment_id_by_filename to query attachment ID by filename. Extended update_user_image_field to detect uploaded files from $_POST['gform_uploaded_files'] JSON or $_FILES and set attachment ID when ACF image field value is empty. No changes to method signatures.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Form (POST)
    participant GPML_ACF_User_Image_Field
    participant WordPress DB

    User->>Form (POST): Submit form with/without image
    Form (POST)->>GPML_ACF_User_Image_Field: Call update_user_image_field($value, $post_id, $field, $input_name)
    alt $value is empty and 'gform_uploaded_files' exists or $_FILES contains file
        GPML_ACF_User_Image_Field->>Form (POST): Extract uploaded filename from $_POST['gform_uploaded_files'] or $_FILES
        GPML_ACF_User_Image_Field->>WordPress DB: Query for attachment post by filename
        WordPress DB-->>GPML_ACF_User_Image_Field: Return attachment ID (if found)
        GPML_ACF_User_Image_Field->>GPML_ACF_User_Image_Field: Set $value = attachment ID
    end
    GPML_ACF_User_Image_Field-->>Form (POST): Continue with updated $value
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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

@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

🧹 Nitpick comments (2)
gp-media-library/gpml-acf-user-image-field.php (2)

47-70: Good fix for the image retention issue, with some security considerations.

The added code effectively solves the issue by checking for uploaded files in the POST data when the ACF field value appears empty. This prevents the user's image from being lost during form resubmission.

However, there are some security considerations:

  1. The database query uses LIKE with wildcards, which could potentially match unintended files if filenames are similar.
  2. Consider adding additional validation to ensure the attachment actually belongs to the user being updated.
-					$attachment_id = $wpdb->get_var( $wpdb->prepare(
-						"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND guid LIKE %s LIMIT 1",
-						'%' . $wpdb->esc_like( $filename ) . '%'
-					));
+					// More precise query that also checks post_author matches the user being updated
+					$attachment_id = $wpdb->get_var( $wpdb->prepare(
+						"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND guid LIKE %s AND (post_author = %d OR post_author = 0) LIMIT 1",
+						'%' . $wpdb->esc_like( $filename ) . '%',
+						$user_id
+					));

48-53: Consider adding error handling for JSON parsing.

The code assumes that $clean_json will always be valid JSON. If the JSON is malformed, json_decode() will return null and no error handling is in place.

-				$clean_json = stripslashes( $raw_json );
-
-				$uploaded_files_array = json_decode( $clean_json, true );
+				$clean_json = stripslashes( $raw_json );
+				$uploaded_files_array = json_decode( $clean_json, true );
+				
+				// Add error handling for JSON parsing
+				if ( json_last_error() !== JSON_ERROR_NONE ) {
+					error_log( 'GPML_ACF_User_Image_Field: Error decoding uploaded files JSON: ' . json_last_error_msg() );
+					$uploaded_files_array = array();
+				}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b547df4 and bfa73b9.

📒 Files selected for processing (1)
  • gp-media-library/gpml-acf-user-image-field.php (1 hunks)
🔇 Additional comments (1)
gp-media-library/gpml-acf-user-image-field.php (1)

72-74: The fix correctly preserves the existing logic.

The original conditional that checks if the value is empty is maintained, with the new code improving detection of uploaded files before this check. This ensures backward compatibility with the existing behavior.

$field_id = $this->_args['field_id'];
$key = 'input_' . $field_id;

if ( isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@saifsultanc does this need to work with single file upload fields as well?

If so, you'll likely need to do a check on $_FILES if it's a single file upload field. You can check the field type like so: $field->multipleFiles

Here's an example of $_FILES with a file for a single file upload field.

image

@saifsultanc saifsultanc requested a review from veryspry June 20, 2025 11:55
Copy link

@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: 3

♻️ Duplicate comments (1)
gp-media-library/gpml-acf-user-image-field.php (1)

61-83: Address code duplication as mentioned in past review.

The database query and value assignment logic is duplicated between the two conditional blocks. This violates the DRY principle and makes maintenance harder.

+private function try_set_value_from_filename( $filename ) {
+	if ( empty( $filename ) ) {
+		return null;
+	}
+	$attachment_id = $this->get_attachment_id_by_filename( $filename );
+	return $attachment_id ? $attachment_id : null;
+}

 if ( empty( $value ) && $raw_json ) {
 	$uploaded_files_array = json_decode( stripslashes( $raw_json ), true );
 	if ( json_last_error() === JSON_ERROR_NONE && 
 	     isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
-		$filename      = $uploaded_files_array[ $key ][0]['uploaded_filename'];
-		$attachment_id = $this->get_attachment_id_by_filename( $filename );
-		if ( $attachment_id ) {
-			$value = $attachment_id;
-		}
+		$value = $this->try_set_value_from_filename( $uploaded_files_array[ $key ][0]['uploaded_filename'] );
 	}
 }

 $field = GFFormsModel::get_field( $form, $field_id );
 if ( empty( $value ) && ! $field->multipleFiles && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ) {
-	$filename      = $_FILES[ $key ]['name'];
-	$attachment_id = $this->get_attachment_id_by_filename( $filename );
-	if ( $attachment_id ) {
-		$value = $attachment_id;
-	}
+	$value = $this->try_set_value_from_filename( $_FILES[ $key ]['name'] );
 }
🧹 Nitpick comments (1)
gp-media-library/gpml-acf-user-image-field.php (1)

45-84: Consider edge cases and add documentation.

The method handles multiple scenarios but lacks documentation explaining the fallback logic. Consider adding comments and handling edge cases like:

  1. What happens if multiple attachments have the same filename?
  2. Should there be logging when fallback methods are used?
  3. What about file extension mismatches?

Add method documentation:

+/**
+ * Updates the ACF user image field with uploaded file data.
+ * 
+ * Tries multiple approaches to find the uploaded file:
+ * 1. Standard ACF field value retrieval
+ * 2. Fallback to gform_uploaded_files POST data
+ * 3. Fallback to $_FILES superglobal for single file uploads
+ * 
+ * @param int   $user_id User ID
+ * @param array $feed    Gravity Forms feed data
+ * @param array $entry   Form entry data
+ */
 function update_user_image_field( $user_id, $feed, $entry ) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e02508e and f97703f.

📒 Files selected for processing (1)
  • gp-media-library/gpml-acf-user-image-field.php (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Danger JS
  • GitHub Check: Danger JS

Comment on lines +61 to +74
$raw_json = $_POST['gform_uploaded_files'] ?? '';
$field_id = $this->_args['field_id'];
$key = 'input_' . $field_id;

if ( empty( $value ) && $raw_json ) {
$uploaded_files_array = json_decode( stripslashes( $raw_json ), true );
if ( isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
$filename = $uploaded_files_array[ $key ][0]['uploaded_filename'];
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for JSON parsing and array access.

The current code assumes the JSON structure and doesn't handle parsing errors or missing array keys, which could cause PHP warnings or unexpected behavior.

 if ( empty( $value ) && $raw_json ) {
 	$uploaded_files_array = json_decode( stripslashes( $raw_json ), true );
-	if ( isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
+	if ( json_last_error() === JSON_ERROR_NONE && 
+	     isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
 		$filename      = $uploaded_files_array[ $key ][0]['uploaded_filename'];
 		$attachment_id = $this->get_attachment_id_by_filename( $filename );
 		if ( $attachment_id ) {
 			$value = $attachment_id;
 		}
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$raw_json = $_POST['gform_uploaded_files'] ?? '';
$field_id = $this->_args['field_id'];
$key = 'input_' . $field_id;
if ( empty( $value ) && $raw_json ) {
$uploaded_files_array = json_decode( stripslashes( $raw_json ), true );
if ( isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
$filename = $uploaded_files_array[ $key ][0]['uploaded_filename'];
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
}
$raw_json = $_POST['gform_uploaded_files'] ?? '';
$field_id = $this->_args['field_id'];
$key = 'input_' . $field_id;
if ( empty( $value ) && $raw_json ) {
$uploaded_files_array = json_decode( stripslashes( $raw_json ), true );
if ( json_last_error() === JSON_ERROR_NONE &&
isset( $uploaded_files_array[ $key ][0]['uploaded_filename'] ) ) {
$filename = $uploaded_files_array[ $key ][0]['uploaded_filename'];
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
}
🤖 Prompt for AI Agents
In gp-media-library/gpml-acf-user-image-field.php around lines 61 to 74, the
code decodes JSON and accesses nested array keys without checking for JSON
parsing errors or verifying the existence of keys, which can cause warnings. Add
error handling by verifying json_decode did not return null and check that the
expected keys exist before accessing them to prevent warnings and ensure robust
behavior.

Comment on lines +77 to +83
if ( empty( $value ) && ! $field->multipleFiles && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ) {
$filename = $_FILES[ $key ]['name'];
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add filename validation for security.

The filename from $_FILES should be validated to prevent potential security issues or unexpected behavior.

 if ( empty( $value ) && ! $field->multipleFiles && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ) {
 	$filename      = $_FILES[ $key ]['name'];
+	// Validate filename
+	if ( ! preg_match( '/^[a-zA-Z0-9._-]+\.(jpg|jpeg|png|gif|webp)$/i', $filename ) ) {
+		return;
+	}
 	$attachment_id = $this->get_attachment_id_by_filename( $filename );
 	if ( $attachment_id ) {
 		$value = $attachment_id;
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ( empty( $value ) && ! $field->multipleFiles && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ) {
$filename = $_FILES[ $key ]['name'];
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
if ( empty( $value ) && ! $field->multipleFiles && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ) {
$filename = $_FILES[ $key ]['name'];
// Validate filename
if ( ! preg_match( '/^[a-zA-Z0-9._-]+\.(jpg|jpeg|png|gif|webp)$/i', $filename ) ) {
return;
}
$attachment_id = $this->get_attachment_id_by_filename( $filename );
if ( $attachment_id ) {
$value = $attachment_id;
}
}
🤖 Prompt for AI Agents
In gp-media-library/gpml-acf-user-image-field.php around lines 77 to 83, the
filename obtained from $_FILES is used directly without validation, which can
pose security risks. Add validation to the $filename variable to ensure it
contains only safe characters and does not include any path traversal sequences
or other malicious input before using it to get the attachment ID. This can be
done by sanitizing the filename or using a whitelist pattern to allow only
expected characters.

Comment on lines +36 to +43
protected function get_attachment_id_by_filename( $filename ) {
global $wpdb;

return $wpdb->get_var( $wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND guid LIKE %s LIMIT 1",
'%' . $wpdb->esc_like( $filename ) . '%'
));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve the filename matching logic for better reliability.

The current approach of using LIKE '%filename%' on the GUID field may produce unreliable results since:

  1. WordPress attachment GUIDs typically contain full URLs, not just filenames
  2. The wildcard matching could match unintended attachments
  3. There's no validation of the input filename

Consider using WordPress's built-in functions or querying by post_title or _wp_attached_file meta instead.

 protected function get_attachment_id_by_filename( $filename ) {
-	global $wpdb;
-
-	return $wpdb->get_var( $wpdb->prepare(
-		"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND guid LIKE %s LIMIT 1",
-		'%' . $wpdb->esc_like( $filename ) . '%'
-	));
+	global $wpdb;
+	
+	// First try exact match on _wp_attached_file meta
+	$attachment_id = $wpdb->get_var( $wpdb->prepare(
+		"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s LIMIT 1",
+		'%' . $wpdb->esc_like( basename( $filename ) )
+	));
+	
+	if ( $attachment_id ) {
+		return $attachment_id;
+	}
+	
+	// Fallback to post_title match
+	return $wpdb->get_var( $wpdb->prepare(
+		"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND post_title = %s LIMIT 1",
+		pathinfo( $filename, PATHINFO_FILENAME )
+	));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected function get_attachment_id_by_filename( $filename ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND guid LIKE %s LIMIT 1",
'%' . $wpdb->esc_like( $filename ) . '%'
));
}
protected function get_attachment_id_by_filename( $filename ) {
global $wpdb;
// First try exact match on the _wp_attached_file meta (stores the file path)
$attachment_id = $wpdb->get_var( $wpdb->prepare(
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s LIMIT 1",
'%' . $wpdb->esc_like( basename( $filename ) )
));
if ( $attachment_id ) {
return $attachment_id;
}
// Fallback to an exact post_title match (filename without extension)
return $wpdb->get_var( $wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND post_title = %s LIMIT 1",
pathinfo( $filename, PATHINFO_FILENAME )
));
}
🤖 Prompt for AI Agents
In gp-media-library/gpml-acf-user-image-field.php around lines 36 to 43, the
current method uses a SQL LIKE query on the GUID field to find an attachment by
filename, which is unreliable because GUIDs contain full URLs and the wildcard
can match unintended results. To fix this, replace the query to search by the
'_wp_attached_file' meta key using a meta query or join with the postmeta table,
ensuring exact matching of the filename. Also, validate the input filename
before querying to avoid invalid or empty values.

@saifsultanc saifsultanc merged commit e579ea3 into master Jun 27, 2025
4 of 5 checks passed
@saifsultanc saifsultanc deleted the saif/fix/82733-fix-gpml-acf-field branch June 27, 2025 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants