#2854 - Field For Uploading Files#2875
#2854 - Field For Uploading Files#2875kodinkat wants to merge 23 commits intoDiscipleTools:developfrom
Conversation
…eld type with customizable options including accepted file types, max file size, and display settings. Updated related components and admin settings to accommodate new field type, ensuring improved user experience for file uploads.
…mproved code readability by adding spaces around array keys and values, ensuring consistent formatting across multiple files. Updated comments for clarity in the dt-posts-endpoints file.
…on formatting by removing unnecessary spaces, enhancing code readability and consistency across the tab-custom-fields file.
|
@corsacca @cairocoder01 ready for reviewing-ish - Will sanity check some more next week.. :) |
…' to 'file_upload' across multiple files for consistency. Enhanced related components and admin settings to support the new naming convention, improving clarity and maintainability of the codebase.
… import references in index files for consistency and improved the key prefix structure in the dt-components.php file to include post ID, enhancing organization and clarity.
…ownloading files associated with posts. The endpoint verifies permissions, retrieves file metadata, generates a presigned URL, and serves the file with appropriate headers. Enhanced error handling for missing parameters and failed fetch attempts.
…ype across multiple files, including activity logging for uploads and deletions. Updated related components to improve user experience and maintainability, ensuring consistent handling of file uploads in the system.
… consistency in file upload and deletion messages across the codebase. Enhanced readability by removing unnecessary whitespace and ensuring uniformity in string interpolation.
…pace to enhance code readability and maintain consistency across the file. This change improves the overall formatting without altering functionality.
|
@corsacca @cairocoder01 latest updates ready for review.... |
…in index files to enhance clarity and maintain uniformity in the component structure. Adjusted the order of exports for better organization without affecting functionality.
…ents in index files to ensure uniformity in the component structure. This change enhances clarity and maintains organization without impacting functionality.
|
Testing just to confirm the front-end and UX:
I haven't reviewed any of the backend code for its implementation. I'll leave that to @corsacca |
…ools-theme into 2854-field-for-uploading-files
… uploads in the new record flow to prevent premature API calls. Added support for displaying file upload counts in modular lists and ensured proper upload handling after record creation. Updated related components for improved user experience and error handling.
…sed on file uploads in the archive template, updated documentation to reflect new filter options, and enhanced JavaScript handling for file upload filters. This improves the user experience by allowing users to search for posts with or without uploaded files.
|
@cairocoder01 @corsacca review ready....
|
|
…ools-theme into 2854-field-for-uploading-files
…gs and improving error handling. This enhances code clarity and maintains functionality during file uploads.
…ructure and enhance file organization.
Code Review: PR #2875 — File Upload FieldThanks for this feature! Adding a dedicated Security Concerns🔴 HTTP Header Injection via Content-Type (
|
…in settings and custom fields. Additionally, sanitize Content-Type in post endpoints to prevent HTTP header injection, ensuring improved security and data integrity.
Code Review: #2875 - Field For Uploading FilesThis is a substantial PR adding a new High Priority1. No server-side file type or size validation ( The Recommendation: After receiving the upload, call 2. Entire file buffered in PHP memory for downloads ( $file_content = wp_remote_retrieve_body( $response );
header( 'Content-Length: ' . strlen( $file_content ) );
echo $file_content;This loads the complete file into PHP memory before outputting it. For large files this will hit PHP memory limits. The comment in the code acknowledges the streaming approach was abandoned due to a bug, but no alternative is in place. Consider either: (a) fixing the streaming approach, (b) returning a redirect to the presigned S3 URL (simpler, no memory issue), or (c) at minimum documenting a recommended Also: Medium Priority3. Orphaned thumbnails when deleting a file ( When a file object is deleted, only the main // Missing after DT_Storage_API::delete_file( $file_key ):
if ( ! empty( $file_object['thumbnail_key'] ) ) {
DT_Storage_API::delete_file( $file_object['thumbnail_key'] );
}
if ( ! empty( $file_object['large_thumbnail_key'] ) ) {
DT_Storage_API::delete_file( $file_object['large_thumbnail_key'] );
}4. Delete endpoint returns success when S3 deletion fails ( If 5. Potential XSS in admin JS template literal (
file_type_icon_html = `<img src="${file_type_icon}" class="field-icon" ...>`;If this value contains 6. header( 'Content-Disposition: attachment; filename="' . esc_attr( $file_name ) . '"' );
Low Priority / Code Quality7.
8. values = [`${fileCount} ${fileCount === 1 ? 'file' : 'files'}`];These hardcoded English strings should go through the JS i18n pattern used elsewhere in the codebase. 9. New-record error leaves user stranded ( If file uploads fail after the record is created, the page stays in an error state with no redirect to the partially-created record. Consider redirecting to the record and surfacing the error there instead. 10.
11. WordPress coding standards require 12. Missing
What's Working Well
Summary
The most important items before merge: server-side file type validation and the download memory issue — both can cause real production problems. The thumbnail deletion bug will silently accumulate orphaned S3 objects and is worth fixing now. Review generated with Claude Code |
|
@cairocoder01 review ready... |
|
This looks good to me. Ready for @corsacca When all looks good, I can merge the associated PR on the component library first, publish a release, and then update the version here in this PR before merging. |
|
Thanks guys! @kodinkat
I think we can enable those by default. No? It looks like we are getting the icons working by default: I don't think we need the extra input for making it different. |
|
Otherwise the upload field is looking good! I don't think the above changes require changing the component, so we might be good there. @cairocoder01 |
|
@corsacca Sounds good. I merged the component request and created release v0.8.10 with this new component in it. Available from NPM now. |
…ools-theme into 2854-field-for-uploading-files
… include various document formats and audio/video types. Refactor related JavaScript and PHP code to improve handling of accepted file types, including the removal of the file type icon feature for a cleaner interface. Update placeholder text and help descriptions for better user guidance on file uploads.
…dability. Adjusted formatting of accepted file types array to enhance code clarity while maintaining existing functionality.
Code Review: #2854 — Field For Uploading FilesThis is a significant feature PR. The overall approach is solid and the UI looks well thought-out. Below are the issues I found, ordered by severity. Critical1. No server-side file type validation ( The Fix required: Add server-side validation against the field's High2. $file_count = is_array( $files['name'] ) ? count( $files['name'] ) : 1; // outer loop bound
for ( $i = 0; $i < $file_count; $i++ ) {
// ...
$file_count = count( $uploaded_file_names ); // OVERWRITES outer $file_count mid-loop
}This will cause the loop to terminate early when multiple files are uploaded and at least one succeeds, resulting in only the first file being processed. Use a different variable name for the inner count. 3. $type = $upload['type'] ?? ''; // Client-supplied, not verified
$client->putObject([
'ContentType' => $type // Attacker-controlled MIME type sent to S3
]);
Medium4. The REST permission callbacks for 5.
$meta_key = sanitize_text_field( wp_unslash( $params['meta_key'] ) );But 6. header( 'Content-Disposition: attachment; filename="' . esc_attr( $file_name ) . '"' );
7. $file_content = wp_remote_retrieve_body( wp_remote_get( $presigned_url, ['timeout' => 300] ) );This loads the entire file into PHP memory before sending it to the client. For large files (video, etc.), this will exhaust
8. When Low / Standards9. N+1 presigned URL generation per page render ( Each file in the list generates 2-3 presigned URL signing operations (main + thumbnails). For a post with many attachments, this can noticeably slow page renders. Consider caching with a short TTL. 10. AND pm.meta_key = '" . esc_sql( $query_key ) . "'WordPress Coding Standards require 11. If the API is called with a JSON body where 12. The PR changes 13. A few places in the PR use SummaryThe most critical item before merging is #1 (server-side file type validation) — the Great work overall on a complex feature — the field registration, REST endpoint structure, and UI integration all look well thought out. |
Code Review: #2875 - Field For Uploading FilesGreat feature addition! The new 🔴 High Priority1. No server-side MIME type validation ( The $file_object = [
'type' => $uploaded_file['type'], // client-controlled!
\];An attacker can spoof this to any MIME type. Recommend using 2. Entire file buffered in memory during download ( $file_content = wp_remote_retrieve_body( $response );
echo $file_content;
🟠 Medium Priority3. No server-side file size limit enforcement ( The 4. $storage_s3_url_duration = ... $params['storage_s3_url_duration'] ...;
new \DateTimeImmutable( $storage_s3_url_duration ); // only checks parseabilityA client can request 5. HTTP header injection risk in header( 'Content-Disposition: attachment; filename="' . esc_attr( $file_name ) . '"' );
$safe_name = preg_replace( '/[\r\n]/', '', $file_name );
header( 'Content-Disposition: attachment; filename="' . rawurlencode( $safe_name ) . '"' );Also apply 6. Unconditional delete_post_meta( $post_id, $meta_key ); // runs even if S3 deletions all failedIf S3 deletions fail (network error, etc.), the post meta is still wiped, creating orphaned S3 objects and losing all file references. Only delete/update meta after confirming the S3 operations succeeded. 🟡 Low Priority7. Thumbnail keys orphaned on single-file delete ( Each file object stores 8. Presigned URLs generated for all files on every page load ( // (always regenerate to refresh expired presigned URLs)
$file['url'] = DT_Storage_API::get_file_url( $file_key );A record with 20 files makes 20+ synchronous S3 API calls on every page render (more with thumbnails). This will degrade page load times noticeably. Consider generating these lazily via JS on demand, or caching with a TTL. 9. header( 'Content-Length: ' . strlen( $file_content ) );Use 10. Inconsistent sanitization in $post_type = $params['post_type']; // not sanitized
$post_id = $params['id']; // not sanitizedCompare with 11. Only ✅ Positive Observations
Review generated with Claude Code |
…adjusting the placement of PHP closing tags. This change enhances the overall structure of the file without altering existing functionality.
PR Review: #2854 – Field For Uploading FilesThis is a substantial and well-structured PR that implements multi-file upload support as a new SecurityGood patterns:
Concerns:
Code Quality
Max file size: 0 and negatives are accepted. $custom_field['max_file_size'] = (int) $post_submission['visibility']['max_file_size'];A value of Default
If the default list ever needs updating, all three need to change together. Consider defining a canonical PHP constant or filter and having JS fetch it from the localized settings object.
'file_upload', // Logged directly per-operation via dt_activity_insert()WordPress coding standards note: } else if ( isset( $custom_field['max_file_size'] ) ) {Should be Documentation
The field settings documentation ( Summary
The main ask before merge would be clarifying/enforcing file type validation server-side and applying |
…s-endpoints.php. Improved security by sanitizing post type, ID, and meta key values. Added normalization for uploaded file metadata to prevent potential issues with file names and types. Updated file download headers to ensure safe handling of file names.
Code Review: PR #2875 — Field For Uploading FilesGreat feature addition! The file upload field is a significant and useful capability. I have done a thorough review of the diff and found several issues ranging from critical security concerns to minor improvements. Please address the critical and high-severity items before merging. Critical1. MIME type sanitization fallback restores raw inputFile: dt-posts/dt-posts-endpoints.php sanitize_mime_type() returns empty string when the input is invalid. The code falls back to the raw browser-supplied value, completely negating the sanitization. An attacker can submit a MIME type that fails sanitization, and the raw value gets stored in post meta and used as the Content-Type header in storage_download. Use 'application/octet-stream' as the fallback instead of the raw input. 2. storage_rename_single does not sanitize post_type or post_idFile: dt-posts/dt-posts-endpoints.php The rename endpoint reads $post_type = $params['post_type'] and $post_id = $params['id'] without sanitize_text_field() or absint() respectively. Compare to storage_delete_single and storage_download which correctly apply those sanitizers. Both values are then passed to get_post_meta(), get_the_title(), and dt_activity_insert unsanitized. Apply the same pattern used in the other endpoints. 3. Content-Disposition header injection via unescaped filenameFile: dt-posts/dt-posts-endpoints.php The code strips CR/LF from the filename but does not escape double-quotes or backslashes before embedding it in the Content-Disposition header as a quoted-string. A filename containing a literal double-quote (e.g. foo".exe) breaks out of the quoted-string per RFC 6266, enabling header injection. Also escape backslash and double-quote characters before building the header value. 4. storage_download uses sanitize_text_field instead of absint for post IDFile: dt-posts/dt-posts-endpoints.php storage_delete_single correctly uses absint() for the post ID. storage_download uses sanitize_text_field(), which allows values like -1 or 1.5 to reach get_post_meta(). Use absint() consistently across all endpoints. High5. display_layout not validated against allowlistFile: dt-core/admin/admin-settings-endpoints.php Any arbitrary string is accepted and stored for display_layout, then emitted as a web component attribute on every page rendering the field. Validate against the expected values (e.g. ['grid', 'list']) using in_array() with strict comparison before storing. 6. accepted_file_types stored without MIME type validationFile: dt-core/admin/admin-settings-endpoints.php The comma-separated accepted file types list is only processed with sanitize_text_field() — no validation that entries are valid MIME types or extension patterns. An admin could store '/' to bypass all upload type restrictions. Validate each entry against a MIME type allowlist. 7. storage_download loads entire file into PHP memoryFile: dt-posts/dt-posts-endpoints.php The endpoint calls wp_remote_get() with a 300-second timeout, buffers the entire response body in PHP memory with wp_remote_retrieve_body(), then echoes it. For large files this will exhaust the PHP memory limit and block a PHP worker for up to 5 minutes. The correct approach is to issue an HTTP redirect to the presigned S3 URL, letting the browser download directly from S3. Presigned URLs are already scoped and time-limited. If server-side mediation is required, use PHP chunked streaming instead of full buffering. 8. $icon output escaping regression in shared_attributes()File: dt-core/utilities/dt-components.php The previous code applied esc_html() to the icon value when building the attribute string. The new code removes this. Since $icon originates from field settings (admin-editable), a crafted font-icon value could break out of the attribute context. Restore esc_html() on the final icon value. 9. Race condition in concurrent file deletionsFile: dt-posts/dt-posts-endpoints.php The delete flow is: (1) fetch post meta, (2) find matching file, (3) delete from S3, (4) write updated array back. Two concurrent deletions on the same field will each read the same initial array and one write will overwrite the other — causing a deleted file to reappear in post meta while it is already gone from S3. Consider serializing deletes with a transient lock or using optimistic locking with a version check. Medium10. File type validated from browser-supplied $_FILES['type']File: dt-posts/dt-posts-endpoints.php $_FILES['type'] is provided by the browser and cannot be trusted for security decisions. The server should verify actual file content using wp_check_filetype_and_ext() (which inspects magic bytes) to confirm the content matches the claimed type. If DT_Storage_API::upload_file() already performs magic-byte validation, please add a comment noting this. 11. meta_key not validated as a registered file_upload fieldAll new storage endpoints. In storage_delete_single, storage_rename_single, and storage_download, the meta_key parameter is sanitized as a string but never verified to be a registered file_upload field on the given post type. A user with can_update permission could pass arbitrary meta keys (e.g. post_status) to the delete endpoint, potentially corrupting unrelated post meta. Validate meta_key against DT_Posts::get_post_settings($post_type)['fields'] and confirm the field type is file_upload before proceeding. 12. No server-side file count limit per uploadFile: dt-posts/dt-posts-endpoints.php There is no upper bound on the number of files uploaded in a single request or accumulated per field. A user with can_update permission could exhaust storage or cause post meta serialization bloat. Add a configurable maximum file count limit enforced server-side. 13. Orphaned S3 objects on DB write failureFile: dt-posts/dt-posts-endpoints.php If S3 deletion succeeds but update_post_meta() fails, the file is gone from storage but still referenced in the database. The reverse (S3 timeout, meta already updated) leaves orphaned S3 objects. At minimum, log failures so they can be reconciled manually. 14. SQL built via string interpolation in has_value_sqlFile: dt-posts/posts.php The new file_upload filter uses string interpolation to build SQL. While the specific values here are safe (wpdb table name, get_current_user_id()), WordPress coding standards require $wpdb->prepare() for all dynamic SQL queries. Please refactor to use prepared statements with placeholders. Low15. No link to created record on file upload failureFile: dt-assets/js/new-record.js If a record is created successfully but a file upload then fails, the user is shown an error with no path to the newly-created record. The catch block should include a link to response.permalink so users can access the record even when uploads fail. 16. Download endpoint uses POST method for a read operationFile: dt-posts/dt-posts-endpoints.php The download endpoint is registered as POST. Per REST semantics, read operations should use GET. Using POST prevents browser caching, bookmarking, and direct linking. Consider GET with query parameters, or (better) switching to presigned URL redirects as described in finding #7. 17. Admin UI strings not translatableFile: dt-core/admin/js/dt-settings.js New strings in the file upload field settings modal are hardcoded English. All other admin PHP UI strings use esc_html_e() or __(). Use wp_localize_script() or @wordpress/i18n for these strings, or add a TODO comment noting the i18n gap. 18. Full serialized file arrays stored in activity logFile: dt-posts/dt-posts-endpoints.php Activity log entries store the complete serialized array of all file objects (including S3 keys) as meta_value and old_value. For records with many files this causes significant activity log table bloat and leaks S3 key paths into the log. Consider logging only file names and counts. Summary
The most important items to address before merging are the MIME type fallback (#1), header injection (#3), missing input sanitization in rename (#2), and the full-file memory buffering in download (#7). The meta_key validation gap (#11) is also important as it is a potential privilege escalation vector for users with update access. The overall architecture and field type integration looks solid — these are fixable issues rather than fundamental design problems. Review generated with Claude Code |
|
@corsacca review ready.....
|








Uh oh!
There was an error while loading. Please reload this page.