class FileUploadHandler
Same name in other branches
- 10 core/modules/file/src/Upload/FileUploadHandler.php \Drupal\file\Upload\FileUploadHandler
- 11.x core/modules/file/src/Upload/FileUploadHandler.php \Drupal\file\Upload\FileUploadHandler
Handles validating and creating file entities from file uploads.
Hierarchy
- class \Drupal\file\Upload\FileUploadHandler
Expanded class hierarchy of FileUploadHandler
1 string reference to 'FileUploadHandler'
- file.services.yml in core/
modules/ file/ file.services.yml - core/modules/file/file.services.yml
1 service uses FileUploadHandler
- file.upload_handler in core/
modules/ file/ file.services.yml - Drupal\file\Upload\FileUploadHandler
File
-
core/
modules/ file/ src/ Upload/ FileUploadHandler.php, line 30
Namespace
Drupal\file\UploadView source
class FileUploadHandler {
/**
* The default extensions if none are provided.
*/
const DEFAULT_EXTENSIONS = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The stream wrapper manager.
*
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
*/
protected $streamWrapperManager;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The MIME type guesser.
*
* @var \Symfony\Component\Mime\MimeTypeGuesserInterface
*/
protected $mimeTypeGuesser;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a FileUploadHandler object.
*
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The file system service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
* The stream wrapper manager.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* The event dispatcher.
* @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mimeTypeGuesser
* The MIME type guesser.
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user.
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
* The request stack.
*/
public function __construct(FileSystemInterface $fileSystem, EntityTypeManagerInterface $entityTypeManager, StreamWrapperManagerInterface $streamWrapperManager, EventDispatcherInterface $eventDispatcher, MimeTypeGuesserInterface $mimeTypeGuesser, AccountInterface $currentUser, RequestStack $requestStack) {
$this->fileSystem = $fileSystem;
$this->entityTypeManager = $entityTypeManager;
$this->streamWrapperManager = $streamWrapperManager;
$this->eventDispatcher = $eventDispatcher;
$this->mimeTypeGuesser = $mimeTypeGuesser;
$this->currentUser = $currentUser;
$this->requestStack = $requestStack;
}
/**
* Creates a file from an upload.
*
* @param \Drupal\file\Upload\UploadedFileInterface $uploadedFile
* The uploaded file object.
* @param array $validators
* The validators to run against the uploaded file.
* @param string $destination
* The destination directory.
* @param int $replace
* Replace behavior when the destination file already exists:
* - FileSystemInterface::EXISTS_REPLACE - Replace the existing file.
* - FileSystemInterface::EXISTS_RENAME - Append _{incrementing number}
* until the filename is unique.
* - FileSystemInterface::EXISTS_ERROR - Throw an exception.
*
* @return \Drupal\file\Upload\FileUploadResult
* The created file entity.
*
* @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
* Thrown when a file upload error occurred.
* @throws \Drupal\Core\File\Exception\FileWriteException
* Thrown when there is an error moving the file.
* @throws \Drupal\Core\File\Exception\FileException
* Thrown when a file system error occurs.
* @throws \Drupal\file\Upload\FileValidationException
* Thrown when file validation fails.
*/
public function handleFileUpload(UploadedFileInterface $uploadedFile, array $validators = [], string $destination = 'temporary://', int $replace = FileSystemInterface::EXISTS_REPLACE) : FileUploadResult {
$originalName = $uploadedFile->getClientOriginalName();
if (!$uploadedFile->isValid()) {
switch ($uploadedFile->getError()) {
case \UPLOAD_ERR_INI_SIZE:
throw new IniSizeFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_FORM_SIZE:
throw new FormSizeFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_PARTIAL:
throw new PartialFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_NO_FILE:
throw new NoFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_CANT_WRITE:
throw new CannotWriteFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_NO_TMP_DIR:
throw new NoTmpDirFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_EXTENSION:
throw new ExtensionFileException($uploadedFile->getErrorMessage());
}
throw new FileException($uploadedFile->getErrorMessage());
}
$extensions = $this->handleExtensionValidation($validators);
// Assert that the destination contains a valid stream.
$destinationScheme = $this->streamWrapperManager::getScheme($destination);
if (!$this->streamWrapperManager
->isValidScheme($destinationScheme)) {
throw new InvalidStreamWrapperException(sprintf('The file could not be uploaded because the destination "%s" is invalid.', $destination));
}
// A file URI may already have a trailing slash or look like "public://".
if (substr($destination, -1) != '/') {
$destination .= '/';
}
// Call an event to sanitize the filename and to attempt to address security
// issues caused by common server setups.
$event = new FileUploadSanitizeNameEvent($originalName, $extensions);
$this->eventDispatcher
->dispatch($event);
$filename = $event->getFilename();
$mimeType = $this->mimeTypeGuesser
->guessMimeType($filename);
$destinationFilename = $this->fileSystem
->getDestinationFilename($destination . $filename, $replace);
if ($destinationFilename === FALSE) {
throw new FileExistsException(sprintf('Destination file "%s" exists', $destinationFilename));
}
$file = File::create([
'uid' => $this->currentUser
->id(),
'status' => 0,
'uri' => $uploadedFile->getRealPath(),
]);
// This will be replaced later with a filename based on the destination.
$file->setFilename($filename);
$file->setMimeType($mimeType);
$file->setSize($uploadedFile->getSize());
// Add in our check of the file name length.
$validators['file_validate_name_length'] = [];
// Call the validation functions specified by this function's caller.
$errors = file_validate($file, $validators);
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
}
$file->setFileUri($destinationFilename);
if (!$this->moveUploadedFile($uploadedFile, $file->getFileUri())) {
throw new FileWriteException('File upload error. Could not move uploaded file.');
}
// Update the filename with any changes as a result of security or renaming
// due to an existing file.
$file->setFilename($this->fileSystem
->basename($file->getFileUri()));
if ($replace === FileSystemInterface::EXISTS_REPLACE) {
$existingFile = $this->loadByUri($file->getFileUri());
if ($existingFile) {
$file->fid = $existingFile->id();
$file->setOriginalId($existingFile->id());
}
}
$result = (new FileUploadResult())->setOriginalFilename($originalName)
->setSanitizedFilename($filename)
->setFile($file);
// If the filename has been modified, let the user know.
if ($event->isSecurityRename()) {
$result->setSecurityRename();
}
// Set the permissions on the new file.
$this->fileSystem
->chmod($file->getFileUri());
// We can now validate the file object itself before it's saved.
$violations = $file->validate();
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
}
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
}
// If we made it this far it's safe to record this file in the database.
$file->save();
// Allow an anonymous user who creates a non-public file to see it. See
// \Drupal\file\FileAccessControlHandler::checkAccess().
if ($this->currentUser
->isAnonymous() && $destinationScheme !== 'public') {
$session = $this->requestStack
->getCurrentRequest()
->getSession();
$allowed_temp_files = $session->get('anonymous_allowed_file_ids', []);
$allowed_temp_files[$file->id()] = $file->id();
$session->set('anonymous_allowed_file_ids', $allowed_temp_files);
}
return $result;
}
/**
* Move the uploaded file from the temporary path to the destination.
*
* @todo Allows a sub-class to override this method in order to handle
* raw file uploads in https://www.drupal.org/project/drupal/issues/2940383.
*
* @param \Drupal\file\Upload\UploadedFileInterface $uploadedFile
* The uploaded file.
* @param string $uri
* The destination URI.
*
* @return bool
* Returns FALSE if moving failed.
*
* @see https://www.drupal.org/project/drupal/issues/2940383
*/
protected function moveUploadedFile(UploadedFileInterface $uploadedFile, string $uri) {
return $this->fileSystem
->moveUploadedFile($uploadedFile->getRealPath(), $uri);
}
/**
* Gets the list of allowed extensions and updates the validators.
*
* This will add an extension validator to the list of validators if one is
* not set.
*
* If the extension validator is set, but no extensions are specified, it
* means all extensions are allowed, so the validator is removed from the list
* of validators.
*
* @param array $validators
* The file validators in use.
*
* @return string
* The space delimited list of allowed file extensions.
*/
protected function handleExtensionValidation(array &$validators) : string {
// Build a list of allowed extensions.
if (isset($validators['file_validate_extensions'])) {
if (!isset($validators['file_validate_extensions'][0])) {
// If 'file_validate_extensions' is set and the list is empty then the
// caller wants to allow any extension. In this case we have to remove the
// validator or else it will reject all extensions.
unset($validators['file_validate_extensions']);
}
}
else {
// No validator was provided, so add one using the default list.
// Build a default non-munged safe list for
// \Drupal\system\EventSubscriber\SecurityFileUploadEventSubscriber::sanitizeName().
$validators['file_validate_extensions'] = [
self::DEFAULT_EXTENSIONS,
];
}
return $validators['file_validate_extensions'][0] ?? '';
}
/**
* Loads the first File entity found with the specified URI.
*
* @param string $uri
* The file URI.
*
* @return \Drupal\file\FileInterface|null
* The first file with the matched URI if found, NULL otherwise.
*
* @todo replace with https://www.drupal.org/project/drupal/issues/3223209
*/
protected function loadByUri(string $uri) : ?FileInterface {
$fileStorage = $this->entityTypeManager
->getStorage('file');
/** @var \Drupal\file\FileInterface[] $files */
$files = $fileStorage->loadByProperties([
'uri' => $uri,
]);
if (count($files)) {
foreach ($files as $item) {
// Since some database servers sometimes use a case-insensitive
// comparison by default, double check that the filename is an exact
// match.
if ($item->getFileUri() === $uri) {
return $item;
}
}
}
return NULL;
}
}
Members
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
FileUploadHandler::$currentUser | protected | property | The current user. |
FileUploadHandler::$entityTypeManager | protected | property | The entity type manager. |
FileUploadHandler::$eventDispatcher | protected | property | The event dispatcher. |
FileUploadHandler::$fileSystem | protected | property | The file system service. |
FileUploadHandler::$mimeTypeGuesser | protected | property | The MIME type guesser. |
FileUploadHandler::$requestStack | protected | property | The request stack. |
FileUploadHandler::$streamWrapperManager | protected | property | The stream wrapper manager. |
FileUploadHandler::DEFAULT_EXTENSIONS | constant | The default extensions if none are provided. | |
FileUploadHandler::handleExtensionValidation | protected | function | Gets the list of allowed extensions and updates the validators. |
FileUploadHandler::handleFileUpload | public | function | Creates a file from an upload. |
FileUploadHandler::loadByUri | protected | function | Loads the first File entity found with the specified URI. |
FileUploadHandler::moveUploadedFile | protected | function | Move the uploaded file from the temporary path to the destination. |
FileUploadHandler::__construct | public | function | Constructs a FileUploadHandler object. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.