upload.module 22.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
17
18
      $output = '<p>' . t('The upload module allows users to upload files to the site. The ability to upload files 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>';
      $output .= '<p>' . t('Users with the upload files permission can upload attachments to posts. Uploads may be enabled for specific content types on the content types settings page. Each user role can be customized to limit or control the file size of uploads, or the maximum dimension of image files.') . '</p>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@upload">Upload module</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
 * Implementation of hook_theme().
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 */
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(
47
48
49
50
51
52
53
54
    'upload files' => array(
      'title' => t('Upload files'),
      'description' => t('Attach images and other files to content.'),
    ),
    'view uploaded files' => array(
      'title' => t('View uploaded files'),
      'description' => t('View and download files attached to content.'),
    ),
55
  );
Dries's avatar
   
Dries committed
56
57
}

58
59
60
/**
 * Implementation of hook_link().
 */
61
function upload_link($type, $node = NULL, $teaser = FALSE) {
62
63
64
  $links = array();

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

  return $links;
}

/**
 * Implementation of hook_menu().
 */
88
89
90
91
92
93
94
function upload_menu() {
  $items['upload/js'] = array(
    'page callback' => 'upload_js',
    'access arguments' => array('upload files'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/uploads'] = array(
95
96
    'title' => 'File uploads',
    'description' => 'Control how files may be attached to content.',
97
98
99
100
101
102
103
    'page callback' => 'drupal_get_form',
    'page arguments' => array('upload_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}
Dries's avatar
   
Dries committed
104

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
 * 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);
127
  $all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
128
  foreach ($user->roles as $rid => $name) {
129
    $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    $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
146
147
}

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

167
/**
168
169
170
171
172
 * 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.
173
 */
174
function upload_node_form_submit(&$form, &$form_state) {
175
  global $user;
176

177
178
179
180
181
182
  $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']),
  );
183

184
  // Save new file uploads.
185
  if (user_access('upload files') && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
186
    $file->list = variable_get('upload_list_default', 1);
187
    $file->description = $file->filename;
188
    $file->weight = 0;
189
190
191
    $file->new = TRUE;
    $form['#node']->files[$file->fid] = $file;
    $form_state['values']['files'][$file->fid] = (array)$file;
192
193
  }

194
195
196
  if (isset($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {
      $form_state['values']['files'][$fid]['new'] = !empty($form['#node']->files[$fid]->new);
197
198
199
200
201
202
203
204
205
206
207
    }
  }

  // Order the form according to the set file weight values.
  if (!empty($form_state['values']['files'])) {
    $microweight = 0.001;
    foreach ($form_state['values']['files'] as $fid => $file) {
      if (is_numeric($fid)) {
        $form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
        $microweight += 0.001;
      }
208
    }
209
    uasort($form_state['values']['files'], 'element_sort');
210
211
212
  }
}

213
function upload_form_alter(&$form, $form_state, $form_id) {
214
215
216
217
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $form['workflow']['upload'] = array(
      '#type' => 'radios',
      '#title' => t('Attachments'),
218
      '#default_value' => variable_get('upload_' . $form['#node_type']->type, 1),
219
220
221
      '#options' => array(t('Disabled'), t('Enabled')),
    );
  }
222

223
  if (!empty($form['#node_edit_form'])) {
224
    $node = $form['#node'];
225
    if (variable_get("upload_$node->type", TRUE)) {
226
      // Attachments fieldset
227
228
      $form['attachments'] = array(
        '#type' => 'fieldset',
229
        '#access' => user_access('upload files'),
230
231
232
233
234
235
        '#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>',
236
        '#weight' => 30,
237
      );
238

239
      // Wrapper for fieldset contents (used by ahah.js).
240
241
242
243
      $form['attachments']['wrapper'] = array(
        '#prefix' => '<div id="attach-wrapper">',
        '#suffix' => '</div>',
      );
244
245
246

      // Make sure necessary directories for upload.module exist and are
      // writable before displaying the attachment form.
247
248
249
250
      $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)) {
251
252
        $form['attachments']['#description'] =  t('File attachments are disabled. The file directories have not been properly configured.');
        if (user_access('administer site configuration')) {
253
          $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')));
254
255
        }
        else {
256
          $form['attachments']['#description'] .= ' ' . t('Please contact the site administrator.');
257
258
259
260
        }
      }
      else {
        $form['attachments']['wrapper'] += _upload_form($node);
261
        $form['#attributes']['enctype'] = 'multipart/form-data';
262
      }
263
    }
264
    $form['#submit'][] = 'upload_node_form_submit';
265
266
267
  }
}

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/**
 * Implementation of hook_file_load().
 */
function upload_file_load(&$file) {
  // Add the upload specific data into the file object.
  $values = db_query('SELECT * FROM {upload} u WHERE u.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_ASSOC);
  foreach ((array)$values as $key => $value) {
    $file->{$key} = $value;
  }
}

/**
 * Implementation of hook_file_references().
 */
function upload_file_references(&$file) {
  // If upload.module is still using a file, do not let other modules delete it.
  $count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField();
  if ($count) {
    // Return the name of the module and how many references it has to the file.
    return array('upload' => $count);
  }
}

/**
 * Implementation of hook_file_delete().
 */
function upload_file_delete(&$file) {
  // Delete all information associated with the file.
  db_delete('upload')->condition('fid', $file->fid)->execute();
}


300
/**
301
 * Implementation of hook_nodeapi_load().
302
 */
303
304
305
306
307
308
function upload_nodeapi_load(&$node, $teaser) {
  if (variable_get("upload_$node->type", 1) == 1) {
    $output = array('files' => upload_load($node));
    return $output;
  }
}
309

310
311
312
313
314
315
316
317
318
319
320
321
322
/**
 * Implementation of hook_nodeapi_view().
 */
function upload_nodeapi_view(&$node, $teaser) {
  if (isset($node->files) && user_access('view uploaded files')) {
    // 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(
          '#markup' => theme('upload_attachments', $node->files),
          '#weight' => 50,
        );
323
      }
324
325
326
    }
  }
}
327

328
329
330
331
332
333
334
335
/**
 * Implementation of hook_nodeapi_insert().
 */
function upload_nodeapi_insert(&$node, $teaser) {
  if (user_access('upload files')) {
    upload_save($node);
  }
}
336

337
338
339
340
341
342
343
344
/**
 * Implementation of hook_nodeapi_update().
 */
function upload_nodeapi_update(&$node, $teaser) {
  if (user_access('upload files')) {
    upload_save($node);
  }
}
345

346
347
348
349
/**
 * Implementation of hook_nodeapi_delete().
 */
function upload_nodeapi_delete(&$node, $teaser) {
350
351
352
353
354
355
356
  db_delete('upload')->condition('nid', $node->nid)->execute();
  if (!is_array($node->files)) {
    return;
  }
  foreach($node->files as $file) {
    file_delete($file);
  }
357
}
358

359
360
361
362
/**
 * Implementation of hook_nodeapi_delete_revision().
 */
function upload_nodeapi_delete_revision(&$node, $teaser) {
363
364
365
366
367
368
369
  db_delete('upload')->condition('vid', $node->vid)->execute();
  if (!is_array($node->files)) {
    return;
  }
  foreach ($node->files as $file) {
    file_delete($file);
  }
370
}
371

372
373
374
375
376
377
/**
 * Implementation of hook_nodeapi_search_result().
 */
function upload_nodeapi_search_result(&$node, $teaser) {
  return isset($node->files) && is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
}
378

379
380
381
382
383
384
385
386
387
/**
 * Implementation of hook_nodeapi_rss_item().
 */
function upload_nodeapi_rss_item(&$node, $teaser) {
  if (is_array($node->files)) {
    $files = array();
    foreach ($node->files as $file) {
      if ($file->list) {
        $files[] = $file;
Dries's avatar
   
Dries committed
388
      }
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    }
    if (count($files) > 0) {
      // RSS only allows one enclosure per item
      $file = array_shift($files);
      return array(
        array(
          'key' => 'enclosure',
          'attributes' => array(
            'url' => file_create_url($file->filepath),
            'length' => $file->filesize,
            'type' => $file->filemime
          )
        )
      );
    }
404
  }
405
  return array();
406
}
407

408
409
/**
 * Displays file attachments in table
410
411
 *
 * @ingroup themeable
412
413
414
415
416
 */
function theme_upload_attachments($files) {
  $header = array(t('Attachment'), t('Size'));
  $rows = array();
  foreach ($files as $file) {
417
418
    $file = (object)$file;
    if ($file->list && empty($file->remove)) {
419
      $href = file_create_url($file->filepath);
420
      $text = $file->description ? $file->description : $file->filename;
421
422
423
424
425
      $rows[] = array(l($text, $href), format_size($file->filesize));
    }
  }
  if (count($rows)) {
    return theme('table', $header, $rows, array('id' => 'attachments'));
426
  }
Dries's avatar
   
Dries committed
427
428
}

429
430
431
432
433
434
/**
 * Determine how much disk space is occupied by a user's uploaded files.
 *
 * @param $uid
 *   The integer user id of a user.
 * @return
435
 *   The amount of disk space used by the user in bytes.
436
437
 */
function upload_space_used($uid) {
438
  return file_space_used($uid);
439
}
Dries's avatar
   
Dries committed
440

441
442
443
444
/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
445
 *   The amount of disk space used by uploaded files in bytes.
446
447
 */
function upload_total_space_used() {
448
  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
449
450
}

451
function upload_save(&$node) {
452
  if (empty($node->files) || !is_array($node->files)) {
453
454
455
    return;
  }

456
  foreach ($node->files as $fid => $file) {
457
    // Convert file to object for compatibility
458
459
460
461
    $file = (object)$file;

    // Remove file. Process removals first since no further processing
    // will be required.
462
    if (!empty($file->remove)) {
463
464
465
466
      // Remove the reference from this revision.
      db_delete('upload')->condition('fid', $file->fid)->condition('vid', $node->vid)->execute();
      // Try a soft delete, if the file isn't used elsewhere it'll be deleted.
      file_delete($file);
467
468
      // Remove it from the session in the case of new uploads,
      // that you want to disassociate before node submission.
469
      unset($node->files[$fid]);
470
471
      // Move on, so the removed file won't be added to new revisions.
      continue;
472
473
    }

474
    // Create a new revision, or associate a new file needed.
475
    if (!empty($node->old_vid) || $file->new) {
476
      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);
477
    }
478
    // Update existing revision.
479
    else {
480
      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);
481
    }
482
483
    $file->status &= FILE_STATUS_PERMANENT;
    $file = file_save($file);
484
  }
Dries's avatar
   
Dries committed
485
486
}

487
function _upload_form($node) {
488
  global $user;
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
489

490
491
492
493
  $form = array(
    '#theme' => 'upload_form_new',
    '#cache' => TRUE,
  );
494

495
  if (!empty($node->files) && is_array($node->files)) {
496
497
    $form['files']['#theme'] = 'upload_form_current';
    $form['files']['#tree'] = TRUE;
498
    foreach ($node->files as $file) {
499
      $file = (object)$file;
500
501
502
      $key = $file->fid;

      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => '<small>' . file_create_url($file->filepath) . '</small>');
503
      $form['files'][$key]['size'] = array('#markup' => format_size($file->filesize));
504
      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
505
      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
506
      $form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
507
508
509
510
511
      $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);
512
      $form['files'][$key]['new'] = array('#type' => 'value', '#value' => FALSE);
Dries's avatar
   
Dries committed
513
514
    }
  }
515

516
  if (user_access('upload files')) {
517
    $limits = _upload_file_limits($user);
518
519
520
521
522
523
524
525
526
527

    $limit_description = t('The maximum size of file uploads is %filesize. ', array('%filesize' => format_size($limits['file_size'])));
    if (!empty($limits['resolution'])) {
      if (image_get_toolkit()) {
        $limit_description .= t('Images larger than %resolution will be resized. ', array('%resolution' => $limits['resolution']));
      }
      else {
        $limit_description .= t('Images may not be larger than %resolution. ', array('%resolution' => $limits['resolution']));
      }
    }
528
    $limit_description .= t('Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions']));
529

530
    $form['new']['#weight'] = 10;
531
532
533
534
    $form['new']['upload'] = array(
      '#type' => 'file',
      '#title' => t('Attach new file'),
      '#size' => 40,
535
      '#description' => $limit_description,
536
    );
537
538
539
540
    $form['new']['attach'] = array(
      '#type' => 'submit',
      '#value' => t('Attach'),
      '#name' => 'attach',
541
542
543
      '#ahah' => array(
        'path' => 'upload/js',
        'wrapper' => 'attach-wrapper',
544
        'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
545
      ),
546
      '#submit' => array('node_form_submit_build_node'),
547
    );
548
  }
Dries's avatar
   
Dries committed
549

550
551
552
  return $form;
}

553
554
/**
 * Theme the attachments list.
555
556
 *
 * @ingroup themeable
557
 */
558
function theme_upload_form_current(&$form) {
559
560
  $header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
561
562

  foreach (element_children($form) as $key) {
563
564
565
566
    // Add class to group weight fields for drag and drop.
    $form[$key]['weight']['#attributes']['class'] = 'upload-weight';

    $row = array('');
567
568
569
    $row[] = drupal_render($form[$key]['remove']);
    $row[] = drupal_render($form[$key]['list']);
    $row[] = drupal_render($form[$key]['description']);
570
    $row[] = drupal_render($form[$key]['weight']);
571
    $row[] = drupal_render($form[$key]['size']);
572
    $rows[] = array('data' => $row, 'class' => 'draggable');
573
  }
574
  $output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
575
  $output .= drupal_render($form);
576
  return $output;
Dries's avatar
   
Dries committed
577
578
}

579
580
581
/**
 * Theme the attachment form.
 * Note: required to output prefix/suffix.
582
583
 *
 * @ingroup themeable
584
585
 */
function theme_upload_form_new($form) {
586
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
587
  $output = drupal_render($form);
588
589
590
  return $output;
}

Dries's avatar
   
Dries committed
591
592
593
function upload_load($node) {
  $files = array();

594
  if ($node->vid) {
595
596
597
    $result = db_query('SELECT u.fid FROM {upload} u WHERE u.vid = :vid ORDER BY u.weight, u.fid', array(':vid' => $node->vid));
    foreach ($result as $file) {
      $files[$file->fid] = file_load($file->fid);
Dries's avatar
   
Dries committed
598
599
600
601
602
603
    }
  }

  return $files;
}

604
605
606
607
/**
 * Menu-callback for JavaScript-based uploads.
 */
function upload_js() {
608
609
610
  $cached_form_state = array();
  $files = array();

611
  // Load the form from the Form API cache.
612
613
614
615
616
617
  if (!($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) || !isset($cached_form['#node']) || !isset($cached_form['attachments'])) {
    form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
    $output = theme('status_messages');
    print drupal_to_js(array('status' => TRUE, 'data' => $output));
    exit();
  }
618
619

  $form_state = array('values' => $_POST);
620

621
  // Handle new uploads, and merge tmp files into node-files.
622
623
  upload_node_form_submit($cached_form, $form_state);

624
  if (!empty($form_state['values']['files'])) {
625
    foreach ($form_state['values']['files'] as $fid => $file) {
626
627
      if (isset($cached_form['#node']->files[$fid])) {
        $files[$fid] = $cached_form['#node']->files[$fid];
628
      }
629
630
    }
  }
631
632
633
634
635

  $node = $cached_form['#node'];

  $node->files = $files;

636
  $form = _upload_form($node);
637

638
639
640
641
642
643
644
  unset($cached_form['attachments']['wrapper']['new']);
  $cached_form['attachments']['wrapper'] = array_merge($cached_form['attachments']['wrapper'], $form);

  $cached_form['attachments']['#collapsed'] = FALSE;

  form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);

645
646
  foreach ($files as $fid => $file) {
    if (is_numeric($fid)) {
647
648
649
650
      $form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description'];
      $form['files'][$fid]['list']['#default_value'] = !empty($form_state['values']['files'][$fid]['list']);
      $form['files'][$fid]['remove']['#default_value'] = !empty($form_state['values']['files'][$fid]['remove']);
      $form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight'];
651
652
653
654
    }
  }

  // Render the form for output.
655
656
657
658
659
660
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
    '#tree' => FALSE,
    '#parents' => array(),
  );
661
  drupal_alter('form', $form, array(), 'upload_js');
662
  $form_state = array('submitted' => FALSE);
663
  $form = form_builder('upload_js', $form, $form_state);
664
  $output = theme('status_messages') . drupal_render($form);
665

666
  // We send the updated file attachments form.
667
  // Don't call drupal_json(). ahah.js uses an iframe and
668
  // the header output by drupal_json() causes problems in some browsers.
669
  print drupal_to_js(array('status' => TRUE, 'data' => $output));
670
671
  exit;
}