file_visibility
Keeps public files of a publicly inaccessible entity in the private filesystem until the entity becomes available for the public. Entity files are all files referred by the entity, either via entity reference fields or uploaded and embedded in text blobs.
Why?
Say, you're building an editorial workflow where, initially, the content is not public, to avoid spam. Even your new node is unpublished by default if there's at least a file field with scheme defaulting to the public file system; a spammer-uploaded file is still available for the public. Following this path, a spammer is able to manipulate the search engines. This process is called Spamdexing.
The site builder might be tempted to set the private file system as the default scheme for all uploaded files. But this is not recommended, as Drupal might crash in heavy traffic. Actually, for each file the webserver, PHP, and Drupal will have to do heavy processing. While a public file is served directly by the webserver, and 90% of the time is retrieved either from the frontend cache or even from the browser's cache.
Conclusion: we need a way to keep files private as long as there's no public content using that file. As soon as, at least, public content is using the file, the file should be moved to the public file system.
The original idea comes from a Drupal core issue.
Terminology
- File
- A file entity attached to an entity which is subject to visibility protection according to this module's scope.
- Source entity
- A content entity that refers to a file entity directly or via some different entities that are located in the middle. The reference could be an entity reference field, a file uploaded and linked via CKEditor, and so on. For instance, a node might expose a paragraph field which contains a text paragraph which embeds a file media entity. In this case the source entity is the node; and paragraph and media are traversable entities.
- Traversable entity
- An entity that is placed in the middle, between the source entity and the files, e.g., paragraphs, media, etc. Such entities can be traversed to find the source-used files.
- Path
- The path shows the trail from the source entity to the file entity. The path starts with the source entity followed by the traversable entities. The order from source to file is preserved.
How does it work?
To be able to move a file from public to private filesystem and vice versa, the module should be able to determine which are the source entities for a certain file and determine their visibility for anonymous users. But checking the source entity visibility is not enough; it should follow the path from the source entity to the file destination and check the visibility of each traversable entity on the way. This is achieved by the Drupal\file_visibility\FileVisibility service. The service interrogates all FileVisibility plugins and collects paths from source entities to the file. If at least one path has all entities publicly visible, then we consider that the file is in use and should be publickly accessible under its original public file system. If there's no path that can be traversed by an anonymous user, the file is moved under the private file system.
The main module, file_visibility, offers the FileVisibility plugin type. This plugin type knows how to determine the relation between a file and its source entity, including all paths from source to the file. By default, the module only provides the plugin discovery attributes, the manager and the interface, but no plugin class. But there is the file_visibility_track_usage submodule that ships a plugin which computes such relation by extracting data from the Track Usage module.
Third-party modules can provide their own plugins using different methods.
Configuration
This section covers the configuration when using the file_visibility_track_usage submodule.
Configure the Track Usage module first. Note that this is critical for the module to work, and it's crucial to understand how Track Usage works and how to track the relation between source entities, such as Content (node), and File (file) entities.
- Visit
/admin/config/system/track-usage/settingsand add a new tracking configuration, dedicated for File Visibility. - After adding a label, you can restrict some tracker plugins from being used. If not sure, leave all unchecked.
- If your site is publicly exposing the source entity revisions, you might want to check the "Track only the active entity revision" checkbox.
- Check "Record entity changes in real time"
- Pay attention to the Source entity types section and check the entities acting as source entities. Usually, the node entity type should be checked.
- If your site is using a complex, nested structure with paragraphs and/media, make sure you do the proper selection under the Traversable entity types section.
- Select the File (
file) entity type under Target entity types section. - It's time to configure the file visibility settings. On a complex setup you might have more than one file visibility plugins. On the
/admin/config/file-visibilitypage you can restrict the plugins to be used. For now just leave all unchecked. - Visit
/admin/config/file-visibility/track-usagepage, select the Track Usage configuration you've just created, and press the Save configuration button. - Now it's time to create the initial track usage records with Drush. From the console run
drush track_usage:update <config-machine-name>, where<config-machine-name>is the name of the Track Usage configuration you've just created. To get some help on the command, rundrush track_usage:update --help.
You're done! Drupal will automatically track and record file usages and will move files accordingly between the public and the private filesystem.
Recommendation
Until a node is saved, the uploaded file is temporary, but its URL is still reachable. It's recommended to configure the upload location under private://file-visibility/, so even a temporary spam file cannot be used. Note that private://file-visibility/ is the location under which the files are moved when they need to be protected from public access.
Contribute
DDEV, a Docker-based PHP development tool for a streamlined and unified development process, is the recommended tool for contributing to the module. The DDEV Drupal Contrib addon makes it easy to develop a Drupal module by offering the tools to set up and test the module.
Install DDEV
- Install a Docker provider by following DDEV Docker Installation instructions for your Operating System.
- Install DDEV, use the documentation that best fits your OS.
- DDEV is used mostly via CLI commands. Configure shell completion & autocomplete according to your environment.
- Configure your IDE to take advantage of the DDEV features. This is a critical step to be able to test and debug your module. Remember, the website runs inside Docker, so pay attention to these configurations:
- PhpStorm Setup
- Configure PhpStorm and VS Code for step debugging.
- Profiling with xhprof, Xdebug and Blackfire.
Checkout the module
Normally, you check out the code form an issue fork:
git clone [email protected]:issue/file_visibility-[issue number].git
cd file_visibility-[issue number]
Start DDEV
Inside the cloned project run:
ddev start
This command will fire up the Docker containers and add all configurations.
Install dependencies
ddev poser
This will install the PHP dependencies. Note that this is a replacement for Composer install command that knows how to bundle together Drupal core and the module. Read more about this command at https://github.com/ddev/ddev-drupal-contrib?tab=readme-ov-file#commands
ddev symlink-project
This symlinks the module inside web/modules/custom. Read more about this command at https://github.com/ddev/ddev-drupal-contrib?tab=readme-ov-file#commands. Note that as soon as vendor/autoload.php has been generated, this command runs automatically on every ddev start.
This command should also be run when adding new directories or files to the root of the module.
ddev exec "cd web/core && yarn install"
Install Node dependencies. This is needed for the ddev eslint command.
Install Drupal
ddev install
This will install Drupal and will enable the module.
Changing the Drupal core version
- Create a file
.ddev/config.local.yaml - In the new config file, set the desired Drupal core version. E.g.,
web_environment: - DRUPAL_CORE=^10.4 - Run
ddev restart
Refer to the original documentation: Changing the Drupal core version
Run tests
ddev phpunit: run PHPUnit testsddev phpcs: run PHP coding standards checksddev phpcbf: fix coding standards findingsddev phpstan: run PHP static analysisddev eslint: Run ESLint on Javascript and YAML files.