upload.module 15.1 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
  $group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 15, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
Dries's avatar
 
Dries committed
87 88 89 90 91 92

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

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

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

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

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

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

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

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

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

164 165
        $file = _upload_image($file);

Steven Wittens's avatar
Steven Wittens committed
166 167 168 169
        // 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.
170
          $total_usersize = upload_space_used($user->uid) + $filesize;
Steven Wittens's avatar
Steven Wittens committed
171 172
          foreach ($user->roles as $rid => $name) {
            $extensions = variable_get("upload_extensions_$rid", 'jpg jpeg gif png txt html doc xls pdf ppt pps');
173 174
            $uploadsize = variable_get("upload_uploadsize_$rid", 1) * 1024 * 1024;
            $usersize = variable_get("upload_usersize_$rid", 1) * 1024 * 1024;
Steven Wittens's avatar
Steven Wittens committed
175 176 177 178 179 180

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

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

182
            if ($uploadsize && $file->filesize > $uploadsize) {
Steven Wittens's avatar
Steven Wittens committed
183 184
              $error['uploadsize']++;
            }
Dries's avatar
 
Dries committed
185

186
            if ($usersize && $total_usersize + $file->filesize > $usersize) {
Steven Wittens's avatar
Steven Wittens committed
187 188
              $error['usersize']++;
            }
Dries's avatar
 
Dries committed
189
          }
Steven Wittens's avatar
Steven Wittens committed
190
        }
Dries's avatar
 
Dries committed
191

Steven Wittens's avatar
Steven Wittens committed
192 193 194 195 196 197
        // 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
198
        }
Dries's avatar
 
Dries committed
199

200
        if ($error['extension'] == count($user->roles) && $user->uid != 1) {
201
          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
202
        }
203
        elseif ($error['uploadsize'] == count($user->roles) && $user->uid != 1) {
204
          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
205
        }
206
        elseif ($error['usersize'] == count($user->roles) && $user->uid != 1) {
207
          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), '%quota' => theme('placeholder', format_size($usersize)))));
Dries's avatar
 
Dries committed
208 209 210 211 212 213 214 215 216 217
        }
        else {
          $key = 'upload_'. count($_SESSION['file_uploads']);
          $file->source = $key;
          $file->list = 1;
          $file = file_save_upload($file);
          $node->files[$key] = $file;
        }
      }
      break;
218

Dries's avatar
 
Dries committed
219
    case 'form post':
220
      if (variable_get("upload_$node->type", 1) == 1 && user_access('upload files')) {
Dries's avatar
 
Dries committed
221 222 223
        $output = upload_form($node);
      }
      break;
224

Dries's avatar
 
Dries committed
225
    case 'load':
226
      if (variable_get("upload_$node->type", 1) == 1) {
Steven Wittens's avatar
Steven Wittens committed
227
        $output['files'] = upload_load($node);
Dries's avatar
 
Dries committed
228 229
      }
      break;
230

Dries's avatar
 
Dries committed
231
    case 'view':
232
      if ($node->files && user_access('view uploaded files')) {
Dries's avatar
 
Dries committed
233
        $header = array(t('Attachment'), t('Size'));
Dries's avatar
 
Dries committed
234 235 236 237 238 239 240
        $rows = array();
        $previews = array();

        // Build list of attached files
        foreach ($node->files as $file) {
          if ($file->list) {
            $rows[] = array(
241
              '<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
242 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
              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) {
268
          $node->body .= theme('table', $header, $rows, array('id' => 'attachments'));
Dries's avatar
 
Dries committed
269 270 271
        }
      }
      break;
272

Dries's avatar
 
Dries committed
273 274
    case 'insert':
    case 'update':
275 276 277
      if (user_access('upload files')) {
        upload_save($node);
      }
Dries's avatar
 
Dries committed
278
      break;
279

Dries's avatar
 
Dries committed
280 281 282
    case 'delete':
      upload_delete($node);
      break;
Dries's avatar
Dries committed
283 284
    case 'search result':
      return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null;
Dries's avatar
 
Dries committed
285
    case 'rss item':
Dries's avatar
 
Dries committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299
      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
300 301 302
        }
      }
      break;
Dries's avatar
 
Dries committed
303 304 305 306 307
  }

  return $output;
}

308 309 310 311 312 313 314 315 316 317 318
/**
 * Determine how much disk space is occupied by a user's uploaded files.
 *
 * @param $uid
 *   The integer user id of a user.
 * @return
 *   The ammount of disk space used by the user in bytes.
 */
function upload_space_used($uid) {
  return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d', $uid));
}
Dries's avatar
 
Dries committed
319

320 321 322 323 324 325 326 327
/**
 * Determine how much disk space is occupied by uploaded files.
 *
 * @return
 *   The ammount of disk space used by uploaded files in bytes.
 */
function upload_total_space_used() {
  return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid'));
Dries's avatar
 
Dries committed
328 329 330
}

function upload_save($node) {
331
  foreach ((array)$node->files as $key => $file) {
Dries's avatar
 
Dries committed
332 333 334 335
    if ($file->source && !$file->remove) {
      // Clean up the session:
      unset($_SESSION['file_uploads'][$file->source]);

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

388
  return '<div class="attachments">'. form_group_collapsible(t('File attachments'), $output, TRUE, 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
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
}

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

404 405 406 407 408 409 410 411 412 413 414 415 416
/**
 * 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);
417
        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)))));
418 419 420 421 422 423 424
      }
    }
  }

  return $file;
}

Dries's avatar
 
Dries committed
425
?>