? backports ? full_backports Index: filemanager.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/filemanager/filemanager.module,v retrieving revision 1.9.2.3 diff -u -r1.9.2.3 filemanager.module --- filemanager.module 16 Jan 2006 23:57:49 -0000 1.9.2.3 +++ filemanager.module 16 Feb 2006 17:16:21 -0000 @@ -76,7 +76,7 @@ * Returns the path to a file in the filestore * * @param $file - * Filestore file to create the url for + * Filestore file or id to create the path for * @param $working * If this is true return the path to where the working copy * would reside. This does not guarantee a working copy exists. @@ -147,6 +147,10 @@ */ function filemanager_add_file($area, $path, $filename, $mimetype = 'application/unknown', $remove = TRUE, $private = FALSE, $file = FALSE) { + if(variable_get('filemanager_force_private_' . $area, 0)) { + $private = TRUE; + } + $size = filesize($path); // Lock on a lock file to prevent naming conflict race conditions @@ -201,7 +205,7 @@ // Move upload file to new location and name $orig_path = $path; - if (file_copy($path, filemanager_create_path($file, TRUE), FILE_EXISTS_ERROR)) { + if (_filemanager_copy($path, filemanager_create_path($file, TRUE), FILE_EXISTS_ERROR)) { $file->working = TRUE; $file->mimetype = $mimetype; @@ -228,39 +232,117 @@ /** * Renames an existing file in the filestore + * * @param $file * file or fid to rename * @param $name - * new name for the file + * new name for the file, use NULL or blank to retain old name * @return * the renamed file object on success or false on failure */ function filemanager_rename($file, $name) { $file = filemanager_get_file_info($file); + + // Exit immediately if the rename does nothing + if (! $file || $name == $file->filename) { + return $file; + } + + // Begin rename operation $oldworking = filemanager_create_path($file, true); $oldactive = filemanager_create_path($file, false); $lock = _filemanager_lock(); + $file->filename = $name; + + $updated = _filemanager_update_file($file, $oldworking, $oldactive); + + if ($file != false) { + db_query("UPDATE {file} SET filename = '%s', directory = '%d' WHERE fid=%d", $file->filename, $file->directory, $file->fid); + } + _filemanager_unlock($lock); + return $file; +} + +/** + * Moves the file from public to private or vice versa + * + * @param $file + * file or fid to modify + * @param $private + * new private state for the file + * @return + * the modified file object on success or false on failure. + */ +function filemanager_set_private($file, $private) { + $file = filemanager_get_file_info($file); + + // The private column is a char, adjust the flag to match + if ($private) { + $private = '1'; + } + else { + $private = ''; + } + + // Exit immediately if file is already in the right state + if (! $file || $file->private == $private) { + return $file; + } + + // Begin set_private operation + $oldworking = filemanager_create_path($file, true); + $oldactive = filemanager_create_path($file, false); + $lock = _filemanager_lock(); + + $file->private = $private; + + $updated = _filemanager_update_file($file, $oldworking, $oldactive); + + if ($file != false) { + db_query("UPDATE {file} SET private='%s', directory = '%d' WHERE fid=%d", $file->private, $file->directory, $file->fid); + } + _filemanager_unlock($lock); + return $file; +} + +/** + * helper function for filemanager_rename and filemanager_set_private + * does the actual file moving from $oldactive and $oldworking to the + * values set in the $file object + * + * @param $file + * file or fid containing new name/directory/private info + * @param $oldworking + * the path to the current working file + * @param $oldactive + * the path to the current active file + * @return + * true on success and false on failure + */ +function _filemanager_update_file(&$file, $oldworking, $oldactive) { + // Using the new file object find/create an appropiate area for this file $file = _filemanager_find_directory($file); $newworking = filemanager_create_path($file, true); $newactive = filemanager_create_path($file, false); if (file_exists($oldworking)) { - if (!file_move($oldworking, $newworking, FILE_EXISTS_ERROR)) { + filemanager_create_directory(dirname(dirname($newworking))); + filemanager_create_directory(dirname($newworking)); + if (!_filemanager_move($oldworking, $newworking, FILE_EXISTS_ERROR)) { drupal_set_message("file exists: {$file->filename}", 'error'); - $file = false; + return false; } } - if ($file != false && file_exists($oldactive)) { - if (!file_move($oldactive, $newactive, FILE_EXISTS_ERROR)) { + if (file_exists($oldactive)) { + filemanager_create_directory(dirname(dirname($newactive))); + filemanager_create_directory(dirname($newactive)); + if (!_filemanager_move($oldactive, $newactive, FILE_EXISTS_ERROR)) { drupal_set_message("file exists: {$file->filename}", 'error'); - $file = false; + return false; } } - if ($file != false) { - db_query("UPDATE {file} SET filename='%s', directory=%d WHERE fid=%d", $file->filename, $file->directory, $file->fid); - } - _filemanager_unlock($lock); - return $file; + + return true; } /** @@ -293,7 +375,7 @@ $active_path = filemanager_create_path($file, false); $working_path = filemanager_create_path($file, true); - if (file_copy($active_path, $working_path, FILE_EXISTS_REPLACE)) { + if (_filemanager_copy($active_path, $working_path, FILE_EXISTS_REPLACE)) { $file->working = 1; db_query("UPDATE {file} SET working=%d WHERE fid=%d", $file->working, $file->fid); } else { @@ -405,7 +487,7 @@ $current_path = filemanager_create_path($file, TRUE); $destination_path = filemanager_create_path($file, FALSE); - if (file_move($current_path, $destination_path, FILE_EXISTS_REPLACE)) { + if (_filemanager_move($current_path, $destination_path, FILE_EXISTS_REPLACE)) { $file->working = FALSE; $file->active = TRUE; $file->size = $size; @@ -489,44 +571,44 @@ * File object or file id you want to promote * @param $working */ -function filemanager_transfer($file, $working) { +function filemanager_transfer($file, $working, $headers = FALSE) { $file = filemanager_get_file_info($file); $filepath = filemanager_create_path($file, $working); + $default_headers = array('Content-Type: '. $file->mimetype, 'Content-Length: '. filesize($filepath), 'Content-Disposition: filename=' . $file->filename); if ($file->private) { if (file_check_location($filepath, variable_get('filemanager_private_path','private')) && file_exists($filepath)) { - $list = module_list(); - foreach ($list as $module) { + foreach (module_list() as $module) { $headers = module_invoke($module, 'filemanager_download', $file); if ($headers === FALSE) { - drupal_access_denied(); + return drupal_access_denied(); } elseif ($headers === TRUE) { - file_transfer($filepath, array('Content-Type: '. $file->mimetype, 'Content-Length: '. filesize($filepath), 'Content-Disposition: filename=' . $file->filename)); + return _filemanager_transfer($filepath, $default_headers); } elseif (is_array($headers)) { - file_transfer($filepath, $headers); + return _filemanager_transfer($filepath, $headers); } } // Since no modules responded check to see if this is a general area file // and allow download if so. - if ($file->area == 'general') { - file_transfer($filepath, array('Content-Type: '. $file->mimetype, 'Content-Length: '. filesize($filepath), 'Content-Disposition: filename=' . $file->filename)); + if ($file->area == 'general' || variable_get('filemanager_force_private_' . $file->area, 0)) { + return _filemanager_transfer($filepath, $default_headers); } } - drupal_not_found(); + return drupal_not_found(); } else { // It's a public file so no auth check is required. - file_transfer($filepath, array('Content-Type: '. $file->mimetype, 'Content-Length: '. filesize($filepath), 'Content-Disposition: filename=' . $file->filename)); + return file_transfer($filepath, (is_array($headers)) ? $headers : $default_headers); } } @@ -615,9 +697,16 @@ $output .= form_textfield(t('Maximum size limit'), 'filemanager_max_size', variable_get('filemanager_max_size', '400'), 6, 10, t('Maximum amount of disk space that can be consumed by all files. Enter in megabytes.')); - $header = array('Area','Description','Max size (Mb)'); + $output .= "
". t("Forcing a module to use private areas will: Force files to be streamed (no direct access), filemanger's private directory will be used, module needs to implement filemanager_download hook (default allows all access).") . "
"; + $header = array('Area','Description','Max size (Mb)', 'Force Private'); foreach(filemanager_area_list() as $area) { - $rows[] = array($area['name'], $area['description'], form_textfield(null, 'filemanager_area_limit_' . $area['area'], variable_get('filemanager_area_limit_' . $area['area'], '-1'), 6, 10)); + //$rows[] = array($area['name'], $area['description'], form_textfield(null, 'filemanager_area_limit_' . $area['area'], variable_get('filemanager_area_limit_' . $area['area'], '-1'), 6, 10)); + $rows[] = array( + $area['name'], + $area['description'], + form_textfield(null, 'filemanager_area_limit_' . $area['area'], variable_get('filemanager_area_limit_' . $area['area'], '-1'), 6, 10), + form_checkbox(null, 'filemanager_force_private_' . $area['area'], 1, variable_get('filemanager_force_private_' . $area['area'], '0')), + ); } $output .= form_item(t('File areas'), theme_table($header, $rows)); return $output; @@ -690,4 +779,168 @@ } return $file; } + +/** + * Copies a file to a new location. This is a powerful function that in many ways + * performs like an advanced version of copy(). + * - Checks if $source and $dest are valid and readable/writable. + * - Performs a file copy if $source is not equal to $dest. + * - If file already exists in $dest either the call will error out, replace the + * file or rename the file based on the $replace parameter. + * + * @param $source A string specifying the file location of the original file. + * This parameter will contain the resulting destination filename in case of + * success. + * @param $dest A string containing the directory $source should be copied to. + * @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 True for success, false for failure. + */ +function _filemanager_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { + + $directory = $dest; + $basename = file_check_path($directory); + + // Make sure we at least have a valid directory. + if ($basename === false) { + drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), 'error'); + watchdog('file system', t('The selected file %file could not not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), WATCHDOG_ERROR); + return 0; + } + + // Process a file upload object. + if (is_object($source)) { + $file = $source; + $source = $file->filepath; + if (!$basename) { + $basename = $file->filename; + } + } + + $source = realpath($source); + + if (!file_exists($source)) { + drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => theme('placeholder', $source))), 'error'); + return 0; + } + + // If the destination file is not specified then use the filename of the source file. + $basename = $basename ? $basename : basename($source); + $dest = $directory .'/'. $basename; + + // Make sure source and destination filenames are not the same, makes no sense + // to copy it if they are. In fact copying the file will most likely result in + // a 0 byte file. Which is bad. Real bad. + if ($source != realpath($dest)) { + if (file_exists($dest)) { + switch ($replace) { + case FILE_EXISTS_RENAME: + // Destination file already exists and we can't replace is so we try and + // and find a new filename. + if ($pos = strrpos($basename, '.')) { + $name = substr($basename, 0, $pos); + $ext = substr($basename, $pos); + } + else { + $name = $basename; + } + + $counter = 0; + do { + $dest = $directory .'/'. $name .'_'. $counter++ . $ext; + } while (file_exists($dest)); + break; + + case FILE_EXISTS_ERROR: + drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => theme('placeholder', $source))), 'error'); + return 0; + } + } + + if (!@copy($source, $dest)) { + drupal_set_message(t('The selected file %file could not be copied.', array('%file' => theme('placeholder', $source))), 'error'); + return 0; + } + + // Give everyone read access so that FTP'd users or + // non-webserver users can see/read these files. + @chmod($dest, 0664); + } + + if (is_object($file)) { + $file->filename = $basename; + $file->filepath = $dest; + $source = $file; + } + else { + $source = $dest; + } + + return 1; // Everything went ok. +} + +/** + * Moves a file to a new location. + * - Checks if $source and $dest are valid and readable/writable. + * - Performs a file move if $source is not equal to $dest. + * - If file already exists in $dest either the call will error out, replace the + * file or rename the file based on the $replace parameter. + * + * @param $source A string specifying the file location of the original file. + * This parameter will contain the resulting destination filename in case of + * success. + * @param $dest A string containing the directory $source should be copied to. + * @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 True for success, false for failure. + */ +function _filemanager_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { + + $path_original = is_object($source) ? $source->filepath : $source; + + if (_filemanager_copy($source, $dest, $replace)) { + $path_current = is_object($source) ? $source->filepath : $source; + + if ($path_original == $path_current || file_delete($path_original)) { + return 1; + } + drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => theme('placeholder', $source))), 'error'); + } + return 0; +} + +/** + * Transfer file using http to client. Pipes a file through Drupal to the + * client. + * + * @param $source File to transfer. + * @param $headers An array of http headers to send along with file. + */ +function _filemanager_transfer($source, $headers) { + ob_end_clean(); + + foreach ($headers as $header) { + // To prevent HTTP header injection, we delete new lines that are + // not followed by a space or a tab. + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + $header = preg_replace('/\r?\n(?!\t| )/', '', $header); + header($header); + } + + // Transfer file in 1024 byte chunks to save memory usage. + if ($fd = fopen($source, 'rb')) { + while (!feof($fd)) { + print fread($fd, 1024); + } + fclose($fd); + } + else { + drupal_not_found(); + } + exit(); +} ?>