file.inc 85.3 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
Kjartan's avatar
Kjartan committed
2

Dries's avatar
 
Dries committed
3 4 5 6 7
/**
 * @file
 * API for handling file uploads and server file management.
 */

8
/**
9 10
 * Manually include stream wrapper code.
 *
11 12 13 14
 * Stream wrapper code is included here because there are cases where
 * File API is needed before a bootstrap, or in an alternate order (e.g.
 * maintenance theme).
 */
15
require_once DRUPAL_ROOT . '/core/includes/stream_wrappers.inc';
16

Kjartan's avatar
Kjartan committed
17
/**
Kjartan's avatar
Kjartan committed
18
 * @defgroup file File interface
Kjartan's avatar
Kjartan committed
19
 * @{
Dries's avatar
 
Dries committed
20
 * Common file handling functions.
21 22
 *
 * Fields on the file object:
23 24 25
 * - fid: File ID
 * - uid: The {users}.uid of the user who is associated with the file.
 * - filename: Name of the file with no path components. This may differ from
26 27
 *   the basename of the filepath if the file is renamed to avoid overwriting
 *   an existing file.
28 29 30 31
 * - uri: URI of the file.
 * - filemime: The file's MIME type.
 * - filesize: The size of the file in bytes.
 * - status: A bitmapped field indicating the status of the file. The first 8
32
 *   bits are reserved for Drupal core. The least significant bit indicates
33 34
 *   temporary (0) or permanent (1). Temporary files older than
 *   DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs.
35
 * - timestamp: UNIX timestamp for the date the file was added to the database.
Dries's avatar
 
Dries committed
36 37
 */

38
/**
39
 * Flag used by file_prepare_directory() -- create directory if not present.
40
 */
41
const FILE_CREATE_DIRECTORY = 1;
42 43

/**
44
 * Flag used by file_prepare_directory() -- file permissions may be changed.
45
 */
46
const FILE_MODIFY_PERMISSIONS = 2;
47 48

/**
49
 * Flag for dealing with existing files: Appends number until name is unique.
50
 */
51
const FILE_EXISTS_RENAME = 0;
52 53 54 55

/**
 * Flag for dealing with existing files: Replace the existing file.
 */
56
const FILE_EXISTS_REPLACE = 1;
57 58 59 60

/**
 * Flag for dealing with existing files: Do nothing and return FALSE.
 */
61
const FILE_EXISTS_ERROR = 2;
Dries's avatar
 
Dries committed
62

63
/**
64 65 66 67 68
 * Indicates that the file is permanent and should not be deleted.
 *
 * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
 * during cron runs, but permanent files will not be removed during the file
 * garbage collection process.
69
 */
70
const FILE_STATUS_PERMANENT = 1;
71

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
/**
 * Methods to manage a registry of stream wrappers.
 */

/**
 * Drupal stream wrapper registry.
 *
 * A stream wrapper is an abstraction of a file system that allows Drupal to
 * use the same set of methods to access both local files and remote resources.
 *
 * Provide a facility for managing and querying user-defined stream wrappers
 * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
 * registered to handle a stream, which we need to be able to find the handler
 * for class instantiation.
 *
 * If a module registers a scheme that is already registered with PHP, the
 * existing scheme will be unregistered and replaced with the specified class.
 *
 * A stream is referenced as "scheme://target".
 *
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
 * The optional $filter parameter can be used to retrieve only the stream
 * wrappers that are appropriate for particular usage. For example, this returns
 * only stream wrappers that use local file storage:
 * @code
 *   $local_stream_wrappers = file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL);
 * @endcode
 *
 * The $filter parameter can only filter to types containing a particular flag.
 * In some cases, you may want to filter to types that do not contain a
 * particular flag. For example, you may want to retrieve all stream wrappers
 * that are not writable, or all stream wrappers that are not local. PHP's
 * array_diff_key() function can be used to help with this. For example, this
 * returns only stream wrappers that do not use local file storage:
 * @code
 *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL));
 * @endcode
 *
109
 * @param $filter
110 111 112 113 114 115
 *   (Optional) Filters out all types except those with an on bit for each on
 *   bit in $filter. For example, if $filter is STREAM_WRAPPERS_WRITE_VISIBLE,
 *   which is equal to (STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE |
 *   STREAM_WRAPPERS_VISIBLE), then only stream wrappers with all three of these
 *   bits set are returned. Defaults to STREAM_WRAPPERS_ALL, which returns all
 *   registered stream wrappers.
116
 *
117
 * @return
118 119 120 121 122
 *   An array keyed by scheme, with values containing an array of information
 *   about the stream wrapper, as returned by hook_stream_wrappers(). If $filter
 *   is omitted or set to STREAM_WRAPPERS_ALL, the entire Drupal stream wrapper
 *   registry is returned. Otherwise only the stream wrappers whose 'type'
 *   bitmask has an on bit for each bit specified in $filter are returned.
123
 *
124 125 126
 * @see hook_stream_wrappers()
 * @see hook_stream_wrappers_alter()
 */
127 128
function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
  $wrappers_storage = &drupal_static(__FUNCTION__);
129

130
  if (!isset($wrappers_storage)) {
131
    $wrappers = module_invoke_all('stream_wrappers');
132 133 134 135
    foreach ($wrappers as $scheme => $info) {
      // Add defaults.
      $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
    }
136 137 138 139 140 141 142 143 144 145 146 147 148
    drupal_alter('stream_wrappers', $wrappers);
    $existing = stream_get_wrappers();
    foreach ($wrappers as $scheme => $info) {
      // We only register classes that implement our interface.
      if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) {
        // Record whether we are overriding an existing scheme.
        if (in_array($scheme, $existing, TRUE)) {
          $wrappers[$scheme]['override'] = TRUE;
          stream_wrapper_unregister($scheme);
        }
        else {
          $wrappers[$scheme]['override'] = FALSE;
        }
149 150
        if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
          stream_wrapper_register($scheme, $info['class']);
151 152
        }
        else {
153
          stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
154
        }
155
      }
156 157 158 159 160
      // Pre-populate the static cache with the filters most typically used.
      $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme];
      if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) {
        $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme];
      }
161 162
    }
  }
163 164 165 166 167

  if (!isset($wrappers_storage[$filter])) {
    $wrappers_storage[$filter] = array();
    foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
      // Bit-wise filter.
168
      if (($info['type'] & $filter) == $filter) {
169 170 171 172 173 174
        $wrappers_storage[$filter][$scheme] = $info;
      }
    }
  }

  return $wrappers_storage[$filter];
175 176 177 178 179 180 181
}

/**
 * Returns the stream wrapper class name for a given scheme.
 *
 * @param $scheme
 *   Stream scheme.
182
 *
183 184 185 186 187 188 189 190 191 192 193 194 195
 * @return
 *   Return string if a scheme has a registered handler, or FALSE.
 */
function file_stream_wrapper_get_class($scheme) {
  $wrappers = file_get_stream_wrappers();
  return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
}

/**
 * Returns the scheme of a URI (e.g. a stream).
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
196
 *
197 198 199
 * @return
 *   A string containing the name of the scheme, or FALSE if none. For example,
 *   the URI "public://example.txt" would return "public".
200 201
 *
 * @see file_uri_target()
202 203
 */
function file_uri_scheme($uri) {
204 205
  $position = strpos($uri, '://');
  return $position ? substr($uri, 0, $position) : FALSE;
206 207 208 209 210 211 212 213 214 215 216
}

/**
 * Check that the scheme of a stream URI is valid.
 *
 * Confirms that there is a registered stream handler for the provided scheme
 * and that it is callable. This is useful if you want to confirm a valid
 * scheme without creating a new instance of the registered handler.
 *
 * @param $scheme
 *   A URI scheme, a stream is referenced as "scheme://target".
217
 *
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
 * @return
 *   Returns TRUE if the string is the name of a validated stream,
 *   or FALSE if the scheme does not have a registered handler.
 */
function file_stream_wrapper_valid_scheme($scheme) {
  // Does the scheme have a registered handler that is callable?
  $class = file_stream_wrapper_get_class($scheme);
  if (class_exists($class)) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

233

234
/**
235
 * Returns the part of an URI after the schema.
236 237 238
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
239
 *
240 241 242 243
 * @return
 *   A string containing the target (path), or FALSE if none.
 *   For example, the URI "public://sample/test.txt" would return
 *   "sample/test.txt".
244 245
 *
 * @see file_uri_scheme()
246 247
 */
function file_uri_target($uri) {
248 249 250 251
  $data = explode('://', $uri, 2);

  // Remove erroneous leading or trailing, forward-slashes and backslashes.
  return count($data) == 2 ? trim($data[1], '\/') : FALSE;
252 253
}

254 255 256 257 258 259 260 261 262 263
/**
 * Get the default file stream implementation.
 *
 * @return
 *   'public', 'private' or any other file scheme defined as the default.
 */
function file_default_scheme() {
  return variable_get('file_default_scheme', 'public');
}

264 265 266 267 268 269 270 271 272 273 274
/**
 * Normalizes a URI by making it syntactically correct.
 *
 * A stream is referenced as "scheme://target".
 *
 * The following actions are taken:
 * - Remove trailing slashes from target
 * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
 *
 * @param $uri
 *   String reference containing the URI to normalize.
275
 *
276 277
 * @return
 *   The normalized URI.
278 279 280 281 282 283 284
 */
function file_stream_wrapper_uri_normalize($uri) {
  $scheme = file_uri_scheme($uri);

  if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
    $target = file_uri_target($uri);

285 286 287
    if ($target !== FALSE) {
      $uri = $scheme . '://' . $target;
    }
288
  }
289 290 291 292
  else {
    // The default scheme is file://
    $url = 'file://' . $uri;
  }
293 294 295 296
  return $uri;
}

/**
297
 * Returns a reference to the stream wrapper class responsible for a given URI.
298 299 300 301 302 303
 *
 * The scheme determines the stream wrapper class that should be
 * used by consulting the stream wrapper registry.
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
304
 *
305 306 307 308 309 310 311 312 313 314
 * @return
 *   Returns a new stream wrapper object appropriate for the given URI or FALSE
 *   if no registered handler could be found. For example, a URI of
 *   "private://example.txt" would return a new private stream wrapper object
 *   (DrupalPrivateStreamWrapper).
 */
function file_stream_wrapper_get_instance_by_uri($uri) {
  $scheme = file_uri_scheme($uri);
  $class = file_stream_wrapper_get_class($scheme);
  if (class_exists($class)) {
315
    $instance = new $class();
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
    $instance->setUri($uri);
    return $instance;
  }
  else {
    return FALSE;
  }
}

/**
 * Returns a reference to the stream wrapper class responsible for a given scheme.
 *
 * This helper method returns a stream instance using a scheme. That is, the
 * passed string does not contain a "://". For example, "public" is a scheme
 * but "public://" is a URI (stream). This is because the later contains both
 * a scheme and target despite target being empty.
 *
 * Note: the instance URI will be initialized to "scheme://" so that you can
 * make the customary method calls as if you had retrieved an instance by URI.
 *
 * @param $scheme
 *   If the stream was "public://target", "public" would be the scheme.
337
 *
338 339 340 341 342 343 344 345 346
 * @return
 *   Returns a new stream wrapper object appropriate for the given $scheme.
 *   For example, for the public scheme a stream wrapper object
 *   (DrupalPublicStreamWrapper).
 *   FALSE is returned if no registered handler could be found.
 */
function file_stream_wrapper_get_instance_by_scheme($scheme) {
  $class = file_stream_wrapper_get_class($scheme);
  if (class_exists($class)) {
347
    $instance = new $class();
348 349 350 351 352 353 354 355
    $instance->setUri($scheme . '://');
    return $instance;
  }
  else {
    return FALSE;
  }
}

Dries's avatar
 
Dries committed
356
/**
357
 * Creates a web-accessible URL for a stream to an external or local file.
Dries's avatar
 
Dries committed
358
 *
359 360
 * Compatibility: normal paths and stream wrappers.
 * @see http://drupal.org/node/515192
Dries's avatar
 
Dries committed
361
 *
362
 * There are two kinds of local files:
363 364 365
 * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
 *   These are files that have either been uploaded by users or were generated
 *   automatically (for example through CSS aggregation).
366 367 368
 * - "shipped files", i.e. those outside of the files directory, which ship as
 *   part of Drupal core or contributed modules or themes.
 *
369
 * @param $uri
370 371
 *   The URI to a file for which we need an external URL, or the path to a
 *   shipped file.
372
 *
373
 * @return
374
 *   A string containing a URL that may be used to access the file.
375 376 377
 *   If the provided string already contains a preceding 'http', 'https', or
 *   '/', nothing is done and the same string is returned. If a stream wrapper
 *   could not be found to generate an external URL, then FALSE is returned.
Dries's avatar
 
Dries committed
378
 */
379
function file_create_url($uri) {
380 381 382
  // Allow the URI to be altered, e.g. to serve a file from a CDN or static
  // file server.
  drupal_alter('file_url', $uri);
383

384 385 386
  $scheme = file_uri_scheme($uri);

  if (!$scheme) {
387 388 389 390 391 392 393 394 395 396 397 398
    // Allow for:
    // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
    // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
    //   http://example.com/bar.jpg by the browser when viewing a page over
    //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
    // Both types of relative URIs are characterized by a leading slash, hence
    // we can use a single check.
    if (drupal_substr($uri, 0, 1) == '/') {
      return $uri;
    }
    else {
      // If this is not a properly formatted stream, then it is a shipped file.
399 400
      // Therefore, return the urlencoded URI with the base URL prepended.
      return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
401
    }
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
  }
  elseif ($scheme == 'http' || $scheme == 'https') {
    // Check for http so that we don't have to implement getExternalUrl() for
    // the http wrapper.
    return $uri;
  }
  else {
    // Attempt to return an external URL using the appropriate wrapper.
    if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
      return $wrapper->getExternalUrl();
    }
    else {
      return FALSE;
    }
  }
Dries's avatar
 
Dries committed
417 418 419
}

/**
420 421 422 423 424
 * Check that the directory exists and is writable.
 *
 * Directories need to have execute permissions to be considered a directory by
 * FTP servers, etc.
 *
425
 * @param $directory
426 427 428
 *   A string reference containing the name of a directory path or URI. A
 *   trailing slash will be trimmed from a path.
 * @param $options
429 430 431
 *   A bitmask to indicate if the directory should be created if it does
 *   not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
 *   (FILE_MODIFY_PERMISSIONS).
432
 *
433
 * @return
434 435
 *   TRUE if the directory exists (or was created) and is writable. FALSE
 *   otherwise.
Dries's avatar
 
Dries committed
436
 */
437 438 439 440 441
function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
  if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) {
    // Only trim if we're not dealing with a stream.
    $directory = rtrim($directory, '/\\');
  }
Dries's avatar
 
Dries committed
442 443 444

  // Check if directory exists.
  if (!is_dir($directory)) {
445 446
    // Let mkdir() recursively create directories and use the default directory
    // permissions.
447 448
    if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
      return drupal_chmod($directory);
Dries's avatar
 
Dries committed
449
    }
450
    return FALSE;
Dries's avatar
 
Dries committed
451
  }
452 453 454 455
  // The directory exists, so check to see if it is writable.
  $writable = is_writable($directory);
  if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
    return drupal_chmod($directory);
Dries's avatar
 
Dries committed
456 457
  }

458
  return $writable;
Dries's avatar
 
Dries committed
459 460 461
}

/**
462
 * If missing, create a .htaccess file in each Drupal files directory.
Dries's avatar
 
Dries committed
463
 */
464
function file_ensure_htaccess() {
465
  file_save_htaccess('public://', FALSE);
466
  if (variable_get('file_private_path', FALSE)) {
467
    file_save_htaccess('private://', TRUE);
468
  }
469
  file_save_htaccess('temporary://', TRUE);
Dries's avatar
 
Dries committed
470 471 472
}

/**
473
 * Creates an .htaccess file in the given directory.
Dries's avatar
 
Dries committed
474
 *
475
 * @param $directory
476 477 478 479
 *   The directory.
 * @param $private
 *   FALSE indicates that $directory should be an open and public directory.
 *   The default is TRUE which indicates a private and protected directory.
Dries's avatar
 
Dries committed
480
 */
481
function file_save_htaccess($directory, $private = TRUE) {
482 483
  if (file_uri_scheme($directory)) {
    $directory = file_stream_wrapper_uri_normalize($directory);
484 485
  }
  else {
486
    $directory = rtrim($directory, '/\\');
487
  }
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
  $htaccess_path =  $directory . '/.htaccess';

  if (file_exists($htaccess_path)) {
    // Short circuit if the .htaccess file already exists.
    return;
  }

  if ($private) {
    // Private .htaccess file.
    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nDeny from all\nOptions None\nOptions +FollowSymLinks";
  }
  else {
    // Public .htaccess file.
    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
  }

  // Write the .htaccess file.
  if (file_put_contents($htaccess_path, $htaccess_lines)) {
    drupal_chmod($htaccess_path, 0444);
  }
  else {
    $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
510
    watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR);
Dries's avatar
 
Dries committed
511 512 513 514
  }
}

/**
515
 * Loads file objects from the database.
516
 *
517 518 519
 * @param $fids
 *   An array of file IDs.
 * @param $conditions
520 521 522 523 524
 *   (deprecated) An associative array of conditions on the {file_managed}
 *   table, where the keys are the database fields and the values are the
 *   values those fields must have. Instead, it is preferable to use
 *   EntityFieldQuery to retrieve a list of entity IDs loadable by
 *   this function.
525
 *
526
 * @return
527
 *   An array of file objects, indexed by fid.
528 529
 *
 * @see hook_file_load()
530
 * @see file_load()
531 532 533 534
 * @see entity_load()
 * @see EntityFieldQuery
 *
 * @todo Remove $conditions in Drupal 8.
535
 */
536
function file_load_multiple($fids = array(), $conditions = array()) {
537
  return entity_load('file', $fids, $conditions);
538
}
539

540 541 542 543
/**
 * Load a file object from the database.
 *
 * @param $fid
544
 *   A file ID.
545
 *
546 547 548 549 550 551 552 553 554
 * @return
 *   A file object.
 *
 * @see hook_file_load()
 * @see file_load_multiple()
 */
function file_load($fid) {
  $files = file_load_multiple(array($fid), array());
  return reset($files);
555 556 557 558 559
}

/**
 * Save a file object to the database.
 *
560
 * If the $file->fid is not set a new record will be added.
561 562 563
 *
 * @param $file
 *   A file object returned by file_load().
564
 *
565 566
 * @return
 *   The updated file object.
567
 *
568 569 570
 * @see hook_file_insert()
 * @see hook_file_update()
 */
571
function file_save(stdClass $file) {
572
  $file->timestamp = REQUEST_TIME;
573
  $file->filesize = filesize($file->uri);
574

575 576 577 578 579
  // Load the stored entity, if any.
  if (!empty($file->fid) && !isset($file->original)) {
    $file->original = entity_load_unchanged('file', $file->fid);
  }

580 581 582
  module_invoke_all('file_presave', $file);
  module_invoke_all('entity_presave', $file, 'file');

583
  if (empty($file->fid)) {
584
    drupal_write_record('file_managed', $file);
585 586
    // Inform modules about the newly added file.
    module_invoke_all('file_insert', $file);
587
    module_invoke_all('entity_insert', $file, 'file');
588 589
  }
  else {
590
    drupal_write_record('file_managed', $file, 'fid');
591 592
    // Inform modules that the file has been updated.
    module_invoke_all('file_update', $file);
593
    module_invoke_all('entity_update', $file, 'file');
594 595
  }

596
  unset($file->original);
597 598 599 600
  return $file;
}

/**
601 602 603 604 605 606 607
 * Determines where a file is used.
 *
 * @param $file
 *   A file object.
 *
 * @return
 *   A nested array with usage data. The first level is keyed by module name,
608 609
 *   the second by object type and the third by the object id. The value
 *   of the third level contains the usage count.
610 611 612 613 614 615 616 617 618 619 620 621
 *
 * @see file_usage_add()
 * @see file_usage_delete()
 */
function file_usage_list(stdClass $file) {
  $result = db_select('file_usage', 'f')
    ->fields('f', array('module', 'type', 'id', 'count'))
    ->condition('fid', $file->fid)
    ->condition('count', 0, '>')
    ->execute();
  $references = array();
  foreach ($result as $usage) {
622
    $references[$usage->module][$usage->type][$usage->id] = $usage->count;
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
  }
  return $references;
}

/**
 * Records that a module is using a file.
 *
 * This usage information will be queried during file_delete() to ensure that
 * a file is not in use before it is physically removed from disk.
 *
 * Examples:
 * - A module that associates files with nodes, so $type would be
 *   'node' and $id would be the node's nid. Files for all revisions are stored
 *   within a single nid.
 * - The User module associates an image with a user, so $type would be 'user'
 *   and the $id would be the user's uid.
 *
 * @param $file
 *   A file object.
 * @param $module
 *   The name of the module using the file.
 * @param $type
645
 *   The type of the object that contains the referenced file.
646
 * @param $id
647
 *   The unique, numeric ID of the object containing the referenced file.
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 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
 * @param $count
 *   (optional) The number of references to add to the object. Defaults to 1.
 *
 * @see file_usage_list()
 * @see file_usage_delete()
 */
function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
  db_merge('file_usage')
    ->key(array(
      'fid' => $file->fid,
      'module' => $module,
      'type' => $type,
      'id' => $id,
    ))
    ->fields(array('count' => $count))
    ->expression('count', 'count + :count', array(':count' => $count))
    ->execute();
}

/**
 * Removes a record to indicate that a module is no longer using a file.
 *
 * The file_delete() function is typically called after removing a file usage
 * to remove the record from the file_managed table and delete the file itself.
 *
 * @param $file
 *   A file object.
 * @param $module
 *   The name of the module using the file.
 * @param $type
 *   (optional) The type of the object that contains the referenced file. May
 *   be omitted if all module references to a file are being deleted.
 * @param $id
 *   (optional) The unique, numeric ID of the object containing the referenced
 *   file. May be omitted if all module references to a file are being deleted.
 * @param $count
 *   (optional) The number of references to delete from the object. Defaults to
 *   1. 0 may be specified to delete all references to the file within a
 *   specific object.
 *
 * @see file_usage_add()
 * @see file_usage_list()
 * @see file_delete()
 */
function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) {
  // Delete rows that have a exact or less value to prevent empty rows.
  $query = db_delete('file_usage')
    ->condition('module', $module)
    ->condition('fid', $file->fid);
  if ($type && $id) {
    $query
      ->condition('type', $type)
      ->condition('id', $id);
  }
  if ($count) {
    $query->condition('count', $count, '<=');
  }
  $result = $query->execute();

  // If the row has more than the specified count decrement it by that number.
708
  if (!$result && $count > 0) {
709 710 711 712 713 714 715 716
    $query = db_update('file_usage')
      ->condition('module', $module)
      ->condition('fid', $file->fid);
    if ($type && $id) {
      $query
        ->condition('type', $type)
        ->condition('id', $id);
    }
717
    $query->expression('count', 'count - :count', array(':count' => $count));
718 719 720 721 722 723
    $query->execute();
  }
}

/**
 * Copies a file to a new location and adds a file record to the database.
724 725 726 727 728 729 730 731 732 733
 *
 * This function should be used when manipulating files that have records
 * stored in the database. This is a powerful function that in many ways
 * performs like an advanced version of copy().
 * - Checks if $source and $destination are valid and readable/writable.
 * - Checks that $source is not equal to $destination; if they are an error
 *   is reported.
 * - If file already exists in $destination either the call will error out,
 *   replace the file or rename the file based on the $replace parameter.
 * - Adds the new file to the files database. If the source file is a
734 735
 *   temporary file, the resulting file will also be a temporary file. See
 *   file_save_upload() for details on temporary files.
736 737 738 739
 *
 * @param $source
 *   A file object.
 * @param $destination
740
 *   A string containing the destination that $source should be copied to.
741
 *   This must be a stream wrapper URI.
742 743
 * @param $replace
 *   Replace behavior when the destination file already exists:
744 745 746
 *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
 *       the destination name exists then its database entry will be updated. If
 *       no database entry is found then a new one will be created.
747
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
748
 *       unique.
749
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
750
 *
751 752
 * @return
 *   File object if the copy is successful, or FALSE in the event of an error.
753
 *
754 755 756
 * @see file_unmanaged_copy()
 * @see hook_file_copy()
 */
757
function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
758
  if (!file_valid_uri($destination)) {
759
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
760
      watchdog('file', 'File %file (%realpath) could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
761 762
    }
    else {
763
      watchdog('file', 'File %file could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
764
    }
765
    drupal_set_message(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
766 767 768
    return FALSE;
  }

769
  if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
770
    $file = clone $source;
771
    $file->fid = NULL;
772 773
    $file->uri = $uri;
    $file->filename = basename($uri);
774 775
    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
776
      $existing_files = file_load_multiple(array(), array('uri' => $uri));
777 778 779 780 781
      if (count($existing_files)) {
        $existing = reset($existing_files);
        $file->fid = $existing->fid;
        $file->filename = $existing->filename;
      }
782
    }
783 784
    // If we are renaming around an existing file (rather than a directory),
    // use its basename for the filename.
785
    elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
786 787 788 789 790 791 792 793 794
      $file->filename = basename($destination);
    }

    $file = file_save($file);

    // Inform modules that the file has been copied.
    module_invoke_all('file_copy', $file, $source);

    return $file;
795 796 797 798
  }
  return FALSE;
}

799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
/**
 * Determine whether the URI has a valid scheme for file API operations.
 *
 * There must be a scheme and it must be a Drupal-provided scheme like
 * 'public', 'private', 'temporary', or an extension provided with
 * hook_stream_wrappers().
 *
 * @param $uri
 *   The URI to be tested.
 *
 * @return
 *   TRUE if the URI is allowed.
 */
function file_valid_uri($uri) {
  // Assert that the URI has an allowed scheme. Barepaths are not allowed.
  $uri_scheme = file_uri_scheme($uri);
  if (empty($uri_scheme) || !file_stream_wrapper_valid_scheme($uri_scheme)) {
    return FALSE;
  }
  return TRUE;
}

821
/**
822
 * Copies a file to a new location without invoking the file API.
Dries's avatar
 
Dries committed
823
 *
824 825 826 827 828 829 830
 * This is a powerful function that in many ways performs like an advanced
 * version of copy().
 * - Checks if $source and $destination are valid and readable/writable.
 * - Checks that $source is not equal to $destination; if they are an error
 *   is reported.
 * - If file already exists in $destination either the call will error out,
 *   replace the file or rename the file based on the $replace parameter.
831 832 833 834
 * - Provides a fallback using realpaths if the move fails using stream
 *   wrappers. This can occur because PHP's copy() function does not properly
 *   support streams if safe_mode or open_basedir are enabled. See
 *   https://bugs.php.net/bug.php?id=60456
835 836
 *
 * @param $source
837
 *   A string specifying the filepath or URI of the source file.
838
 * @param $destination
839 840 841 842
 *   A URI containing the destination that $source should be copied to. The
 *   URI may be a bare filepath (without a scheme) and in that case the default
 *   scheme (file://) will be used. If this value is omitted, Drupal's default
 *   files scheme will be used, usually "public://".
843 844 845 846
 * @param $replace
 *   Replace behavior when the destination file already exists:
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
847
 *       unique.
848
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
849
 *
850 851
 * @return
 *   The path to the new file, or FALSE in the event of an error.
852
 *
853
 * @see file_copy()
Dries's avatar
 
Dries committed
854
 */
855
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
856 857 858
  $original_source = $source;
  $original_destination = $destination;

859
  // Assert that the source file actually exists.
860
  if (!file_exists($source)) {
861
    // @todo Replace drupal_set_message() calls with exceptions instead.
862
    drupal_set_message(t('The specified file %file could not be copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
863 864 865 866 867 868
    if (($realpath = drupal_realpath($original_source)) !== FALSE) {
      watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
    }
    else {
      watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
    }
869 870
    return FALSE;
  }
Dries's avatar
 
Dries committed
871

872 873 874 875
  // Build a destination URI if necessary.
  if (!isset($destination)) {
    $destination = file_build_uri(basename($source));
  }
Dries's avatar
 
Dries committed
876 877


878 879 880 881 882 883 884 885 886 887
  // Prepare the destination directory.
  if (file_prepare_directory($destination)) {
    // The destination is already a directory, so append the source basename.
    $destination = file_stream_wrapper_uri_normalize($destination . '/' . basename($source));
  }
  else {
    // Perhaps $destination is a dir/file?
    $dirname = drupal_dirname($destination);
    if (!file_prepare_directory($dirname)) {
      // The destination is not valid.
888 889
      watchdog('file', 'File %file could not be copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
      drupal_set_message(t('The specified file %file could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
890 891 892
      return FALSE;
    }
  }
893

894 895
  // Determine whether we can perform this operation based on overwrite rules.
  $destination = file_destination($destination, $replace);
896
  if ($destination === FALSE) {
897
    drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
898
    watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination));
899
    return FALSE;
Dries's avatar
 
Dries committed
900
  }
901 902

  // Assert that the source and destination filenames are not the same.
903 904 905
  $real_source = drupal_realpath($source);
  $real_destination = drupal_realpath($destination);
  if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
906
    drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
907
    watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
908
    return FALSE;
Dries's avatar
 
Dries committed
909
  }
910 911 912
  // Make sure the .htaccess files are present.
  file_ensure_htaccess();
  // Perform the copy operation.
913
  if (!@copy($source, $destination)) {
914 915 916 917 918 919
    // If the copy failed and realpaths exist, retry the operation using them
    // instead.
    if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
      watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
      return FALSE;
    }
Dries's avatar
 
Dries committed
920
  }
Dries's avatar
 
Dries committed
921

922 923
  // Set the permissions on the new file.
  drupal_chmod($destination);
924 925

  return $destination;
Dries's avatar
 
Dries committed
926 927
}

928 929 930 931
/**
 * Given a relative path, construct a URI into Drupal's default files location.
 */
function file_build_uri($path) {
932
  $uri = file_default_scheme() . '://' . $path;
933 934 935
  return file_stream_wrapper_uri_normalize($uri);
}

936 937 938 939
/**
 * Determines the destination path for a file depending on how replacement of
 * existing files should be handled.
 *
940
 * @param $destination
941
 *   A string specifying the desired final URI or filepath.
942 943
 * @param $replace
 *   Replace behavior when the destination file already exists.
944
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
945
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
946
 *       unique.
947
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
948
 *
949
 * @return
950 951
 *   The destination filepath, or FALSE if the file already exists
 *   and FILE_EXISTS_ERROR is specified.
952 953 954 955
 */
function file_destination($destination, $replace) {
  if (file_exists($destination)) {
    switch ($replace) {
956 957 958 959
      case FILE_EXISTS_REPLACE:
        // Do nothing here, we want to overwrite the existing file.
        break;

960 961
      case FILE_EXISTS_RENAME:
        $basename = basename($destination);
962
        $directory = drupal_dirname($destination);
963 964 965 966
        $destination = file_create_filename($basename, $directory);
        break;

      case FILE_EXISTS_ERROR:
967
        // Error reporting handled by calling function.
968 969 970 971 972 973
        return FALSE;
    }
  }
  return $destination;
}

Dries's avatar
 
Dries committed
974
/**
975 976 977 978 979 980 981 982 983 984 985 986 987
 * Move a file to a new location and update the file's database entry.
 *
 * Moving a file is performed by copying the file to the new location and then
 * deleting the original.
 * - Checks if $source and $destination are valid and readable/writable.
 * - Performs a file move if $source is not equal to $destination.
 * - If file already exists in $destination either the call will error out,
 *   replace the file or rename the file based on the $replace parameter.
 * - Adds the new file to the files database.
 *
 * @param $source
 *   A file object.
 * @param $destination
988
 *   A string containing the destination that $source should be moved to.
989
 *   This must be a stream wrapper URI.
990 991
 * @param $replace
 *   Replace behavior when the destination file already exists:
992 993 994 995 996
 *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
 *       the destination name exists then its database entry will be updated and
 *       file_delete() called on the source file after hook_file_move is called.
 *       If no database entry is found then the source files record will be
 *       updated.
997
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
998
 *       unique.
999
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1000
 *
1001 1002
 * @return
 *   Resulting file object for success, or FALSE in the event of an error.
1003
 *
1004 1005 1006
 * @see file_unmanaged_move()
 * @see hook_file_move()
 */
1007
function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1008
  if (!file_valid_uri($destination)) {
1009
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
1010
      watchdog('file', 'File %file (%realpath) could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
1011 1012
    }
    else {
1013
      watchdog('file', 'File %file could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
1014
    }
1015
    drupal_set_message(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
1016 1017 1018
    return FALSE;
  }

1019
  if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
1020 1021
    $delete_source = FALSE;

1022
    $file = clone $source;
1023
    $file->uri = $uri;
1024 1025
    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
1026
      $existing_files = file_load_multiple(array(), array('uri' => $uri));
1027 1028 1029 1030 1031 1032 1033 1034
      if (count($existing_files)) {
        $existing = reset($existing_files);
        $delete_source = TRUE;
        $file->fid = $existing->fid;
      }
    }
    // If we are renaming around an existing file (rather than a directory),
    // use its basename for the filename.
1035
    elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
1036
      $file->filename = basename($destination);
1037
    }
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049

    $file = file_save($file);

    // Inform modules that the file has been moved.
    module_invoke_all('file_move', $file, $source);

    if ($delete_source) {
      // Try a soft delete to remove original if it's not in use elsewhere.
      file_delete($source);
    }

    return $file;
1050 1051 1052 1053 1054 1055 1056
  }
  return FALSE;
}

/**
 * Move a file to a new location without calling any hooks or making any
 * changes to the database.
Dries's avatar
 
Dries committed
1057
 *
1058
 * @param $source
1059
 *   A string specifying the filepath or URI of the original file.
1060
 * @param $destination
1061 1062 1063
 *   A string containing the destination that $source should be moved to.
 *   This must be a stream wrapper URI. If this value is omitted, Drupal's
 *   default files scheme will be used, usually "public://".
1064 1065 1066 1067
 * @param $replace
 *   Replace behavior when the destination file already exists:
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
1068
 *       unique.
1069
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1070
 *
1071
 * @return
1072
 *   The URI of the moved file, or FALSE in the event of an error.
1073
 *
1074
 * @see file_move()
Dries's avatar
 
Dries committed
1075
 */
1076 1077 1078
function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  $filepath = file_unmanaged_copy($source, $destination, $replace);
  if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
1079
    return FALSE;
Dries's avatar
 
Dries committed
1080
  }
1081
  return $filepath;
Dries's avatar
 
Dries committed
1082 1083
}

1084
/**
1085
 * Modify a filename as needed for security purposes.
1086
 *
1087 1088 1089 1090 1091