upload.module 33.5 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().
 */
Dries's avatar
   
Dries committed
13
14
function upload_help($section) {
  switch ($section) {
15
    case 'admin/help#upload':
16
17
      $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>';
      $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/access'), '@types' => url('admin/settings/types'))) .'</p>';
Dries's avatar
   
Dries committed
22
23
24
  }
}

25
26
27
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
28
function upload_perm() {
29
  return array('upload files', 'view uploaded files');
Dries's avatar
   
Dries committed
30
31
}

32
33
34
/**
 * Implementation of hook_link().
 */
35
function upload_link($type, $node = NULL, $teaser = FALSE) {
36
37
38
  $links = array();

  // Display a link with the number of attachments
39
  if ($teaser && $type == 'node' && isset($node->files) && user_access('view uploaded files')) {
40
41
42
43
44
45
46
    $num_files = 0;
    foreach ($node->files as $file) {
      if ($file->list) {
        $num_files++;
      }
    }
    if ($num_files) {
47
      $links['upload_attachments'] = array(
48
        'title' => format_plural($num_files, '1 attachment', '@count attachments'),
49
50
51
        'href' => "node/$node->nid",
        'attributes' => array('title' => t('Read full article to view attachments.')),
        'fragment' => 'attachments'
52
      );
53
54
55
56
57
58
59
60
61
    }
  }

  return $links;
}

/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
62
63
64
65
function upload_menu($may_cache) {
  $items = array();

  if ($may_cache) {
66
67
68
69
70
71
    $items[] = array(
      'path' => 'upload/js',
      'callback' => 'upload_js',
      'access' => user_access('upload files'),
      'type' => MENU_CALLBACK
    );
Dries's avatar
Dries committed
72
    $items[] = array('path' => 'admin/settings/uploads',
73
      'title' => t('File uploads'),
74
      'description' => t('Control how files may be attached to content.'),
75
76
      'callback' => 'drupal_get_form',
      'callback arguments' => array('upload_admin_settings'),
Dries's avatar
Dries committed
77
78
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM);
79
80
  }
  else {
Dries's avatar
   
Dries committed
81
    // Add handlers for previewing new uploads.
82
    if (isset($_SESSION['file_previews'])) {
83
      foreach ($_SESSION['file_previews'] as $fid => $file) {
Dries's avatar
   
Dries committed
84
        $filename = file_create_filename($file->filename, file_create_path());
85
        if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==  FILE_DOWNLOADS_PRIVATE) {
Dries's avatar
Dries committed
86
          // strip file_directory_path() from filename. @see file_create_url
87
          if (strpos($filename, file_directory_path()) !== FALSE) {
drumm's avatar
drumm committed
88
89
90
            $filename = trim(substr($filename, strlen(file_directory_path())), '\\/');
          }
          $filename = 'system/files/' . $filename;
91
92
        }

Dries's avatar
   
Dries committed
93
        $items[] = array(
94
          'path' => $filename, 'title' => t('File download'),
Dries's avatar
   
Dries committed
95
          'callback' => 'upload_download',
96
          'access' => user_access('view uploaded files'),
97
          'type' => MENU_CALLBACK
Dries's avatar
   
Dries committed
98
        );
99
        $_SESSION['file_previews'][$fid]->_filename = $filename;
Dries's avatar
   
Dries committed
100
      }
Dries's avatar
   
Dries committed
101
102
    }
  }
Dries's avatar
   
Dries committed
103

Dries's avatar
   
Dries committed
104
105
106
  return $items;
}

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * Form API callback to validate the upload settings form.
 */
function upload_admin_settings_validate($form_id, $form_values) {
  if (($form_values['upload_max_resolution'] != '0')) {
    if (!preg_match('/^[0-9]+x[0-9]+$/', $form_values['upload_max_resolution'])) {
      form_set_error('upload_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
    }
  }

  $default_uploadsize = $form_values['upload_uploadsize_default'];
  $default_usersize = $form_values['upload_usersize_default'];

  $exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size MB.', array('%size' => file_upload_max_size())).'<br/>';
  $more_info = t("Depending on your sever environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory.");

  if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) {
    form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
  }
  if (!is_numeric($default_usersize) || ($default_usersize <= 0)) {
    form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
  }
  if ($default_uploadsize > file_upload_max_size()) {
   form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info);
   $more_info = '';
  }
  if ($default_uploadsize > $default_usersize) {
   form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default'))));
  }

  foreach ($form_values['roles'] as $rid => $role) {
    $uploadsize = $form_values['upload_uploadsize_'. $rid];
    $usersize = $form_values['upload_usersize_'. $rid];

    if (!is_numeric($uploadsize) || ($uploadsize <= 0)) {
142
      form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
143
144
    }
    if (!is_numeric($usersize) || ($usersize <= 0)) {
145
      form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
146
147
148
149
150
151
    }
    if ($uploadsize > file_upload_max_size()) {
     form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info);
     $more_info = '';
    }
    if ($uploadsize > $usersize) {
152
     form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role)));
153
154
155
156
157
158
159
    }
  }
}

/**
 * Menu callback for the upload settings form.
 */
Dries's avatar
Dries committed
160
function upload_admin_settings() {
161
162
  $upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps');
  $upload_uploadsize_default = variable_get('upload_uploadsize_default', 1);
163
  $upload_usersize_default = variable_get('upload_usersize_default', 1);
164
165

  $form['settings_general'] = array(
166
167
    '#type' => 'fieldset',
    '#title' => t('General settings'),
168
169
    '#collapsible' => TRUE,
  );
170
  $form['settings_general']['upload_max_resolution'] = array(
171
172
    '#type' => 'textfield',
    '#title' => t('Maximum resolution for uploaded images'),
173
    '#default_value' => variable_get('upload_max_resolution', 0),
174
175
    '#size' => 15,
    '#maxlength' => 10,
176
    '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction.'),
177
    '#field_suffix' => '<kbd>'. t('WIDTHxHEIGHT') .'</kbd>'
178
  );
179
  $form['settings_general']['upload_list_default'] = array(
180
    '#type' => 'select',
181
182
183
    '#title' => t('List files by default'),
    '#default_value' => variable_get('upload_list_default', 1),
    '#options' => array(0 => t('No'), 1 => t('Yes')),
184
185
    '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'),
  );
186

187
  $form['settings_general']['upload_extensions_default'] = array(
188
189
    '#type' => 'textfield',
    '#title' => t('Default permitted file extensions'),
190
    '#default_value' => $upload_extensions_default,
191
    '#maxlength' => 255,
192
193
194
    '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
  );
  $form['settings_general']['upload_uploadsize_default'] = array(
195
196
    '#type' => 'textfield',
    '#title' => t('Default maximum file size per upload'),
197
    '#default_value' => $upload_uploadsize_default,
198
199
    '#size' => 5,
    '#maxlength' => 5,
200
201
    '#description' => t('The default maximum file size a user can upload.'),
    '#field_suffix' => t('MB')
202
203
  );
  $form['settings_general']['upload_usersize_default'] = array(
204
205
    '#type' => 'textfield',
    '#title' => t('Default total file size per user'),
206
    '#default_value' => $upload_usersize_default,
207
208
    '#size' => 5,
    '#maxlength' => 5,
209
210
    '#description' => t('The default maximum size of all files a user can have on the site.'),
    '#field_suffix' => t('MB')
211
212
  );

213
214
  $form['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size MB.', array('%size' => file_upload_max_size())).'</p>');

Dries's avatar
   
Dries committed
215
  $roles = user_roles(0, 'upload files');
216
  $form['roles'] = array('#type' => 'value', '#value' => $roles);
Dries's avatar
   
Dries committed
217
218

  foreach ($roles as $rid => $role) {
219
    $form['settings_role_'. $rid] = array(
220
      '#type' => 'fieldset',
221
      '#title' => t('Settings for @role', array('@role' => $role)),
222
      '#collapsible' => TRUE,
223
224
      '#collapsed' => TRUE,
    );
225
    $form['settings_role_'. $rid]['upload_extensions_'. $rid] = array(
226
227
      '#type' => 'textfield',
      '#title' => t('Permitted file extensions'),
228
      '#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default),
229
      '#maxlength' => 255,
230
      '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
231
    );
232
    $form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array(
233
234
      '#type' => 'textfield',
      '#title' => t('Maximum file size per upload'),
235
      '#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default),
236
237
      '#size' => 5,
      '#maxlength' => 5,
238
      '#description' => t('The maximum size of a file a user can upload (in megabytes).'),
239
    );
240
    $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
241
242
      '#type' => 'textfield',
      '#title' => t('Total file size per user'),
243
      '#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default),
244
245
      '#size' => 5,
      '#maxlength' => 5,
246
      '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'),
247
    );
Dries's avatar
   
Dries committed
248
249
  }

250
  return system_settings_form($form);
Dries's avatar
   
Dries committed
251
252
253
}

function upload_download() {
254
  foreach ($_SESSION['file_previews'] as $file) {
Dries's avatar
   
Dries committed
255
    if ($file->_filename == $_GET['q']) {
Dries's avatar
Dries committed
256
      file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize));
Dries's avatar
   
Dries committed
257
258
259
260
261
    }
  }
}

function upload_file_download($file) {
262
263
264
265
  $file = file_create_path($file);
  $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file);
  if ($file = db_fetch_object($result)) {
    if (user_access('view uploaded files')) {
266
267
268
269
      $node = node_load($file->nid);
      if (node_access('view', $node)) {
        $name = mime_header_encode($file->filename);
        $type = mime_header_encode($file->filemime);
drumm's avatar
drumm committed
270
271
272
        return array(
          'Content-Type: '. $type .'; name='. $name,
          'Content-Length: '. $file->filesize,
273
          'Cache-Control: private'
drumm's avatar
drumm committed
274
        );
275
      }
276
277
278
      else {
        return -1;
      }
279
    }
280
281
282
    else {
      return -1;
    }
283
  }
Dries's avatar
   
Dries committed
284
285
}

286
/**
287
288
289
290
291
292
293
294
295
296
 * Save new uploads and attach them to the node object.
 * append file_previews to the node object as well.
 */
function _upload_prepare(&$node) {

  // Clean up old file previews if a post didn't get the user to this page.
  // i.e. the user left the edit page, because they didn't want to upload anything.
  if(count($_POST) == 0) {
    if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) {
      foreach($_SESSION['file_previews'] as $fid => $file) {
drumm's avatar
drumm committed
297
        file_delete($file->filepath);
298
299
300
301
302
      }
      unset($_SESSION['file_previews']);
    }
  }

303
  // $_SESSION['file_current_upload'] tracks the fid of the file submitted this page request.
304
305
306
307
308
  // 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.

309
  unset($_SESSION['file_current_upload']);
310

Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
311
312
  global $user;

313
314
315
316
317
318
319
320
321
322
323
324
325
326
  // Save new file uploads to tmp dir.
  if (($file = file_check_upload()) && user_access('upload files')) {

    // Scale image uploads.
    $file = _upload_image($file);

    $key = 'upload_'. count($_SESSION['file_previews']);
    $file->fid = $key;
    $file->source = $key;
    $file->list = variable_get('upload_list_default',1);
    $_SESSION['file_previews'][$key] = $file;

    // Store the uploaded fid for this page request in case of submit without
    // preview or attach. See earlier notes.
327
    $_SESSION['file_current_upload'] = $key;
328
329
330
331
332
  }

  // Attach file previews to node object.
  if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) {
    foreach($_SESSION['file_previews'] as $fid => $file) {
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
333
334
335
336
337
      if ($user->uid != 1) {
        // Here something.php.pps becomes something.php_.pps
        $file->filename = upload_munge_filename($file->filename, NULL, 0);
        $file->description = $file->filename;
      }
338
339
340
341
342
      $node->files[$fid] = $file;
    }
  }
}

343
function upload_form_alter($form_id, &$form) {
344
345
346
347
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $form['workflow']['upload'] = array(
      '#type' => 'radios',
      '#title' => t('Attachments'),
348
      '#default_value' => variable_get('upload_'. $form['#node_type']->type, 1),
349
350
351
      '#options' => array(t('Disabled'), t('Enabled')),
    );
  }
352

353
  if (isset($form['type'])) {
354
    $node = $form['#node'];
355
    if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
356
357
      drupal_add_js('misc/progress.js');
      drupal_add_js('misc/upload.js');
358

359
      // Attachments fieldset
360
361
      $form['attachments'] = array(
        '#type' => 'fieldset',
362
        '#access' => user_access('upload files'),
363
364
365
366
367
368
        '#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>',
369
        '#weight' => 30,
370
      );
371
372
373
374
375
376

      // Wrapper for fieldset contents (used by upload JS).
      $form['attachments']['wrapper'] = array(
        '#prefix' => '<div id="attach-wrapper">',
        '#suffix' => '</div>',
      );
377
378
379

      // Make sure necessary directories for upload.module exist and are
      // writable before displaying the attachment form.
380
381
382
383
      $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)) {
384
385
386
387
388
389
390
391
392
393
394
395
        $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';
      }
396
    }
397
398
399
  }
}

400
function _upload_validate(&$node) {
401
402
403
404
  // Accumulator for disk space quotas.
  $filesize = 0;

  // Check if node->files exists, and if it contains something.
405
  if (is_array($node->files)) {
406
407
    // Update existing files with form data.
    foreach($node->files as $fid => $file) {
408
      // Convert file to object for compatibility
409
      $file = (object)$file;
410

411
      // Validate new uploads.
412
      if (strpos($fid, 'upload') !== FALSE && !$file->remove) {
Dries's avatar
   
Dries committed
413
414
        global $user;

415
        // Bypass validation for uid  = 1.
Steven Wittens's avatar
Steven Wittens committed
416
        if ($user->uid != 1) {
417
          // Update filesize accumulator.
418
419
420
421
422
          $filesize += $file->filesize;

          // Validate file against all users roles.
          // Only denies an upload when all roles prevent it.

423
          $total_usersize = upload_space_used($user->uid) + $filesize;
424
          $error = array();
Steven Wittens's avatar
Steven Wittens committed
425
          foreach ($user->roles as $rid => $name) {
426
427
            $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps'));
            $uploadsize = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
428
            $usersize = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
Steven Wittens's avatar
Steven Wittens committed
429
430
431
432
433
434

            $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';

            if (!preg_match($regex, $file->filename)) {
              $error['extension']++;
            }
Dries's avatar
   
Dries committed
435

436
            if ($uploadsize && $file->filesize > $uploadsize) {
Steven Wittens's avatar
Steven Wittens committed
437
438
              $error['uploadsize']++;
            }
Dries's avatar
   
Dries committed
439

440
            if ($usersize && $total_usersize + $file->filesize > $usersize) {
Steven Wittens's avatar
Steven Wittens committed
441
442
              $error['usersize']++;
            }
Dries's avatar
   
Dries committed
443
          }
444
445
446
447

          $user_roles = count($user->roles);
          $valid = TRUE;
          if ($error['extension'] == $user_roles) {
448
            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' => $file->filename, '%files-allowed' => $extensions)));
449
            $valid = FALSE;
450
          }
451
          elseif ($error['uploadsize'] == $user_roles) {
452
            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' => $file->filename, '%maxsize' => format_size($uploadsize))));
453
            $valid = FALSE;
454
          }
455
          elseif ($error['usersize'] == $user_roles) {
456
            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' => $file->filename, '%quota' => format_size($usersize))));
457
            $valid = FALSE;
458
          }
459
          elseif (strlen($file->filename) > 255) {
460
            form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is too long.', array('%name' => $file->filename)));
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
461
462
463
            $valid = FALSE;
          }

464
          if (!$valid) {
465
466
467
            unset($node->files[$fid], $_SESSION['file_previews'][$fid]);
            file_delete($file->filepath);
          }
468
        }
469
      }
470
471
472
473
474
475
476
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 */
477
function upload_nodeapi(&$node, $op, $teaser) {
478
  switch ($op) {
479

Dries's avatar
   
Dries committed
480
    case 'load':
drumm's avatar
drumm committed
481
      $output = '';
482
      if (variable_get("upload_$node->type", 1) == 1) {
Steven Wittens's avatar
Steven Wittens committed
483
        $output['files'] = upload_load($node);
484
        return $output;
Dries's avatar
   
Dries committed
485
      }
486
487
488
489
490
491
      break;

    case 'prepare':
      _upload_prepare($node);
      break;

492
493
494
495
    case 'validate':
      _upload_validate($node);
      break;

Dries's avatar
   
Dries committed
496
    case 'view':
497
      if (isset($node->files) && user_access('view uploaded files')) {
498
499
500
501
502
503
504
505
        // 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
506
          }
507
508
509
510
511
512
513
514
515
        }
      }
      break;
    case 'alter':
      if (isset($node->files) && user_access('view uploaded files')) {
        // Manipulate so that inline references work in preview
        if (!variable_get('clean_url', 0)) {
          $previews = array();
          foreach ($node->files as $file) {
516
            if (strpos($file->fid, 'upload') !== FALSE) {
517
              $previews[] = $file;
518
            }
519
520
521
522
523
524
525
526
          }

          // URLs to files being previewed are actually Drupal paths. When Clean
          // URLs are disabled, the two do not match. We perform an automatic
          // replacement from temporary to permanent URLs. That way, the author
          // can use the final URL in the body before having actually saved (to
          // place inline images for example).
          foreach ($previews as $file) {
527
            $old = file_create_filename($file->filename, file_create_path());
528
529
530
            $new = url($old);
            $node->body = str_replace($old, $new, $node->body);
            $node->teaser = str_replace($old, $new, $node->teaser);
Dries's avatar
   
Dries committed
531
532
533
534
          }
        }
      }
      break;
535
536
537
538
539
540
541
542
543
544
545
    case 'insert':
    case 'update':
      if (user_access('upload files')) {
        upload_save($node);
      }
      break;

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

546
547
548
    case 'delete revision':
      upload_delete_revision($node);
      break;
549

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

Dries's avatar
   
Dries committed
553
    case 'rss item':
554
      if (is_array($node->files)) {
Dries's avatar
   
Dries committed
555
556
557
558
559
560
561
562
563
        $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
564
565
566
567
568
569
570
571
572
573
          return array(
            array(
              'key' => 'enclosure',
              'attributes' => array(
                'url' => file_create_url($file->filepath),
                'length' => $file->filesize,
                'type' => $file->filemime
              )
            )
          );
Dries's avatar
   
Dries committed
574
575
        }
      }
576
      return array();
577
578
  }
}
Dries's avatar
   
Dries committed
579

580
581
582
583
584
585
586
587
/**
 * Displays file attachments in table
 */
function theme_upload_attachments($files) {
  $header = array(t('Attachment'), t('Size'));
  $rows = array();
  foreach ($files as $file) {
    if ($file->list) {
588
589
      $href = $file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()));
      $text = $file->description ? $file->description : $file->filename;
590
591
592
593
594
      $rows[] = array(l($text, $href), format_size($file->filesize));
    }
  }
  if (count($rows)) {
    return theme('table', $header, $rows, array('id' => 'attachments'));
595
  }
Dries's avatar
   
Dries committed
596
597
}

598
599
600
601
602
603
/**
 * Determine how much disk space is occupied by a user's uploaded files.
 *
 * @param $uid
 *   The integer user id of a user.
 * @return
604
 *   The amount of disk space used by the user in bytes.
605
606
 */
function upload_space_used($uid) {
607
  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));
608
}
Dries's avatar
   
Dries committed
609

610
611
612
613
/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
614
 *   The amount of disk space used by uploaded files in bytes.
615
616
 */
function upload_total_space_used() {
617
  return db_result(db_query('SELECT SUM(filesize) FROM {files}'));
Dries's avatar
   
Dries committed
618
619
}

Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
/**
 * Munge the filename as needed for security purposes.
 *
 * @param $filename
 *   The name of a file to modify.
 * @param $extensions
 *   A space separated list of valid extensions. If this is blank, we'll use
 *   the admin-defined defaults for the user role from upload_extensions_$rid.
 * @param $alerts
 *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
 * @return $filename
 *   The potentially modified $filename.
 */
function upload_munge_filename($filename, $extensions = NULL, $alerts = 1) {
  global $user;

  $original = $filename;

  // Allow potentially insecure uploads for very savvy users and admin
  if (!variable_get('allow_insecure_uploads', 0)) {

    if (!isset($extensions)) {
      $extensions = '';
      foreach ($user->roles as $rid => $name) {
        $extensions .= ' '. variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps'));
      }

    }

    $whitelist = array_unique(explode(' ', trim($extensions)));

    $filename_parts = explode('.', $filename);

    $new_filename = array_shift($filename_parts); // Remove file basename.
    $final_extension = array_pop($filename_parts); // Remove final extension.

    foreach($filename_parts as $filename_part) {
      $new_filename .= ".$filename_part";
      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
        $new_filename .= '_';
      }
    }
    $filename = "$new_filename.$final_extension";
  }

  if ($alerts && $original != $filename) {
    $message = t('Your filename has been renamed to conform to site policy.');
    drupal_set_message($message);
  }

  return $filename;
}

/**
 * Undo the effect of upload_munge_filename().
 */
function upload_unmunge_filename($filename) {
  return str_replace('_.', '.', $filename);
}

680
function upload_save(&$node) {
681
682
683
684
  if (!is_array($node->files)) {
    return;
  }

685
  foreach ($node->files as $fid => $file) {
686
    // Convert file to object for compatibility
687
688
689
690
691
692
    $file = (object)$file;

    // Remove file. Process removals first since no further processing
    // will be required.
    if ($file->remove) {
      // Remove file previews...
693
      if (strpos($file->fid, 'upload') !== FALSE) {
drumm's avatar
drumm committed
694
        file_delete($file->filepath);
Dries's avatar
   
Dries committed
695
      }
696

697
698
699
      // Remove managed files.
      else {
        db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid);
700
        // Only delete a file if it isn't used by any revision
701
        $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid));
702
        if ($count < 1) {
703
          db_query('DELETE FROM {files} WHERE fid = %d', $fid);
704
705
          file_delete($file->filepath);
        }
Dries's avatar
   
Dries committed
706
      }
707
    }
708

709
    // New file upload
710
    elseif (strpos($file->fid, 'upload') !== FALSE) {
711
712
713
714
      if ($file = file_save_upload($file, $file->filename)) {
        $file->fid = db_next_id('{files}_fid');
        db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize);
        db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
715
716
        // Tell other modules where the file was stored.
        $node->files[$fid] = $file;
717
      }
718
719
720
721
722
723
724
725
726
727
728
      unset($_SESSION['file_previews'][$fid]);
    }

    // Create a new revision, as needed
    elseif ($node->old_vid && is_numeric($fid)) {
      db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
    }

    // Update existing revision
    else {
      db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid);
729
730
    }
  }
Dries's avatar
   
Dries committed
731
732
}

733
function upload_delete($node) {
734
  $files = array();
735
  $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid);
736
737
738
739
740
  while ($file = db_fetch_object($result)) {
    $files[$file->fid] = $file;
  }

  foreach ($files as $fid => $file) {
741
    // Delete all file revision information associated with the node
742
    db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid);
Dries's avatar
   
Dries committed
743
744
    file_delete($file->filepath);
  }
745

746
  // Delete all files associated with the node
747
  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
748
749
750
}

function upload_delete_revision($node) {
751
752
753
754
755
756
757
758
759
760
  if (is_array($node->files)) {
    foreach ($node->files as $file) {
      // Check if the file will be used after this revision is deleted
      $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $file->fid));

      // if the file won't be used, delete it
      if ($count < 2) {
        db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
        file_delete($file->filepath);
      }
761
762
763
764
765
    }
  }

  // delete the revision
  db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
Dries's avatar
   
Dries committed
766
767
}

768
function _upload_form($node) {
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
769

770
  $form['#theme'] = 'upload_form_new';
771

772
  if (is_array($node->files) && count($node->files)) {
773
774
    $form['files']['#theme'] = 'upload_form_current';
    $form['files']['#tree'] = TRUE;
Dries's avatar
   
Dries committed
775
    foreach ($node->files as $key => $file) {
776
      $description = file_create_url((strpos($file->fid, 'upload') === FALSE ? $file->filepath : file_create_filename($file->filename, file_create_path())));
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
777
      $description = "<small>". check_plain($description) ."</small>";
778
      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
Gerhard Killesreiter's avatar
Gerhard Killesreiter committed
779

780
      $form['files'][$key]['size'] = array('#value' => format_size($file->filesize));
781
782
      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => $file->remove);
      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
783
784
785
786
787
      // 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 ($_SESSION['file_current_upload'] == $file->fid) {
        $form['files'][$key]['list']['#value'] = variable_get('upload_list_default',1);
      }
788
789
790
791
792
      $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
793
794
    }
  }
795

796
  if (user_access('upload files')) {
797
798
799
800
801
    // This div is hidden when the user uploads through JS.
    $form['new'] = array(
      '#prefix' => '<div id="attach-hide">',
      '#suffix' => '</div>',
    );
802
    $form['new']['upload'] = array('#type' => 'file', '#title' => t('Attach new file'), '#size' => 40);
803
    $form['new']['attach'] = array('#type' => 'button', '#value' => t('Attach'), '#name' => 'attach', '#attributes' => array('id' => 'attach-button'));
804
    // The class triggers the js upload behaviour.
805
    $form['attach-url'] = array('#type' => 'hidden', '#value' => url('upload/js', NULL, NULL, TRUE), '#attributes' => array('class' => 'upload'));
806
  }
Dries's avatar
   
Dries committed
807

808
809
  // Needed for JS
  $form['current']['vid'] = array('#type' => 'hidden', '#value' => $node->vid);
810
811
812
  return $form;
}

813
814
815
/**
 * Theme the attachments list.
 */
816
817
function theme_upload_form_current(&$form) {
  $header = array(t('Delete'), t('List'), t('Description'), t('Size'));
818
819

  foreach (element_children($form) as $key) {
820
    $row = array();
821
822
823
824
    $row[] = drupal_render($form[$key]['remove']);
    $row[] = drupal_render($form[$key]['list']);
    $row[] = drupal_render($form[$key]['description']);
    $row[] = drupal_render($form[$key]['size']);
825
826
827
    $rows[] = $row;
  }
  $output = theme('table', $header, $rows);
828
  $output .= drupal_render($form);
829
  return $output;
Dries's avatar
   
Dries committed
830
831
}

832
833
834
835
836
/**
 * Theme the attachment form.
 * Note: required to output prefix/suffix.
 */
function theme_upload_form_new($form) {
837
  $output = drupal_render($form);
838
839
840
  return $output;
}

Dries's avatar
   
Dries committed
841
842
843
function upload_load($node) {
  $files = array();

844
  if ($node->vid) {
845
    $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);
Dries's avatar
   
Dries committed
846
847
848
849
850
851
852
853
    while ($file = db_fetch_object($result)) {
      $files[$file->fid] = $file;
    }
  }

  return $files;
}

854
855
856
857
858
859
860
861
862
863
864
865
866
/**
 * Check an upload, if it is an image, make sure it fits within the
 * maximum dimensions allowed.
 */
function _upload_image($file) {
  $info = image_get_info($file->filepath);

  if ($info) {
    list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
    if ($width && $height) {
      $result = image_scale($file->filepath, $file->filepath, $width, $height);
      if ($result) {
        $file->filesize = filesize($file->filepath);
867
        drupal_set_message(t('The image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => variable_get('upload_max_resolution', 0))));
868
869
870
871
872
873
874
      }
    }
  }

  return $file;
}

875
876
877
878
879
/**
 * Menu-callback for JavaScript-based uploads.
 */
function upload_js() {
  // We only do the upload.module part of the node validation process.
880
  $node = (object)$_POST;
881
882
883
884
885
886

  // Load existing node files.
  $node->files = upload_load($node);

  // Handle new uploads, and merge tmp files into node-files.
  _upload_prepare($node);
887
  _upload_validate($node);
888

889
  $form = _upload_form($node);
890
891
892
893
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
    $function('upload_js', $form);
  }
894
  $form = form_builder('upload_js', $form);
895
  $output = theme('status_messages') . drupal_render($form);
896
  // We send the updated file attachments form.
897
  print drupal_to_js(array('status' => TRUE, 'data' => $output));
898
899
  exit;
}