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, default FALSE) on FileItem, with a schema entry in file.schema.yml and 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's uri_scheme for new uploads.
  • ManagedFile::valueCallback(): new uploads replace the scheme in #upload_location with 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 with file.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(): adds uri_scheme_field: false to 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_file elements built by FileWidget: #uri_scheme_field and #uri_scheme_default.
  • New uri_scheme value in the widget's submitted input. It is passed on to the field item values and used by FileFieldItemList::postSave(). It is not stored.

Data model changes

  • New field storage setting uri_scheme_field (boolean) for the file field type, inherited by image.
  • A post-update function sets uri_scheme_field to FALSE on 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.

Issue fork drupal-1202074

Command icon 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

dhalbert’s picture

Version: 7.x-dev » 8.x-dev

Moving to D8 for attention, as I doubt this would get added to 7 at this point.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

finne’s picture

Version: 8.4.x-dev » 8.5.x-dev
Issue summary: View changes
StatusFileSize
new15.93 KB

I 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.

finne’s picture

Issue summary: View changes
Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 6: toggleable-1202074-6.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

finne’s picture

Status: Needs work » Needs review
StatusFileSize
new1.13 KB
new17.56 KB

New 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.

lendude’s picture

Status: Needs review » Needs work

Some points I see:

  1. +++ b/core/modules/file/config/schema/file.schema.yml
    @@ -38,6 +38,9 @@ field.storage_settings.file:
    +    uri_scheme_field:
    +      type: boolean
    +      label: 'Enable Upload destination field'
    

    This would require an update path for existing file fields.

  2. +++ b/core/modules/file/file.field.inc
    @@ -64,8 +70,11 @@ function template_preprocess_file_widget_multiple(&$variables) {
    +    // Delay rendering of the "Display" and "Location" option and the weight selector, so that
    

    longer then 80 characters

  3. +++ b/core/modules/file/file.field.inc
    @@ -74,7 +83,14 @@ function template_preprocess_file_widget_multiple(&$variables) {
    -    $display = '';
    

    why is this getting removed?

  4. +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
    @@ -139,6 +140,13 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
    +      '#title' => $this->t('Enable <em>Upload destination</em> field'),
    ...
    +      '#description' => $this->t('The upload destination option allows users to choose where the final files should be stored.'),
    

    Not sure about the 'upload destination' name. Isn't it more 'file system used for upload' or something along those lines?

  5. +++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
    @@ -404,6 +410,23 @@ public static function process($element, FormStateInterface $form_state, $form)
    +      $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
    ...
    +        $file = \Drupal::service('entity_type.manager')->getStorage('file')->load($item['target_id']);
    

    These can be injected right?

  6. +++ b/core/modules/file/tests/src/Functional/FileFieldUploadDestinationTest.php
    @@ -0,0 +1,137 @@
    +    self::assertCount(1, $fields, 'Upload destination radio "public" input exists.');
    

    core uses $this-> not self:: in tests.

  7. +++ b/core/modules/file/tests/src/Functional/FileFieldUploadDestinationTest.php
    @@ -0,0 +1,137 @@
    +    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
    +    $node_storage->resetCache([$nid]);
    +    $node = $node_storage->load($nid);
    

    why is this needed?

  8. +++ b/core/modules/file/tests/src/Functional/FileFieldUploadDestinationTest.php
    @@ -0,0 +1,137 @@
    +    // Test if the newly added file is saved in private://.
    

    it's not newly added now anymore right? It's the same file that was moved?

finne’s picture

Re #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.

finne’s picture

Patch contained some files that should not have been there. Correction uploaded.

finne’s picture

Status: Needs work » Needs review
idebr’s picture

Status: Needs review » Needs work

Updating status to 'Needs work' for #10.1

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

idebr’s picture

Status: Needs work » Needs review
StatusFileSize
new1.38 KB
new17.29 KB

Added sanity check to check whether the file is still available in the widget.

#10.1 is not yet addressed.

Status: Needs review » Needs work

The last submitted patch, 16: toggleable-1202074-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new16.65 KB
new1.22 KB

Rerolled the patch for 8.5.4 and tried to fix the test.

Status: Needs review » Needs work

The last submitted patch, 18: 1202074-18.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

ralphvdhoudt’s picture

StatusFileSize
new16.1 KB

Rerolled the patch for 8.9.6

kapilv’s picture

Assigned: Unassigned » kapilv
kapilv’s picture

Assigned: kapilv » Unassigned
Status: Needs work » Needs review
StatusFileSize
new10.68 KB
new11 KB

Hear an updated patch for 9.2.x

Status: Needs review » Needs work

The last submitted patch, 27: 1202074-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

raman.b’s picture

Status: Needs work » Needs review
StatusFileSize
new13.16 KB
new2.7 KB
solideogloria’s picture

Issue summary: View changes

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

seanb’s picture

StatusFileSize
new12.8 KB

Reroll for 9.3.x.

Status: Needs review » Needs work

The last submitted patch, 33: 1202074-33-9.3.x.patch, failed testing. View results

ralphvdhoudt’s picture

StatusFileSize
new13.21 KB

Reroll 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).

ralphvdhoudt’s picture

StatusFileSize
new12.84 KB

Reroll 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).

cilefen’s picture

Status: Needs work » Needs review
ranjith_kumar_k_u’s picture

StatusFileSize
new13 KB
new1.06 KB

Fixed CS error.

Status: Needs review » Needs work

The last submitted patch, 38: 1202074-38.patch, failed testing. View results

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

seanb’s picture

StatusFileSize
new13.11 KB

Rerolled #38 for 9.5.x

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

esfonl’s picture

StatusFileSize
new13.14 KB
new833 bytes

small compatibility fix for Drupal 10 undefined call to file_move

esfonl’s picture

StatusFileSize
new13.15 KB

fix stupid issue with previous patch where i passed the plain file object to move instead of calling getFileUri on it.

johan den hollander’s picture

The #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.

esfonl’s picture

StatusFileSize
new13.14 KB

Used correct service this time.

pradhumanjain2311’s picture

Status: Needs work » Needs review
StatusFileSize
new12.78 KB

Try to fix patch #47.

needs-review-queue-bot’s picture

Status: Needs review » Needs work

The 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.)

pradhumanjain2311’s picture

Status: Needs work » Needs review
StatusFileSize
new12.57 KB
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

Please include interdiffs with all patches.

Issue summary could use some updating.

johan den hollander’s picture

Issue summary: View changes
johan den hollander’s picture

StatusFileSize
new13.04 KB
new825 bytes

Updated 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.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

johan den hollander’s picture

StatusFileSize
new9.4 KB
new1.09 KB

Rerolled 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.

johan den hollander’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

This needs an issue summary update before review. Also changes should be in MRs vs patches please.

daffie made their first commit to this issue’s fork.

daffie’s picture

Hidden the patch files.

daffie’s picture

Issue summary: View changes

Updated the IS with the help of AI.

daffie’s picture

Status: Needs work » Needs review
Issue tags: -Needs issue summary update

Created a CR with the help of AI.
I have updated the MR with the help of AI.

daffie’s picture

Issue tags: +Needs manual testing

The change need manual testing for instance in combination with S3 storage on AWS.

johan den hollander’s picture

Thanks 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_field enabled. 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_VISIBLE wrapper, 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:

  • Public files (s3fs)
  • S3 File System
  • Private files (or "Private files (s3fs)" when s3fs also takes over private://)

Storage modules that swap out public:// or private:// 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 in ManagedFile::valueCallback() / FileFieldItemList would validate against the same list instead of all WRITE_VISIBLE wrappers. 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 #process callback 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_value points 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.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new1.23 KB

The 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.