Skip to content

Fix custom upload directory not applied during attachment import - #7598

Open
faisalahammad wants to merge 2 commits into
pods-framework:release/3.4.0from
faisalahammad:fix/7393-custom-upload-dir-import
Open

Fix custom upload directory not applied during attachment import#7598
faisalahammad wants to merge 2 commits into
pods-framework:release/3.4.0from
faisalahammad:fix/7393-custom-upload-dir-import

Conversation

@faisalahammad

Copy link
Copy Markdown
Contributor

Description

When a File/Image/Video field is configured with "Upload Directory - Custom directory" and a custom path (for example my-folder/another-folder/{@ID}), files uploaded through a front-end form were still saved to the default wp-content/uploads/yyyy/mm folder.

The custom upload directory logic only existed in the AJAX upload path (PodsField_File::admin_ajax_upload()). Front-end imports such as the Gravity Forms integration and the Pods API save flow route through pods_attachment_import(), which called wp_upload_dir() without registering the custom directory filter, so imported files always landed in the default date-based folder.

This change refactors the shared custom directory setup and teardown logic out of the AJAX path into reusable static helpers on PodsField_File, then has pods_attachment_import() honor the field's custom directory setting. The upload_dir filter stays active through attachment metadata generation so image sub-sizes also land in the custom directory, matching the AJAX path. The Pods API save path now passes the field and pod configuration so the fix applies to normal saves too.

Related GitHub issue(s)

Fixes #7393

Testing instructions

  1. Create a Pod with a File field and set "Upload Directory" to "Custom directory" with a custom path such as my-custom-dir/{@ID}.
  2. Save a new item through a front-end pods_form() or via the Pods API with a file value as a URL/GUID.
  3. Check the saved attachment path in the media library.
  4. Confirm the file is stored under the custom directory instead of wp-content/uploads/yyyy/mm, and that image sub-sizes (thumbnails) are also generated in the custom directory.
  5. Confirm a field still using the default "WordPress uploads directory" option keeps the default date-based path (no regression).

Screenshots / screencast

Not applicable.

Changelog text for these changes

Bug: Files uploaded through a front-end form or the Pods API now respect a custom upload directory properly. #7393 (@faisalahammad)

PR checklist

When a file field is configured to use a custom upload directory, files
imported through pods_attachment_import() were still saved to the default
wp-content/uploads/yyyy/mm path. This affected front-end imports such as
the Gravity Forms integration and the Pods API save flow.

Refactor the shared custom directory setup and teardown logic out of the
AJAX upload path into reusable static helpers on PodsField_File, and have
pods_attachment_import() honor the field's custom directory setting. The
upload_dir filter stays active through attachment metadata generation so
image sub-sizes also land in the custom directory, matching the AJAX path.

Update the Pods API save path to pass the field and pod configuration so
custom directory imports work for normal saves too.

Fixes pods-framework#7393
@faisalahammad
faisalahammad changed the base branch from main to release/3.4.0 August 18, 2026 11:11
@faisalahammad

Copy link
Copy Markdown
Contributor Author

AI disclosure: this PR was written with Claude Opus 5 assistance, reviewed and tested by me.

@faisalahammad

Copy link
Copy Markdown
Contributor Author

Review: the tests are red, and half the fix is unreachable

Three things to resolve before this can merge.

1. All three new tests fail as written

pods_attachment_import() rejects anything that isn't http/https:

$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) { return ... 0; }

MediaTest.php passes $this->source_file, an absolute path to tests/codeception/_data/images/zoltar.jpg. The function returns 0, so every assertGreaterThan( 0, $attachment_id ) fails. This isn't a hollow test — it's red for all three cases.

Either call PodsField_File::use_custom_upload_dir() / filter_upload_dir() directly, or short-circuit download_url() with a pre_http_request filter and pass an https:// URL.

2. The PodsAPI half can never run

At classes/PodsAPI.php:6082:

$v = pods_image_id_from_field( $v );

if ( empty( $v ) ) {
    $v = pods_attachment_import( $v );
}

pods_image_id_from_field() returns (int) 0 when it finds nothing, so the original URL is already destroyed by the time the empty() check runs. pods_attachment_import() is therefore always called with 0 and always returns 0. Adding $item_id, false, false, $field, $pod to that call changes nothing.

(The exception message at :6096 echoing an empty $v is the same pre-existing bug.)

Fix the lost value first, or drop the PodsAPI hunk from this PR:

$image_id = pods_image_id_from_field( $v );

if ( empty( $image_id ) ) {
    $v = pods_attachment_import( $v, ... );
} else {
    $v = $image_id;
}

3. Two unannounced behaviour changes

  • The new second argument is $post_parent in pods_attachment_import(), so API imports now set the attachment's "Uploaded to" — unconditionally, even when no custom upload dir is configured. That's separate from the stated purpose and should be opt-in.
  • filter_upload_dir() becomes public static and registers as [ self::class, ... ] instead of [ $this, ... ]. Any third-party remove_filter( 'upload_dir', [ $field_obj, 'filter_upload_dir' ] ) silently stops matching.

Minor

$pieces['params']->id is 0 while creating a new item, so {@ID} in a custom path would resolve empty on first save.

Disclosure: this review was produced with AI assistance and verified against the branch code before posting.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom directory is not working for File Upload field

1 participant