Commit f841d1a7 authored by webchick's avatar webchick

#142995 by dopry, drewish, quicksketch, jpetso, and flobruit: Adding...

#142995 by dopry, drewish, quicksketch, jpetso, and flobruit: Adding hook_file_X(). This is an enabler of lots and lots of goodies. See CHANGELOG.txt for more. Awesome work, guys. :)
parent 72e09d7b
......@@ -52,6 +52,16 @@ Drupal 7.0, xxxx-xx-xx (development version)
and memory improvements.
- Theme system:
* Converted the 'bluemarine' theme to a tableless layout.
- File handling:
* Files are now first class Drupal objects with file_load(), file_save(),
and file_validate() functions and corresponding hooks.
* The file_move(), file_copy() and file_delete() functions now operate on
file objects and invoke file hooks so that modules are notified and can
respond to changes.
* For the occasions when only basic file manipulation are needed--such as
uploading a site logo--that don't require the overhead of databases and
hooks, the current unmanaged copy, move and delete operations have been
preserved but renamed to file_unmanaged_*().
Drupal 6.0, 2008-02-13
----------------------
......
......@@ -648,7 +648,7 @@ function _drupal_get_last_caller($backtrace) {
// The first trace is the call itself.
// It gives us the line and the file of the last call.
$call = $backtrace[0];
// The second call give us the function where the call originated.
if (isset($backtrace[1])) {
if (isset($backtrace[1]['class'])) {
......@@ -1851,7 +1851,7 @@ function drupal_build_css_cache($types, $filename) {
$data = implode('', $matches[0]) . $data;
// Create the CSS file.
file_save_data($data, $csspath . '/' . $filename, FILE_EXISTS_REPLACE);
file_unmanaged_save_data($data, $csspath . '/' . $filename, FILE_EXISTS_REPLACE);
}
return $csspath . '/' . $filename;
}
......@@ -1952,7 +1952,7 @@ function _drupal_load_stylesheet($matches) {
* Delete all cached CSS files.
*/
function drupal_clear_css_cache() {
file_scan_directory(file_create_path('css'), '/.*/', array('.', '..', 'CVS'), 'file_delete', TRUE);
file_scan_directory(file_create_path('css'), '/.*/', array('.', '..', 'CVS'), 'file_unmanaged_delete', TRUE);
}
/**
......@@ -2315,7 +2315,7 @@ function drupal_build_js_cache($files, $filename) {
}
// Create the JS file.
file_save_data($contents, $jspath . '/' . $filename, FILE_EXISTS_REPLACE);
file_unmanaged_save_data($contents, $jspath . '/' . $filename, FILE_EXISTS_REPLACE);
}
return $jspath . '/' . $filename;
......@@ -2325,7 +2325,7 @@ function drupal_build_js_cache($files, $filename) {
* Delete all cached JS files.
*/
function drupal_clear_js_cache() {
file_scan_directory(file_create_path('js'), '/.*/', array('.', '..', 'CVS'), 'file_delete', TRUE);
file_scan_directory(file_create_path('js'), '/.*/', array('.', '..', 'CVS'), 'file_unmanaged_delete', TRUE);
variable_set('javascript_parsed', array());
}
......
This diff is collapsed.
......@@ -2165,7 +2165,7 @@ function _locale_rebuild_js($langcode = NULL) {
// Save the file.
$dest = $dir . '/' . $language->language . '_' . $data_hash . '.js';
if (file_save_data($data, $dest)) {
if (file_unmanaged_save_data($data, $dest)) {
$language->javascript = $data_hash;
$status = ($status == 'deleted') ? 'updated' : 'created';
}
......
......@@ -151,7 +151,7 @@ class AggregatorTestCase extends DrupalWebTestCase {
EOF;
$path = file_directory_path() . '/valid-opml.xml';
return file_save_data($opml, $path);
return file_unmanaged_save_data($opml, $path);
}
/**
......@@ -168,7 +168,7 @@ EOF;
EOF;
$path = file_directory_path() . '/invalid-opml.xml';
return file_save_data($opml, $path);
return file_unmanaged_save_data($opml, $path);
}
/**
......@@ -190,7 +190,7 @@ EOF;
EOF;
$path = file_directory_path() . '/empty-opml.xml';
return file_save_data($opml, $path);
return file_unmanaged_save_data($opml, $path);
}
function getRSS091Sample() {
......@@ -223,7 +223,7 @@ EOF;
EOT;
$path = file_directory_path() . '/rss091.xml';
return file_save_data($feed, $path);
return file_unmanaged_save_data($feed, $path);
}
}
......
......@@ -385,7 +385,7 @@ function blogapi_metaweblog_new_media_object($blogid, $username, $password, $fil
return blogapi_error(t('No file sent.'));
}
if (!$filepath = file_save_data($data, $name)) {
if (!$filepath = file_unmanaged_save_data($data, $name)) {
return blogapi_error(t('Error storing file.'));
}
......
......@@ -308,7 +308,7 @@ function color_scheme_form_submit($form, &$form_state) {
foreach ($info['copy'] as $file) {
$base = basename($file);
$source = $paths['source'] . $file;
$filepath = file_copy($source, $paths['target'] . $base);
$filepath = file_unmanaged_copy($source, $paths['target'] . $base);
$paths['map'][$file] = $base;
$paths['files'][] = $filepath;
}
......@@ -435,7 +435,7 @@ function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) {
* Save the rewritten stylesheet to disk.
*/
function _color_save_stylesheet($file, $style, &$paths) {
$filepath = file_save_data($style, $file, FILE_EXISTS_REPLACE);
$filepath = file_unmanaged_save_data($style, $file, FILE_EXISTS_REPLACE);
$paths['files'][] = $filepath;
// Set standard file permissions for webserver-generated files.
......
......@@ -33,7 +33,7 @@ function simpletest_install() {
$original = drupal_get_path('module', 'simpletest') . '/files';
$files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
foreach ($files as $file) {
file_copy($file->filename, $path . '/' . $file->basename);
file_unmanaged_copy($file->filename, $path . '/' . $file->basename);
}
$generated = TRUE;
}
......
......@@ -571,7 +571,7 @@ function simpletest_clean_temporary_directory($path) {
simpletest_clean_temporary_directory($file_path);
}
else {
file_delete($file_path);
file_unmanaged_delete($file_path);
}
}
}
......
This diff is collapsed.
......@@ -50,3 +50,117 @@ function _file_test_form_submit(&$form, &$form_state) {
drupal_set_message(t('Epic upload FAIL!'), 'error');
}
}
/**
* Reset/initialize the history of calls to the file_* hooks.
*/
function file_test_reset() {
// Keep track of calls to these hooks
$GLOBALS['file_test_results'] = array(
'load' => array(),
'validate' => array(),
'download' => array(),
'references' => array(),
'status' => array(),
'insert' => array(),
'update' => array(),
'copy' => array(),
'move' => array(),
'delete' => array(),
);
// These hooks will return these values.
$GLOBALS['file_test_hook_return'] = array(
'validate' => NULL,
'download' => NULL,
'references' => NULL,
);
}
/**
* Get the values passed to a the hook calls for a given operation.
*
* @param $op One of the hook_file_* operations.
* @returns Array of the parameters passed to each call.
*/
function file_test_get_calls($op) {
return $GLOBALS['file_test_results'][$op];
}
/**
* Implementation of hook_file_load().
*/
function file_test_file_load(&$file) {
$GLOBALS['file_test_results']['load'][] = func_get_args();
// Assign a value on the object so that we can test that the $file is passed
// by reference.
$file->file_test['loaded'] = TRUE;
}
/**
* Implementation of hook_file_validate().
*/
function file_test_file_validate(&$file) {
$GLOBALS['file_test_results']['validate'][] = func_get_args();
return $GLOBALS['file_test_hook_return']['validate'];
}
/**
* Implementation of hook_file_status().
*/
function file_test_file_status(&$file) {
$GLOBALS['file_test_results']['status'][] = func_get_args();
}
/**
* Implementation of hook_file_download().
*/
function file_test_file_download(&$file) {
$GLOBALS['file_test_results']['download'][] = func_get_args();
return $GLOBALS['file_test_hook_return']['download'];
}
/**
* Implementation of hook_file_references().
*/
function file_test_file_references(&$file) {
$GLOBALS['file_test_results']['references'][] = func_get_args();
return $GLOBALS['file_test_hook_return']['references'];
}
/**
* Implementation of hook_file_insert().
*/
function file_test_file_insert(&$file) {
$GLOBALS['file_test_results']['insert'][] = func_get_args();
}
/**
* Implementation of hook_file_update().
*/
function file_test_file_update(&$file) {
$GLOBALS['file_test_results']['update'][] = func_get_args();
}
/**
* Implemenation of hook_file_copy().
*/
function file_test_file_copy(&$file, &$source) {
$GLOBALS['file_test_results']['copy'][] = func_get_args();
}
/**
* Implemenation of hook_file_move().
*/
function file_test_file_move(&$file, &$source) {
$GLOBALS['file_test_results']['move'][] = func_get_args();
}
/**
* Implementation of hook_file_delete().
*/
function file_test_file_delete(&$file) {
$GLOBALS['file_test_results']['delete'][] = func_get_args();
}
......@@ -338,7 +338,7 @@ function system_theme_settings(&$form_state, $key = '') {
// The image was saved using file_save_upload() and was added to the
// files table as a temporary file. We'll make a copy and let the garbage
// collector delete the original upload.
if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
if ($filepath = file_unmanaged_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
$_POST['default_logo'] = 0;
$_POST['logo_path'] = $filepath;
$_POST['toggle_logo'] = 1;
......@@ -353,7 +353,7 @@ function system_theme_settings(&$form_state, $key = '') {
// The image was saved using file_save_upload() and was added to the
// files table as a temporary file. We'll make a copy and let the garbage
// collector delete the original upload.
if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
if ($filepath = file_unmanaged_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
$_POST['default_favicon'] = 0;
$_POST['favicon_path'] = $filepath;
$_POST['toggle_favicon'] = 1;
......
......@@ -1405,7 +1405,7 @@ function system_cron() {
if (file_exists($file->filepath)) {
// If files that exist cannot be deleted, continue so the database remains
// consistent.
if (!file_delete($file->filepath)) {
if (!file_delete($file)) {
watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath), WATCHDOG_ERROR);
continue;
}
......
......@@ -262,6 +262,38 @@ function upload_form_alter(&$form, $form_state, $form_id) {
}
}
/**
* Implementation of hook_file_load().
*/
function upload_file_load(&$file) {
// Add the upload specific data into the file object.
$values = db_query('SELECT * FROM {upload} u WHERE u.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_ASSOC);
foreach ((array)$values as $key => $value) {
$file->{$key} = $value;
}
}
/**
* Implementation of hook_file_references().
*/
function upload_file_references(&$file) {
// If upload.module is still using a file, do not let other modules delete it.
$count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField();
if ($count) {
// Return the name of the module and how many references it has to the file.
return array('upload' => $count);
}
}
/**
* Implementation of hook_file_delete().
*/
function upload_file_delete(&$file) {
// Delete all information associated with the file.
db_delete('upload')->condition('fid', $file->fid)->execute();
}
/**
* Implementation of hook_nodeapi_load().
*/
......@@ -289,7 +321,7 @@ function upload_nodeapi_view(&$node, $teaser) {
}
}
}
/**
* Implementation of hook_nodeapi_prepare().
*/
......@@ -325,23 +357,35 @@ function upload_nodeapi_update(&$node, $teaser) {
* Implementation of hook_nodeapi_delete().
*/
function upload_nodeapi_delete(&$node, $teaser) {
upload_delete($node);
db_delete('upload')->condition('nid', $node->nid)->execute();
if (!is_array($node->files)) {
return;
}
foreach($node->files as $file) {
file_delete($file);
}
}
/**
* Implementation of hook_nodeapi_delete_revision().
*/
function upload_nodeapi_delete_revision(&$node, $teaser) {
upload_delete_revision($node);
db_delete('upload')->condition('vid', $node->vid)->execute();
if (!is_array($node->files)) {
return;
}
foreach ($node->files as $file) {
file_delete($file);
}
}
/**
* Implementation of hook_nodeapi_search_result().
*/
function upload_nodeapi_search_result(&$node, $teaser) {
return isset($node->files) && is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
}
/**
* Implementation of hook_nodeapi_rss_item().
*/
......@@ -370,7 +414,7 @@ function upload_nodeapi_rss_item(&$node, $teaser) {
}
return array();
}
/**
* Displays file attachments in table
*
......@@ -426,15 +470,10 @@ function upload_save(&$node) {
// Remove file. Process removals first since no further processing
// will be required.
if (!empty($file->remove)) {
db_query('DELETE FROM {upload} WHERE fid = %d AND vid = %d', $fid, $node->vid);
// If the file isn't used by any other revisions delete it.
$count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $fid));
if ($count < 1) {
file_delete($file->filepath);
db_query('DELETE FROM {files} WHERE fid = %d', $fid);
}
// Remove the reference from this revision.
db_delete('upload')->condition('fid', $file->fid)->condition('vid', $node->vid)->execute();
// Try a soft delete, if the file isn't used elsewhere it'll be deleted.
file_delete($file);
// Remove it from the session in the case of new uploads,
// that you want to disassociate before node submission.
unset($_SESSION['upload_files'][$fid]);
......@@ -457,41 +496,6 @@ function upload_save(&$node) {
unset($_SESSION['upload_files']);
}
function upload_delete($node) {
$files = array();
$result = db_query('SELECT DISTINCT f.* FROM {upload} u INNER JOIN {files} f ON u.fid = f.fid WHERE u.nid = %d', $node->nid);
while ($file = db_fetch_object($result)) {
$files[$file->fid] = $file;
}
foreach ($files as $fid => $file) {
// Delete all files associated with the node
db_query('DELETE FROM {files} WHERE fid = %d', $fid);
file_delete($file->filepath);
}
// Delete all file revision information associated with the node
db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid);
}
function upload_delete_revision($node) {
if (is_array($node->files)) {
foreach ($node->files as $file) {
// Check if the file will be used after this revision is deleted
$count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid));
// if the file won't be used, delete it
if ($count < 2) {
db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
file_delete($file->filepath);
}
}
}
// delete the revision
db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid);
}
function _upload_form($node) {
global $user;
......@@ -503,11 +507,11 @@ function _upload_form($node) {
if (!empty($node->files) && is_array($node->files)) {
$form['files']['#theme'] = 'upload_form_current';
$form['files']['#tree'] = TRUE;
foreach ($node->files as $key => $file) {
foreach ($node->files as $file) {
$file = (object)$file;
$description = file_create_url($file->filepath);
$description = "<small>" . check_plain($description) . "</small>";
$form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
$key = $file->fid;
$form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => '<small>' . file_create_url($file->filepath) . '</small>');
$form['files'][$key]['size'] = array('#markup' => format_size($file->filesize));
$form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
$form['files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list);
......@@ -522,12 +526,26 @@ function _upload_form($node) {
if (user_access('upload files')) {
$limits = _upload_file_limits($user);
$limit_description = t('The maximum size of file uploads is %filesize. ', array('%filesize' => format_size($limits['file_size'])));
if (!empty($limits['resolution'])) {
if (image_get_toolkit()) {
$limit_description .= t('Images larger than %resolution will be resized. ', array('%resolution' => $limits['resolution']));
}
else {
$limit_description .= t('Images may not be larger than %resolution. ', array('%resolution' => $limits['resolution']));
}
}
if ($user->uid != 1) {
$limit_description .= t('Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions']));
}
$form['new']['#weight'] = 10;
$form['new']['upload'] = array(
'#type' => 'file',
'#title' => t('Attach new file'),
'#size' => 40,
'#description' => ($limits['resolution'] ? t('Images are larger than %resolution will be resized. ', array('%resolution' => $limits['resolution'])) : '') . t('The maximum upload size is %filesize. Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions'], '%filesize' => format_size($limits['file_size']))),
'#description' => $limit_description,
);
$form['new']['attach'] = array(
'#type' => 'submit',
......@@ -589,9 +607,9 @@ function upload_load($node) {
$files = array();
if ($node->vid) {
$result = db_query('SELECT * FROM {files} f INNER JOIN {upload} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY r.weight, f.fid', $node->vid);
while ($file = db_fetch_object($result)) {
$files[$file->fid] = $file;
$result = db_query('SELECT u.fid FROM {upload} u WHERE u.vid = :vid ORDER BY u.weight, u.fid', array(':vid' => $node->vid));
foreach ($result as $file) {
$files[$file->fid] = file_load($file->fid);
}
}
......
......@@ -100,16 +100,25 @@ class UploadTestCase extends DrupalWebTestCase {
$this->drupalLogin($web_user);
$node = $this->drupalCreateNode();
$text_files = $this->drupalGetTestFiles('text');
$html_files = $this->drupalGetTestFiles('html');
$files = array(current($text_files)->filename, current($html_files)->filename);
// Attempt to upload .txt file when .test is only extension allowed.
$this->uploadFile($node, $files[0], FALSE);
$this->assertRaw(t('The specified file %name could not be uploaded. Only files with the following extensions are allowed: %files-allowed.', array('%name' => basename($files[0]), '%files-allowed' => $settings['upload_extensions'])), 'File '. $files[0] . ' was not allowed to be uploaded');
// Attempt to upload .test file when .test is only extension allowed.
$this->uploadFile($node, $files[1]);
// Attempt to upload .txt file when .html is only extension allowed.
$text_files = array_values($this->drupalGetTestFiles('text'));
// Select a file that's less than the 1MB upload limit so we only test one
// limit at a time.
$text_file = $text_files[2]->filename;
$this->uploadFile($node, $text_file, FALSE);
// Test the error message in two steps in case there are additional errors
// that change the error message's format.
$this->assertRaw(t('The specified file %name could not be uploaded.', array('%name' => basename($text_file))), t('File %filename was not allowed to be uploaded', array('%filename' => $text_file)));
$this->assertRaw(t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $settings['upload_extensions'])), t('File extension cited as reason for failure'));
// Attempt to upload .html file when .html is only extension allowed.
$html_files = array_values($this->drupalGetTestFiles('html'));
// Use the HTML file with the .html extension, $html_files[0] has a .txt
// extension.
$html_file = $html_files[1]->filename;
$this->uploadFile($node, $html_file);
$this->assertNoRaw(t('The specified file %name could not be uploaded.', array('%name' => basename($html_file))), t('File '. $html_file . ' was allowed to be uploaded'));
}
/**
......@@ -143,7 +152,10 @@ class UploadTestCase extends DrupalWebTestCase {
$filename = basename($file);
$filesize = format_size($info['size']);
$maxsize = format_size(parse_size(($settings['upload_uploadsize'] * 1024) . 'KB')); // Won't parse decimals.
$this->assertRaw(t('The specified file %name could not be uploaded. The file is %filesize exceeding the maximum file size of %maxsize.', array('%name' => $filename, '%filesize' => $filesize, '%maxsize' => $maxsize)), t('File upload was blocked since it was larger than maxsize.'));
// Test the error message in two steps in case there are additional errors
// that change the error message's format.
$this->assertRaw(t('The specified file %name could not be uploaded.', array('%name' => $filename)), t('File upload was blocked'));
$this->assertRaw(t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => $filesize, '%maxsize' => $maxsize)), t('File size cited as problem with upload'));
}
function setUploadSettings($settings, $rid = NULL) {
......
......@@ -407,7 +407,7 @@ function user_validate_picture(&$form, &$form_state) {
if ($file = file_save_upload('picture_upload', $validators)) {
// Remove the old picture.
if (isset($form_state['values']['_account']->picture) && file_exists($form_state['values']['_account']->picture)) {
file_delete($form_state['values']['_account']->picture);
file_unmanaged_delete($form_state['values']['_account']->picture);
}
// The image was saved using file_save_upload() and was added to the
......@@ -415,7 +415,7 @@ function user_validate_picture(&$form, &$form_state) {
// collector delete the original upload.
$info = image_get_info($file->filepath);
$destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension']);
if ($filepath = file_copy($file->filepath, $destination, FILE_EXISTS_REPLACE)) {
if ($filepath = file_unmanaged_copy($file->filepath, $destination, FILE_EXISTS_REPLACE)) {
$form_state['values']['picture'] = $filepath;
}
else {
......@@ -1558,7 +1558,7 @@ function _user_edit_submit($uid, &$edit) {
// Delete picture if requested, and if no replacement picture was given.
if (!empty($edit['picture_delete'])) {
if ($user->picture && file_exists($user->picture)) {
file_delete($user->picture);
file_unmanaged_delete($user->picture);
}
$edit['picture'] = '';
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment