upload.module 23.1 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
 * Inject links into $node for attachments.
60
 */
61
function upload_nodeapi_links($node, $teaser) {
62 63 64
  $links = array();

  // Display a link with the number of attachments
65
  if ($teaser && 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
      $node->content['links']['upload_attachments'] = array(
        '#type' => 'node_links',
        '#value' => $links,
      );
83 84 85 86 87 88 89
    }
  }
}

/**
 * Implementation of hook_menu().
 */
90 91 92 93 94 95 96
function upload_menu() {
  $items['upload/js'] = array(
    'page callback' => 'upload_js',
    'access arguments' => array('upload files'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/uploads'] = array(
97 98
    'title' => 'File uploads',
    'description' => 'Control how files may be attached to content.',
99 100 101 102 103 104 105
    '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
106

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

150 151 152
/**
 * Implementation of hook_file_download().
 */
153 154
function upload_file_download($filepath) {
  $filepath = file_create_path($filepath);
155 156 157 158 159 160 161 162 163 164
  $file = db_query("SELECT f.*, u.nid FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = :path", array(':path' => $filepath))->fetchObject();

  if ($file && 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 {
    return -1;
165
  }
Dries's avatar
 
Dries committed
166 167
}

168
/**
169 170 171 172 173
 * 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.
174
 */
175
function upload_node_form_submit(&$form, &$form_state) {
176
  global $user;
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_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 192
    $file->new = TRUE;
    $form['#node']->files[$file->fid] = $file;
    $form_state['values']['files'][$file->fid] = (array)$file;
193 194
  }

195 196 197
  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);
198 199 200 201 202 203 204 205 206 207 208
    }
  }

  // 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;
      }
209
    }
210
    uasort($form_state['values']['files'], 'element_sort');
211 212 213
  }
}

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

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

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

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


301
/**
302
 * Implementation of hook_nodeapi_load().
303
 */
304 305 306 307 308
function upload_nodeapi_load($nodes, $types) {
  foreach ($nodes as $node) {
    if (variable_get("upload_$node->type", 1) == 1) {
      $node->files = upload_load($node);
    }
309 310
  }
}
311

312 313 314
/**
 * Implementation of hook_nodeapi_view().
 */
315
function upload_nodeapi_view($node, $teaser, $page) {
316 317 318 319 320 321 322 323 324
  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,
        );
325
      }
326
    }
327 328
    
    upload_nodeapi_links($node, $teaser);
329 330
  }
}
331

332 333 334
/**
 * Implementation of hook_nodeapi_insert().
 */
335
function upload_nodeapi_insert($node) {
336 337 338 339
  if (user_access('upload files')) {
    upload_save($node);
  }
}
340

341 342 343
/**
 * Implementation of hook_nodeapi_update().
 */
344
function upload_nodeapi_update($node) {
345 346 347 348
  if (user_access('upload files')) {
    upload_save($node);
  }
}
349

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

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

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

383 384 385
/**
 * Implementation of hook_nodeapi_rss_item().
 */
386
function upload_nodeapi_rss_item($node) {
387 388 389 390 391
  if (is_array($node->files)) {
    $files = array();
    foreach ($node->files as $file) {
      if ($file->list) {
        $files[] = $file;
Dries's avatar
 
Dries committed
392
      }
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
    }
    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
          )
        )
      );
    }
408
  }
409
  return array();
410
}
411

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

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

445 446 447 448
/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
449
 *   The amount of disk space used by uploaded files in bytes.
450 451
 */
function upload_total_space_used() {
452
  return db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid')->fetchField();
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
453 454
}

455
function upload_save(&$node) {
456
  if (empty($node->files) || !is_array($node->files)) {
457 458 459
    return;
  }

460
  foreach ($node->files as $fid => $file) {
461
    // Convert file to object for compatibility
462 463 464 465
    $file = (object)$file;

    // Remove file. Process removals first since no further processing
    // will be required.
466
    if (!empty($file->remove)) {
467 468 469 470
      // 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);
471 472
      // Remove it from the session in the case of new uploads,
      // that you want to disassociate before node submission.
473
      unset($node->files[$fid]);
474 475
      // Move on, so the removed file won't be added to new revisions.
      continue;
476 477
    }

478
    // Create a new revision, or associate a new file needed.
479
    if (!empty($node->old_vid) || $file->new) {
480 481 482 483 484 485 486 487 488 489
      db_insert('upload')
        ->fields(array(
          'fid' => $file->fid,
          'nid' => $node->nid,
          'vid' => $node->vid,
          'list' => $file->list,
          'description' => $file->description,
          'weight' => $file->weight,
        ))
        ->execute();
490
    }
491
    // Update existing revision.
492
    else {
493 494 495 496 497 498 499 500 501
      db_update('upload')
        ->fields(array(
          'list' => $file->list,
          'description' => $file->description,
          'weight' => $file->weight,
        ))
        ->condition('fid', $file->fid, '=')
        ->condition('vid', $node->vid, '=')
        ->execute();
502
    }
503 504
    $file->status &= FILE_STATUS_PERMANENT;
    $file = file_save($file);
505
  }
Dries's avatar
 
Dries committed
506 507
}

508
function _upload_form($node) {
509
  global $user;
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
510

511 512 513 514
  $form = array(
    '#theme' => 'upload_form_new',
    '#cache' => TRUE,
  );
515

516
  if (!empty($node->files) && is_array($node->files)) {
517 518
    $form['files']['#theme'] = 'upload_form_current';
    $form['files']['#tree'] = TRUE;
519
    foreach ($node->files as $file) {
520
      $file = (object)$file;
521 522 523
      $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>');
524
      $form['files'][$key]['size'] = array('#markup' => format_size($file->filesize));
525
      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
526
      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
527
      $form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
528 529 530 531 532
      $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);
533
      $form['files'][$key]['new'] = array('#type' => 'value', '#value' => FALSE);
Dries's avatar
 
Dries committed
534 535
    }
  }
536

537
  if (user_access('upload files')) {
538
    $limits = _upload_file_limits($user);
539 540 541 542 543 544 545 546 547 548

    $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']));
      }
    }
549
    $limit_description .= t('Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions']));
550

551
    $form['new']['#weight'] = 10;
552 553 554 555
    $form['new']['upload'] = array(
      '#type' => 'file',
      '#title' => t('Attach new file'),
      '#size' => 40,
556
      '#description' => $limit_description,
557
    );
558 559 560 561
    $form['new']['attach'] = array(
      '#type' => 'submit',
      '#value' => t('Attach'),
      '#name' => 'attach',
562 563 564
      '#ahah' => array(
        'path' => 'upload/js',
        'wrapper' => 'attach-wrapper',
565
        'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
566
      ),
567
      '#submit' => array('node_form_submit_build_node'),
568
    );
569
  }
Dries's avatar
 
Dries committed
570

571 572 573
  return $form;
}

574 575
/**
 * Theme the attachments list.
576 577
 *
 * @ingroup themeable
578
 */
579
function theme_upload_form_current(&$form) {
580 581
  $header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
582 583

  foreach (element_children($form) as $key) {
584 585 586 587
    // Add class to group weight fields for drag and drop.
    $form[$key]['weight']['#attributes']['class'] = 'upload-weight';

    $row = array('');
588 589 590
    $row[] = drupal_render($form[$key]['remove']);
    $row[] = drupal_render($form[$key]['list']);
    $row[] = drupal_render($form[$key]['description']);
591
    $row[] = drupal_render($form[$key]['weight']);
592
    $row[] = drupal_render($form[$key]['size']);
593
    $rows[] = array('data' => $row, 'class' => 'draggable');
594
  }
595
  $output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
596
  $output .= drupal_render($form);
597
  return $output;
Dries's avatar
 
Dries committed
598 599
}

600 601 602
/**
 * Theme the attachment form.
 * Note: required to output prefix/suffix.
603 604
 *
 * @ingroup themeable
605 606
 */
function theme_upload_form_new($form) {
607
  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
608
  $output = drupal_render($form);
609 610 611
  return $output;
}

Dries's avatar
 
Dries committed
612 613 614
function upload_load($node) {
  $files = array();

615
  if ($node->vid) {
616 617 618
    $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
619 620 621 622 623 624
    }
  }

  return $files;
}

625 626 627 628
/**
 * Menu-callback for JavaScript-based uploads.
 */
function upload_js() {
629 630 631
  $cached_form_state = array();
  $files = array();

632
  // Load the form from the Form API cache.
633 634 635 636 637 638
  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();
  }
639 640

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

642
  // Handle new uploads, and merge tmp files into node-files.
643 644
  upload_node_form_submit($cached_form, $form_state);

645
  if (!empty($form_state['values']['files'])) {
646
    foreach ($form_state['values']['files'] as $fid => $file) {
647 648
      if (isset($cached_form['#node']->files[$fid])) {
        $files[$fid] = $cached_form['#node']->files[$fid];
649
      }
650 651
    }
  }
652 653 654 655 656

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

  $node->files = $files;

657
  $form = _upload_form($node);
658

659 660 661 662 663 664 665
  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);

666 667
  foreach ($files as $fid => $file) {
    if (is_numeric($fid)) {
668 669 670 671
      $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'];
672 673 674 675
    }
  }

  // Render the form for output.
676 677 678 679 680 681
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
    '#tree' => FALSE,
    '#parents' => array(),
  );
682
  drupal_alter('form', $form, array(), 'upload_js');
683
  $form_state = array('submitted' => FALSE);
684
  $form = form_builder('upload_js', $form, $form_state);
685
  $output = theme('status_messages') . drupal_render($form);
686

687
  // We send the updated file attachments form.
688
  // Don't call drupal_json(). ahah.js uses an iframe and
689
  // the header output by drupal_json() causes problems in some browsers.
690
  print drupal_to_js(array('status' => TRUE, 'data' => $output));
691 692
  exit;
}