upload.module 14.8 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 97
    $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
98 99 100 101 102 103 104 105 106 107 108 109 110 111
  }

  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) {
112 113
  if (user_access('view uploaded files')) {
    $file = file_create_path($file);
Steven Wittens's avatar
Steven Wittens committed
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 167
        $file = _upload_image($file);

        $maxsize = variable_get("upload_maxsize_total", 0);
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('Error attaching file %name: total file size exceeded', array('%name' => theme('placeholder', $file->filename))));
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('Error attaching file %name: invalid extension', array('%name' => theme('placeholder', $file->filename))));
Dries's avatar
 
Dries committed
211
        }
212
        elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
213
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array('%name' => theme('placeholder', $file->filename))));
Dries's avatar
 
Dries committed
214
        }
215
        elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
216
          form_set_error('upload', t('Error attaching file %name: exceeds maximum file size', array('%name' => theme('placeholder', $file->filename))));
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 250 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
        $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) {
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 415 416 417 418 419 420 421 422
/**
 * 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
423
?>