add node : allowed to everyone
[plewww.git] / modules / upload.module
1 <?php
2 // $Id: upload.module 144 2007-03-28 07:52:20Z thierry $
3
4 /**
5  * @file
6  * File-handling and attaching files to nodes.
7  *
8  */
9
10 /**
11  * Implementation of hook_help().
12  */
13 function upload_help($section) {
14   switch ($section) {
15     case 'admin/help#upload':
16       $output = '<p>'. t('The upload module allows users to upload files to the site.  The ability to upload files to a site is important for members of a community who want to share work. It is also useful to administrators who want to keep uploaded files connected to a node or page.') .'</p>';
17       $output .= '<p>'. t('Users with the upload files permission can upload attachments. You can choose which post types can take attachments on the content types settings page.  Each user role can be customized for the file size of uploads, and the dimension of image files.') .'</p>';
18       $output .= t('<p>You can</p>
19 <ul>
20 <li>administer user permissions at <a href="%admin-access">administer &gt;&gt; access control</a>.</li>
21 <li>administer content at <a href="%admin-content-types">administer &gt;&gt; settings &gt;&gt; content types</a>.</li>
22 <li>administer upload at <a href="%admin-upload">administer &gt;&gt; settings &gt;&gt; upload</a>.</li>
23 </ul>
24 ', array('%admin-access' => url('admin/access'), '%admin-content-types' => url('admin/settings/content-types'), '%admin-upload' => url('admin/settings/upload')));
25       $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%upload">Upload page</a>.', array('%upload' => 'http://drupal.org/handbook/modules/upload/')) .'</p>';
26       return $output;
27     case 'admin/modules#description':
28       return t('Allows users to upload and attach files to content.');
29     case 'admin/settings/upload':
30       return t('<p>Users with the <a href="%permissions">upload files permission</a> can upload attachments. Users with the <a href="%permissions">view uploaded files permission</a> can view uploaded 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')));
31   }
32 }
33
34 /**
35  * Implementation of hook_perm().
36  */
37 function upload_perm() {
38   return array('upload files', 'view uploaded files');
39 }
40
41 /**
42  * Implementation of hook_link().
43  */
44 function upload_link($type, $node = 0, $main = 0) {
45   $links = array();
46
47   // Display a link with the number of attachments
48   if ($main && $type == 'node' && isset($node->files) && user_access('view uploaded files')) {
49     $num_files = 0;
50     foreach ($node->files as $file) {
51       if ($file->list) {
52         $num_files++;
53       }
54     }
55     if ($num_files) {
56       $links[] = l(format_plural($num_files, '1 attachment', '%count attachments'), "node/$node->nid", array('title' => t('Read full article to view attachments.')), NULL, 'attachments');
57     }
58   }
59
60   return $links;
61 }
62
63 /**
64  * Implementation of hook_menu().
65  */
66 function upload_menu($may_cache) {
67   $items = array();
68
69   if ($may_cache) {
70     $items[] = array(
71       'path' => 'upload/js',
72       'callback' => 'upload_js',
73       'access' => user_access('upload files'),
74       'type' => MENU_CALLBACK
75     );
76   }
77   else {
78     // Add handlers for previewing new uploads.
79     if (isset($_SESSION['file_previews'])) {
80       foreach ($_SESSION['file_previews'] as $fid => $file) {
81         $filename = file_create_filename($file->filename, file_create_path());
82         if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==  FILE_DOWNLOADS_PRIVATE) {
83           // strip file_directory_path() from filename. @see file_create_url
84           if (strpos($filename, file_directory_path()) !== false) {
85             $filename = trim(substr($filename, strlen(file_directory_path())), '\\/');
86           }
87           $filename = 'system/files/' . $filename;
88         }
89
90         $items[] = array(
91           'path' => $filename, 'title' => t('file download'),
92           'callback' => 'upload_download',
93           'access' => user_access('view uploaded files'),
94           'type' => MENU_CALLBACK
95         );
96         $_SESSION['file_previews'][$fid]->_filename = $filename;
97       }
98     }
99   }
100
101   return $items;
102 }
103
104 function upload_settings() {
105   $form['settings_general'] = array('#type' => 'fieldset', '#title' => t('General settings'));
106   $form['settings_general']['upload_max_resolution'] = array(
107     '#type' => 'textfield', '#title' => t('Maximum resolution for uploaded images'), '#default_value' => variable_get('upload_max_resolution', 0),
108     '#size' => 15, '#maxlength' => 10, '#description' => t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.')
109   );
110
111   $form['settings_general']['upload_list_default'] = array('#type' => 'select', '#title' => t('List files by default'),
112     '#default_value' => variable_get('upload_list_default',1),
113     '#options' => array( 0 => t('No'), 1 => t('Yes') ),
114     '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'),
115   );
116
117   $roles = user_roles(0, 'upload files');
118
119   foreach ($roles as $rid => $role) {
120     $form["settings_role_$rid"] = array('#type' => 'fieldset', '#title' => t('Settings for %role', array('%role' => theme('placeholder', $role))), '#collapsible' => TRUE, '#collapsed' => TRUE);
121     $form["settings_role_$rid"]["upload_extensions_$rid"] = array(
122       '#type' => 'textfield', '#title' => t('Permitted file extensions'), '#default_value' => variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps"),
123       '#maxlength' => 255, '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.')
124     );
125     $form["settings_role_$rid"]["upload_uploadsize_$rid"] = array(
126       '#type' => 'textfield', '#title' => t('Maximum file size per upload'), '#default_value' => variable_get("upload_uploadsize_$rid", 1),
127       '#size' => 5, '#maxlength' => 5, '#description' => t('The maximum size of a file a user can upload (in megabytes).')
128     );
129     $form["settings_role_$rid"]["upload_usersize_$rid"] = array(
130       '#type' => 'textfield', '#title' => t('Total file size per user'), '#default_value' => variable_get("upload_usersize_$rid", 10),
131       '#size' => 5, '#maxlength' => 5, '#description' => t('The maximum size of all files a user can have on the site (in megabytes).')
132     );
133   }
134
135   return $form;
136 }
137
138 function upload_download() {
139   foreach ($_SESSION['file_previews'] as $file) {
140     if ($file->_filename == $_GET['q']) {
141       file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize));
142     }
143   }
144 }
145
146 function upload_file_download($file) {
147   $file = file_create_path($file);
148   $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file);
149   if ($file = db_fetch_object($result)) {
150     if (user_access('view uploaded files')) {
151       $node = node_load($file->nid);
152       if (node_access('view', $node)) {
153         $name = mime_header_encode($file->filename);
154         $type = mime_header_encode($file->filemime);
155         // Serve images and text inline for the browser to display rather than download.
156         $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
157         return array(
158           'Content-Type: '. $type .'; name='. $name,
159           'Content-Length: '. $file->filesize,
160           'Content-Disposition: '. $disposition .'; filename='. $name
161         );
162       }
163       else {
164         return -1;
165       }
166     }
167     else {
168       return -1;
169     }
170   }
171 }
172
173 /**
174  * Save new uploads and attach them to the node object.
175  * append file_previews to the node object as well.
176  */
177 function _upload_prepare(&$node) {
178
179   // Clean up old file previews if a post didn't get the user to this page.
180   // i.e. the user left the edit page, because they didn't want to upload anything.
181   if(count($_POST) == 0) {
182     if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) {
183       foreach($_SESSION['file_previews'] as $fid => $file) {
184         file_delete($file->filepath);
185       }
186       unset($_SESSION['file_previews']);
187     }
188   }
189
190   // $_SESSION['file_current_upload'] tracks the fid of the file submitted this page request.
191   // form_builder sets the value of file->list to 0 for checkboxes added to a form after
192   // it has been submitted. Since unchecked checkboxes have no return value and do not
193   // get a key in _POST form_builder has no way of knowing the difference between a check
194   // box that wasn't present on the last form build, and a checkbox that is unchecked.
195
196   unset($_SESSION['file_current_upload']);
197
198   global $user;
199
200   // Save new file uploads to tmp dir.
201   if (($file = file_check_upload()) && user_access('upload files')) {
202
203     // Scale image uploads.
204     $file = _upload_image($file);
205
206     $key = 'upload_'. count($_SESSION['file_previews']);
207     $file->fid = $key;
208     $file->source = $key;
209     $file->list = variable_get('upload_list_default',1);
210     $_SESSION['file_previews'][$key] = $file;
211
212     // Store the uploaded fid for this page request in case of submit without
213     // preview or attach. See earlier notes.
214     $_SESSION['file_current_upload'] = $key;
215   }
216
217   // Attach file previews to node object.
218   if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) {
219     foreach($_SESSION['file_previews'] as $fid => $file) {
220       if ($user->uid != 1) {
221         // Here something.php.pps becomes something.php_.pps
222         $file->filename = upload_munge_filename($file->filename, NULL, 0);
223         $file->description = $file->filename;
224       }
225       $node->files[$fid] = $file;
226     }
227   }
228 }
229
230 function upload_form_alter($form_id, &$form) {
231   if (isset($form['type'])) {
232     if ($form['type']['#value'] .'_node_settings' == $form_id) {
233       $form['workflow']['upload_'. $form['type']['#value']] = array(
234         '#type' => 'radios', '#title' => t('Attachments'), '#default_value' => variable_get('upload_'. $form['type']['#value'], 1),
235         '#options' => array(t('Disabled'), t('Enabled')),
236       );
237     }
238
239     $node = $form['#node'];
240     if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE) && user_access('upload files')) {
241       drupal_add_js('misc/progress.js');
242       drupal_add_js('misc/upload.js');
243
244       // Attachments fieldset
245       $form['attachments'] = array(
246         '#type' => 'fieldset',
247         '#title' => t('File attachments'),
248         '#collapsible' => TRUE,
249         '#collapsed' => empty($node->files),
250         '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'),
251         '#prefix' => '<div class="attachments">',
252         '#suffix' => '</div>',
253         '#weight' => 30,
254       );
255
256       // Wrapper for fieldset contents (used by upload JS).
257       $form['attachments']['wrapper'] = array(
258         '#prefix' => '<div id="attach-wrapper">',
259         '#suffix' => '</div>',
260       );
261       $form['attachments']['wrapper'] += _upload_form($node);
262       $form['#attributes']['enctype'] = 'multipart/form-data';
263     }
264   }
265 }
266
267 function _upload_validate(&$node) {
268   // Accumulator for disk space quotas.
269   $filesize = 0;
270
271   // Check if node->files exists, and if it contains something.
272   if (is_array($node->files)) {
273     // Update existing files with form data.
274     foreach($node->files as $fid => $file) {
275       // Convert file to object for compatibility
276       $file = (object)$file;
277
278       // Validate new uploads.
279       if (strpos($fid, 'upload') !== false && !$file->remove) {
280         global $user;
281
282         // Bypass validation for uid  = 1.
283         if ($user->uid != 1) {
284           // Update filesize accumulator.
285           $filesize += $file->filesize;
286
287           // Validate file against all users roles.
288           // Only denies an upload when all roles prevent it.
289
290           $total_usersize = upload_space_used($user->uid) + $filesize;
291           $error = array();
292           foreach ($user->roles as $rid => $name) {
293             $extensions = variable_get("upload_extensions_$rid", 'jpg jpeg gif png txt html doc xls pdf ppt pps');
294             $uploadsize = variable_get("upload_uploadsize_$rid", 1) * 1024 * 1024;
295             $usersize = variable_get("upload_usersize_$rid", 1) * 1024 * 1024;
296
297             $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
298
299             if (!preg_match($regex, $file->filename)) {
300               $error['extension']++;
301             }
302
303             if ($uploadsize && $file->filesize > $uploadsize) {
304               $error['uploadsize']++;
305             }
306
307             if ($usersize && $total_usersize + $file->filesize > $usersize) {
308               $error['usersize']++;
309             }
310           }
311
312           $user_roles = count($user->roles);
313           $valid = TRUE;
314           if ($error['extension'] == $user_roles) {
315             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))));
316             $valid = FALSE;
317           }
318           elseif ($error['uploadsize'] == $user_roles) {
319             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)))));
320             $valid = FALSE;
321           }
322           elseif ($error['usersize'] == $user_roles) {
323             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)))));
324             $valid = FALSE;
325           }
326           elseif (strlen($file->filename) > 255) {
327             form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is too long.', array('%name' => theme('placeholder', $file->filename))));
328             $valid = FALSE;
329           }
330
331           if (!$valid) {
332             unset($node->files[$fid], $_SESSION['file_previews'][$fid]);
333             file_delete($file->filepath);
334           }
335         }
336       }
337     }
338   }
339 }
340
341 /**
342  * Implementation of hook_nodeapi().
343  */
344 function upload_nodeapi(&$node, $op, $teaser) {
345   switch ($op) {
346
347     case 'load':
348       $output = '';
349       if (variable_get("upload_$node->type", 1) == 1) {
350         $output['files'] = upload_load($node);
351         return $output;
352       }
353       break;
354
355     case 'prepare':
356       _upload_prepare($node);
357       break;
358
359     case 'validate':
360       _upload_validate($node);
361       break;
362
363     case 'view':
364       if (isset($node->files) && user_access('view uploaded files')) {
365         // Manipulate so that inline references work in preview
366         if (!variable_get('clean_url', 0)) {
367           $previews = array();
368           foreach ($node->files as $file) {
369             if (strpos($file->fid, 'upload') !== false) {
370               $previews[] = $file;
371             }
372           }
373
374           // URLs to files being previewed are actually Drupal paths. When Clean
375           // URLs are disabled, the two do not match. We perform an automatic
376           // replacement from temporary to permanent URLs. That way, the author
377           // can use the final URL in the body before having actually saved (to
378           // place inline images for example).
379           foreach ($previews as $file) {
380             $old = file_create_filename($file->filename, file_create_path());
381             $new = url($old);
382             $node->body = str_replace($old, $new, $node->body);
383             $node->teaser = str_replace($old, $new, $node->teaser);
384           }
385         }
386
387         // Add the attachments list to node body
388         if (count($node->files) && !$teaser) {
389           $node->body .= theme('upload_attachments', $node->files);
390         }
391       }
392       break;
393
394     case 'insert':
395     case 'update':
396       if (user_access('upload files')) {
397         upload_save($node);
398       }
399       break;
400
401     case 'delete':
402       upload_delete($node);
403       break;
404
405     case 'delete revision':
406       upload_delete_revision($node);
407       break;
408
409     case 'search result':
410       return is_array($node->files) ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
411
412     case 'rss item':
413       if (is_array($node->files)) {
414         $files = array();
415         foreach ($node->files as $file) {
416           if ($file->list) {
417             $files[] = $file;
418           }
419         }
420         if (count($files) > 0) {
421           // RSS only allows one enclosure per item
422           $file = array_shift($files);
423           return array(
424             array(
425               'key' => 'enclosure',
426               'attributes' => array(
427                 'url' => file_create_url($file->filepath),
428                 'length' => $file->filesize,
429                 'type' => $file->filemime
430               )
431             )
432           );
433         }
434       }
435       return array();
436   }
437 }
438
439 /**
440  * Displays file attachments in table
441  */
442 function theme_upload_attachments($files) {
443   $header = array(t('Attachment'), t('Size'));
444   $rows = array();
445   foreach ($files as $file) {
446     if ($file->list) {
447       $href = check_url(($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))));
448       $text = check_plain($file->description ? $file->description : $file->filename);
449       $rows[] = array(l($text, $href), format_size($file->filesize));
450     }
451   }
452   if (count($rows)) {
453     return theme('table', $header, $rows, array('id' => 'attachments'));
454   }
455 }
456
457 /**
458  * Determine how much disk space is occupied by a user's uploaded files.
459  *
460  * @param $uid
461  *   The integer user id of a user.
462  * @return
463  *   The amount of disk space used by the user in bytes.
464  */
465 function upload_space_used($uid) {
466   return db_result(db_query('SELECT SUM(filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE n.uid = %d', $uid));
467 }
468
469 /**
470  * Determine how much disk space is occupied by uploaded files.
471  *
472  * @return
473  *   The amount of disk space used by uploaded files in bytes.
474  */
475 function upload_total_space_used() {
476   return db_result(db_query('SELECT SUM(filesize) FROM {files}'));
477 }
478
479 /**
480  * Munge the filename as needed for security purposes.
481  *
482  * @param $filename
483  *   The name of a file to modify.
484  * @param $extensions
485  *   A space separated list of valid extensions. If this is blank, we'll use
486  *   the admin-defined defaults for the user role from upload_extensions_$rid.
487  * @param $alerts
488  *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
489  * @return $filename
490  *   The potentially modified $filename.
491  */
492 function upload_munge_filename($filename, $extensions = NULL, $alerts = 1) {
493   global $user;
494
495   $original = $filename;
496
497   // Allow potentially insecure uploads for very savvy users and admin
498   if (!variable_get('allow_insecure_uploads', 0)) {
499
500     if (!isset($extensions)) {
501       $extensions = '';
502       foreach ($user->roles as $rid => $name) {
503         $extensions .= ' '. variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps'));
504       }
505
506     }
507
508     $whitelist = array_unique(explode(' ', trim($extensions)));
509
510     $filename_parts = explode('.', $filename);
511
512     $new_filename = array_shift($filename_parts); // Remove file basename.
513     $final_extension = array_pop($filename_parts); // Remove final extension.
514
515     foreach($filename_parts as $filename_part) {
516       $new_filename .= ".$filename_part";
517       if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
518         $new_filename .= '_';
519       }
520     }
521     $filename = "$new_filename.$final_extension";
522   }
523
524   if ($alerts && $original != $filename) {
525     $message = t('Your filename has been renamed to conform to site policy.');
526     drupal_set_message($message);
527   }
528
529   return $filename;
530 }
531
532 /**
533  * Undo the effect of upload_munge_filename().
534  */
535 function upload_unmunge_filename($filename) {
536   return str_replace('_.', '.', $filename);
537 }
538
539 function upload_save($node) {
540   if (!is_array($node->files)) {
541     return;
542   }
543
544   foreach ($node->files as $fid => $file) {
545     // Convert file to object for compatibility
546     $file = (object)$file;
547
548     // Remove file. Process removals first since no further processing
549     // will be required.
550     if ($file->remove) {
551       // Remove file previews...
552       if (strpos($file->fid, 'upload') !== false) {
553         file_delete($file->filepath);
554       }
555
556       // Remove managed files.
557       else {
558         db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid);
559         // Only delete a file if it isn't used by any revision
560         $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid));
561         if ($count < 1) {
562           db_query('DELETE FROM {files} WHERE fid = %d', $fid);
563           file_delete($file->filepath);
564         }
565       }
566     }
567
568     // New file upload
569     elseif (strpos($file->fid, 'upload') !== false) {
570       if ($file = file_save_upload($file, $file->filename)) {
571         $file->fid = db_next_id('{files}_fid');
572         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);
573         db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
574       }
575       unset($_SESSION['file_previews'][$fid]);
576     }
577
578     // Create a new revision, as needed
579     elseif ($node->old_vid && is_numeric($fid)) {
580       db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
581     }
582
583     // Update existing revision
584     else {
585       db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid);
586     }
587   }
588 }
589
590 function upload_delete($node) {
591   $files = array();
592   $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid);
593   while ($file = db_fetch_object($result)) {
594     $files[$file->fid] = $file;
595   }
596
597   foreach ($files as $fid => $file) {
598     // Delete all file revision information associated with the node
599     db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid);
600     file_delete($file->filepath);
601   }
602
603   // Delete all files associated with the node
604   db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
605 }
606
607 function upload_delete_revision($node) {
608   if (is_array($node->files)) {
609     foreach ($node->files as $file) {
610       // Check if the file will be used after this revision is deleted
611       $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $file->fid));
612
613       // if the file won't be used, delete it
614       if ($count < 2) {
615         db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
616         file_delete($file->filepath);
617       }
618     }
619   }
620
621   // delete the revision
622   db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
623 }
624
625 function _upload_form($node) {
626
627   $form['#theme'] = 'upload_form_new';
628
629   if (is_array($node->files) && count($node->files)) {
630     $form['files']['#theme'] = 'upload_form_current';
631     $form['files']['#tree'] = TRUE;
632     foreach ($node->files as $key => $file) {
633       $description = file_create_url((strpos($file->fid, 'upload') === false ? $file->filepath : file_create_filename($file->filename, file_create_path())));
634       $description = "<small>". check_plain($description) ."</small>";
635       $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
636
637       $form['files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize));
638       $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => $file->remove);
639       $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
640       // if the file was uploaded this page request, set value. this fixes the problem
641       // formapi has recognizing new checkboxes. see comments in _upload_prepare.
642       if ($_SESSION['file_current_upload'] == $file->fid) {
643         $form['files'][$key]['list']['#value'] = variable_get('upload_list_default',1);
644       }
645       $form['files'][$key]['filename'] = array('#type' => 'value',  '#value' => $file->filename);
646       $form['files'][$key]['filepath'] = array('#type' => 'value',  '#value' => $file->filepath);
647       $form['files'][$key]['filemime'] = array('#type' => 'value',  '#value' => $file->filemime);
648       $form['files'][$key]['filesize'] = array('#type' => 'value',  '#value' => $file->filesize);
649       $form['files'][$key]['fid'] = array('#type' => 'value',  '#value' => $file->fid);
650     }
651   }
652
653   if (user_access('upload files')) {
654     // This div is hidden when the user uploads through JS.
655     $form['new'] = array(
656       '#prefix' => '<div id="attach-hide">',
657       '#suffix' => '</div>',
658     );
659     $form['new']['upload'] = array('#type' => 'file', '#title' => t('Attach new file'), '#size' => 40);
660     $form['new']['attach'] = array('#type' => 'button', '#value' => t('Attach'), '#name'=> 'attach', '#attributes' => array('id' => 'attach'));
661     // The class triggers the js upload behaviour.
662     $form['attach'] = array('#type' => 'hidden', '#value' => url('upload/js', NULL, NULL, TRUE), '#attributes' => array('class' => 'upload'));
663   }
664
665   // Needed for JS
666   $form['current']['vid'] = array('#type' => 'hidden', '#value' => $node->vid);
667   return $form;
668 }
669
670 /**
671  * Theme the attachments list.
672  */
673 function theme_upload_form_current(&$form) {
674   $header = array(t('Delete'), t('List'), t('Description'), t('Size'));
675
676   foreach (element_children($form) as $key) {
677     $row = array();
678     $row[] = form_render($form[$key]['remove']);
679     $row[] = form_render($form[$key]['list']);
680     $row[] = form_render($form[$key]['description']);
681     $row[] = form_render($form[$key]['size']);
682     $rows[] = $row;
683   }
684   $output = theme('table', $header, $rows);
685   $output .= form_render($form);
686   return $output;
687 }
688
689 /**
690  * Theme the attachment form.
691  * Note: required to output prefix/suffix.
692  */
693 function theme_upload_form_new($form) {
694   $output = form_render($form);
695   return $output;
696 }
697
698 function upload_load($node) {
699   $files = array();
700
701   if ($node->vid) {
702     $result = db_query('SELECT * FROM {files} f INNER JOIN {file_revisions} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY f.fid', $node->vid);
703     while ($file = db_fetch_object($result)) {
704       $files[$file->fid] = $file;
705     }
706   }
707
708   return $files;
709 }
710
711 /**
712  * Check an upload, if it is an image, make sure it fits within the
713  * maximum dimensions allowed.
714  */
715 function _upload_image($file) {
716   $info = image_get_info($file->filepath);
717
718   if ($info) {
719     list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
720     if ($width && $height) {
721       $result = image_scale($file->filepath, $file->filepath, $width, $height);
722       if ($result) {
723         $file->filesize = filesize($file->filepath);
724         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)))));
725       }
726     }
727   }
728
729   return $file;
730 }
731
732 /**
733  * Menu-callback for JavaScript-based uploads.
734  */
735 function upload_js() {
736   // We only do the upload.module part of the node validation process.
737   $node = (object)$_POST['edit'];
738
739   // Load existing node files.
740   $node->files = upload_load($node);
741
742   // Handle new uploads, and merge tmp files into node-files.
743   _upload_prepare($node);
744   _upload_validate($node);
745
746   $form = _upload_form($node);
747   foreach (module_implements('form_alter') as $module) {
748     $function = $module .'_form_alter';
749     $function('upload_js', $form);
750   }
751   $form = form_builder('upload_js', $form);
752   $output = theme('status_messages') . form_render($form);
753   // We send the updated file attachments form.
754   print drupal_to_js(array('status' => TRUE, 'data' => $output));
755   exit;
756 }