upload.module 21.8 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
Dries's avatar
Dries committed
2
// $Id$
Dries's avatar
   
Dries committed
3

Dries's avatar
   
Dries committed
4
5
6
/**
 * @file
 * File-handling and attaching files to nodes.
7
 *
Dries's avatar
   
Dries committed
8
9
 */

10
11
12
/**
 * Implementation of hook_help().
 */
13
14
function upload_help($path, $arg) {
  switch ($path) {
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 posts.') .'</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 .= '<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>';
19
      return $output;
20
    case 'admin/settings/upload':
21
      return '<p>'. t('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.', array('@permissions' => url('admin/user/permissions'), '@types' => url('admin/settings/types'))) .'</p>';
Dries's avatar
   
Dries committed
22
23
24
  }
}

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * Implementation of hook_theme()
 */
function upload_theme() {
  return array(
    'upload_attachments' => array(
      'arguments' => array('files' => NULL),
    ),
    'upload_form_current' => array(
      'arguments' => array('form' => NULL),
    ),
    'upload_form_new' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

42
43
44
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
45
function upload_perm() {
46
  return array('upload files', 'view uploaded files');
Dries's avatar
   
Dries committed
47
48
}

49
50
51
/**
 * Implementation of hook_link().
 */
52
function upload_link($type, $node = NULL, $teaser = FALSE) {
53
54
55
  $links = array();

  // Display a link with the number of attachments
56
  if ($teaser && $type == 'node' && isset($node->files) && user_access('view uploaded files')) {
57
58
59
60
61
62
63
    $num_files = 0;
    foreach ($node->files as $file) {
      if ($file->list) {
        $num_files++;
      }
    }
    if ($num_files) {
64
      $links['upload_attachments'] = array(
65
        'title' => format_plural($num_files, '1 attachment', '@count attachments'),
66
67
68
        'href' => "node/$node->nid",
        'attributes' => array('title' => t('Read full article to view attachments.')),
        'fragment' => 'attachments'
69
      );
70
71
72
73
74
75
76
77
78
    }
  }

  return $links;
}

/**
 * Implementation of hook_menu().
 */
79
80
81
82
83
84
85
function upload_menu() {
  $items['upload/js'] = array(
    'page callback' => 'upload_js',
    'access arguments' => array('upload files'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/uploads'] = array(
86
87
    'title' => 'File uploads',
    'description' => 'Control how files may be attached to content.',
88
89
90
91
    'page callback' => 'drupal_get_form',
    'page arguments' => array('upload_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
92
    'file' => 'upload.admin.inc',
93
94
95
  );
  return $items;
}
Dries's avatar
   
Dries committed
96

97
98
99
function upload_menu_alter(&$items) {
  $items['system/files']['access arguments'] = array('view uploaded files');
}
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
 * Determine the limitations on files that a given user may upload. The user
 * may be in multiple roles so we select the most permissive limitations from
 * all of their roles.
 *
 * @param $user
 *   A Drupal user object.
 * @return
 *   An associative array with the following keys:
 *     'extensions'
 *       A white space separated string containing all the file extensions this
 *       user may upload.
 *     'file_size'
 *       The maximum size of a file upload in bytes.
 *     'user_size'
 *       The total number of bytes for all for a user's files.
 *     'resolution'
 *       A string specifying the maximum resolution of images.
 */
function _upload_file_limits($user) {
  $file_limit = variable_get('upload_uploadsize_default', 1);
  $user_limit = variable_get('upload_usersize_default', 1);
123
  $all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
124
  foreach ($user->roles as $rid => $name) {
125
    $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
    $all_extensions = array_merge($all_extensions, explode(' ', $extensions));

    // A zero value indicates no limit, take the least restrictive limit.
    $file_size = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
    $file_limit = ($file_limit && $file_size) ? max($file_limit, $file_size) : 0;

    $user_size = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
    $user_limit = ($user_limit && $user_size) ? max($user_limit, $user_size) : 0;
  }
  $all_extensions = implode(' ', array_unique($all_extensions));
  return array(
    'extensions' => $all_extensions,
    'file_size' => $file_limit,
    'user_size' => $user_limit,
    'resolution' => variable_get('upload_max_resolution', 0),
  );
Dries's avatar
   
Dries committed
142
143
}

144
145
146
/**
 * Implementation of hook_file_download().
 */
Dries's avatar
   
Dries committed
147
function upload_file_download($file) {
148
149
150
  if (!user_access('view uploaded files')) {
    return -1;
  }
151
  $file = file_create_path($file);
152
  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $file);
153
  if ($file = db_fetch_object($result)) {
154
155
156
157
    return array(
      'Content-Type: '. $file->filemime,
      'Content-Length: '. $file->filesize,
    );
158
  }
Dries's avatar
   
Dries committed
159
160
}

161
/**
162
163
164
165
166
 * Save new uploads and store them in the session to be associated to the node
 * on upload_save.
 *
 * @param $node
 *   A node object to associate with uploaded files.
167
 */
168
function upload_node_form_submit($form, &$form_state) {
169
  global $user;
170

171
  // $_SESSION['upload_current_file'] tracks the fid of the file submitted this page request.
172
173
174
175
  // 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.
176
  unset($_SESSION['upload_current_file']);
177

178
179
180
181
182
183
  $limits = _upload_file_limits($user);
  $validators = array(
    'file_validate_extensions' => array($limits['extensions']),
    'file_validate_image_resolution' => array($limits['resolution']),
    'file_validate_size' => array($limits['file_size'], $limits['user_size']),
  );
184

185
  // Save new file uploads.
186
  if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
187
    $file->list = variable_get('upload_list_default', 1);
188
    $file->description = $file->filename;
189
    $file->weight = 0;
190
191
    $_SESSION['upload_current_file'] = $file->fid;
    $_SESSION['upload_files'][$file->fid] = $file;
192
193
  }

194
  // attach session files to node.
195
  if (!empty($_SESSION['upload_files'])) {
196
    foreach ($_SESSION['upload_files'] as $fid => $file) {
197
      $form_state['values']['files'][$fid] = $file;
198
199
200
201
    }
  }
}

202
function upload_form_alter(&$form, $form_state, $form_id) {
203
204
205
206
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $form['workflow']['upload'] = array(
      '#type' => 'radios',
      '#title' => t('Attachments'),
207
      '#default_value' => variable_get('upload_'. $form['#node_type']->type, 1),
208
209
210
      '#options' => array(t('Disabled'), t('Enabled')),
    );
  }
211

212
  if (isset($form['type']) && isset($form['#node'])) {
213
    $node = $form['#node'];
214
    if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
215
      // Attachments fieldset
216
217
      $form['attachments'] = array(
        '#type' => 'fieldset',
218
        '#access' => user_access('upload files'),
219
220
221
222
223
224
        '#title' => t('File attachments'),
        '#collapsible' => TRUE,
        '#collapsed' => empty($node->files),
        '#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.'),
        '#prefix' => '<div class="attachments">',
        '#suffix' => '</div>',
225
        '#weight' => 30,
226
      );
227

228
      // Wrapper for fieldset contents (used by ahah.js).
229
230
231
232
      $form['attachments']['wrapper'] = array(
        '#prefix' => '<div id="attach-wrapper">',
        '#suffix' => '</div>',
      );
233
234
235

      // Make sure necessary directories for upload.module exist and are
      // writable before displaying the attachment form.
236
237
238
239
      $path = file_directory_path();
      $temp = file_directory_temp();
      // Note: pass by reference
      if (!file_check_directory($path, FILE_CREATE_DIRECTORY) || !file_check_directory($temp, FILE_CREATE_DIRECTORY)) {
240
241
242
243
244
245
246
247
248
249
250
251
        $form['attachments']['#description'] =  t('File attachments are disabled. The file directories have not been properly configured.');
        if (user_access('administer site configuration')) {
          $form['attachments']['#description'] .= ' '. t('Please visit the <a href="@admin-file-system">file system configuration page</a>.', array('@admin-file-system' => url('admin/settings/file-system')));
        }
        else {
          $form['attachments']['#description'] .= ' '. t('Please contact the site administrator.');
        }
      }
      else {
        $form['attachments']['wrapper'] += _upload_form($node);
        $form['#attributes']['enctype'] = 'multipart/form-data';
      }
252
    }
253
    $form['#submit'][] = 'upload_node_form_submit';
254
255
256
  }
}

257
258
259
/**
 * Implementation of hook_nodeapi().
 */
260
function upload_nodeapi(&$node, $op, $teaser) {
261
  switch ($op) {
262

Dries's avatar
   
Dries committed
263
    case 'load':
drumm's avatar
drumm committed
264
      $output = '';
265
      if (variable_get("upload_$node->type", 1) == 1) {
Steven Wittens's avatar
Steven Wittens committed
266
        $output['files'] = upload_load($node);
267
        return $output;
Dries's avatar
   
Dries committed
268
      }
269
270
      break;

Dries's avatar
   
Dries committed
271
    case 'view':
272
      if (isset($node->files) && user_access('view uploaded files')) {
273
274
275
276
277
278
279
280
        // Add the attachments list to node body with a heavy
        // weight to ensure they're below other elements
        if (count($node->files)) {
          if (!$teaser && user_access('view uploaded files')) {
            $node->content['files'] = array(
              '#value' => theme('upload_attachments', $node->files),
              '#weight' => 50,
            );
Dries's avatar
   
Dries committed
281
          }
282
283
284
285
        }
      }
      break;

286
287
288
289
290
291
292
293
294
295
    case 'prepare':
      // Initialize $_SESSION['upload_files'] if no post occurred.
      // This clears the variable from old forms and makes sure it
      // is an array to prevent notices and errors in other parts
      // of upload.module.
      if (!$_POST) {
        $_SESSION['upload_files'] = array();
      }
      break;

296
297
298
299
300
301
302
303
304
305
306
    case 'insert':
    case 'update':
      if (user_access('upload files')) {
        upload_save($node);
      }
      break;

    case 'delete':
      upload_delete($node);
      break;

307
308
309
    case 'delete revision':
      upload_delete_revision($node);
      break;
310

Dries's avatar
Dries committed
311
    case 'search result':
312
      return is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
313

Dries's avatar
   
Dries committed
314
    case 'rss item':
315
      if (is_array($node->files)) {
Dries's avatar
   
Dries committed
316
317
318
319
320
321
322
323
324
        $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);
drumm's avatar
drumm committed
325
326
327
328
329
330
331
332
333
334
          return array(
            array(
              'key' => 'enclosure',
              'attributes' => array(
                'url' => file_create_url($file->filepath),
                'length' => $file->filesize,
                'type' => $file->filemime
              )
            )
          );
Dries's avatar
   
Dries committed
335
336
        }
      }
337
      return array();
338
339
  }
}
Dries's avatar
   
Dries committed
340

341
342
343
344
345
346
347
/**
 * Displays file attachments in table
 */
function theme_upload_attachments($files) {
  $header = array(t('Attachment'), t('Size'));
  $rows = array();
  foreach ($files as $file) {
348
349
    $file = (object)$file;
    if ($file->list && empty($file->remove)) {
350
      $href = file_create_url($file->filepath);
351
      $text = $file->description ? $file->description : $file->filename;
352
353
354
355
356
      $rows[] = array(l($text, $href), format_size($file->filesize));
    }
  }
  if (count($rows)) {
    return theme('table', $header, $rows, array('id' => 'attachments'));
357
  }
Dries's avatar
   
Dries committed
358
359
}

360
361
362
363
364
365
/**
 * Determine how much disk space is occupied by a user's uploaded files.
 *
 * @param $uid
 *   The integer user id of a user.
 * @return
366
 *   The amount of disk space used by the user in bytes.
367
368
 */
function upload_space_used($uid) {
369
  return file_space_used($uid);
370
}
Dries's avatar
   
Dries committed
371

372
373
374
375
/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
376
 *   The amount of disk space used by uploaded files in bytes.
377
378
 */
function upload_total_space_used() {
379
  return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid'));
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
380
381
}

382
function upload_save(&$node) {
383
  if (empty($node->files) || !is_array($node->files)) {
384
385
386
    return;
  }

387
  foreach ($node->files as $fid => $file) {
388
    // Convert file to object for compatibility
389
390
391
392
    $file = (object)$file;

    // Remove file. Process removals first since no further processing
    // will be required.
393
    if (!empty($file->remove)) {
394
      db_query('DELETE FROM {upload} WHERE fid = %d AND vid = %d', $fid, $node->vid);
395
396
397
398
399
400
401
402

      // 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);
      }

403
404
405
406
407
      // Remove it from the session in the case of new uploads,
      // that you want to disassociate before node submission.
      unset($_SESSION['upload_files'][$fid]);
      // Move on, so the removed file won't be added to new revisions.
      continue;
408
409
    }

410
    // Create a new revision, or associate a new file needed.
411
    if (!empty($node->old_vid) || isset($_SESSION['upload_files'][$fid])) {
412
      db_query("INSERT INTO {upload} (fid, nid, vid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $file->fid, $node->nid, $node->vid, $file->list, $file->description, $file->weight);
413
      file_set_status($file, FILE_STATUS_PERMANENT);
414
    }
415
    // Update existing revision.
416
    else {
417
      db_query("UPDATE {upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->weight, $file->fid, $node->vid);
418
      file_set_status($file, FILE_STATUS_PERMANENT);
419
420
    }
  }
421
422
423
  // Empty the session storage after save. We use this variable to track files
  // that haven't been related to the node yet.
  unset($_SESSION['upload_files']);
Dries's avatar
   
Dries committed
424
425
}

426
function upload_delete($node) {
427
  $files = array();
428
  $result = db_query('SELECT DISTINCT f.* FROM {upload} u INNER JOIN {files} f ON u.fid = f.fid WHERE u.nid = %d', $node->nid);
429
430
431
432
433
  while ($file = db_fetch_object($result)) {
    $files[$file->fid] = $file;
  }

  foreach ($files as $fid => $file) {
434
435
436
    // Delete all files associated with the node
    db_query('DELETE FROM {files} WHERE fid = %d', $fid);
    file_delete($file->filepath);
Dries's avatar
   
Dries committed
437
  }
438

439
440
  // Delete all file revision information associated with the node
  db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid);
441
442
443
}

function upload_delete_revision($node) {
444
445
446
  if (is_array($node->files)) {
    foreach ($node->files as $file) {
      // Check if the file will be used after this revision is deleted
447
      $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid));
448
449
450

      // if the file won't be used, delete it
      if ($count < 2) {
451
452
        db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
        file_delete($file->filepath);
453
      }
454
455
456
    }
  }

457
458
  // delete the revision
  db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid);
Dries's avatar
   
Dries committed
459
460
}

461
function _upload_form($node) {
462
  global $user;
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
463

464
  $form['#theme'] = 'upload_form_new';
465

466
  if (!empty($node->files) && is_array($node->files)) {
467
468
    $form['files']['#theme'] = 'upload_form_current';
    $form['files']['#tree'] = TRUE;
Dries's avatar
   
Dries committed
469
    foreach ($node->files as $key => $file) {
470
      $file = (object)$file;
471
      $description = file_create_url($file->filepath);
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
472
      $description = "<small>". check_plain($description) ."</small>";
473
      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
474
      $form['files'][$key]['size'] = array('#value' => format_size($file->filesize));
475
      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
476
      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
477
478
479
480
      // If the file was uploaded this page request, set value. this fixes the
      // problem formapi has recognizing new checkboxes. see comments in
      // _upload_prepare.
      if (isset($_SESSION['upload_current_file']) && $_SESSION['upload_current_file'] == $file->fid) {
481
        $form['files'][$key]['list']['#value'] = variable_get('upload_list_default', 1);
482
      }
483
      $form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
484
485
486
487
488
      $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);
Dries's avatar
   
Dries committed
489
490
    }
  }
491

492
  if (user_access('upload files')) {
493
494
495
496
497
498
499
    $limits = _upload_file_limits($user);
    $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']))),
    );
500
501
502
503
    $form['new']['attach'] = array(
      '#type' => 'submit',
      '#value' => t('Attach'),
      '#name' => 'attach',
504
505
506
      '#ahah' => array(
        'path' => 'upload/js',
        'wrapper' => 'attach-wrapper',
507
        'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
508
      ),
509
      '#submit' => array('node_form_submit_build_node'),
510
    );
511
  }
Dries's avatar
   
Dries committed
512

513
  // This value is used in upload_js().
514
  $form['current']['vid'] = array('#type' => 'hidden', '#value' => isset($node->vid) ? $node->vid : 0);
515
516
517
  return $form;
}

518
519
520
/**
 * Theme the attachments list.
 */
521
function theme_upload_form_current(&$form) {
522
523
  $header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
524
525

  foreach (element_children($form) as $key) {
526
527
528
529
    // Add class to group weight fields for drag and drop.
    $form[$key]['weight']['#attributes']['class'] = 'upload-weight';

    $row = array('');
530
531
532
    $row[] = drupal_render($form[$key]['remove']);
    $row[] = drupal_render($form[$key]['list']);
    $row[] = drupal_render($form[$key]['description']);
533
    $row[] = drupal_render($form[$key]['weight']);
534
    $row[] = drupal_render($form[$key]['size']);
535
    $rows[] = array('data' => $row, 'class' => 'draggable');
536
  }
537
  $output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
538
  $output .= drupal_render($form);
539
  return $output;
Dries's avatar
   
Dries committed
540
541
}

542
543
544
545
546
/**
 * Theme the attachment form.
 * Note: required to output prefix/suffix.
 */
function theme_upload_form_new($form) {
547
  $output = drupal_render($form);
548
549
550
  return $output;
}

Dries's avatar
   
Dries committed
551
552
553
function upload_load($node) {
  $files = array();

554
  if ($node->vid) {
555
    $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);
Dries's avatar
   
Dries committed
556
557
558
559
560
561
562
563
    while ($file = db_fetch_object($result)) {
      $files[$file->fid] = $file;
    }
  }

  return $files;
}

564
565
566
567
568
/**
 * Menu-callback for JavaScript-based uploads.
 */
function upload_js() {
  // We only do the upload.module part of the node validation process.
569
  $node = (object)$_POST;
570
  $form_state = array();
571

572
573
  // Handle new uploads, and merge tmp files into node-files.
  upload_node_form_submit(array(), $form_state);
574
575
576
577
578
579
  $node->files = upload_load($node);
  if (!empty($form_state['values']['files'])) {
    foreach($form_state['values']['files'] as $fid => $file) {
      $node->files[$fid] = $file;
    }
  }
580

581
  $form = _upload_form($node);
582
583
584
585
586
587
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
    '#tree' => FALSE,
    '#parents' => array(),
  );
588
  drupal_alter('form', $form, array(), 'upload_js');
589
  $form_state = array('submitted' => FALSE);
590
  $form = form_builder('upload_js', $form, $form_state);
591
592

  // Maintain the list and delete checkboxes values.
593
  $files = isset($_POST['files']) ? $_POST['files'] : array();
594
  foreach ($files as $fid => $file) {
595
596
597
598
    if (is_numeric($fid)) {
      $form['files'][$fid]['list']['#value'] = isset($file['list']) ? 1 : 0;
      $form['files'][$fid]['remove']['#value'] = isset($file['remove']) ? 1 : 0;
    }
599
600
  }

601
  $output = theme('status_messages') . drupal_render($form);
602

603
  // We send the updated file attachments form.
604
  // Don't call drupal_json(). ahah.js uses an iframe and
605
  // the header output by drupal_json() causes problems in some browsers.
606
  print drupal_to_js(array('status' => TRUE, 'data' => $output));
607
608
  exit;
}