diff options
Diffstat (limited to 'includes/file.inc')
-rw-r--r-- | includes/file.inc | 333 |
1 files changed, 307 insertions, 26 deletions
diff --git a/includes/file.inc b/includes/file.inc index e15aa3925..eb2cc32bf 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -254,7 +254,140 @@ function file_check_location($source, $directory = '') { } /** - * Copy a file to a new location. + * Load a file object from the database. + * + * @param $param + * Either the id of a file or an array of conditions to match against in the + * database query. + * @param $reset + * Whether to reset the internal file_load cache. + * @return + * A file object. + * + * @see hook_file_load() + */ +function file_load($param, $reset = NULL) { + static $files = array(); + + if ($reset) { + $files = array(); + } + + if (is_numeric($param)) { + if (isset($files[(string) $param])) { + return is_object($files[$param]) ? clone $files[$param] : $files[$param]; + } + $result = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid', array(':fid' => $param)); + } + elseif (is_array($param)) { + // Turn the conditions into a query. + $cond = array(); + $arguments = array(); + foreach ($param as $key => $value) { + $cond[] = 'f.' . db_escape_table($key) . " = '%s'"; + $arguments[] = $value; + } + $result = db_query('SELECT f.* FROM {files} f WHERE ' . implode(' AND ', $cond), $arguments); + } + else { + return FALSE; + } + $file = $result->fetch(PDO::FETCH_OBJ); + + if ($file && $file->fid) { + // Allow modules to add or change the file object. + module_invoke_all('file_load', $file); + + // Cache the fully loaded value. + $files[(string) $file->fid] = clone $file; + } + + return $file; +} + +/** + * Save a file object to the database. + * + * If the $file->fid is not set a new record will be added. Re-saving an + * existing file will not change its status. + * + * @param $file + * A file object returned by file_load(). + * @return + * The updated file object. + * @see hook_file_insert() + * @see hook_file_update() + */ +function file_save($file) { + $file = (object)$file; + $file->timestamp = REQUEST_TIME; + $file->filesize = filesize($file->filepath); + + if (empty($file->fid)) { + drupal_write_record('files', $file); + // Inform modules about the newly added file. + module_invoke_all('file_insert', $file); + } + else { + drupal_write_record('files', $file, 'fid'); + // Inform modules that the file has been updated. + module_invoke_all('file_update', $file); + } + + return $file; +} + +/** + * Copy a file to a new location and adds a file record to the database. + * + * This function should be used when manipulating files that have records + * stored in the database. This is a powerful function that in many ways + * performs like an advanced version of copy(). + * - Checks if $source and $destination are valid and readable/writable. + * - Checks that $source is not equal to $destination; if they are an error + * is reported. + * - If file already exists in $destination either the call will error out, + * replace the file or rename the file based on the $replace parameter. + * - Adds the new file to the files database. If the source file is a + * temporary file, the resulting file will also be a temporary file. + * @see file_save_upload about temporary files. + * + * @param $source + * A file object. + * @param $destination + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. + * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * @return + * File object if the copy is successful, or FALSE in the event of an error. + * @see file_unmanaged_copy() + * @see hook_file_copy() + */ +function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + $source = (object)$source; + + if ($filepath = file_unmanaged_copy($source->filepath, $destination, $replace)) { + $file = clone $source; + $file->fid = NULL; + $file->filename = basename($filepath); + $file->filepath = $filepath; + if ($file = file_save($file)) { + // Inform modules that the file has been copied. + module_invoke_all('file_copy', $file, $source); + return $file; + } + } + return FALSE; +} + +/** + * Copy a file to a new location without calling any hooks or making any + * changes to the database. * * This is a powerful function that in many ways performs like an advanced * version of copy(). @@ -277,8 +410,9 @@ function file_check_location($source, $directory = '') { * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * @return * The path to the new file, or FALSE in the event of an error. + * @see file_copy() */ -function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { +function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $source = realpath($source); if (!file_exists($source)) { drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); @@ -360,7 +494,52 @@ function file_destination($destination, $replace) { } /** - * Move a file to a new location. + * Move a file to a new location and update the file's database entry. + * + * Moving a file is performed by copying the file to the new location and then + * deleting the original. + * - Checks if $source and $destination are valid and readable/writable. + * - Performs a file move if $source is not equal to $destination. + * - If file already exists in $destination either the call will error out, + * replace the file or rename the file based on the $replace parameter. + * - Adds the new file to the files database. + * + * @param $source + * A file object. + * @param $destination + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. + * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * @return + * Resulting file object for success, or FALSE in the event of an error. + * @see file_unmanaged_move() + * @see hook_file_move() + */ +function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + $source = (object)$source; + + if ($filepath = file_unmanaged_move($source->filepath, $destination, $replace)) { + $file = clone $source; + $file->filename = basename($filepath); + $file->filepath = $filepath; + if ($file = file_save($file)) { + // Inform modules that the file has been moved. + module_invoke_all('file_move', $file, $source); + return $file; + } + drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $source->filepath)), 'error'); + } + return FALSE; +} + +/** + * Move a file to a new location without calling any hooks or making any + * changes to the database. * * @param $source * A string specifying the file location of the original file. @@ -375,10 +554,11 @@ function file_destination($destination, $replace) { * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * @return * The filepath of the moved file, or FALSE in the event of an error. + * @see file_move() */ -function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $filepath = file_copy($source, $destination, $replace); - if ($filepath == FALSE || file_delete($source) == FALSE) { +function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + $filepath = file_unmanaged_copy($source, $destination, $replace); + if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) { return FALSE; } return $filepath; @@ -470,17 +650,64 @@ function file_create_filename($basename, $directory) { } /** - * Delete a file. + * Delete a file and its database record. + * + * If the $force parameter is not TRUE hook_file_references() will be called + * to determine if the file is being used by any modules. If the file is being + * used is the delete will be canceled. + * + * @param $file + * A file object. + * @param $force + * Boolean indicating that the file should be deleted even if + * hook_file_references() reports that the file is in use. + * @return mixed + * TRUE for success, FALSE in the event of an error, or an array if the file + * is being used by another module. The array keys are the module's name and + * the values are the number of references. + * @see file_unmanaged_delete() + * @see hook_file_references() + * @see hook_file_delete() + */ +function file_delete($file, $force = FALSE) { + $file = (object)$file; + + // If any module returns a value from the reference hook, the file will not + // be deleted from Drupal, but file_delete will return a populated array that + // tests as TRUE. + if (!$force && ($references = module_invoke_all('file_references', $file))) { + return $references; + } + + // Let other modules clean up any references to the deleted file. + module_invoke_all('file_delete', $file); + + // Make sure the file is deleted before removing its row from the + // database, so UIs can still find the file in the database. + if (file_unmanaged_delete($file->filepath)) { + db_delete('files')->condition('fid', $file->fid)->execute(); + return TRUE; + } + return FALSE; +} + +/** + * Delete a file without calling any hooks or making any changes to the + * database. + * + * This function should be used when the file to be deleted does not have an + * entry recorded in the files table. * * @param $path * A string containing a file path. * @return * TRUE for success or path does not exist, or FALSE in the event of an * error. + * @see file_delete() */ -function file_delete($path) { +function file_unmanaged_delete($path) { if (is_dir($path)) { - watchdog('file', t('%path is a directory and cannot be removed using file_delete().', array('%path' => $path)), WATCHDOG_ERROR); + watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR); return FALSE; } if (is_file($path)) { @@ -489,7 +716,7 @@ function file_delete($path) { // Return TRUE for non-existant file, but log that nothing was actually // deleted, as the current state is the indended result. if (!file_exists($path)) { - watchdog('file', t('The file %path was not deleted, because it does not exist.', array('%path' => $path)), WATCHDOG_NOTICE); + watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE); return TRUE; } // Catch all for everything else: sockets, symbolic links, etc. @@ -510,9 +737,9 @@ function file_delete($path) { */ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { if (!is_null($uid)) { - return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d AND status & %d', array($uid, $status))); + return db_query('SELECT SUM(filesize) FROM {files} WHERE uid = :uid AND status & :status', array(':uid' => $uid, ':status' => $status))->fetchField(); } - return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE status & %d', array($status))); + return db_query('SELECT SUM(filesize) FROM {files} WHERE status & :status', array(':status' => $status))->fetchField(); } /** @@ -642,14 +869,11 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, } // If we made it this far it's safe to record this file in the database. - $file->status = FILE_STATUS_TEMPORARY; - $file->timestamp = REQUEST_TIME; - drupal_write_record('files', $file); - - // Add file to the cache. - $upload_cache[$source] = $file; - return $file; - + if ($file = file_save($file)) { + // Add file to the cache. + $upload_cache[$source] = $file; + return $file; + } } return FALSE; } @@ -658,6 +882,9 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, /** * Check that a file meets the criteria specified by the validators. * + * After executing the validator callbacks specified hook_file_validate() will + * also be called to allow other modules to report errors about the file. + * * @param $file * A Drupal file object. * @param $validators @@ -669,6 +896,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, * the order specified. * @return * An array contaning validation error messages. + * @see hook_file_validate() */ function file_validate(&$file, $validators = array()) { // Call the validation functions specified by this function's caller. @@ -678,7 +906,8 @@ function file_validate(&$file, $validators = array()) { $errors = array_merge($errors, call_user_func_array($function, $args)); } - return $errors; + // Let other modules perform validation on the new file. + return array_merge($errors, module_invoke_all('file_validate', $file)); } /** @@ -833,7 +1062,7 @@ function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimu } /** - * Save a string to the specified destination. + * Save a string to the specified destination and create a database file entry. * * @param $data * A string containing the contents of the file. @@ -848,9 +1077,49 @@ function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimu * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * @return - * A string with the path of the resulting file, or FALSE on error. + * A file object, or FALSE on error. + * @see file_unmanaged_save_data() */ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + global $user; + + if ($filepath = file_unmanaged_save_data($data, $destination, $replace)) { + // Create a file object. + $file = new stdClass(); + $file->filepath = $filepath; + $file->filename = basename($file->filepath); + $file->filemime = file_get_mimetype($file->filepath); + $file->uid = $user->uid; + $file->status = FILE_STATUS_PERMANENT; + return file_save($file); + } + return FALSE; +} + +/** + * Save a string to the specified destination without calling any hooks or + * making any changes to the database. + * + * This function is identical to file_save_data() except the file will not be + * saved to the files table and none of the file_* hooks will be called. + * + * @param $data + * A string containing the contents of the file. + * @param $destination + * A string containing the destination location. If no value is provided + * then a randomly name will be generated and the file saved in Drupal's + * files directory. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. + * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * @return + * A string with the path of the resulting file, or FALSE on error. + * @see file_save_data() + */ +function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { // Write the data to a temporary file. $temp_name = tempnam(file_directory_temp(), 'file'); if (file_put_contents($temp_name, $data) === FALSE) { @@ -859,7 +1128,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM } // Move the file to its final destination. - return file_move($temp_name, $destination, $replace); + return file_unmanaged_move($temp_name, $destination, $replace); } /** @@ -876,11 +1145,21 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM * @return * File object if the change is successful, or FALSE in the event of an * error. + * @see hook_file_status() */ function file_set_status($file, $status = FILE_STATUS_PERMANENT) { - if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', array($status, $file->fid))) { + $file = (object)$file; + + $num_updated = db_update('files') + ->fields(array('status' => $status)) + ->condition('fid', $file->fid) + ->execute(); + + if ($num_updated) { $file->status = $status; - return TRUE; + // Notify other modules that the file's status has changed. + module_invoke_all('file_status', $file); + return $file; } return FALSE; } @@ -928,6 +1207,8 @@ function file_transfer($source, $headers) { * returns -1 drupal_access_denied() will be returned. If one or more modules * returned headers the download will start with the returned headers. If no * modules respond drupal_not_found() will be returned. + * + * @see hook_file_download() */ function file_download() { // Merge remainder of arguments from GET['q'], into relative file path. |