upload.module 15.4 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.
 */

9 10 11
/**
 * Implementation of hook_help().
 */
Dries's avatar
 
Dries committed
12 13 14
function upload_help($section) {
  switch ($section) {
    case 'admin/modules#description':
15
      return t('Allows users to upload and attach files to content.');
16 17
    case 'admin/settings/upload':
      return t('<p>Users with the <a href="%permissions">upload files permission</a> can upload attachments. You can choose which post types can take attachments on the <a href="%types">content types settings</a> page.</p>', array('%permissions' => url('admin/access'), '%types' => url('admin/node/configure/types')));
Dries's avatar
 
Dries committed
18 19 20
  }
}

21 22 23
/**
 * Implementation of hook_perm().
 */
Dries's avatar
 
Dries committed
24
function upload_perm() {
25
  return array('upload files', 'view uploaded files');
Dries's avatar
 
Dries committed
26 27
}

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/**
 * Implementation of hook_link().
 */
function upload_link($type, $node = 0, $main = 0) {
  $links = array();

  // Display a link with the number of attachments
  if ($main && $type == 'node' && $node->files && user_access('view uploaded files')) {
    $num_files = 0;
    foreach ($node->files as $file) {
      if ($file->list) {
        $num_files++;
      }
    }
    if ($num_files) {
      $links[] = l(format_plural($num_files, '1 attachment', '%count attachments'), "node/$node->nid", array('title' => t('Read full article to view attachments.')), NULL, 'attachments');
    }
  }

  return $links;
}

/**
 * Implementation of hook_menu().
 */
Dries's avatar
 
Dries committed
53 54 55 56
function upload_menu($may_cache) {
  $items = array();

  if ($may_cache) {
57
    $items[] = array(
58
      'path' => 'admin/settings/upload', 'title' => t('uploads'),
59
      'callback' => 'upload_admin',
60
      'access' => user_access('administer site configuration'),
61 62 63 64
      'type' => MENU_NORMAL_ITEM
    );
  }
  else {
Dries's avatar
 
Dries committed
65 66 67 68 69 70 71
    // 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',
72
          'access' => user_access('view uploaded files'),
73
          'type' => MENU_CALLBACK
Dries's avatar
 
Dries committed
74 75 76
        );
        $_SESSION['file_uploads'][$key]->_filename = $filename;
      }
Dries's avatar
 
Dries committed
77 78
    }
  }
Dries's avatar
 
Dries committed
79

Dries's avatar
 
Dries committed
80 81 82 83 84 85
  return $items;
}

function upload_admin() {
  system_settings_save();

86 87
  $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
88 89 90 91 92 93

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

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

  foreach ($roles as $rid => $role) {
Dries's avatar
Dries committed
94 95 96
    $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).'));
97
    $output .= form_group(t('Settings for %role', array('%role' => theme('placeholder', $role))), $group);
Dries's avatar
 
Dries committed
98 99
  }

Dries's avatar
 
Dries committed
100
  return system_settings_form($output);
Dries's avatar
 
Dries committed
101 102 103 104 105 106 107 108 109 110 111
}

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) {
112 113
  if (user_access('view uploaded files')) {
    $file = file_create_path($file);
114
    $result = db_query(db_rewrite_sql("SELECT f.nid, f.* FROM {files} f WHERE filepath = '%s'", 'f'), $file);
115 116 117 118 119 120 121 122
    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
123 124 125
  }
}

126 127 128
/**
 * Implementation of hook_nodeapi().
 */
Dries's avatar
 
Dries committed
129 130 131
function upload_nodeapi(&$node, $op, $arg) {
  switch ($op) {
    case 'settings':
132 133
      return form_radios(t('Attachments'), 'upload_'. $node->type, variable_get('upload_'. $node->type, 1), array(t('Disabled'), t('Enabled')));

Dries's avatar
 
Dries committed
134
    case 'form param':
135
      if (variable_get("upload_$node->type", 1) && user_access('upload files')) {
Dries's avatar
 
Dries committed
136 137 138
        $output['options'] = array('enctype' => 'multipart/form-data');
      }
      break;
139

Dries's avatar
 
Dries committed
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    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
161

162
      if (($file = file_check_upload('upload')) && user_access('upload files')) {
Dries's avatar
 
Dries committed
163 164
        global $user;

165 166
        $file = _upload_image($file);

167
        $maxsize = variable_get("upload_maxsize_total", 0) * 1024 * 1024;
Dries's avatar
 
Dries committed
168 169 170 171
        $total_size = upload_count_size() + $filesize;
        $total_usersize = upload_count_size($user->uid) + $filesize;

        if ($maxsize && $total_size > $maxsize) {
172
          form_set_error('upload', t('The selected file %name can not be attached to this post, because it exceeded the maximum filesize of %max-size', array('%name' => theme('placeholder', $file->filename), '%max-size' => theme('placeholder', format_size($maxsize)))));
Dries's avatar
 
Dries committed
173 174 175
          break;
        }

Steven Wittens's avatar
Steven Wittens committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189
        // 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
190

Steven Wittens's avatar
Steven Wittens committed
191 192 193
            if ($file->filesize > $uploadsize * 1024 * 1024) {
              $error['uploadsize']++;
            }
Dries's avatar
 
Dries committed
194

Steven Wittens's avatar
Steven Wittens committed
195 196 197
            if ($total_usersize + $file->filesize > $usersize * 1024 * 1024) {
              $error['usersize']++;
            }
Dries's avatar
 
Dries committed
198
          }
Steven Wittens's avatar
Steven Wittens committed
199
        }
Dries's avatar
 
Dries committed
200

Steven Wittens's avatar
Steven Wittens committed
201 202 203 204 205 206
        // 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
207
        }
Dries's avatar
 
Dries committed
208

209
        if ($error['extension'] == count($user->roles) && $user->uid != 1) {
210
          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' => theme('placeholder', $file->filename), '%files-allowed' => theme('placeholder', $extensions))));
Dries's avatar
 
Dries committed
211
        }
212
        elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
213
          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' => theme('placeholder', $file->filename), '%maxsize' => theme('placeholder', format_size($uploadsize)))));
Dries's avatar
 
Dries committed
214
        }
215
        elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
216
          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' => theme('placeholder', $file->filename), '%quote' => theme('placeholder', format_size($usersize)))));
Dries's avatar
 
Dries committed
217 218 219 220 221 222 223 224 225 226
        }
        else {
          $key = 'upload_'. count($_SESSION['file_uploads']);
          $file->source = $key;
          $file->list = 1;
          $file = file_save_upload($file);
          $node->files[$key] = $file;
        }
      }
      break;
227

Dries's avatar
 
Dries committed
228
    case 'form post':
229
      if (variable_get("upload_$node->type", 1) == 1 && user_access('upload files')) {
Dries's avatar
 
Dries committed
230 231 232
        $output = upload_form($node);
      }
      break;
233

Dries's avatar
 
Dries committed
234
    case 'load':
235
      if (variable_get("upload_$node->type", 1) == 1) {
Steven Wittens's avatar
Steven Wittens committed
236
        $output['files'] = upload_load($node);
Dries's avatar
 
Dries committed
237 238
      }
      break;
239

Dries's avatar
 
Dries committed
240
    case 'view':
241
      if ($node->files && user_access('view uploaded files')) {
Dries's avatar
 
Dries committed
242
        $header = array(t('Attachment'), t('Size'));
Dries's avatar
 
Dries committed
243 244 245 246 247 248 249
        $rows = array();
        $previews = array();

        // Build list of attached files
        foreach ($node->files as $file) {
          if ($file->list) {
            $rows[] = array(
250
              '<a href="'. check_url(($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path())))) .'">'. check_plain($file->filename) .'</a>',
Dries's avatar
 
Dries committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
              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) {
277
          $node->body .= theme('table', $header, $rows, array('id' => 'attachments'));
Dries's avatar
 
Dries committed
278 279 280
        }
      }
      break;
281

Dries's avatar
 
Dries committed
282 283
    case 'insert':
    case 'update':
284 285 286
      if (user_access('upload files')) {
        upload_save($node);
      }
Dries's avatar
 
Dries committed
287
      break;
288

Dries's avatar
 
Dries committed
289 290 291
    case 'delete':
      upload_delete($node);
      break;
Dries's avatar
Dries committed
292 293
    case 'search result':
      return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
Dries's avatar
 
Dries committed
294
    case 'rss item':
Dries's avatar
 
Dries committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308
      if ($node->files) {
        $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)));
Dries's avatar
 
Dries committed
309 310 311
        }
      }
      break;
Dries's avatar
 
Dries committed
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
  }

  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) {
329
  foreach ((array)$node->files as $key => $file) {
Dries's avatar
 
Dries committed
330 331 332 333
    if ($file->source && !$file->remove) {
      // Clean up the session:
      unset($_SESSION['file_uploads'][$file->source]);

334 335 336 337 338 339
      // 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
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
    }
    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
364
  $header = array(t('Delete'), t('List'), t('Url'), t('Size'));
Dries's avatar
 
Dries committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378
  $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
379
    $output = theme('table', $header, $rows);
Dries's avatar
 
Dries committed
380
  }
381 382 383 384
  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
385

Dries's avatar
 
Dries committed
386
  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
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
}

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

402 403 404 405 406 407 408 409 410 411 412 413 414
/**
 * 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);
415
        drupal_set_message(t('The image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => theme('placeholder', variable_get('upload_max_resolution', 0)))));
416 417 418 419 420 421 422
      }
    }
  }

  return $file;
}

Dries's avatar
 
Dries committed
423
?>