foldershare-8.x-1.2/src/Entity/FolderShareTraits/OperationAddFileTrait.php
src/Entity/FolderShareTraits/OperationAddFileTrait.php
<?php namespace Drupal\foldershare\Entity\FolderShareTraits; use Drupal\Component\Utility\Environment; use Drupal\Core\File\FileSystemInterface; use Drupal\file\FileInterface; use Drupal\file\Entity\File; use Drupal\foldershare\ManageLog; use Drupal\foldershare\ManageFilenameExtensions; use Drupal\foldershare\ManageFileSystem; use Drupal\foldershare\Utilities\FileUtilities; use Drupal\foldershare\Utilities\FormatUtilities; use Drupal\foldershare\FolderShareInterface; use Drupal\foldershare\Entity\Exception\LockException; use Drupal\foldershare\Entity\Exception\ValidationException; /** * Add File entity as FolderShare entity. * * This trait includes methods to wrap File entities as FolderShare * entities with kind 'file' or 'image'. The File object's ID is saved * to the 'file' or 'image' entity reference field. * * <B>Internal trait</B> * This trait is internal to the FolderShare module and used to define * features of the FolderShare entity class. It is a mechanism to group * functionality to improve code management. * * @ingroup foldershare */ trait OperationAddFileTrait { /*--------------------------------------------------------------------- * * Add file(s) to root. * *---------------------------------------------------------------------*/ /** * Adds a file to the root list. * * If $allowRename is FALSE, an exception is thrown if the file's * name is not unique within the root list. But if $allowRename is * TRUE and the name is not unique, the file's name is adjusted * to include a sequence number immediately before the first "." * in the name, or at the end of the name if there is no "." * (e.g. "myfile.png" becomes "myfile 1.png"). * * An exception is thrown if the file is already in a folder. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Process locks</B> * The user's root list is locked for exclusive editing access by this * function for the duration of the modification. * * <B>Activity log</B> * This method posts a log message after each file is added. * * @param \Drupal\file\FileInterface $file * The file to be added to the root list. * @param bool $allowRename * (optional) When TRUE, the file's name should be automatically renamed to * insure it is unique within the folder. When FALSE, non-unique * file names cause an exception to be thrown. Defaults to FALSE. * * @return \Drupal\foldershare\FolderShareInterface * Returns the new FolderShare entity that wraps the given File entity. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * @throws \Drupal\foldershare\Entity\Exception\ValidationException * If the file could not be added because the file is already * in a folder, or if the file doesn't pass validation because * the name is invalid or already in use (if $allowRename FALSE). * * @see ::addFiles() */ public static function addFileToRoot( FileInterface $file , bool $allowRename = FALSE) { // Add the file, checking that the name is legal (possibly rename it). // Check if the file is already in the root list, and lock the root list // as needed. $ary = self::addFilesInternal( NULL, [ $file ], (-1), TRUE, $allowRename , TRUE); if ( empty ( $ary ) === TRUE) { return NULL; } return $ary [0]; } /** * Adds files to the root list. * * If $allowRename is FALSE, an exception is thrown if the file's * name is not unique within the root list. But if $allowRename is * TRUE and the name is not unique, the file's name is adjusted * to include a sequence number immediately before the first "." * in the name, or at the end of the name if there is no "." * (e.g. "myfile.png" becomes "myfile 1.png"). * * An exception is thrown if any file is already in a folder. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Process locks</B> * The user's root list is locked for exclusive editing access by this * function for the duration of the modification. * * <B>Activity log</B> * This method posts a log message after each file is added. * * @param \Drupal\file\FileInterface[] $files * An array of files to be added to the root list. NULL files * are silently skipped. * @param bool $allowRename * (optional) When TRUE, the file's name should be automatically renamed to * insure it is unique within the folder. When FALSE, non-unique * file names cause an exception to be thrown. Defaults to FALSE. * * @return \Drupal\foldershare\FolderShareInterface * Returns the new FolderShare entity that wraps the given File entity. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * @throws \Drupal\foldershare\Entity\Exception\ValidationException * If the file could not be added because the file is already * in a folder, or if the file doesn't pass validation because * the name is invalid or already in use (if $allowRename FALSE). * * @see ::addFiles() */ public static function addFilesToRoot( array $files , bool $allowRename = FALSE) { // Add the files, checking that their names are legal (possibly rename it). // Check if the files are already in the folder, and lock the folder // as needed. $ary = self::addFilesInternal( NULL, $files , (-1), TRUE, $allowRename , TRUE); if ( empty ( $ary ) === TRUE) { return NULL; } return $ary [0]; } /*--------------------------------------------------------------------- * * Add file(s) to folder. * *---------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function addFile( FileInterface $file , bool $allowRename = FALSE) { // Add the file, checking that the name is legal (possibly rename it). // Check if the file is already in the folder, and lock the folder // as needed. $ary = self::addFilesInternal( $this , [ $file ], (-1), TRUE, $allowRename , TRUE); if ( empty ( $ary ) === TRUE) { return NULL; } return $ary [0]; } /** * {@inheritdoc} */ public function addFiles( array $files , bool $allowRename = FALSE) { // Add the files, checking that their names are legal (possibly rename it). // Check if the files are already in the folder, and lock the folder // as needed. $ary = self::addFilesInternal( $this , $files , (-1), TRUE, $allowRename , TRUE); if ( empty ( $ary ) === TRUE) { return NULL; } return $ary [0]; } /*--------------------------------------------------------------------- * * Add files implementation. * *---------------------------------------------------------------------*/ /** * Implements addition of files to this folder. * * <B>This method is internal and strictly for use by the FolderShare * module itself.</B> * * If $allowRename is FALSE, an exception is thrown if the file's * name is not unique within the folder. But if $allowRename is * TRUE and the name is not unique, the file's name is adjusted * to include a sequence number immediately before the first "." * in the name, or at the end of the name if there is no "." * (e.g. "myfile.png" becomes "myfile 1.png"). * * All files are checked and renamed before any files are added * to the folder. If any file is invalid, an exception is thrown * and no files are added. * * An exception is thrown if any file is already in a folder, or if * this entity is not a folder. * * <B>Process locks</B> * This method locks the user's root list for additions at the root level, * or locks the destination parent's root folder tree for additions to a * folder. Locks are maintained for the duration of the addition. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Activity log</B> * This method posts a log message after each file is added. * * @param \Drupal\foldershare\FolderShareInterface $parent * The parent entity, or NULL for the root list, to which to add * the files. * @param \Drupal\foldershare\file\FileInterface[] $files * An array of File enities to be added to this folder. An empty * array and NULL file objects are silently skipped. * @param int $requestingUid * (optional, default = current user) The user ID of the user requesting * the operation. When interactive, this is the current user. When this * is a background task, this is the original requesting user. * @param bool $checkNames * When TRUE, each file's name is checked to be sure that it is * valid and not already in use. When FALSE, name checking is * skipped (including renaming) and the caller must assure that * names are good. * @param bool $allowRename * When TRUE (and when $checkNames is TRUE), each file's name * will be automatically renamed, if needed, to insure that it * is unique within the folder. When FALSE (and when $checkNames * is TRUE), non-unique file names cause an exception to be thrown. * @param bool $checkForInFolder * When TRUE, an exception is thrown if the file is already in a * folder. When FALSE, this expensive check is skipped. * * @return \Drupal\foldershare\FolderShareInterface[] * Returns an array of the newly created FolderShare file entities * added to this folder using the given $files File entities. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * @throws \Drupal\foldershare\Entity\Exception\ValidationException * Thrown if addition of the files does not validate (very unlikely). * If $checkForInFolder, also thrown if any file is already in a * folder. If $checkNames, also thrown if any name is illegal. * If $checkNames and $allowRename, also thrown if a unique name * could not be found. If $checkNames and !$allowRename, also * thrown if a name is already in use. * * @see ::addFile() * @see ::addFiles() * @see ::addUploadFiles() */ private static function addFilesInternal( FolderShareInterface $parent = NULL, array $files = [], int $requestingUid = (-1), bool $checkNames = TRUE, bool $allowRename = FALSE, bool $checkForInFolder = TRUE) { if ( empty ( $files ) === TRUE) { // Nothing to add. return NULL; } if ( $requestingUid < 0) { $requestingUid = self::getCurrentUserId()[0]; } // // Validate // -------- // Confirm the parent is provide and it is a folder. if ( $parent !== NULL && $parent ->isFolder() === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( '@method was called with an item that is not a folder.' , [ '@method' => __METHOD__ , ]))); } // // Check that files are not in a folder. // ------------------------------------- // Check that all of the files are NOT in a folder already. $filesWithGoodParentage = []; if ( $checkForInFolder === TRUE) { foreach ( $files as $index => $file ) { if ( $file === NULL) { continue ; } // Get the file's parent folder ID. There are three cases: // - FALSE = file has no parent, which is what we want. // - $parent->id = file's parent is this folder (if any), so skip it. // - other = file already has parent, so error. $fileParentId = self::findFileWrapperId( $file ); if ( $fileParentId !== FALSE) { // The file is apparently already wrapped by a FolderShare entity. if ( $parent !== NULL && $fileParentId === (int) $parent ->id()) { // Already in this folder. Skip it. continue ; } // Complain. $v = new ValidationException(FormatUtilities::createFormattedMessage( t( '@method was called to add a file to a folder, but it is already there.' , [ '@method' => __METHOD__ , ]))); $v ->setItemNumber( $index ); throw $v ; } // The file is not already in a folder, so add it to the list // to process. $filesWithGoodParentage [] = $file ; } } else { // Files are assumed to be not in a folder. foreach ( $files as $file ) { if ( $file !== NULL) { $filesWithGoodParentage [] = $file ; } } } if ( empty ( $filesWithGoodParentage ) === TRUE) { // Nothing to add. return NULL; } // // Check name legality. // -------------------- // Check that all files have legal names. if ( $checkNames === TRUE) { foreach ( $filesWithGoodParentage as $index => $file ) { // Check that the new name is legal. $name = $file ->getFilename(); if (self::isNameLegal( $name ) === FALSE) { throw new ValidationException( self::getStandardIllegalNameExceptionMessage( $name )); } // Verify that the name meets any file name extension restrictions. if (ManageFilenameExtensions::isNameExtensionAllowed( $name , NULL) === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The file type used by "@name" is not supported.' , [ '@name' => $name , ]), t( 'The file name uses a file type extension "@extension" that is not supported on this site.' , [ '@extension' => ManageFilenameExtensions::getExtensionFromPath( $name ), ]), t( 'Supported file type extensions:' ), ManageFilenameExtensions::getAllowedNameExtensions())); } } } // // Lock root list or root folder. // ------------------------------ // When adding files to the user's root list, lock that root list. // This lock is needed while we check for name collisions there. // // When adding files to a folder, lock that folder. This too is // needed while we check for name collisions theer. if ( $parent === NULL) { // LOCK USER'S ROOT LIST. if (self::acquireUserRootListLock() === FALSE) { throw new LockException( self::getStandardLockExceptionMessage(t( 'added' ), $name )); } } else { // LOCK PARENT ROOT'S FOLDER TREE. $rootId = $parent ->getRootItemId(); if (self::acquireRootOperationLock( $rootId ) === FALSE) { throw new LockException( self::getStandardLockExceptionMessage(t( 'added' ), $name )); } } // // Check names. // ------------ // Check that names do not collide with whatever is already there. $filesToAdd = []; $originalNames = []; $savedException = NULL; if ( $checkNames === TRUE) { // Get a list of names to check against. if ( $parent === NULL) { $childNames = self::findAllRootItemNames( $requestingUid ); } else { $childNames = $parent ->findChildrenNames(); } foreach ( $filesWithGoodParentage as $index => $file ) { $name = $file ->getFilename(); if ( $allowRename === TRUE) { // If not unique, rename. $uniqueName = self::createUniqueName( $childNames , $name ); if ( $uniqueName === FALSE) { $savedException = new ValidationException( self::getStandardCannotCreateUniqueNameExceptionMessage( 'new file' )); $savedException ->setItemNumber( $index ); break ; } // Change public file name. This name also appears in the URI. $originalNames [(int) $file ->id()] = $name ; $file ->setFilename( $uniqueName ); $file ->setFileUri(ManageFileSystem::getFileUri( $file )); $file ->save(); // Add to the child name list so that further checks // will catch collisions. $childNames [ $uniqueName ] = 1; } else { if (isset( $childNames [ $name ]) === TRUE) { // Not unique and not allowed to rename. Fail. $savedException = new ValidationException( self::getStandardNameInUseExceptionMessage( $name )); $savedException ->setItemNumber( $index ); break ; } // Add to the child name list so that further checks // will catch collisions. $childNames [ $name ] = 1; } $filesToAdd [] = $file ; } if ( $savedException !== NULL) { // Something went wrong. Some files might already have // been renamed. Restore them to their prior names. if ( $allowRename === TRUE) { foreach ( $filesWithGoodParentage as $file ) { if (isset( $originalNames [(int) $file ->id()]) === TRUE) { // Restore the prior name. $file ->setFilename( $originalNames [(int) $file ->id()]); $file ->setFileUri(ManageFileSystem::getFileUri( $file )); $file ->save(); } } } if ( $parent === NULL) { // UNLOCK USER'S ROOT LIST. self::releaseUserRootListLock(); } else { // UNLOCK PARENT ROOT'S FOLDER TREE. self::releaseRootOperationLock( $parent ->getRootItemId()); } throw $savedException ; } } else { // All of the files are assumed to have safe names. $filesToAdd = $filesWithGoodParentage ; } if ( empty ( $filesToAdd ) === TRUE) { if ( $parent === NULL) { // UNLOCK USER'S ROOT LIST. self::releaseUserRootListLock(); } else { // UNLOCK PARENT ROOT'S FOLDER TREE. self::releaseRootOperationLock( $parent ->getRootItemId()); } return NULL; } // // Add files. // ---------- // Create FolderShare entities for the files. For each one, // set the parent ID to be the parent folder, and the root ID to // be the parent folder's root. $parentid = ( $parent === NULL) ? NULL : $parent ->id(); $rootid = ( $parent === NULL) ? NULL : $parent ->getRootItemId(); $newEntities = []; foreach ( $filesToAdd as $file ) { // Get the MIME type for the file. $mimeType = $file ->getMimeType(); // If the file is an image, set the 'image' field to the file ID // and the kind to IMAGE_KIND. Otherwise set the 'file' field to // the file ID and the kind to FILE_KIND. if (self::isMimeTypeImage( $mimeType ) === TRUE) { $valueForFileField = NULL; $valueForImageField = $file ->id(); $valueForKindField = self::IMAGE_KIND; } else { $valueForFileField = $file ->id(); $valueForImageField = NULL; $valueForKindField = self::FILE_KIND; } // Create new FolderShare entity in the parent folder or the root list. // - Automatic id. // - Automatic uuid. // - Automatic creation date. // - Automatic changed date. // - Automatic langcode. // - Empty description. // - Empty author grants. // - Empty view grants. // - Empty disabled grants. $f = self::create([ 'name' => $file ->getFilename(), 'uid' => $requestingUid , 'kind' => $valueForKindField , 'mime' => $mimeType , 'file' => $valueForFileField , 'image' => $valueForImageField , 'size' => $file ->getSize(), 'parentid' => $parentid , 'rootid' => $rootid , ]); // Add default grants to a root item. $f ->addDefaultAccessGrants(); $f ->save(); $newEntities [] = $f ; } // Update the sizes of the parent folder and its ancestors. if ( $parent !== NULL) { $parent ->updateSizeAndAncestors(); } // // Unlock. // ------- // Unlock the user's root list or the parent root folder tree, depending // on which one was locked earlier. if ( $parent === NULL) { // UNLOCK USER'S ROOT LIST. self::releaseUserRootListLock(); } else { // UNLOCK PARENT ROOT'S FOLDER TREE. self::releaseRootOperationLock( $parent ->getRootItemId()); } // // Hook & log. // ----------- // Announce the addition. if ( $parent === NULL) { self::postOperationHook( 'add_files' , [ NULL, $newEntities , $requestingUid , ]); foreach ( $newEntities as $e ) { ManageLog::activity( "Added file '@name' (# @id) to top level." , [ '@id' => $e ->id(), '@name' => $e ->getName(), 'entity' => $e , 'uid' => $requestingUid , ]); } } else { self::postOperationHook( 'add_files' , [ $parent , $newEntities , $requestingUid , ]); foreach ( $newEntities as $e ) { ManageLog::activity( "Added file '@name' (# @id) to '@parentName' (# @parentId)." , [ '@id' => $e ->id(), '@name' => $e ->getName(), '@parentId' => $parent ->id(), '@parentName' => $parent ->getName(), 'entity' => $e , 'uid' => $requestingUid , ]); } } return $newEntities ; } /*--------------------------------------------------------------------- * * File utilities. * * These functions handle quirks of the File module. * *---------------------------------------------------------------------*/ /** * Creates a File entity for an existing local file. * * <B>This method is internal and strictly for use by the FolderShare * module itself.</B> * * A new File entity is created using the existing file, and named using * the given file name. In the process, the file is moved into the proper * FolderShare directory tree and the stored file renamed using the * module's numeric naming scheme. * * The filename, MIME type, and file size are set, the File entity is * marked as a permanent file, and the file's permissions are set for * access by the web server. * * <B>Process locks</B> * This function does not lock access. The caller should lock around changes * to the entity. * * @param string $uri * The URI to a stored file on the local file system. * @param string $name * The publically visible file name for the new File entity. This should * be a legal file name, without a preceding path or scheme. The MIME * type of the new File entity is based on this name. * * @return \Drupal\file\FileInterface * Returns a new File entity with the given file name, and a properly * set MIME type and size. The entity is owned by the current user. * The File's URI points to a FolderShare directory tree file moved * from the given local path. A NULL is returned if the local path is * empty. * * @throws \Drupal\foldershare\Entity\Exception\SystemException * Thrown if an error occurs when trying to create any file or directory * or move the local file to the proper directory. */ private static function createFileEntityFromLocalFile( string $uri , string $name = NULL) { // If the path is empty, there is no file to create. if ( empty ( $uri ) === TRUE || empty ( $name ) === TRUE) { return NULL; } // // Setup // ----- // Get the new file's MIME type and size. $mimeType = \Drupal::service( 'file.mime_type.guesser' )->guess( $name ); $fileSize = FileUtilities:: filesize ( $uri ); // // Create initial File entity // -------------------------- // Create a File object that wraps the file in its current location. // This is not the final File object, which must be adjusted by moving // the file from its current location to FolderShare's directory tree. // // The status of 0 marks the file as temporary. If left this way, the // File module will automatically delete the file in the future. $file = File::create([ 'uid' => \Drupal::currentUser()->id(), 'uri' => $uri , 'filename' => $name , 'filemime' => $mimeType , 'filesize' => $fileSize , 'status' => 0, ]); $file ->save(); // // Move file and update File entity // -------------------------------- // Creating the File object assigns it a unique ID. We need this ID // to create a new long-term local file name and location when the // file is in its proper location in the FolderShare directory tree. // // Create the new proper file URI with a numeric ID-based name and path. $storedUri = ManageFileSystem::getFileUri( $file ); // Move the stored file from its current location to the FolderShare // directory tree and rename it using the File entity's ID. This updates // the File entity. // // Moving the file also changes the file name and MIME type to values // based on the new URI. This is not what we want, so we'll have to // fix this below. $newFile = file_move( $file , $storedUri , FileSystemInterface::EXISTS_REPLACE); if ( $newFile === FALSE) { // The file move has failed. $file -> delete (); ManageLog::critical( "Local file system: A file at '@name' could not be moved to '@destination'.\nThere may be a problem with server's directories or permissions." , [ '@name' => $file ->getFilename(), '@destination' => 'temp directory' , ]); throw new SystemException(t( "System error. A file at '@name' could not be moved to '@destination'.\nThere may be a problem with directories or permissions. Please report this to the site administrator." , [ '@name' => $name , '@destination' => $storedUri , ])); } // Mark the file permanent and fix the name and MIME type. $newFile ->setPermanent(); $newFile ->setFilename( $name ); $newFile ->setFileUri(ManageFileSystem::getFileUri( $newFile )); $newFile ->setMimeType( $mimeType ); $newFile ->save(); return $newFile ; } /** * Duplicates a File object. * * <B>This method is internal and strictly for use by the FolderShare * module itself.</B> * * The file is duplicated, creating a new copy on the local file system. * * Exceptions are very unlikely and should only occur when something * catastrophic happens to the underlying file system, such as if it * runs out of space, if someone deletes key directories, or if the * file system goes offline. * * <B>Process locks</B> * This function does not lock access. The caller should lock around changes * to the entity. * * @param \Drupal\file\FileInterface $file * The file to copy. * @param string $newName * (optional, default = '') The new name for the file copy. If empty, * the existing name is used. * @param int $newOwnerId * (optional, default = (-1)) The new owner ID for the file copy. * If negative, the existing owner ID is used. * * @return \Drupal\file\FileInterface * The new file copy. * * @throws \Drupal\foldershare\Entity\Exception\SystemException * Throws an exception if a serious system error occurred, such as a * file system becomes unreadable/unwritable, gets full, or goes offline. * * @see ::copyAndAddFilesInternal() */ private static function duplicateFileEntityInternal( FileInterface $file , string $newName = '' , int $newOwnerId = (-1)) { // // Implementation note: // // The File module's file_copy() will copy a File object into a new // File object with a new URI for the file name. However, our // file naming scheme for local storage file names uses the object's // entity ID, and we don't know that before calling file_copy(). // // So, this function calls file_copy() to copy the given file into a // temp file, and then calls file_move() to move the temp file into // a file with the proper entity ID-based name. // // Complicating things, file_copy() and file_move() both invoke // hook functions that can rename the file, which is not appropriate // here. So, after moving the file, the file name is restored to // something reasonable. if ( $file === NULL) { return NULL; } // Copy the file into a temp location. // // Allow the file to be renamed in order to avoid name collisions // with any other temp files in progress. $newFile = file_copy( $file , ManageFileSystem::createLocalTempFile(), FileSystemInterface::EXISTS_RENAME); if ( $newFile === FALSE) { // Unfortunately, file_copy returns FALSE on an error // and provides no further details to us on the problem. // Instead, it writes a message to the log file and/or // to the output page, which is not useful to us or // meaningful to the user. // // Looking at the source code, the following types of // errors could occur: // - The source file doesn't exist. // - The destination directory doesn't exist. // - The destination directory can't be written. // - The destination filename is in use. // - The source and destination are the same. // - Some other error occurred (probably a system error). // // Since the directory and file names are under our control // and are valid, the only errors that can occur here are // catastrophic, such as: // - File deleted out from under us. // - File system changed out from under Drupal. // - File system full, offline, hard disk dead, etc. // // For any of these, it is unlikely we can continue with // copying anything. Time to abort. ManageLog::critical( "Local file system: A local file at \"@name\" could not be copied to the site's temporary directory.\nThere may be a problem with server's directories and/or permissions." , [ '@name' => $file ->getFilename(), ]); throw new SystemException(FormatUtilities::createFormattedMessage( t( 'System error. A local file at "@name" could not be copied to the site\'s temporary directory.' , [ '@name' => $file ->getFilename(), ]), t( "There may be a problem with the site's directories or permissions. Please report this to the site administrator." ))); } // The File copy's fields are mostly correct: // - 'filesize' matches the original file's size. // - 'uid' is for the current user. // - 'fid', 'uuid', 'created', and 'changed' are new and uptodate. // - 'langcode' is at a default. // - 'status' is permanent. // // The following fields need correcting: // - 'uri' and 'filename' are for a temp file. // // Change the copied file's user-visible file name to match the // original file's name. This does not change the name on disk. // This must be set before we create the new URI because the filename's // extension is used in the new URI. // // Change the copied file's MIME type to match the original file's // MIME type. // // The URI is fixed now by moving the file. // // With an entity ID for the new file, create the correct URI // and move the file there. $newFile ->setFilename( $file ->getFilename()); $newUri = ManageFileSystem::getFileUri( $newFile ); $newerFile = file_move( $newFile , $newUri , FileSystemInterface::EXISTS_REPLACE); // Because of EXISTS_REPLACE, the returned File object in // $newerFile has the same entity ID as $newFile, unless the // call returns FALSE to indicate an error. if ( $newerFile === FALSE) { // See the above comment about ways file_copy() can fail. // The same applies here. $newFile -> delete (); ManageLog::critical( "Local file system: A local file at '@name' could not be moved to '@destination'.\nThere may be a problem with the server's directories and/or permissions." , [ '@name' => $file ->getFilename(), '@destination' => $newUri , ]); throw new SystemException(FormatUtilities::createFormattedMessage( t( 'System error. A local file at "@name" could not be moved to "@destination".' , [ '@name' => $file ->getFilename(), '@destination' => $newUri , ]), t( "There may be a problem with the site's directories or permissions. Please report this to the site administrator." ))); } unset( $newUri ); $newerFile ->setMimeType( $file ->getMimeType()); if ( empty ( $newName ) === FALSE) { $newerFile ->setFilename( $newName ); } if ( $newOwnerId >= 0) { $newerFile ->setOwnerId( $newOwnerId ); } $newerFile ->save(); // Garbage collect. The file_move() above creates multiple objects // then deletes them. Flush them from memory ASAP. gc_collect_cycles(); return $newerFile ; } /*--------------------------------------------------------------------- * * Upload files. * *---------------------------------------------------------------------*/ /** * Adds uploaded files for the named form field into the root list. * * When a file is uploaded via an HTTP form post from a browser, PHP * automatically saves the data into "upload" files saved in a * PHP-managed temporary directory. This method sweeps those uploaded * files, pulls out the ones associated with the named form field, * and adds them to this folder with their original names. * * If there are no uploaded files, this method returns immediately. * * Files may be automatically renamed, if needed, to insure they have * unique names within the folder. * * <B>Process locks</B> * This method locks the user's root list for the duration of the addition. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Activity log</B> * This method posts a log message after the files have been added. * * @param string $formFieldName * The name of the form field with associated uploaded files pending. * @param bool $allowRename * (optional, default = TRUE) When TRUE, if a file's name collides with the * name of an existing entity, the name is modified by adding a number on * the end so that it doesn't collide. When FALSE, a file name collision * throws an exception. * * @return array * The returned array contains one entry per uploaded file. * An entry may be a File object uploaded into the current folder, * or a string containing an error message about that file and * indicating that it could not be uploaded and added to the folder. * An empty array is returned if there were no files to upload * and add to the folder. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * * @see ::addFile() * @see ::addFiles() * * @todo The error messages returned here should be removed in favor of * error codes and arguments so that the caller can know which file * had which error and do something about it or report their own * error message. Returning text messages here is not flexible. */ public static function addUploadFilesToRoot( string $formFieldName , bool $allowRename = TRUE) { return self::addUploadFilesInternal( NULL, $formFieldName , (-1), $allowRename ); } /** * {@inheritdoc} */ public function addUploadFiles( string $formFieldName , bool $allowRename = TRUE) { return self::addUploadFilesInternal( $this , $formFieldName , (-1), $allowRename ); } /** * Adds uploaded files to the selected parent folder or root list. * * <B>Process locks</B> * If there is no parent, the user's root list is locked for the duration * of the addition. If there is a parent, the parent's root folder tree is * locked for the duration of the addition. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Activity log</B> * This method posts a log message after the files have been added. * * @param \Drupal\foldershare\FolderShareInterface $parent * The parent folder into which to place the uploaded files. If NULL, * uploaded files are added to the root list. * @param string $formFieldName * The name of the form field with associated uploaded files pending. * @param int $requestingUid * (optional, default = current user) The user ID of the user requesting * the operation. When interactive, this is the current user. When this * is a background task, this is the original requesting user. * @param bool $allowRename * (optional, default = TRUE) When TRUE, if a file's name collides with the * name of an existing entity, the name is modified by adding a number on * the end so that it doesn't collide. When FALSE, a file name collision * throws an exception. * * @return array * The returned array contains one entry per uploaded file. * An entry may be a File object uploaded into the current folder, * or a string containing an error message about that file and * indicating that it could not be uploaded and added to the folder. * An empty array is returned if there were no files to upload * and add to the folder. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * * @todo The error messages returned here should be removed in favor of * error codes and arguments so that the caller can know which file * had which error and do something about it or report their own * error message. Returning text messages here is not flexible. */ private static function addUploadFilesInternal( FolderShareInterface $parent = NULL, string $formFieldName = '' , int $requestingUid = (-1), bool $allowRename = TRUE) { static $uploadCache ; // // Drupal's file_save_upload() function is widely used for handling // uploaded files. It does several activities at once: // // - 1. Collect all uploaded files. // - 2. Validate that the uploads completed. // - 3. Run plug-in validators. // - 4. Optionally check for file name extensions. // - 5. Add ".txt" on executable files. // - 6. Rename inner extensions (e.g. ".tar.gz" -> "._tar.gz"). // - 7. Validate file names are not too long. // - 8. Validate the destination exists. // - 9. Optionally rename files to avoid destination collisions. // - 10. chmod the file to be accessible. // - 11. Create a File object. // - 12. Set the File object's URI, filename, and MIME type. // - 13. Optionally replace a prior File object with a new file. // - 14. Save the File object. // - 15. Cache the File objects. // // There are several problems, though. // // - The plug-in validators in (3) are not needed by us. // // - The extension checking in (4) can only be turned off for // the first file checked, due to a bug in the current code. // // - The extension changes in (5) and (6) are mandatory, but // nonsense when we'll be storing files without extensions. // // - The file name length check in (7) uses PHP functions that // are not multi-byte character safe, and it limits names to // 240 characters, independent of the actual field length. // // - The file name handling loses the original file name that // we need to maintain and show to users. // // - The file movement can't leave the file in our desired // destination directory because that directory's name is a // function of the entity ID, which isn't known until after // file_save_upload() has created the file and moved it to // what it thinks is the final destination. // // - Any errors generated are logged and reported directly to // to the user. No exceptions are thrown. The only error // indicator returned to the caller is that the array of // returned File objects can include a FALSE for a file that // failed. But there is no indication about which file it was // that failed, or why. // // THIS function repeats some of the steps in file_save_upload(), // but skips ones we don't need. It also keeps track of errors and // returns error messages instead of blurting them out to the user. // // Validate inputs // --------------- // Validate uploads exist and that they are for this field. if ( empty ( $formFieldName ) === TRUE) { // No field to get files for. return []; } if ( $requestingUid < 0) { $requestingUid = self::getCurrentUserId()[0]; } // Get a list of all uploaded files, across all ongoing activity // for all uploads of any type. $allFiles = \Drupal::request()->files->get( 'files' , []); // If there is nothing for the requested form field, return. if (isset( $allFiles [ $formFieldName ]) === FALSE) { return []; } // If the file list for the requested form field is empty, return. $filesToProcess = $allFiles [ $formFieldName ]; unset( $allFiles ); if ( empty ( $filesToProcess ) === TRUE) { return []; } // If there is just one item, turn it into an array of items // to simplify further code. if ( is_array ( $filesToProcess ) === FALSE) { $filesToProcess = [ $filesToProcess ]; } // // Cache shortcut // -------------- // It is conceivable that this function gets called multiple // times on the same form. To avoid redundant processing, // check a cache of recently uploaded files and return from // that cache quickly if possible. // // The cache will be cleared and set to a new list of files // at the end of this function. if (isset( $uploadCache [ $formFieldName ]) === TRUE) { return $uploadCache [ $formFieldName ]; } // // Validate upload success // ----------------------- // Loop through the available uploaded files and separate out the // ones that failed, along with an error message about why it failed. $goodFiles = []; $failedMessages = []; foreach ( $filesToProcess as $index => $fileInfo ) { if ( $fileInfo === NULL) { // Odd. A file is listed in the uploads, but it isn't really there. $failedMessages [ $index ] = (string) t( "System error. The @index-th uploaded file could not be found. Please try again." , [ '@index' => $index , ]); continue ; } $filename = $fileInfo ->getClientOriginalName(); // Check for errors. On any error, create an error message // and add it to the messages array. If the error is very // severe, also log it. switch ( $fileInfo ->getError()) { case UPLOAD_ERR_INI_SIZE: // Exceeds max PHP size. case UPLOAD_ERR_FORM_SIZE: // Exceeds max form size. $failedMessages [ $index ] = (string) t( "Maximum file size limit exceeded.\nThe file '@file' could not be added to the folder because it exceeds the website's maximum allowed file size of @maxsize." , [ '@file' => $filename , '@maxsize' => FormatUtilities::formatBytes(Environment::getUploadMaxSize()), ]); break ; case UPLOAD_ERR_PARTIAL: // Uploaded only partially uploaded. $failedMessages [ $index ] = (string) t( "Interrupted file upload.\nThe file '@file' could not be added to the folder because the upload was interrupted and only part of the file was received." , [ '@file' => $filename , ]); break ; case UPLOAD_ERR_NO_FILE: // Upload wasn't started. $failedMessages [ $index ] = (string) t( "Maximum upload number limit exceeded.\nThe file '@file' could not be added to the folder because its inclusion exceeds the website's maximum allowed number of file uploads at one time." , [ '@file' => $filename , ]); break ; case UPLOAD_ERR_NO_TMP_DIR: // No temp directory configured. $failedMessages [ $index ] = (string) t( "Website configuration problem.\nThe file '@file' could not be added to the folder because the website encountered a site configuration error about a missing temporary directory. Please report this to the site administrator." , [ '@file' => $filename , ]); ManageLog::critical( "Local file system: File upload failed because the server's PHP temporary directory is missing!" ); break ; case UPLOAD_ERR_CANT_WRITE: // Temp directory not writable. $failedMessages [ $index ] = (string) t( "Website configuration problem.\nThe file '@file' could not be added to the folder because the website encountered a site configuration error. The site's temporary directory is missing write permissions. Please report this to the site administrator." , [ '@file' => $filename , ]); ManageLog::critical( "Local file system: File upload failed because the server's PHP temporary directory is not writable!" ); break ; case UPLOAD_ERR_EXTENSION: // PHP extension failed for some reason. $failedMessages [ $index ] = (string) t( "Website configuration problem.\nThe file '@file' could not be added to the folder because the website encountered a site configuration error. Please report this to the site administrator." , [ '@file' => $filename , ]); ManageLog::critical( "Misconfigured web site: File upload failed because a PHP extension failed for an unknown reason.\nThe server's installation of PHP appears to be misconfigured." ); break ; case UPLOAD_ERR_OK: // Success! if ( is_uploaded_file ( $fileInfo ->getRealPath()) === FALSE) { // But the file doesn't actually exist! $failedMessages [ $index ] = (string) t( "Website internal error.\nThe file '@file' could not be added to the folder because the data was lost during the upload." , [ '@file' => $filename , ]); ManageLog::error( "Local file system: File upload failed because the local uploaded file went missing after the upload completed." ); } else { $goodFiles [ $index ] = $fileInfo ; } break ; default : // Unknown error. $failedMessages [ $index ] = (string) t( "Website internal error.\nThe file '@file' could not be added to the folder because of an unknown problem." , [ '@file' => $filename , ]); ManageLog::warning( "Local file system: File upload failed with an unrecognized error '@code'." , [ '@code' => $fileInfo ->getError(), ]); break ; } } unset( $filesToProcess ); // // Validate names // -------------- // Check that all of the original file names are legal for storage // in this module. This checks file name length and character content, // and allows for multi-byte characters. $passedFiles = []; foreach ( $goodFiles as $index => $fileInfo ) { $filename = $fileInfo ->getClientOriginalName(); if (self::isNameLegal( $filename ) === FALSE) { $failedMessages [ $index ] = (string) t( "The name '@name' cannot be used.\nThe file '@file' could not be added to the folder because it's name must be between 1 and 255 characters long and the name cannot use ':', '/', or '\\' characters." , [ '@file' => $filename , ]); } else { $passedFiles [ $index ] = $fileInfo ; } } // And reduce the good files list to the ones that passed. $goodFiles = $passedFiles ; unset( $passedFiles ); // If there are no good files left, return the errors. if ( empty ( $goodFiles ) === TRUE) { $uploadCache [ $formFieldName ] = $failedMessages ; return $failedMessages ; } // // Validate extensions // ------------------- // The folder's 'file' field contains the allowed filename extensions // for this site. If the list is empty, do not do extension checking. // // Note that we specifically DO NOT do some of the extension handling // found in file_save_upload(): // // - We do not add ".txt" to the end of executable files // (.php, .pl, .py, .cgi, .asp, and .js). This was intended // to protect web servers from unintentionally executing // uploaded files. However, for this module all uploaded files // will stored without extensions, so this is not necessary. // // - We do not replace inner extensions (e.g. "archive.tar.gz") // with a "._". Again, this was intended to protect web servers // from falling back from the last extension to an inner // extension and, again, unintentionally executing uploaded // files. However, for this module all uploaded files will be // stored without extensions, so this is not necessary. $extensionsString = ManageFilenameExtensions::getAllowedNameExtensions(); if ( empty ( $extensionsString ) === FALSE) { // Break up the extensions. $extensions = mb_split( ' ' , $extensionsString ); // Loop through the good files again and split off the // ones with good extensions. $passedFiles = []; foreach ( $goodFiles as $index => $fileInfo ) { $filename = $fileInfo ->getClientOriginalName(); if (ManageFilenameExtensions::isNameExtensionAllowed( $filename , $extensions ) === FALSE) { // Extension is not allowed. $failedMessages [ $index ] = (string) t( "Unsupported file type.\nThe file '@file' could not be added to the folder because it uses a file name extension that is not allowed by this website." , [ '@file' => $filename , ]); } else { $passedFiles [ $index ] = $fileInfo ; } } // And reduce the good files list to the ones that passed. $goodFiles = $passedFiles ; unset( $passedFiles ); // If there are no good files left, return the errors. if ( empty ( $goodFiles ) === TRUE) { $uploadCache [ $formFieldName ] = $failedMessages ; return $failedMessages ; } } // // Process files // ------------- // At this point we have a list of uploaded files that all exist // on the server and have acceptable file name lengths and extensions. // We can now try to create File objects. // // Get the file system service. $fileSystem = \Drupal::service( 'file_system' ); // Get the MIME type service. $mimeGuesser = \Drupal::service( 'file.mime_type.guesser' ); // Loop through the files and create initial File objects. Move // each file into the Drupal temporary directory. $fileObjects = []; foreach ( $goodFiles as $index => $fileInfo ) { $filename = $fileInfo ->getClientOriginalName(); $filemime = $mimeGuesser ->guess( $filename ); $filesize = $fileInfo ->getSize(); $uploadPath = $fileInfo ->getRealPath(); $tempUri = $fileSystem ->getDestinationFilename( FileSystemInterface::EXISTS_RENAME); // Move file to Drupal temp directory. // // The file needs to be moved out of PHP's temporary directory // into Drupal's temporary directory. // // PHP's move_uploaded_file() can do this, but it doesn't // handle Drupal streams. So use Drupal's file system for this. // // Let the URI get changed to avoid collisions. This does not // affect the user-visible file name. if ( $fileSystem ->moveUploadedFile( $uploadPath , $tempUri ) === FALSE) { // Failure likely means a site problem, such as a bad // file system, full disk, etc. Try to keep going with // the rest of the files. $drupalTemp = file_directory_temp(); $failedMessages [ $index ] = (string) t( "Website configuration error.\nThe file '@file' could not be added to the folder because the website encountered a site configuration error about a missing Drupal temporary directory. Please report this to the site administrator." , [ '@file' => $filename , ]); ManageLog::critical( "Local file system: File upload failed because local Drupal temporary directory '@dir' is missing!" , [ '@dir' => $drupalTemp , ]); continue ; } // Set permissions. Make the file accessible to the web server, etc. FileUtilities:: chmod ( $tempUri ); // Create a File object. Make it owned by the current user. Give // it the temp URI, file name, MIME type, etc. A status of 0 means // the file is temporary still. $file = File::create([ 'uid' => $requestingUid , 'uri' => $tempUri , 'filename' => $filename , 'filemime' => $filemime , 'filesize' => $filesize , 'status' => 0, 'source' => $formFieldName , ]); // Save! Saving the File object assigns it a unique entity ID. $file ->save(); $fileObjects [ $index ] = $file ; } unset( $goodFiles ); // If there are no good files left, return the errors. if ( empty ( $fileObjects ) === TRUE) { $uploadCache [ $formFieldName ] = $failedMessages ; return $failedMessages ; } // // Move into local directory // ------------------------- // This module manages files within a directory tree built from // the entity ID. This entity ID is not known until after the // File object is done. So we now need another pass through the // File objects to use their entity IDs and move the files to their // final destinations. // // Along the way we also mark the file as permanent and attach it // to this folder. $movedObjects = []; foreach ( $fileObjects as $index => $file ) { // Create the final destination URI. This is the URI that uses // the entity ID. $finalUri = ManageFileSystem::getFileUri( $file ); // Move it there. The directory will be created automatically // if needed. $newFile = file_move( $file , $finalUri , FileSystemInterface::EXISTS_REPLACE); if ( $newFile === FALSE) { // Unfortunately, file_move() just returns FALSE on an error // and provides no further details to us on the problem. // Instead, it writes a message to the log file and/or // to the output page, which is not useful to us or // meaningful to the user. // // Looking at the source code, the following types of // errors could occur: // - The source file doesn't exist // - The destination directory doesn't exist // - The destination directory can't be written // - The destination filename is in use // - The source and destination are the same // - Some other error occurred (probably a system error) // // Since the directory and file names are under our control // and are valid, the only errors that can occur here are // catastrophic, such as: // - File deleted out from under us // - File system changed out from under Drupal // - File system full, offline, hard disk dead, etc. // // For any of these, it is unlikely we can continue with // anything. $failedMessages [ $index ] = (string) t( 'The file "@file" could not be added to the folder because the website encountered a system failure.' , [ '@file' => $filename ]); $file -> delete (); } else { // On success, $file has already been deleted and we now // need to use $newFile. $movedObjects [ $index ] = $newFile ; // Mark it permanent. $newFile ->setPermanent(); $newFile ->save(); } } // And reduce the good files list to the ones that got moved. $fileObjects = $movedObjects ; unset( $movedObjects ); // If there are no good files left, return the errors. if ( empty ( $fileObjects ) === TRUE) { $uploadCache [ $formFieldName ] = $failedMessages ; return $failedMessages ; } // // Add to folder // ------------- // At this point, $fileObjects contains a list of fully-created // File objects for files that have already been moved into their // correct locations. Add them to the folder! try { // Add them. Watch for bad names or name collisions and rename // if needed. Don't bother checking if the files are already in // the folder since we know they aren't. Do lock. self::addFilesInternal( $parent , $fileObjects , $requestingUid , TRUE, $allowRename , FALSE); } catch (\Exception $e ) { // The add can fail if: // - A file name is illegal (but we already checked). // // - A unique name could not be created because $allowRename was FALSE. // // - A lock could not be acquired. // // On any failure, none of the files have been added. foreach ( $fileObjects as $index => $file ) { $filename = $file ->getFilename(); $failedMessages [ $index ] = (string) t( 'The file "@file" could not be added to the folder because the folder is locked for exclusive use by another user.' , [ '@file' => $filename ]); $file -> delete (); } $uploadCache [ $formFieldName ] = $failedMessages ; return $failedMessages ; } // // Cache and return // ---------------- // $fileObjects contains the new File objects, indexed by the original // upload indexes. // // $failedMessages contains text messages for all failures, indexed // by the original upload indexes. // // Merge these. We cannot use PHP's array_merge() because it will // renumber the entries. $result = $fileObjects ; foreach ( $failedMessages as $index => $message ) { $result [ $index ] = $message ; } $uploadCache [ $formFieldName ] = $result ; return $result ; } /*--------------------------------------------------------------------- * * Redirect input to a file. * *---------------------------------------------------------------------*/ /** * Reads a PHP input stream into a new temporary file. * * PHP's input stream is opened and read to acquire incoming data * to route into a new Drupal temporary file. The URI of the new * file is returned. * * @return string * The URI of the new temporary file. * * @throws \Drupal\foldershare\Entity\Exception\SystemException * Throws an exception if one of the following occurs: * - The input stream cannot be read. * - A temporary file cannot be created. * - A temporary file cannot be written. */ private static function inputDataToFile() { // // Open stream // ----------- // Use the "php" stream to access incoming data appended to the // current HTTP request. The stream is opened for reading in // binary (the binary part is required for Windows). if ( $stream === FALSE) { throw new SystemException(t( "System error. An input stream cannot be opened or read." )); } // // Create temp file // ---------------- // Use the Drupal "temproary" stream to create a temporary file and // open it for writing in binary. $temp = fopen ( $tempUri , 'wb' ); if ( $temp === FALSE) { fclose( $stream ); throw new SystemException(t( "System error. A temporary file at '@path' could not be created.\nThere may be a problem with directories or permissions. Please report this to the site administrator." , [ '@path' => $tempUri , ])); } // // Copy stream to file // ------------------- // Loop through the input stream until EOF, copying data into the // temporary file. while ( feof ( $stream ) === FALSE) { $data = fread ( $stream , 8192); if ( $data === FALSE) { // We aren't at EOF, but the read failed. Something has gone wrong. fclose( $stream ); fclose( $temp ); throw new SystemException(t( "System error. An input stream cannot be opened or read." )); } if (fwrite( $temp , $data ) === FALSE) { fclose( $stream ); fclose( $temp ); throw new SystemException(t( "System error. A file at '@path' could not be written.\nThere may be a problem with permissions. Please report this to the site administrator." , [ '@path' => $temp , ])); } } // // Clean up // -------- // Close the stream and file. fclose( $stream ); fclose( $temp ); return $tempUri ; } /** * Adds a PHP input stream file into the root list. * * When a file is uploaded via an HTTP post handled by a web services * "REST" resource, the file's data is available via the PHP input * stream. This method reads that stream, creates a file, and adds * that file to this folder with the given name. * * <B>Post-operation hooks</B> * This method calls the "hook_foldershare_post_operation_add_files" hook. * * <B>Process locks</B> * The user's root list is locked for exclusive use for the duration of * this operation. * * <B>Activity log</B> * This method posts a log message after the file has been added. * * @param string $filename * The name for the new file. * @param bool $allowRename * (optional, default = TRUE) When TRUE, if $filename collides with the * name of an existing entity, the name is modified by adding a number on * the end so that it doesn't collide. When FALSE, a file name collision * throws an exception. * * @return \Drupal\foldershare\FolderShareInterface * Returns the newly added FolderShare entity wrapping the file. * * @throws \Drupal\foldershare\Entity\Exception\LockException * Throws an exception if an access lock could not be acquired. * * @see ::addFile() * @see ::addFiles() * @see ::addUploadFiles() */ public static function addInputFileToRoot( string $filename , bool $allowRename = TRUE) { // // Validate // -------- // There must be a file name. if ( empty ( $filename ) === TRUE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The name "@name" cannot be used.' , [ '@name' => $filename , ]), t( 'The file could not be added created because it\'s name is too long or it uses one of the prohibited ":", "/", or "\\" punctuation marks.' ))); } // // Read data into file // ------------------- // Read PHP input into a local temporary file. This throws an exception // if the input stream cannot be read or a temporary file created. $tempUri = self::inputDataToFile(); // // Create a File entity // -------------------- // Create a File entity that wraps the given file. This moves the file // into the module's directory tree. An exception is thrown the file // cannot be moved. $file = self::createFileEntityFromLocalFile( $tempUri , $filename ); // // Add file to root list // --------------------- // Add the File entity to the root list, adjusting the file name if // needed. try { return self::addFileToRoot( $file , $allowRename ); } catch (\Exception $e ) { // Delete the file. $file -> delete (); throw $e ; } } /** * {@inheritdoc} */ public function addInputFile(string $filename , bool $allowRename = TRUE) { // // Validate // -------- // There must be a file name. if ( empty ( $filename ) === TRUE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The name "@name" cannot be used.' , [ '@name' => $filename , ]), t( 'The file could not be added to the folder because it\'s name is too long or it uses one of the prohibited ":", "/", or "\\" punctuation marks.' ))); } // // Read data into file // ------------------- // Read PHP input into a local temporary file. This throws an exception // if the input stream cannot be read or a temporary file created. $tempUri = self::inputDataToFile(); // // Create a File entity // -------------------- // Create a File entity that wraps the given file. This moves the file // into the module's directory tree. An exception is thrown the file // cannot be moved. $file = self::createFileEntityFromLocalFile( $tempUri , $filename ); // // Add file to folder // ------------------ // Add the File entity to this folder, adjusting the file name if // needed. try { return $this ->addFile( $file , $allowRename ); } catch (\Exception $e ) { // Delete the file. $file -> delete (); throw $e ; } } } |