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

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

Dries's avatar
 
Dries committed
9
10
11
12
13
function upload_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('File-handling and attaching files to nodes.');
    case 'admin/upload':
14
      return t('<p>Users with the <a href="%permissions"><em>upload files</em> permission</a> can upload attachments. You can choose which node types can take attachments on the <a href="%workflow">workflow settings</a> page.</p>', array('%permissions' => url('admin/user/configure/permission'), '%workflow' => url('admin/node/configure/defaults')));
Dries's avatar
 
Dries committed
15
16
    case 'admin/node/configure/defaults':
      return t('<p>If you want users to be able to attach files to nodes, check the <em>attachments</em> column in the appropriate column.</p>');
17
18
19
20
21
22
23
24
25
    case 'admin/help#upload':
      return t('
<h3>Background</h3>
<p>The upload module allows users to upload attachments. You can choose which node types can take attachments on the <a href="%workflow">workflow settings</a> page.</p>
<h3>Permissions</h3>
<p>Two permissions are related to uploads: <em>upload files</em> and <em>view uploaded files</em>.</p>
<ol><li><strong>upload files</strong> - Allows users to upload attachments.</li><li><strong>view uploaded files</strong> - Allows users to view and download attachments. Keep in mind that if you are using the <a href="%settings">public download method</a>, anyone will be able to access uploaded files with a direct URL regardless of this permission.</li></ol>
<p>Lastly, users with the <em>administer site configuration</em> permission will be able to configure <a href="%upload">role-specific upload settings</a> such as allowed file types, maximum file size per upload and total file size per user.</p>
', array('%settings' => url('admin/settings'), '%workflow' => url('admin/node/configure/defaults'), '%upload' => url('admin/upload')));
Dries's avatar
 
Dries committed
26
27
28
29
  }
}

function upload_perm() {
30
  return array('upload files', 'view uploaded files');
Dries's avatar
 
Dries committed
31
32
}

Dries's avatar
   
Dries committed
33
34
35
36
function upload_menu($may_cache) {
  $items = array();

  if ($may_cache) {
37
    $items[] = array(
38
      'path' => 'admin/settings/upload', 'title' => t('uploads'),
39
      'callback' => 'upload_admin',
40
      'access' => user_access('administer site configuration'),
41
42
43
44
      'type' => MENU_NORMAL_ITEM
    );
  }
  else {
Dries's avatar
   
Dries committed
45
46
47
48
49
50
51
    // Add handlers for previewing new uploads.
    if ($_SESSION['file_uploads']) {
      foreach ($_SESSION['file_uploads'] as $key => $file) {
        $filename = file_create_filename($file->filename, file_create_path());
        $items[] = array(
          'path' => $filename, 'title' => t('file download'),
          'callback' => 'upload_download',
52
          'access' => user_access('view uploaded files'),
53
          'type' => MENU_CALLBACK
Dries's avatar
   
Dries committed
54
55
56
        );
        $_SESSION['file_uploads'][$key]->_filename = $filename;
      }
Dries's avatar
 
Dries committed
57
58
    }
  }
Dries's avatar
   
Dries committed
59

Dries's avatar
 
Dries committed
60
61
62
63
64
65
  return $items;
}

function upload_admin() {
  system_settings_save();

66
67
  $group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 10, 10, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
  $group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 10, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
Dries's avatar
 
Dries committed
68
69
70
71
72
73

  $output = form_group(t('General settings'), $group);

  $roles = user_roles(0, 'upload files');

  foreach ($roles as $rid => $role) {
Dries's avatar
Dries committed
74
75
76
77
    $group = form_textfield(t('Permitted file extensions'), "upload_extensions_$rid", variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps"), 60, 255, t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'));
    $group .= form_textfield(t('Maximum file size per upload'), "upload_uploadsize_$rid", variable_get("upload_uploadsize_$rid", 1), 5, 5, t('The maximum size of a file a user can upload (in megabytes).'));
    $group .= form_textfield(t('Total file size per user'), "upload_usersize_$rid", variable_get("upload_usersize_$rid", 10), 5, 5, t('The maximum size of all files a user can have on the site (in megabytes).'));
    $output .= form_group(t('Settings for %role', array('%role' => "<em>$role</em>")), $group);
Dries's avatar
 
Dries committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  }

  print theme('page', system_settings_form($output));
}

function upload_download() {
  foreach ($_SESSION['file_uploads'] as $file) {
    if ($file->_filename == $_GET['q']) {
      file_transfer($file->filepath, array('Content-Type: '. $file->filemime, 'Content-Length: '. $file->filesize));
    }
  }
}

function upload_file_download($file) {
92
93
  if (user_access('view uploaded files')) {
    $file = file_create_path($file);
94
    $result = db_query(db_rewrite_sql("SELECT f.nid, * from {files} f WHERE filepath = '%s", 'f'), $file);
95
96
97
98
99
100
101
102
    if ($file = db_fetch_object($result)) {
      $name = mime_header_encode($file->filename);
      // Serve images and text inline for the browser to display rather than download.
      $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
      return array('Content-Type: '. $file->filemime .'; name='. $name,
                   'Content-Length: '. $file->filesize,
                   'Content-Disposition: '. $disposition .'; filename='. $name);
    }
Dries's avatar
 
Dries committed
103
104
105
106
107
108
  }
}

function upload_nodeapi(&$node, $op, $arg) {
  switch ($op) {
    case 'settings':
109
110
      return form_radios(t('Attachments'), 'upload_'. $node->type, variable_get('upload_'. $node->type, 1), array(t('Disabled'), t('Enabled')));

Dries's avatar
 
Dries committed
111
    case 'form param':
112
      if (variable_get("upload_$node->type", 1) && user_access('upload files')) {
Dries's avatar
 
Dries committed
113
114
115
        $output['options'] = array('enctype' => 'multipart/form-data');
      }
      break;
116

Dries's avatar
 
Dries committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    case 'validate':
      $node->files = upload_load($node);

      // Double check existing files:
      if (is_array($node->list)) {
        foreach ($node->list as $key => $value) {
          if ($file = file_check_upload($key)) {
            $node->files[$file->source] = $file;
            $node->files[$key]->list = $node->list[$key];
            $node->files[$key]->remove = $node->remove[$key];
            if ($file->source) {
              $filesize += $file->filesize;
            }
          }
        }
      }
      else {
        foreach ($node->files as $key => $file) {
          $node->list[$key] = $file->list;
        }
      }
Dries's avatar
   
Dries committed
138

139
      if (($file = file_check_upload('upload')) && user_access('upload files')) {
Dries's avatar
 
Dries committed
140
141
        global $user;

142
143
144
        $file = _upload_image($file);

        $maxsize = variable_get("upload_maxsize_total", 0);
Dries's avatar
 
Dries committed
145
146
147
148
        $total_size = upload_count_size() + $filesize;
        $total_usersize = upload_count_size($user->uid) + $filesize;

        if ($maxsize && $total_size > $maxsize) {
Dries's avatar
Dries committed
149
          form_set_error('upload', t('Error attaching file %name: total file size exceeded', array('%name' => "<em>$file->filename</em>")));
Dries's avatar
 
Dries committed
150
151
152
          break;
        }

Steven Wittens's avatar
Steven Wittens committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
        // Don't do any checks for uid #1.
        if ($user->uid != 1) {
          // Validate file against all users roles. Only denies an upload when
          // all roles prevent it.
          foreach ($user->roles as $rid => $name) {
            $extensions = variable_get("upload_extensions_$rid", 'jpg jpeg gif png txt html doc xls pdf ppt pps');
            $uploadsize = variable_get("upload_uploadsize_$rid", 1);
            $usersize = variable_get("upload_usersize_$rid", 1);

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

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

Steven Wittens's avatar
Steven Wittens committed
168
169
170
            if ($file->filesize > $uploadsize * 1024 * 1024) {
              $error['uploadsize']++;
            }
Dries's avatar
 
Dries committed
171

Steven Wittens's avatar
Steven Wittens committed
172
173
174
            if ($total_usersize + $file->filesize > $usersize * 1024 * 1024) {
              $error['usersize']++;
            }
Dries's avatar
 
Dries committed
175
          }
Steven Wittens's avatar
Steven Wittens committed
176
        }
Dries's avatar
 
Dries committed
177

Steven Wittens's avatar
Steven Wittens committed
178
179
180
181
182
183
        // Rename possibly executable scripts to prevent accidental execution.
        // Uploaded files are attachments and should be shown in their original
        // form, rather than run.
        if (preg_match('/\.(php|pl|py|cgi|asp)$/i', $file->filename)) {
          $file->filename .= '.txt';
          $file->filemime = 'text/plain';
Dries's avatar
 
Dries committed
184
        }
Dries's avatar
   
Dries committed
185

186
        if ($error['extension'] == count($user->roles) && $user->uid != 1) {
Dries's avatar
Dries committed
187
          form_set_error('upload', t('Error attaching file %name: invalid extension', array('%name' => "<em>$file->filename</em>")));
Dries's avatar
 
Dries committed
188
        }
189
        elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
Dries's avatar
Dries committed
190
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array('%name' => "<em>$file->filename</em>")));
Dries's avatar
 
Dries committed
191
        }
192
        elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
Dries's avatar
Dries committed
193
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array('%name' => "<em>$file->filename</em>")));
Dries's avatar
 
Dries committed
194
195
196
197
198
199
200
201
202
203
        }
        else {
          $key = 'upload_'. count($_SESSION['file_uploads']);
          $file->source = $key;
          $file->list = 1;
          $file = file_save_upload($file);
          $node->files[$key] = $file;
        }
      }
      break;
204

Dries's avatar
 
Dries committed
205
    case 'form post':
206
      if (variable_get("upload_$node->type", 1) == 1 && user_access('upload files')) {
Dries's avatar
 
Dries committed
207
208
209
        $output = upload_form($node);
      }
      break;
210

Dries's avatar
 
Dries committed
211
    case 'load':
212
      if (variable_get("upload_$node->type", 1) == 1) {
Steven Wittens's avatar
Steven Wittens committed
213
        $output['files'] = upload_load($node);
Dries's avatar
 
Dries committed
214
215
      }
      break;
216

Dries's avatar
 
Dries committed
217
    case 'view':
218
      if ($node->files && user_access('view uploaded files')) {
Dries's avatar
   
Dries committed
219
        $header = array(t('Attachment'), t('Size'));
Dries's avatar
 
Dries committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
        $rows = array();
        $previews = array();

        // Build list of attached files
        foreach ($node->files as $file) {
          if ($file->list) {
            $rows[] = array(
              '<a href="'. ($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))) . '">'. $file->filename .'</a>',
              format_size($file->filesize)
            );
            // We save the list of files still in preview for later
            if (!$file->fid) {
              $previews[] = $file;
            }
          }
        }

        // 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).
        if (!variable_get('clean_url', 0)) {
          foreach ($previews as $file) {
            $old = file_create_filename($file->filename, file_create_path());
            $new = url($old);
            $node->body = str_replace($old, $new, $node->body);
            $node->teaser = str_replace($old, $new, $node->teaser);
          }
        }

        $teaser = $arg;
        // Add the attachments list
        if (count($rows) && !$teaser) {
          $node->body .= theme('table', $header, $rows);
        }
      }
      break;
258

Dries's avatar
 
Dries committed
259
260
    case 'insert':
    case 'update':
261
262
263
      if (user_access('upload files')) {
        upload_save($node);
      }
Dries's avatar
 
Dries committed
264
      break;
265

Dries's avatar
 
Dries committed
266
267
268
    case 'delete':
      upload_delete($node);
      break;
Dries's avatar
Dries committed
269
270
    case 'search result':
      return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
Dries's avatar
   
Dries committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
    case 'rss item':
      $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);
        return array(array('key' => 'enclosure',
                           'attributes' => array('url' => file_create_url($file->filepath),
                                                 'length' => $file->filesize,
                                                 'type' => $file->filemime)));
      }
      break;
Dries's avatar
 
Dries committed
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
  }

  return $output;
}

function upload_count_size($uid = 0) {
  if ($uid) {
    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d", $uid);
  }
  else {
    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid");
  }

  return db_result($result);
}

function upload_save($node) {
304
  foreach ((array)$node->files as $key => $file) {
Dries's avatar
 
Dries committed
305
306
307
308
    if ($file->source && !$file->remove) {
      // Clean up the session:
      unset($_SESSION['file_uploads'][$file->source]);

309
310
311
312
313
314
      // Insert new files:
      if ($file = file_save_upload($file, $file->filename)) {
        $fid = db_next_id('{files}_fid');
        db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)",
                 $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
      }
Dries's avatar
 
Dries committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    }
    else {
      // Remove or update existing files:
      if ($node->remove[$key]) {
        file_delete($file->filepath);
        db_query("DELETE FROM {files} WHERE fid = %d", $key);
      }
      if ($file->list != $node->list[$key]) {
        db_query("UPDATE {files} SET list = %d WHERE fid = %d", $node->list[$key], $key);
      }
    }
  }
  return;
}

function upload_delete($node) {
  $node->files = upload_load($node);
  foreach ($node->files as $file) {
    file_delete($file->filepath);
  }
  db_query("DELETE FROM {files} WHERE nid = %d", $node->nid);
}

function upload_form($node) {
Dries's avatar
   
Dries committed
339
  $header = array(t('Delete'), t('List'), t('Url'), t('Size'));
Dries's avatar
 
Dries committed
340
341
342
343
344
345
346
347
348
349
350
351
352
353
  $rows = array();

  if (is_array($node->files)) {
    foreach ($node->files as $key => $file) {
      $rows[] = array(
        form_checkbox('', "remove][$key", 1, $file->remove),
        form_checkbox('', "list][$key", 1, $file->list),
        $file->filename ."<br /><small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>",
        format_size($file->filesize)
      );
    }
  }

  if (count($node->files)) {
Dries's avatar
   
Dries committed
354
    $output = theme('table', $header, $rows);
Dries's avatar
 
Dries committed
355
  }
356
357
358
359
  if (user_access('upload files')) {
    $output .= form_file(t('Attach new file'), "upload", 40);
    $output .= form_button(t('Attach'), 'fileop');
  }
Dries's avatar
 
Dries committed
360

Dries's avatar
   
Dries committed
361
  return '<div class="attachments">'. form_group(t('Attachments'), $output, t('Changes made to the attachments are not permanent until you save this post.  The first "listed" file will be included in RSS feeds.')) .'</div>';
Dries's avatar
 
Dries committed
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
}

function upload_load($node) {
  $files = array();

  if ($node->nid) {
    $result = db_query("SELECT * FROM {files} WHERE nid = %d", $node->nid);
    while ($file = db_fetch_object($result)) {
      $files[$file->fid] = $file;
    }
  }

  return $files;
}

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
/**
 * 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);
        drupal_set_message(t('Your image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => variable_get('upload_max_resolution', 0))));
      }
    }
  }

  return $file;
}

Dries's avatar
 
Dries committed
398
?>