Temporary files are handled by file_file_download()
, an implementation of hook_file_download()
done from the File module. The code it uses is the following one.
// Find out if a temporary file is still used in the system.
if ($file->isTemporary()) {
$usage = \Drupal::service('file.usage')->listUsage($file);
if (empty($usage) && $file->getOwnerId() != \Drupal::currentUser()
->id()) {
// Deny access to temporary files without usage that are not owned by the
// same user. This prevents the security issue that a private file that
// was protected by field permissions becomes available after its usage
// was removed and before it is actually deleted from the file system.
// Modules that depend on this behavior should make the file permanent
// instead.
return -1;
}
}
Reading the comments, that is done on purpose to avoid a private file, protected by field permissions, is visible after the field permissions are changed, but before the file is deleted.
Looking at the code that invokes that hook, in FileDownloadController::download()
for example, I don't see any way to avoid that, as the code doesn't use hook_file_download_alter()
.
A workaround could be setting that file as being used, since the code checks the file is not used, before blocking the access.
function mymodule_file_download($uri) {
if (StreamWrapperManager::getScheme($uri) == 'temporary') {
if ($files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $uri])){
if ($file = reset($files)) {
// Access is granted.
\Drupal::service('file.usage')->add($file, 'mymodule', 'unexisting_entity', 10);
$headers = file_get_content_headers($file);
return $headers;
}
}
}
}
I used 'unexisting_entity'
and 10 as entity type and entity ID. If you have real values for them, you should use those.
Note that FileUsageBase::add()
, the DatabaseFileUsageBackend::add()
parent method, changes the file to permanent, in the case it is not already.
// Make sure that a used file is permanent.
if (!$file->isPermanent()) {
$file->setPermanent();
$file->save();
}
When a file usage is decremented and becomes 0, the file is changed to temporary from FileUsageBase::delete()
.
// If there are no more remaining usages of this file, mark it as temporary,
// which result in a delete through system_cron().
$usage = \Drupal::service('file.usage')->listUsage($file);
if (empty($usage)) {
$file->setTemporary();
$file->save();
}
I would rather increase the file usage, instead of making directly a file permanent, as decreasing the file usage doesn't conflict with other modules that could set the same file as permanent.
Alternatively, I would use the following code for hook_file_download()
.
function mymodule_file_download($uri) {
if (StreamWrapperManager::getScheme($uri) == 'temporary') {
if ($files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $uri])){
if ($file = reset($files)) {
// Access is granted.
if (!$file->isPermanent()) {
$file->setPermanent();
$file->save();
}
$headers = file_get_content_headers($file);
return $headers;
}
}
}
}
In this case, to make the file temporary again, I would use the following code.
// Store the file entity reference in $file.
$usage = \Drupal::service('file.usage')->listUsage($file);
if (empty($usage) && !$file->isTemporary()) {
$file->setTemporary();
$file->save();
}
To achieve what you want, it would be also possible to change the controller for the system.temporary route, but that seems excessive.