Problem/Motivation
A file field's upload destination (uri_scheme) is a storage-level setting, and it is locked once the field has data. Every file in a field is therefore either public or private. Sites often need to mix the two in one field, for example a media document field where most files are public but some must stay behind access control.
Proposed resolution
Add an opt-in field storage setting that lets editors choose the upload destination for each file in the widget:
- New storage setting
uri_scheme_field(boolean, defaultFALSE) onFileItem, with a schema entry infile.schema.ymland a checkbox on the storage settings form: "Enable file system used for upload field". FileWidget: when the setting is enabled, each file item gets a required Upload destination radios element. Its options are all writable, visible stream wrappers (StreamWrapperInterface::WRITE_VISIBLE). The default is the scheme of the existing file, or the field'suri_schemefor new uploads.ManagedFile::valueCallback(): new uploads replace the scheme in#upload_locationwith the selected one. Only writable, visible schemes are accepted. Existing files are not moved here.FileFieldItemList::postSave(): when the entity is saved, each file whose selected scheme differs from its current scheme is moved to the same path under the new scheme withfile.repository->move(). The target directory is prepared first. A failed move is logged, shows an error message, and keeps the file in place. Validation errors, AJAX requests and abandoned forms do not move files.file_post_update_add_uri_scheme_field(): addsuri_scheme_field: falseto existing file and image field storages.FileThemeHooks::preprocessFileWidgetMultiple(): multi-value widgets render the radios in a separate Location column, next to Display and the weight column.
Remaining tasks
TBD
User interface changes
- New checkbox on the file and image field storage settings form: "Enable file system used for upload field".
- When enabled, a required Upload destination radios element appears for each file in the file widget, shown as a Location column in multi-value tables.
Introduced terminology
None.
API changes
- New form element properties on
managed_fileelements built byFileWidget:#uri_scheme_fieldand#uri_scheme_default. - New
uri_schemevalue in the widget's submitted input. It is passed on to the field item values and used byFileFieldItemList::postSave(). It is not stored.
Data model changes
- New field storage setting
uri_scheme_field(boolean) for thefilefield type, inherited byimage. - A post-update function sets
uri_scheme_fieldtoFALSEon existing file and image field storages. - No database schema changes. The scheme is still taken from each file entity's URI.
Release notes snippet
File and image fields can now let editors choose the upload destination (for example public or private) for each file, through a new opt-in field storage setting.
| Comment | File | Size | Author |
|---|
Issue fork drupal-1202074
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #1
dhalbert commentedMoving to D8 for attention, as I doubt this would get added to 7 at this point.
Comment #6
finneI have created a patch that allows for the choice of file upload location (private/public) for each file uploaded using the file field widget. The patch also supports switching file location of existing files.
This can be used for use cases like private groups: public nodes have public files, and if a private group also uses this node type you can now save private files to to go with them, without using a separate field.
Comment #7
finneComment #9
finneNew patch.
Added fix for tests that check for file field config settings.
\Drupal\system\Tests\Installer\StandardInstallerTest::testStandardConfig
\Drupal\Tests\field\Kernel\Migrate\d6\MigrateFieldInstanceTest::testFieldInstanceMigration
These now know about the new uri_scheme_field setting.
Comment #10
lendudeSome points I see:
This would require an update path for existing file fields.
longer then 80 characters
why is this getting removed?
Not sure about the 'upload destination' name. Isn't it more 'file system used for upload' or something along those lines?
These can be injected right?
core uses $this-> not self:: in tests.
why is this needed?
it's not newly added now anymore right? It's the same file that was moved?
Comment #11
finneRe #10:
1) Yes - I would need some pointers on how to implement this.
2) Fixed.
3) Fixed - should not have been removed.
4) Fixed - renamed.
5) Can't use injection because the function is static.
6) Fixed.
7) This was unnecessary - removed.
8) Fixed.
Comment #12
finnePatch contained some files that should not have been there. Correction uploaded.
Comment #13
finneComment #14
idebr commentedUpdating status to 'Needs work' for #10.1
Comment #16
idebr commentedAdded sanity check to check whether the file is still available in the widget.
#10.1 is not yet addressed.
Comment #18
seanbRerolled the patch for 8.5.4 and tried to fix the test.
Comment #25
ralphvdhoudt commentedRerolled the patch for 8.9.6
Comment #26
kapilv commentedComment #27
kapilv commentedHear an updated patch for 9.2.x
Comment #29
raman.b commentedComment #30
solideogloria commentedComment #33
seanbReroll for 9.3.x.
Comment #35
ralphvdhoudt commentedReroll 9.2 to fix
Error: Call to undefined function Drupal\file\Element\file_prepare_directory() in Drupal\file\Element\ManagedFile::valueCallback() (line 106 of core/modules/file/src/Element/ManagedFile.php).Comment #36
ralphvdhoudt commentedReroll 9.3 to fix
Error: Call to undefined function Drupal\file\Element\file_prepare_directory() in Drupal\file\Element\ManagedFile::valueCallback() (line 106 of core/modules/file/src/Element/ManagedFile.php).Comment #37
cilefen commentedComment #38
ranjith_kumar_k_u commentedFixed CS error.
Comment #42
seanbRerolled #38 for 9.5.x
Comment #44
esfonl commentedsmall compatibility fix for Drupal 10
undefined call to file_moveComment #45
esfonl commentedfix stupid issue with previous patch where i passed the plain file object to move instead of calling getFileUri on it.
Comment #46
johan den hollander commentedThe #45 patch is a small step forward but some odd things:
When creating a new Media entity with this patch enabled:
First add file, then select private storage.
-Upload a file
-Select Private storage
-Save entity
-Edit media entity again.
-Notice it has been stored as a public file.
First select storage, then upload a file.
-Set storage to private.
-Upload a file
-Notice that private has been reset to public, set to private again
-Save entity
-Edit media entity again.
-Notice is has the correct setting, private storage.
Now when you want to change the file from private to public it is not possible anymore. It will always be private.
Comment #47
esfonl commentedUsed correct service this time.
Comment #48
pradhumanjain2311 commentedTry to fix patch #47.
Comment #49
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue.
While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)
Comment #50
pradhumanjain2311 commentedComment #51
smustgrave commentedPlease include interdiffs with all patches.
Issue summary could use some updating.
Comment #52
johan den hollander commentedComment #53
johan den hollander commentedUpdated the patch to be compatible with the latest 10.5.x release.
Main differences:
1. Updated imports in FileWidget.php to match Drupal 10.5.x structure
2. Fixed import ordering to be compatible with current codebase
core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php:
- Added missing import for StreamWrapperInterface (line 14)
- Added missing import for FileInterface (line 19)
- Adjusted import ordering to match Drupal 10.5.x conventions
- Added use statement for Drupal\Core\Url which was missing in patch #49
Technical details:
------------------
The original patch #49 failed to apply to Drupal 10.5.x because:
1. The import structure in FileWidget.php changed between versions
2. Additional imports (like Drupal\Core\Url) were added to 10.5.x
3. The patch context didn't match the current file structure
These changes make the patch compatible with Drupal 10.5.x while
maintaining all the original functionality for the uri_scheme_field feature.
All other functionality remains identical to patch #49.
Comment #55
johan den hollander commentedRerolled for Drupal 11.3.x (from #53 which targeted 10.5.x).
The functional changes are identical to #53. The patch needed a reroll because of structural changes in Drupal 11 core:
file.module → FileThemeHooks.php: The template_preprocess_file_widget_multiple() logic was moved to \Drupal\file\Hook\FileThemeHooks::preprocessFileWidgetMultiple() as part of the hook attribute conversion (https://www.drupal.org/node/3504125). The Location column header, hide($widget['uri_scheme']), and Location column rendering are now applied in FileThemeHooks.php instead of file.module.
All other files (ManagedFile.php, FileItem.php, FileWidget.php, file.schema.yml, MigrateFieldInstanceTest.php) have the same functional changes — only context lines shifted due to D11 code changes.
Tested against: Drupal 11.3.3. Patch applies cleanly and is in active use on a production multilingual Drupal site.
Comment #56
johan den hollander commentedComment #57
smustgrave commentedThis needs an issue summary update before review. Also changes should be in MRs vs patches please.
Comment #59
daffie commentedHidden the patch files.
Comment #61
daffie commentedUpdated the IS with the help of AI.
Comment #62
daffie commentedCreated a CR with the help of AI.
I have updated the MR with the help of AI.
Comment #63
daffie commentedThe change need manual testing for instance in combination with S3 storage on AWS.
Comment #64
johan den hollander commentedThanks for turning this into an MR, @davidbekker.
We've been running the patch from #55 (re-rolled for 11.3.x) on a production site with a media "Document" type that has
uri_scheme_fieldenabled. I tested it on 11.3.17: uploading to public and to private both work, and switching an existing file from public to private moves the file correctly. I haven't tested MR !17381 itself yet, since it targets main.Two things we ran into that I think the MR should still address:
1. Restrict which schemes are offered
The widget lists every
StreamWrapperInterface::WRITE_VISIBLEwrapper, so any module that registers a writable stream wrapper (s3fs, flysystem, cloud storage wrappers, …) adds an upload destination for editors. The labels also come from the wrapper, so they expose storage details. With s3fs, for example, editors see:private://)Storage modules that swap out
public://orprivate://are infrastructure choices that can differ per environment, while what editors want to decide is public or private access.I'd suggest a field storage setting next to
uri_scheme_field, e.g.uri_scheme_options, as checkboxes of the write-visible wrappers. The widget would only offer the selected schemes, and the server-side check inManagedFile::valueCallback()/FileFieldItemListwould validate against the same list instead of allWRITE_VISIBLEwrappers. A sensible default could be the field's default scheme plus private, so a newly installed storage module doesn't silently add options to existing fields. We now work around this with a#processcallback that limits the options to public and private.2. Existing files on a scheme that isn't offered
If a file is stored on a scheme that is not (or no longer) in the options,
#default_valuepoints to a missing option. With#required => TRUE, the editor gets a "field is required" error on an unrelated edit. It would be better to fall back to the field's default scheme, or to keep the current scheme as an option for that file.Comment #65
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.