FileHooks.php
Namespace
Drupal\file\HookFile
-
core/
modules/ file/ src/ Hook/ FileHooks.php
View source
<?php
namespace Drupal\file\Hook;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\StringTranslation\ByteSizeMarkup;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for file.
*/
class FileHooks {
// cspell:ignore widthx
/**
* Implements hook_help().
*/
public function help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.file':
$output = '';
$output .= '<h2>' . t('About') . '</h2>';
$output .= '<p>' . t('The File module allows you to create fields that contain files. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href=":file_documentation">online documentation for the File module</a>.', [
':field' => Url::fromRoute('help.page', [
'name' => 'field',
])->toString(),
':field_ui' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', [
'name' => 'field_ui',
])->toString() : '#',
':file_documentation' => 'https://www.drupal.org/documentation/modules/file',
]) . '</p>';
$output .= '<h2>' . t('Uses') . '</h2>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying file fields') . '</dt>';
$output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the file field can be configured separately. See the <a href=":field_ui">Field UI help</a> for more information on how to manage fields and their display.', [
':field_ui' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', [
'name' => 'field_ui',
])->toString() : '#',
]) . '</dd>';
$output .= '<dt>' . t('Allowing file extensions') . '</dt>';
$output .= '<dd>' . t('In the field settings, you can define the allowed file extensions (for example <em>pdf docx psd</em>) for the files that will be uploaded with the file field.') . '</dd>';
$output .= '<dt>' . t('Storing files') . '</dt>';
$output .= '<dd>' . t('Uploaded files can either be stored as <em>public</em> or <em>private</em>, depending on the <a href=":file-system">File system settings</a>. For more information, see the <a href=":system-help">System module help page</a>.', [
':file-system' => Url::fromRoute('system.file_system_settings')->toString(),
':system-help' => Url::fromRoute('help.page', [
'name' => 'system',
])->toString(),
]) . '</dd>';
$output .= '<dt>' . t('Restricting the maximum file size') . '</dt>';
$output .= '<dd>' . t('The maximum file size that users can upload is limited by PHP settings of the server, but you can restrict by entering the desired value as the <em>Maximum upload size</em> setting. The maximum file size is automatically displayed to users in the help text of the file field.') . '</dd>';
$output .= '<dt>' . t('Displaying files and descriptions') . '<dt>';
$output .= '<dd>' . t('In the field settings, you can allow users to toggle whether individual files are displayed. In the display settings, you can then choose one of the following formats: <ul><li><em>Generic file</em> displays links to the files and adds icons that symbolize the file extensions. If <em>descriptions</em> are enabled and have been submitted, then the description is displayed instead of the file name.</li><li><em>URL to file</em> displays the full path to the file as plain text.</li><li><em>Table of files</em> lists links to the files and the file sizes in a table.</li><li><em>RSS enclosure</em> only displays the first file, and only in a RSS feed, formatted according to the RSS 2.0 syntax for enclosures.</li></ul> A file can still be linked to directly by its URI even if it is not displayed.') . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_field_widget_info_alter().
*/
public function fieldWidgetInfoAlter(array &$info) : void {
// Allows using the 'uri' widget for the 'file_uri' field type, which uses it
// as the default widget.
// @see \Drupal\file\Plugin\Field\FieldType\FileUriItem
$info['uri']['field_types'][] = 'file_uri';
}
/**
* Implements hook_theme().
*/
public function theme() : array {
return [
// From file.module.
'file_link' => [
'variables' => [
'file' => NULL,
'description' => NULL,
'attributes' => [],
],
],
'file_managed_file' => [
'render element' => 'element',
],
'file_audio' => [
'variables' => [
'files' => [],
'attributes' => NULL,
],
],
'file_video' => [
'variables' => [
'files' => [],
'attributes' => NULL,
],
],
'file_widget_multiple' => [
'render element' => 'element',
],
'file_upload_help' => [
'variables' => [
'description' => NULL,
'upload_validators' => NULL,
'cardinality' => NULL,
],
],
];
}
/**
* Implements hook_file_download().
*/
public function fileDownload($uri) {
// Get the file record based on the URI. If not in the database just return.
/** @var \Drupal\file\FileRepositoryInterface $file_repository */
$file_repository = \Drupal::service('file.repository');
$file = $file_repository->loadByUri($uri);
if (!$file) {
return;
}
// 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;
}
}
// Find out which (if any) fields of this type contain the file.
$references = file_get_file_references($file, NULL, EntityStorageInterface::FIELD_LOAD_CURRENT, NULL);
// Stop processing if there are no references in order to avoid returning
// headers for files controlled by other modules. Make an exception for
// temporary files where the host entity has not yet been saved (for example,
// an image preview on a node/add form) in which case, allow download by the
// file's owner.
if (empty($references) && ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id())) {
return;
}
if (!$file->access('download')) {
return -1;
}
// Access is granted.
$headers = file_get_content_headers($file);
return $headers;
}
/**
* Implements hook_cron().
*/
public function cron() : void {
$age = \Drupal::config('system.file')->get('temporary_maximum_age');
$file_storage = \Drupal::entityTypeManager()->getStorage('file');
/** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
// Only delete temporary files if older than $age. Note that automatic cleanup
// is disabled if $age set to 0.
if ($age) {
$fids = \Drupal::entityQuery('file')->accessCheck(FALSE)
->condition('status', FileInterface::STATUS_PERMANENT, '<>')
->condition('changed', \Drupal::time()->getRequestTime() - $age, '<')
->range(0, 100)
->execute();
$files = $file_storage->loadMultiple($fids);
foreach ($files as $file) {
$references = \Drupal::service('file.usage')->listUsage($file);
if (empty($references)) {
if (!file_exists($file->getFileUri())) {
if (!$stream_wrapper_manager->isValidUri($file->getFileUri())) {
\Drupal::logger('file system')->warning('Temporary file "%path" that was deleted during garbage collection did not exist on the filesystem. This could be caused by a missing stream wrapper.', [
'%path' => $file->getFileUri(),
]);
}
else {
\Drupal::logger('file system')->warning('Temporary file "%path" that was deleted during garbage collection did not exist on the filesystem.', [
'%path' => $file->getFileUri(),
]);
}
}
// Delete the file entity. If the file does not exist, this will
// generate a second notice in the watchdog.
$file->delete();
}
else {
\Drupal::logger('file system')->info('Did not delete temporary file "%path" during garbage collection because it is in use by the following modules: %modules.', [
'%path' => $file->getFileUri(),
'%modules' => implode(', ', array_keys($references)),
]);
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_predelete() for file entities.
*/
public function filePredelete(File $file) {
// @todo Remove references to a file that is in-use.
}
/**
* Implements hook_tokens().
*/
public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$token_service = \Drupal::token();
$url_options = [
'absolute' => TRUE,
];
if (isset($options['langcode'])) {
$url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
$langcode = $options['langcode'];
}
else {
$langcode = NULL;
}
$replacements = [];
if ($type == 'file' && !empty($data['file'])) {
/** @var \Drupal\file\FileInterface $file */
$file = $data['file'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Basic keys and values.
case 'fid':
$replacements[$original] = $file->id();
break;
case 'uuid':
$replacements[$original] = $file->uuid();
break;
// Essential file data
case 'name':
$replacements[$original] = $file->getFilename();
break;
case 'path':
$replacements[$original] = $file->getFileUri();
break;
case 'mime':
$replacements[$original] = $file->getMimeType();
break;
case 'size':
$replacements[$original] = ByteSizeMarkup::create($file->getSize());
break;
case 'url':
// Ideally, this would use return a relative URL, but because tokens
// are also often used in emails, it's better to keep absolute file
// URLs. The 'url.site' cache context is associated to ensure the
// correct absolute URL is used in case of a multisite setup.
$replacements[$original] = $file->createFileUrl(FALSE);
$bubbleable_metadata->addCacheContexts([
'url.site',
]);
break;
// These tokens are default variations on the chained tokens handled below.
case 'created':
$date_format = DateFormat::load('medium');
$bubbleable_metadata->addCacheableDependency($date_format);
$replacements[$original] = \Drupal::service('date.formatter')->format($file->getCreatedTime(), 'medium', '', NULL, $langcode);
break;
case 'changed':
$date_format = DateFormat::load('medium');
$bubbleable_metadata = $bubbleable_metadata->addCacheableDependency($date_format);
$replacements[$original] = \Drupal::service('date.formatter')->format($file->getChangedTime(), 'medium', '', NULL, $langcode);
break;
case 'owner':
$owner = $file->getOwner();
$bubbleable_metadata->addCacheableDependency($owner);
$name = $owner->label();
$replacements[$original] = $name;
break;
}
}
if ($date_tokens = $token_service->findWithPrefix($tokens, 'created')) {
$replacements += $token_service->generate('date', $date_tokens, [
'date' => $file->getCreatedTime(),
], $options, $bubbleable_metadata);
}
if ($date_tokens = $token_service->findWithPrefix($tokens, 'changed')) {
$replacements += $token_service->generate('date', $date_tokens, [
'date' => $file->getChangedTime(),
], $options, $bubbleable_metadata);
}
if (($owner_tokens = $token_service->findWithPrefix($tokens, 'owner')) && $file->getOwner()) {
$replacements += $token_service->generate('user', $owner_tokens, [
'user' => $file->getOwner(),
], $options, $bubbleable_metadata);
}
}
return $replacements;
}
/**
* Implements hook_token_info().
*/
public function tokenInfo() {
$types['file'] = [
'name' => t("Files"),
'description' => t("Tokens related to uploaded files."),
'needs-data' => 'file',
];
// File related tokens.
$file['fid'] = [
'name' => t("File ID"),
'description' => t("The unique ID of the uploaded file."),
];
$file['uuid'] = [
'name' => t('UUID'),
'description' => t('The UUID of the uploaded file.'),
];
$file['name'] = [
'name' => t("File name"),
'description' => t("The name of the file on disk."),
];
$file['path'] = [
'name' => t("Path"),
'description' => t("The location of the file relative to Drupal root."),
];
$file['mime'] = [
'name' => t("MIME type"),
'description' => t("The MIME type of the file."),
];
$file['size'] = [
'name' => t("File size"),
'description' => t("The size of the file."),
];
$file['url'] = [
'name' => t("URL"),
'description' => t("The web-accessible URL for the file."),
];
$file['created'] = [
'name' => t("Created"),
'description' => t("The date the file created."),
'type' => 'date',
];
$file['changed'] = [
'name' => t("Changed"),
'description' => t("The date the file was most recently changed."),
'type' => 'date',
];
$file['owner'] = [
'name' => t("Owner"),
'description' => t("The user who originally uploaded the file."),
'type' => 'user',
];
return [
'types' => $types,
'tokens' => [
'file' => $file,
],
];
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Injects the file sanitization options into /admin/config/media/file-system.
*
* These settings are enforced during upload by the FileEventSubscriber that
* listens to the FileUploadSanitizeNameEvent event.
*
* @see \Drupal\system\Form\FileSystemForm
* @see \Drupal\Core\File\Event\FileUploadSanitizeNameEvent
* @see \Drupal\file\EventSubscriber\FileEventSubscriber
*/
public function formSystemFileSystemSettingsAlter(array &$form, FormStateInterface $form_state) : void {
$config = \Drupal::config('file.settings');
$form['filename_sanitization'] = [
'#type' => 'details',
'#title' => t('Sanitize filenames'),
'#description' => t('These settings only apply to new files as they are uploaded. Changes here do not affect existing file names.'),
'#open' => TRUE,
'#tree' => TRUE,
];
$form['filename_sanitization']['replacement_character'] = [
'#type' => 'select',
'#title' => t('Replacement character'),
'#default_value' => $config->get('filename_sanitization.replacement_character'),
'#options' => [
'-' => t('Dash (-)'),
'_' => t('Underscore (_)'),
],
'#description' => t('Used when replacing whitespace, replacing non-alphanumeric characters or transliterating unknown characters.'),
];
$form['filename_sanitization']['transliterate'] = [
'#type' => 'checkbox',
'#title' => t('Transliterate'),
'#default_value' => $config->get('filename_sanitization.transliterate'),
'#description' => t('Transliteration replaces any characters that are not alphanumeric, underscores, periods or hyphens with the replacement character. It ensures filenames only contain ASCII characters. It is recommended to keep transliteration enabled.'),
];
$form['filename_sanitization']['replace_whitespace'] = [
'#type' => 'checkbox',
'#title' => t('Replace whitespace with the replacement character'),
'#default_value' => $config->get('filename_sanitization.replace_whitespace'),
];
$form['filename_sanitization']['replace_non_alphanumeric'] = [
'#type' => 'checkbox',
'#title' => t('Replace non-alphanumeric characters with the replacement character'),
'#default_value' => $config->get('filename_sanitization.replace_non_alphanumeric'),
'#description' => t('Alphanumeric characters, dots <span aria-hidden="true">(.)</span>, underscores <span aria-hidden="true">(_)</span> and dashes <span aria-hidden="true">(-)</span> are preserved.'),
];
$form['filename_sanitization']['deduplicate_separators'] = [
'#type' => 'checkbox',
'#title' => t('Replace sequences of dots, underscores and/or dashes with the replacement character'),
'#default_value' => $config->get('filename_sanitization.deduplicate_separators'),
];
$form['filename_sanitization']['lowercase'] = [
'#type' => 'checkbox',
'#title' => t('Convert to lowercase'),
'#default_value' => $config->get('filename_sanitization.lowercase'),
];
$form['#submit'][] = 'file_system_settings_submit';
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
FileHooks | Hook implementations for file. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.