474 lines
17 KiB
Plaintext
474 lines
17 KiB
Plaintext
<?php
|
|
// $Id$
|
|
|
|
/**
|
|
* @file
|
|
* File-handling and attaching files to nodes.
|
|
*/
|
|
|
|
/**
|
|
* Implementation of hook_help().
|
|
*/
|
|
function upload_help($section) {
|
|
switch ($section) {
|
|
case 'admin/modules#description':
|
|
return t('Allows users to upload and attach files to content.');
|
|
case 'admin/settings/upload':
|
|
return t('<p>Users with the <a href="%permissions">upload files permission</a> can upload attachments. You can choose which post types can take attachments on the <a href="%types">content types settings</a> page.</p>', array('%permissions' => url('admin/access'), '%types' => url('admin/settings/content-types')));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_perm().
|
|
*/
|
|
function upload_perm() {
|
|
return array('upload files', 'view uploaded files');
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_link().
|
|
*/
|
|
function upload_link($type, $node = 0, $main = 0) {
|
|
$links = array();
|
|
|
|
// Display a link with the number of attachments
|
|
if ($main && $type == 'node' && $node->files && user_access('view uploaded files')) {
|
|
$num_files = 0;
|
|
foreach ($node->files as $file) {
|
|
if ($file->list) {
|
|
$num_files++;
|
|
}
|
|
}
|
|
if ($num_files) {
|
|
$links[] = l(format_plural($num_files, '1 attachment', '%count attachments'), "node/$node->nid", array('title' => t('Read full article to view attachments.')), NULL, 'attachments');
|
|
}
|
|
}
|
|
|
|
return $links;
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_menu().
|
|
*/
|
|
function upload_menu($may_cache) {
|
|
$items = array();
|
|
|
|
if ($may_cache) {
|
|
$items[] = array(
|
|
'path' => 'admin/settings/upload', 'title' => t('uploads'),
|
|
'callback' => 'upload_admin',
|
|
'access' => user_access('administer site configuration'),
|
|
'type' => MENU_NORMAL_ITEM
|
|
);
|
|
$items[] = array(
|
|
'path' => 'upload/js',
|
|
'callback' => 'upload_js',
|
|
'access' => user_access('upload files'),
|
|
'type' => MENU_CALLBACK
|
|
);
|
|
}
|
|
else {
|
|
// Add handlers for previewing new uploads.
|
|
if ($_SESSION['file_uploads']) {
|
|
foreach ($_SESSION['file_uploads'] as $key => $file) {
|
|
$filename = file_create_filename($file->filename, file_create_path());
|
|
$items[] = array(
|
|
'path' => $filename, 'title' => t('file download'),
|
|
'callback' => 'upload_download',
|
|
'access' => user_access('view uploaded files'),
|
|
'type' => MENU_CALLBACK
|
|
);
|
|
$_SESSION['file_uploads'][$key]->_filename = $filename;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
function upload_admin() {
|
|
system_settings_save();
|
|
|
|
$group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 15, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
|
|
|
|
$output = form_group(t('General settings'), $group);
|
|
|
|
$roles = user_roles(0, 'upload files');
|
|
|
|
foreach ($roles as $rid => $role) {
|
|
$group = form_textfield(t('Permitted file extensions'), "upload_extensions_$rid", variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps"), 60, 255, t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'));
|
|
$group .= form_textfield(t('Maximum file size per upload'), "upload_uploadsize_$rid", variable_get("upload_uploadsize_$rid", 1), 5, 5, t('The maximum size of a file a user can upload (in megabytes).'));
|
|
$group .= form_textfield(t('Total file size per user'), "upload_usersize_$rid", variable_get("upload_usersize_$rid", 10), 5, 5, t('The maximum size of all files a user can have on the site (in megabytes).'));
|
|
$output .= form_group(t('Settings for %role', array('%role' => theme('placeholder', $role))), $group);
|
|
}
|
|
|
|
return system_settings_form($output);
|
|
}
|
|
|
|
function upload_download() {
|
|
foreach ($_SESSION['file_uploads'] as $file) {
|
|
if ($file->_filename == $_GET['q']) {
|
|
file_transfer($file->filepath, array('Content-Type: '. $file->filemime, 'Content-Length: '. $file->filesize));
|
|
}
|
|
}
|
|
}
|
|
|
|
function upload_file_download($file) {
|
|
if (user_access('view uploaded files')) {
|
|
$file = file_create_path($file);
|
|
$result = db_query(db_rewrite_sql("SELECT f.nid, f.* FROM {files} f WHERE filepath = '%s'", 'f'), $file);
|
|
if ($file = db_fetch_object($result)) {
|
|
$name = mime_header_encode($file->filename);
|
|
// Serve images and text inline for the browser to display rather than download.
|
|
$disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
|
|
return array('Content-Type: '. $file->filemime .'; name='. $name,
|
|
'Content-Length: '. $file->filesize,
|
|
'Content-Disposition: '. $disposition .'; filename='. $name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_nodeapi().
|
|
*/
|
|
function upload_nodeapi(&$node, $op, $arg) {
|
|
switch ($op) {
|
|
case 'settings':
|
|
return form_radios(t('Attachments'), 'upload_'. $node->type, variable_get('upload_'. $node->type, 1), array(t('Disabled'), t('Enabled')));
|
|
|
|
case 'form param':
|
|
if (variable_get("upload_$node->type", 1) && user_access('upload files')) {
|
|
$output['options'] = array('enctype' => 'multipart/form-data');
|
|
}
|
|
break;
|
|
|
|
case 'validate':
|
|
$node->files = upload_load($node);
|
|
|
|
// Double check existing files:
|
|
if (is_array($node->list)) {
|
|
foreach ($node->list as $key => $value) {
|
|
if ($file = file_check_upload($key)) {
|
|
$node->files[$file->source] = $file;
|
|
$node->files[$key]->list = $node->list[$key];
|
|
$node->files[$key]->remove = $node->remove[$key];
|
|
if ($file->source) {
|
|
$filesize += $file->filesize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
foreach ($node->files as $key => $file) {
|
|
$node->list[$key] = $file->list;
|
|
}
|
|
}
|
|
|
|
if (($file = file_check_upload('upload')) && user_access('upload files')) {
|
|
global $user;
|
|
|
|
$file = _upload_image($file);
|
|
|
|
// Don't do any checks for uid #1.
|
|
if ($user->uid != 1) {
|
|
// Validate file against all users roles. Only denies an upload when
|
|
// all roles prevent it.
|
|
$total_usersize = upload_space_used($user->uid) + $filesize;
|
|
foreach ($user->roles as $rid => $name) {
|
|
$extensions = variable_get("upload_extensions_$rid", 'jpg jpeg gif png txt html doc xls pdf ppt pps');
|
|
$uploadsize = variable_get("upload_uploadsize_$rid", 1) * 1024 * 1024;
|
|
$usersize = variable_get("upload_usersize_$rid", 1) * 1024 * 1024;
|
|
|
|
$regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
|
|
|
|
if (!preg_match($regex, $file->filename)) {
|
|
$error['extension']++;
|
|
}
|
|
|
|
if ($uploadsize && $file->filesize > $uploadsize) {
|
|
$error['uploadsize']++;
|
|
}
|
|
|
|
if ($usersize && $total_usersize + $file->filesize > $usersize) {
|
|
$error['usersize']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rename possibly executable scripts to prevent accidental execution.
|
|
// Uploaded files are attachments and should be shown in their original
|
|
// form, rather than run.
|
|
if (preg_match('/\.(php|pl|py|cgi|asp)$/i', $file->filename)) {
|
|
$file->filename .= '.txt';
|
|
$file->filemime = 'text/plain';
|
|
}
|
|
|
|
if ($error['extension'] == count($user->roles) && $user->uid != 1) {
|
|
form_set_error('upload', t('The selected file %name can not be attached to this post, because it is only possible to attach files with the following extensions: %files-allowed.', array('%name' => theme('placeholder', $file->filename), '%files-allowed' => theme('placeholder', $extensions))));
|
|
}
|
|
elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
|
|
form_set_error('upload', t('The selected file %name can not be attached to this post, because it exceeded the maximum filesize of %maxsize.', array('%name' => theme('placeholder', $file->filename), '%maxsize' => theme('placeholder', format_size($uploadsize)))));
|
|
}
|
|
elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
|
|
form_set_error('upload', t('The selected file %name can not be attached to this post, because the disk quota of %quota has been reached.', array('%name' => theme('placeholder', $file->filename), '%quota' => theme('placeholder', format_size($usersize)))));
|
|
}
|
|
else {
|
|
$key = 'upload_'. count($_SESSION['file_uploads']);
|
|
$file->source = $key;
|
|
$file->list = 1;
|
|
$file = file_save_upload($file);
|
|
$node->files[$key] = $file;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'form post':
|
|
if (variable_get("upload_$node->type", 1) == 1 && user_access('upload files')) {
|
|
$output = upload_form($node);
|
|
}
|
|
break;
|
|
|
|
case 'load':
|
|
if (variable_get("upload_$node->type", 1) == 1) {
|
|
$output['files'] = upload_load($node);
|
|
}
|
|
break;
|
|
|
|
case 'view':
|
|
if ($node->files && user_access('view uploaded files')) {
|
|
$header = array(t('Attachment'), t('Size'));
|
|
$rows = array();
|
|
$previews = array();
|
|
|
|
// Build list of attached files
|
|
foreach ($node->files as $file) {
|
|
if ($file->list) {
|
|
$rows[] = array(
|
|
'<a href="'. check_url(($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path())))) .'">'. check_plain($file->filename) .'</a>',
|
|
format_size($file->filesize)
|
|
);
|
|
// We save the list of files still in preview for later
|
|
if (!$file->fid) {
|
|
$previews[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
// URLs to files being previewed are actually Drupal paths. When Clean
|
|
// URLs are disabled, the two do not match. We perform an automatic
|
|
// replacement from temporary to permanent URLs. That way, the author
|
|
// can use the final URL in the body before having actually saved (to
|
|
// place inline images for example).
|
|
if (!variable_get('clean_url', 0)) {
|
|
foreach ($previews as $file) {
|
|
$old = file_create_filename($file->filename, file_create_path());
|
|
$new = url($old);
|
|
$node->body = str_replace($old, $new, $node->body);
|
|
$node->teaser = str_replace($old, $new, $node->teaser);
|
|
}
|
|
}
|
|
|
|
$teaser = $arg;
|
|
// Add the attachments list
|
|
if (count($rows) && !$teaser) {
|
|
$node->body .= theme('table', $header, $rows, array('id' => 'attachments'));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'insert':
|
|
case 'update':
|
|
if (user_access('upload files')) {
|
|
upload_save($node);
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
upload_delete($node);
|
|
break;
|
|
case 'search result':
|
|
return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
|
|
case 'rss item':
|
|
if ($node->files) {
|
|
$files = array();
|
|
foreach ($node->files as $file) {
|
|
if ($file->list) {
|
|
$files[] = $file;
|
|
}
|
|
}
|
|
if (count($files) > 0) {
|
|
// RSS only allows one enclosure per item
|
|
$file = array_shift($files);
|
|
return array(array('key' => 'enclosure',
|
|
'attributes' => array('url' => file_create_url($file->filepath),
|
|
'length' => $file->filesize,
|
|
'type' => $file->filemime)));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Determine how much disk space is occupied by a user's uploaded files.
|
|
*
|
|
* @param $uid
|
|
* The integer user id of a user.
|
|
* @return
|
|
* The ammount of disk space used by the user in bytes.
|
|
*/
|
|
function upload_space_used($uid) {
|
|
return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node_revisions} n ON f.vid = n.vid WHERE uid = %d', $uid));
|
|
}
|
|
|
|
/**
|
|
* Determine how much disk space is occupied by uploaded files.
|
|
*
|
|
* @return
|
|
* The ammount of disk space used by uploaded files in bytes.
|
|
*/
|
|
function upload_total_space_used() {
|
|
return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node_revisions} n ON f.vid = n.vid'));
|
|
}
|
|
|
|
function upload_save($node) {
|
|
foreach ((array)$node->files as $key => $file) {
|
|
if ($file->source && !$file->remove) {
|
|
// Clean up the session:
|
|
unset($_SESSION['file_uploads'][$file->source]);
|
|
|
|
// Insert new files:
|
|
if ($file = file_save_upload($file, $file->filename)) {
|
|
$fid = db_next_id('{files}_fid');
|
|
db_query("INSERT INTO {files} (fid, nid, vid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, %d, '%s', '%s', '%s', %d, %d)",
|
|
$fid, $node->nid, $node->vid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
|
|
}
|
|
}
|
|
}
|
|
// Remove or update existing files:
|
|
foreach ((array)$node->remove as $key => $value) {
|
|
if ($node->remove[$key]) {
|
|
db_query('DELETE FROM {files} WHERE fid = %d AND vid = %d', $key, $node->vid);
|
|
// We only delete a file if it isn't used anymore by any revision.
|
|
$count = db_result(db_query('SELECT COUNT(fid) FROM {files} WHERE fid = %d', $key));
|
|
if (!($count > 0)) {
|
|
file_delete($file->filepath);
|
|
}
|
|
}
|
|
}
|
|
foreach ((array)$node->list as $key => $value) {
|
|
if (!$node->remove[$key]) {
|
|
db_query('UPDATE {files} SET list = %d WHERE fid = %d AND vid = %d', $node->list[$key], $key, $node->vid);
|
|
}
|
|
}
|
|
if ($node->old_vid) {
|
|
foreach ((array)$node->remove as $key => $remove) {
|
|
if (!$remove) {
|
|
$file = db_fetch_object(db_query('SELECT * FROM {files} WHERE vid = %d AND fid = %d', $node->old_vid, $key));
|
|
db_query("INSERT INTO {files} (fid, nid, vid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, %d, '%s', '%s', '%s', %d, %d)",
|
|
$key, $node->nid, $node->vid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $file->list);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
function upload_delete($node) {
|
|
$node->files = upload_load($node);
|
|
foreach ($node->files as $file) {
|
|
file_delete($file->filepath);
|
|
}
|
|
db_query("DELETE FROM {files} WHERE nid = %d", $node->nid);
|
|
}
|
|
|
|
function upload_form($node) {
|
|
drupal_add_js('misc/progress.js');
|
|
drupal_add_js('misc/upload.js');
|
|
|
|
$output = '<div id="fileop-wrapper">'. _upload_form($node) .'</div>';
|
|
|
|
return '<div class="attachments">'. form_group_collapsible(t('File attachments'), $output, empty($node->files), t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.')) .'</div>';
|
|
}
|
|
|
|
function _upload_form($node) {
|
|
$header = array(t('Delete'), t('List'), t('Url'), t('Size'));
|
|
$rows = array();
|
|
$output = '';
|
|
|
|
if (is_array($node->files)) {
|
|
foreach ($node->files as $key => $file) {
|
|
$rows[] = array(
|
|
form_checkbox('', "remove][$key", 1, $file->remove),
|
|
form_checkbox('', "list][$key", 1, $file->list),
|
|
$file->filename ."<br /><small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>",
|
|
format_size($file->filesize)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (count($node->files)) {
|
|
$output .= theme('table', $header, $rows);
|
|
}
|
|
|
|
if (user_access('upload files')) {
|
|
$output .= '<div id="fileop-hide">';
|
|
$output .= form_file(t('Attach new file'), "upload", 40);
|
|
$output .= form_button(t('Attach'), 'fileop');
|
|
// The class triggers the js upload behaviour.
|
|
$output .= form_hidden('fileop', url('upload/js', NULL, NULL, TRUE), 'edit', array('class' => 'upload'));
|
|
$output .= '</div>';
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
function upload_load($node) {
|
|
$files = array();
|
|
|
|
if ($node->vid) {
|
|
$result = db_query("SELECT * FROM {files} WHERE vid = %d", $node->vid);
|
|
while ($file = db_fetch_object($result)) {
|
|
$files[$file->fid] = $file;
|
|
}
|
|
}
|
|
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* Check an upload, if it is an image, make sure it fits within the
|
|
* maximum dimensions allowed.
|
|
*/
|
|
function _upload_image($file) {
|
|
$info = image_get_info($file->filepath);
|
|
|
|
if ($info) {
|
|
list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
|
|
if ($width && $height) {
|
|
$result = image_scale($file->filepath, $file->filepath, $width, $height);
|
|
if ($result) {
|
|
$file->filesize = filesize($file->filepath);
|
|
drupal_set_message(t('The image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => theme('placeholder', variable_get('upload_max_resolution', 0)))));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* Menu-callback for JavaScript-based uploads.
|
|
*/
|
|
function upload_js() {
|
|
// We only do the upload.module part of the node validation process.
|
|
$node = array2object($_POST['edit']);
|
|
upload_nodeapi(&$node, 'validate', NULL);
|
|
$output = theme('status_messages') . _upload_form($node);
|
|
|
|
// We send the updated file attachments form.
|
|
print drupal_call_js('window.parent.iframeHandler', $output);
|
|
exit;
|
|
}
|