diff --git a/modules/upload.module b/modules/upload.module index 6e481657a06d6335e6a1b3b90dacd3cee7c95dc6..c153dbf9859f08ea6b774a6a1882bd34948d2d08 100644 --- a/modules/upload.module +++ b/modules/upload.module @@ -4,6 +4,7 @@ /** * @file * File-handling and attaching files to nodes. + * */ /** @@ -75,8 +76,8 @@ function upload_menu($may_cache) { } else { // Add handlers for previewing new uploads. - if ($_SESSION['file_uploads']) { - foreach ($_SESSION['file_uploads'] as $key => $file) { + if ($_SESSION['file_previews']) { + foreach ($_SESSION['file_previews'] as $fid => $file) { $filename = file_create_filename($file->filename, file_create_path()); $items[] = array( 'path' => $filename, 'title' => t('file download'), @@ -84,7 +85,7 @@ function upload_menu($may_cache) { 'access' => user_access('view uploaded files'), 'type' => MENU_CALLBACK ); - $_SESSION['file_uploads'][$key]->_filename = $filename; + $_SESSION['file_previews'][$fid]->_filename = $filename; } } } @@ -99,6 +100,12 @@ function upload_settings() { '#size' => 15, '#maxlength' => 10, '#description' => t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.') ); + $form['settings_general']['upload_list_default'] = array('#type' => 'select', '#title' => t('List files by default'), + '#default_value' => variable_get('upload_list_default',1), + '#options' => array( 0 => t('No'), 1 => t('Yes') ), + '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'), + ); + $roles = user_roles(0, 'upload files'); foreach ($roles as $rid => $role) { @@ -121,7 +128,7 @@ function upload_settings() { } function upload_download() { - foreach ($_SESSION['file_uploads'] as $file) { + foreach ($_SESSION['file_previews'] as $file) { if ($file->_filename == $_GET['q']) { file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize)); } @@ -153,6 +160,61 @@ function upload_file_download($file) { } } + + + +/* + * Save new uploads and attach them to the node object. + * append file_previews to the node object as well. + */ + +function _upload_prepare(&$node) { + + // Clean up old file previews if a post didn't get the user to this page. + // i.e. the user left the edit page, because they didn't want to upload anything. + if(count($_POST) == 0) { + if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { + foreach($_SESSION['file_previews'] as $fid => $file) { + file_delete($file->filepath); + } + unset($_SESSION['file_previews']); + } + } + + // $_SESSION['file_submitted'] tracks the fid of the file submitted this page request. + // form_builder sets the value of file->list to 0 for checkboxes added to a form after + // it has been submitted. Since unchecked checkboxes have no return value and do not + // get a key in _POST form_builder has no way of knowing the difference between a check + // box that wasn't present on the last form build, and a checkbox that is unchecked. + + unset($_SESSION['file_submitted']); + + // Save new file uploads to tmp dir. + if (($file = file_check_upload()) && user_access('upload files')) { + global $user; + + // Scale image uploads. + $file = _upload_image($file); + + $key = 'upload_'. count($_SESSION['file_previews']); + $file->fid = $key; + $file->source = $key; + $file->list = variable_get('upload_list_default',1); + $_SESSION['file_previews'][$key] = $file; + + // Store the uploaded fid for this page request in case of submit without + // preview or attach. See earlier notes. + $_SESSION['file_submitted'] = $key; + } + + // Attach file previews to node object. + if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { + foreach($_SESSION['file_previews'] as $fid => $file) { + $node->files[$fid] = $file; + } + } +} + function upload_form_alter($form_id, &$form) { if (isset($form['type'])) { if ($form['type']['#value'] .'_node_settings' == $form_id) { @@ -166,13 +228,7 @@ function upload_form_alter($form_id, &$form) { if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE) && user_access('upload files')) { drupal_add_js('misc/progress.js'); drupal_add_js('misc/upload.js'); - // Clears our files in session when you enter the edit view the first time. - // This is so files don't linger around if you happen to leave the node - // and come back into it. - if(count($_POST) == 0) { - unset($_SESSION['file_uploads']); - } - upload_nodeapi($node, 'validate', NULL); + $form['attachments'] = array( '#type' => 'fieldset', '#title' => t('File attachments'), @@ -189,42 +245,28 @@ function upload_form_alter($form_id, &$form) { } } -/** - * Implementation of hook_nodeapi(). - */ -function upload_nodeapi(&$node, $op, $arg) { - switch ($op) { - case 'validate': - $node->files = upload_load($node); - // Double check existing files: - if (is_array($node->list)) { - foreach ($node->list as $key => $value) { - // Ensure file is valid and retrieve contents of $_FILES array - 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]; - $node->files[$key]->description = $node->description[$key]; - if ($file->source) { - $filesize += $file->filesize; - } - } - } - } - else { - foreach ($node->files as $key => $file) { - $node->list[$key] = $file->list; - } - } +function _upload_validate(&$node) { + // Accumulator for disk space quotas. + $filesize = 0; + + + // Check if node->files exists, and if it contains something. + if (count($node->files) && is_array($node->files)) { + // Update existing files with form data. + foreach($node->files as $fid => $file) { - if (($file = file_check_upload('upload')) && user_access('upload files')) { + // Validate new uploads. + if (strpos($fid, 'upload') !== false && !$file->remove) { global $user; - $file = _upload_image($file); - // Don't do any checks for uid #1. + // Bypass validation for uid = 1. if ($user->uid != 1) { - // Validate file against all users roles. Only denies an upload when - // all roles prevent it. + //Update filesize accumulator. + $filesize += $file->filesize; + + // 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'); @@ -245,37 +287,41 @@ function upload_nodeapi(&$node, $op, $arg) { $error['usersize']++; } } - } - - 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; - } - } - for ($x = 0; $x < count($_SESSION['file_uploads']); $x++) { - $key = 'upload_' . $x; - if ($file = file_check_upload($key)) { - $node->files[$key] = $file; + 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))))); + } } } - break; + } + } +} + + +/** + * Implementation of hook_nodeapi(). + */ +function upload_nodeapi(&$node, $op, $arg) { + switch ($op) { case 'load': if (variable_get("upload_$node->type", 1) == 1) { $output['files'] = upload_load($node); } + return $output; + break; + + case 'prepare': + _upload_prepare($node); + break; + + case 'validate': + _upload_validate($node); break; case 'view': @@ -286,13 +332,13 @@ function upload_nodeapi(&$node, $op, $arg) { // Build list of attached files foreach ($node->files as $key => $file) { - if ($file->list && !$node->remove[$key]) { + 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->description ? $file->description : $file->filename) .'</a>', format_size($file->filesize) ); // We save the list of files still in preview for later - if (!$file->fid) { + if (strpos($file->fid, 'upload') !== false) { $previews[] = $file; } } @@ -326,14 +372,18 @@ function upload_nodeapi(&$node, $op, $arg) { upload_save($node); } break; + case 'delete': upload_delete($node); break; + case 'delete revision': upload_delete_revision($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(); @@ -352,9 +402,8 @@ function upload_nodeapi(&$node, $op, $arg) { } } return array(); - } - return $output; + } } /** @@ -380,58 +429,54 @@ function upload_total_space_used() { } function upload_save($node) { - $node->old_files = isset($node->files) ? $node->files : array(); - upload_nodeapi($node, 'validate', NULL); - $node->files = $node->old_files + $node->files; - - foreach ((array)$node->files as $key => $file) { - // New file upload - if ($file->source) { - // Only add a file if it's not marked for removal - if (!$node->remove[$key]) { - if ($file = file_save_upload($file, $file->filename)) { - $fid = db_next_id('{files}_fid'); - db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize); - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $fid, $node->vid, $node->list[$key], $node->description[$key]); - } + foreach ($node->files as $fid => $file) { + // Convert file to object for compatability + $file = (object)$file; + + // Remove file. Process removals first since no further processing + // will be required. + if ($file->remove) { + // Remove file previews... + if (strpos($file->fid, 'upload') !== false) { + file_delete($file->filepath); } - // Clean up the session - unset($_SESSION['file_uploads'][$file->source]); - } - - // Update existing file - else { - // Remove existing file, as needed - if ($node->remove[$key]) { - db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $key, $node->vid); + // Remove managed files. + else { + db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid); // Only delete a file if it isn't used by any revision - $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $key)); + $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid)); if ($count < 1) { - db_query('DELETE FROM {files} WHERE fid = %d', $key); + db_query('DELETE FROM {files} WHERE fid = %d', $fid); file_delete($file->filepath); } } + } - else { - // Create a new revision, as needed - if ($node->old_vid && is_numeric($key)) { - // new revision - if (isset($node->list)) { - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $key, $node->vid, $node->list[$key], $node->description[$key]); - } - - // copy of old revision - else { - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $key, $node->vid, $file->list, $file->description); - } + // New file upload + elseif (strpos($file->fid, 'upload') !== false) { + if ($file = file_save_upload($file, $file->filename)) { + // Track the file which was submitted last, in case of a direct submission + // without preview or attach. See notes in upload_prepare. + if ($_SESSION['file_submitted'] == $file->fid) { + $file->list = variable_get('upload_list_default',1); } - // Update existing revision - else { - db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $node->list[$key], $node->description[$key], $key, $node->vid); - } + $file->fid = db_next_id('{files}_fid'); + db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize); + db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); } + unset($_SESSION['file_previews'][$fid]); + } + + // Create a new revision, as needed + elseif ($node->old_vid && is_numeric($fid)) { + db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); + } + + // Update existing revision + else { + db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid); } } @@ -446,20 +491,18 @@ function upload_delete($node) { } foreach ($files as $fid => $file) { - // delete all file revision information associated with the node + // Delete all file revision information associated with the node db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid); file_delete($file->filepath); } - // delete all files associated with the node + // Delete all files associated with the node db_query('DELETE FROM {files} WHERE nid = %d', $node->nid); } function upload_delete_revision($node) { - $files = upload_load($node); - - foreach ($files as $file) { - // check if the file will be used after this revision is deleted + 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 {file_revisions} WHERE fid = %d', $file->fid)); // if the file won't be used, delete it @@ -473,6 +516,7 @@ function upload_delete_revision($node) { db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid); } + function _upload_form($node) { $header = array(t('Delete'), t('List'), t('Description'), t('Size')); $rows = array(); @@ -480,27 +524,23 @@ function _upload_form($node) { $form['#theme'] = 'upload_form_new'; if (is_array($node->files) && count($node->files)) { - $form['current']['#theme'] = 'upload_form_current'; - $form['current']['description']['#tree'] = TRUE; + $form['files']['#theme'] = 'upload_form_current'; + $form['files']['#tree'] = TRUE; foreach ($node->files as $key => $file) { - $options[$key] = ''; - if ($file->remove || $node->remove[$key]) { - $remove[] = $key; - } - if ($file->list || $node->list[$key]) { - $list[] = $key; - } - $description = "<small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>"; - $form['current']['description'][$key] = array('#type' => 'textfield', '#default_value' => $file->description ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); - $form['current']['size'][$key] = array('#type' => 'markup', '#value' => format_size($file->filesize)); + $description = "<small>". file_create_url((strpos($file->fid,'upload') === false ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>"; + $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); + $form['files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize)); + $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => $file->remove); + $form['files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list); + $form['files'][$key]['filename'] = array('#type' => 'value', '#value' => $file->filename); + $form['files'][$key]['filepath'] = array('#type' => 'value', '#value' => $file->filepath); + $form['files'][$key]['filemime'] = array('#type' => 'value', '#value' => $file->filemime); + $form['files'][$key]['filesize'] = array('#type' => 'value', '#value' => $file->filesize); + $form['files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid); } - $form['current']['remove'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $remove); - $form['current']['list'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $list); - $form['files'][$key] = array('#type' => 'hidden', '#value' => 1); } if (user_access('upload files')) { - $form['new']['upload'] = array('#type' => 'file', '#title' => t('Attach new file'), '#size' => 40); $form['new']['fileop'] = array('#type' => 'button', '#value' => t('Attach'), '#name'=> 'fileop', '#attributes' => array('id' => 'fileop')); // The class triggers the js upload behaviour. @@ -523,12 +563,13 @@ function theme_upload_form_new($form) { function theme_upload_form_current(&$form) { $header = array(t('Delete'), t('List'), t('Description'), t('Size')); - foreach (element_children($form['description']) as $key) { + + foreach (element_children($form) as $key) { $row = array(); - $row[] = form_render($form['remove'][$key]); - $row[] = form_render($form['list'][$key]); - $row[] = form_render($form['description'][$key]); - $row[] = form_render($form['size'][$key]); + $row[] = form_render($form[$key]['remove']); + $row[] = form_render($form[$key]['list']); + $row[] = form_render($form[$key]['description']); + $row[] = form_render($form[$key]['size']); $rows[] = $row; } $output = theme('table', $header, $rows); @@ -576,7 +617,14 @@ function _upload_image($file) { function upload_js() { // We only do the upload.module part of the node validation process. $node = (object)$_POST['edit']; - upload_nodeapi($node, 'validate', NULL); + + // Load existing node files. + $node->files = upload_load($node); + + // Handle new uploads, and merge tmp files into node-files. + _upload_prepare($node); + _upload_validate($node); + $form = _upload_form($node); $form = form_builder('upload_js', $form); $output = theme('status_messages') . form_render($form); diff --git a/modules/upload/upload.module b/modules/upload/upload.module index 6e481657a06d6335e6a1b3b90dacd3cee7c95dc6..c153dbf9859f08ea6b774a6a1882bd34948d2d08 100644 --- a/modules/upload/upload.module +++ b/modules/upload/upload.module @@ -4,6 +4,7 @@ /** * @file * File-handling and attaching files to nodes. + * */ /** @@ -75,8 +76,8 @@ function upload_menu($may_cache) { } else { // Add handlers for previewing new uploads. - if ($_SESSION['file_uploads']) { - foreach ($_SESSION['file_uploads'] as $key => $file) { + if ($_SESSION['file_previews']) { + foreach ($_SESSION['file_previews'] as $fid => $file) { $filename = file_create_filename($file->filename, file_create_path()); $items[] = array( 'path' => $filename, 'title' => t('file download'), @@ -84,7 +85,7 @@ function upload_menu($may_cache) { 'access' => user_access('view uploaded files'), 'type' => MENU_CALLBACK ); - $_SESSION['file_uploads'][$key]->_filename = $filename; + $_SESSION['file_previews'][$fid]->_filename = $filename; } } } @@ -99,6 +100,12 @@ function upload_settings() { '#size' => 15, '#maxlength' => 10, '#description' => t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.') ); + $form['settings_general']['upload_list_default'] = array('#type' => 'select', '#title' => t('List files by default'), + '#default_value' => variable_get('upload_list_default',1), + '#options' => array( 0 => t('No'), 1 => t('Yes') ), + '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'), + ); + $roles = user_roles(0, 'upload files'); foreach ($roles as $rid => $role) { @@ -121,7 +128,7 @@ function upload_settings() { } function upload_download() { - foreach ($_SESSION['file_uploads'] as $file) { + foreach ($_SESSION['file_previews'] as $file) { if ($file->_filename == $_GET['q']) { file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize)); } @@ -153,6 +160,61 @@ function upload_file_download($file) { } } + + + +/* + * Save new uploads and attach them to the node object. + * append file_previews to the node object as well. + */ + +function _upload_prepare(&$node) { + + // Clean up old file previews if a post didn't get the user to this page. + // i.e. the user left the edit page, because they didn't want to upload anything. + if(count($_POST) == 0) { + if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { + foreach($_SESSION['file_previews'] as $fid => $file) { + file_delete($file->filepath); + } + unset($_SESSION['file_previews']); + } + } + + // $_SESSION['file_submitted'] tracks the fid of the file submitted this page request. + // form_builder sets the value of file->list to 0 for checkboxes added to a form after + // it has been submitted. Since unchecked checkboxes have no return value and do not + // get a key in _POST form_builder has no way of knowing the difference between a check + // box that wasn't present on the last form build, and a checkbox that is unchecked. + + unset($_SESSION['file_submitted']); + + // Save new file uploads to tmp dir. + if (($file = file_check_upload()) && user_access('upload files')) { + global $user; + + // Scale image uploads. + $file = _upload_image($file); + + $key = 'upload_'. count($_SESSION['file_previews']); + $file->fid = $key; + $file->source = $key; + $file->list = variable_get('upload_list_default',1); + $_SESSION['file_previews'][$key] = $file; + + // Store the uploaded fid for this page request in case of submit without + // preview or attach. See earlier notes. + $_SESSION['file_submitted'] = $key; + } + + // Attach file previews to node object. + if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { + foreach($_SESSION['file_previews'] as $fid => $file) { + $node->files[$fid] = $file; + } + } +} + function upload_form_alter($form_id, &$form) { if (isset($form['type'])) { if ($form['type']['#value'] .'_node_settings' == $form_id) { @@ -166,13 +228,7 @@ function upload_form_alter($form_id, &$form) { if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE) && user_access('upload files')) { drupal_add_js('misc/progress.js'); drupal_add_js('misc/upload.js'); - // Clears our files in session when you enter the edit view the first time. - // This is so files don't linger around if you happen to leave the node - // and come back into it. - if(count($_POST) == 0) { - unset($_SESSION['file_uploads']); - } - upload_nodeapi($node, 'validate', NULL); + $form['attachments'] = array( '#type' => 'fieldset', '#title' => t('File attachments'), @@ -189,42 +245,28 @@ function upload_form_alter($form_id, &$form) { } } -/** - * Implementation of hook_nodeapi(). - */ -function upload_nodeapi(&$node, $op, $arg) { - switch ($op) { - case 'validate': - $node->files = upload_load($node); - // Double check existing files: - if (is_array($node->list)) { - foreach ($node->list as $key => $value) { - // Ensure file is valid and retrieve contents of $_FILES array - 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]; - $node->files[$key]->description = $node->description[$key]; - if ($file->source) { - $filesize += $file->filesize; - } - } - } - } - else { - foreach ($node->files as $key => $file) { - $node->list[$key] = $file->list; - } - } +function _upload_validate(&$node) { + // Accumulator for disk space quotas. + $filesize = 0; + + + // Check if node->files exists, and if it contains something. + if (count($node->files) && is_array($node->files)) { + // Update existing files with form data. + foreach($node->files as $fid => $file) { - if (($file = file_check_upload('upload')) && user_access('upload files')) { + // Validate new uploads. + if (strpos($fid, 'upload') !== false && !$file->remove) { global $user; - $file = _upload_image($file); - // Don't do any checks for uid #1. + // Bypass validation for uid = 1. if ($user->uid != 1) { - // Validate file against all users roles. Only denies an upload when - // all roles prevent it. + //Update filesize accumulator. + $filesize += $file->filesize; + + // 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'); @@ -245,37 +287,41 @@ function upload_nodeapi(&$node, $op, $arg) { $error['usersize']++; } } - } - - 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; - } - } - for ($x = 0; $x < count($_SESSION['file_uploads']); $x++) { - $key = 'upload_' . $x; - if ($file = file_check_upload($key)) { - $node->files[$key] = $file; + 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))))); + } } } - break; + } + } +} + + +/** + * Implementation of hook_nodeapi(). + */ +function upload_nodeapi(&$node, $op, $arg) { + switch ($op) { case 'load': if (variable_get("upload_$node->type", 1) == 1) { $output['files'] = upload_load($node); } + return $output; + break; + + case 'prepare': + _upload_prepare($node); + break; + + case 'validate': + _upload_validate($node); break; case 'view': @@ -286,13 +332,13 @@ function upload_nodeapi(&$node, $op, $arg) { // Build list of attached files foreach ($node->files as $key => $file) { - if ($file->list && !$node->remove[$key]) { + 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->description ? $file->description : $file->filename) .'</a>', format_size($file->filesize) ); // We save the list of files still in preview for later - if (!$file->fid) { + if (strpos($file->fid, 'upload') !== false) { $previews[] = $file; } } @@ -326,14 +372,18 @@ function upload_nodeapi(&$node, $op, $arg) { upload_save($node); } break; + case 'delete': upload_delete($node); break; + case 'delete revision': upload_delete_revision($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(); @@ -352,9 +402,8 @@ function upload_nodeapi(&$node, $op, $arg) { } } return array(); - } - return $output; + } } /** @@ -380,58 +429,54 @@ function upload_total_space_used() { } function upload_save($node) { - $node->old_files = isset($node->files) ? $node->files : array(); - upload_nodeapi($node, 'validate', NULL); - $node->files = $node->old_files + $node->files; - - foreach ((array)$node->files as $key => $file) { - // New file upload - if ($file->source) { - // Only add a file if it's not marked for removal - if (!$node->remove[$key]) { - if ($file = file_save_upload($file, $file->filename)) { - $fid = db_next_id('{files}_fid'); - db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize); - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $fid, $node->vid, $node->list[$key], $node->description[$key]); - } + foreach ($node->files as $fid => $file) { + // Convert file to object for compatability + $file = (object)$file; + + // Remove file. Process removals first since no further processing + // will be required. + if ($file->remove) { + // Remove file previews... + if (strpos($file->fid, 'upload') !== false) { + file_delete($file->filepath); } - // Clean up the session - unset($_SESSION['file_uploads'][$file->source]); - } - - // Update existing file - else { - // Remove existing file, as needed - if ($node->remove[$key]) { - db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $key, $node->vid); + // Remove managed files. + else { + db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid); // Only delete a file if it isn't used by any revision - $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $key)); + $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid)); if ($count < 1) { - db_query('DELETE FROM {files} WHERE fid = %d', $key); + db_query('DELETE FROM {files} WHERE fid = %d', $fid); file_delete($file->filepath); } } + } - else { - // Create a new revision, as needed - if ($node->old_vid && is_numeric($key)) { - // new revision - if (isset($node->list)) { - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $key, $node->vid, $node->list[$key], $node->description[$key]); - } - - // copy of old revision - else { - db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $key, $node->vid, $file->list, $file->description); - } + // New file upload + elseif (strpos($file->fid, 'upload') !== false) { + if ($file = file_save_upload($file, $file->filename)) { + // Track the file which was submitted last, in case of a direct submission + // without preview or attach. See notes in upload_prepare. + if ($_SESSION['file_submitted'] == $file->fid) { + $file->list = variable_get('upload_list_default',1); } - // Update existing revision - else { - db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $node->list[$key], $node->description[$key], $key, $node->vid); - } + $file->fid = db_next_id('{files}_fid'); + db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize); + db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); } + unset($_SESSION['file_previews'][$fid]); + } + + // Create a new revision, as needed + elseif ($node->old_vid && is_numeric($fid)) { + db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); + } + + // Update existing revision + else { + db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid); } } @@ -446,20 +491,18 @@ function upload_delete($node) { } foreach ($files as $fid => $file) { - // delete all file revision information associated with the node + // Delete all file revision information associated with the node db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid); file_delete($file->filepath); } - // delete all files associated with the node + // Delete all files associated with the node db_query('DELETE FROM {files} WHERE nid = %d', $node->nid); } function upload_delete_revision($node) { - $files = upload_load($node); - - foreach ($files as $file) { - // check if the file will be used after this revision is deleted + 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 {file_revisions} WHERE fid = %d', $file->fid)); // if the file won't be used, delete it @@ -473,6 +516,7 @@ function upload_delete_revision($node) { db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid); } + function _upload_form($node) { $header = array(t('Delete'), t('List'), t('Description'), t('Size')); $rows = array(); @@ -480,27 +524,23 @@ function _upload_form($node) { $form['#theme'] = 'upload_form_new'; if (is_array($node->files) && count($node->files)) { - $form['current']['#theme'] = 'upload_form_current'; - $form['current']['description']['#tree'] = TRUE; + $form['files']['#theme'] = 'upload_form_current'; + $form['files']['#tree'] = TRUE; foreach ($node->files as $key => $file) { - $options[$key] = ''; - if ($file->remove || $node->remove[$key]) { - $remove[] = $key; - } - if ($file->list || $node->list[$key]) { - $list[] = $key; - } - $description = "<small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>"; - $form['current']['description'][$key] = array('#type' => 'textfield', '#default_value' => $file->description ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); - $form['current']['size'][$key] = array('#type' => 'markup', '#value' => format_size($file->filesize)); + $description = "<small>". file_create_url((strpos($file->fid,'upload') === false ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>"; + $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); + $form['files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize)); + $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => $file->remove); + $form['files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list); + $form['files'][$key]['filename'] = array('#type' => 'value', '#value' => $file->filename); + $form['files'][$key]['filepath'] = array('#type' => 'value', '#value' => $file->filepath); + $form['files'][$key]['filemime'] = array('#type' => 'value', '#value' => $file->filemime); + $form['files'][$key]['filesize'] = array('#type' => 'value', '#value' => $file->filesize); + $form['files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid); } - $form['current']['remove'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $remove); - $form['current']['list'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $list); - $form['files'][$key] = array('#type' => 'hidden', '#value' => 1); } if (user_access('upload files')) { - $form['new']['upload'] = array('#type' => 'file', '#title' => t('Attach new file'), '#size' => 40); $form['new']['fileop'] = array('#type' => 'button', '#value' => t('Attach'), '#name'=> 'fileop', '#attributes' => array('id' => 'fileop')); // The class triggers the js upload behaviour. @@ -523,12 +563,13 @@ function theme_upload_form_new($form) { function theme_upload_form_current(&$form) { $header = array(t('Delete'), t('List'), t('Description'), t('Size')); - foreach (element_children($form['description']) as $key) { + + foreach (element_children($form) as $key) { $row = array(); - $row[] = form_render($form['remove'][$key]); - $row[] = form_render($form['list'][$key]); - $row[] = form_render($form['description'][$key]); - $row[] = form_render($form['size'][$key]); + $row[] = form_render($form[$key]['remove']); + $row[] = form_render($form[$key]['list']); + $row[] = form_render($form[$key]['description']); + $row[] = form_render($form[$key]['size']); $rows[] = $row; } $output = theme('table', $header, $rows); @@ -576,7 +617,14 @@ function _upload_image($file) { function upload_js() { // We only do the upload.module part of the node validation process. $node = (object)$_POST['edit']; - upload_nodeapi($node, 'validate', NULL); + + // Load existing node files. + $node->files = upload_load($node); + + // Handle new uploads, and merge tmp files into node-files. + _upload_prepare($node); + _upload_validate($node); + $form = _upload_form($node); $form = form_builder('upload_js', $form); $output = theme('status_messages') . form_render($form);