file.inc 86.2 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
 * Provides Drupal stream wrapper registry.
74 75 76 77 78 79 80 81 82 83 84 85 86 87
 *
 * 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".
 *
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
 * 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
 *
105
 * @param $filter
106 107 108 109 110 111
 *   (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.
112
 *
113
 * @return
114 115 116 117 118
 *   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.
119
 *
120 121 122
 * @see hook_stream_wrappers()
 * @see hook_stream_wrappers_alter()
 */
123 124
function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
  $wrappers_storage = &drupal_static(__FUNCTION__);
125

126
  if (!isset($wrappers_storage)) {
127
    $wrappers = module_invoke_all('stream_wrappers');
128 129 130 131
    foreach ($wrappers as $scheme => $info) {
      // Add defaults.
      $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
    }
132 133 134 135 136 137 138 139 140 141 142 143 144
    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;
        }
145 146
        if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
          stream_wrapper_register($scheme, $info['class']);
147 148
        }
        else {
149
          stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
150
        }
151
      }
152 153 154 155 156
      // 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];
      }
157 158
    }
  }
159 160 161 162 163

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

  return $wrappers_storage[$filter];
171 172 173 174 175 176 177
}

/**
 * Returns the stream wrapper class name for a given scheme.
 *
 * @param $scheme
 *   Stream scheme.
178
 *
179 180 181 182 183 184 185 186 187 188 189 190 191
 * @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".
192
 *
193 194 195
 * @return
 *   A string containing the name of the scheme, or FALSE if none. For example,
 *   the URI "public://example.txt" would return "public".
196 197
 *
 * @see file_uri_target()
198 199
 */
function file_uri_scheme($uri) {
200 201
  $position = strpos($uri, '://');
  return $position ? substr($uri, 0, $position) : FALSE;
202 203 204
}

/**
205
 * Checks that the scheme of a stream URI is valid.
206 207 208 209 210 211 212
 *
 * 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".
213
 *
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
 * @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;
  }
}

229

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

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

250
/**
251
 * Gets the default file stream implementation.
252 253 254 255 256 257 258 259
 *
 * @return
 *   'public', 'private' or any other file scheme defined as the default.
 */
function file_default_scheme() {
  return variable_get('file_default_scheme', 'public');
}

260 261 262 263 264 265 266 267 268 269 270
/**
 * 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.
271
 *
272 273
 * @return
 *   The normalized URI.
274 275 276 277 278 279 280
 */
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);

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

/**
293
 * Returns a reference to the stream wrapper class responsible for a given URI.
294 295 296 297 298 299
 *
 * 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".
300
 *
301 302 303 304 305 306 307 308 309 310
 * @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)) {
311
    $instance = new $class();
312 313 314 315 316 317 318 319 320
    $instance->setUri($uri);
    return $instance;
  }
  else {
    return FALSE;
  }
}

/**
321
 * Returns a reference to the stream wrapper class responsible for a scheme.
322 323 324 325 326 327 328 329 330 331 332
 *
 * 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.
333
 *
334 335 336 337 338 339 340 341 342
 * @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)) {
343
    $instance = new $class();
344 345 346 347 348 349 350 351
    $instance->setUri($scheme . '://');
    return $instance;
  }
  else {
    return FALSE;
  }
}

Dries's avatar
 
Dries committed
352
/**
353
 * Creates a web-accessible URL for a stream to an external or local file.
Dries's avatar
 
Dries committed
354
 *
355
 * Compatibility: normal paths and stream wrappers.
Dries's avatar
 
Dries committed
356
 *
357
 * There are two kinds of local files:
358 359 360
 * - "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).
361 362 363
 * - "shipped files", i.e. those outside of the files directory, which ship as
 *   part of Drupal core or contributed modules or themes.
 *
364
 * @param $uri
365 366
 *   The URI to a file for which we need an external URL, or the path to a
 *   shipped file.
367
 *
368
 * @return
369
 *   A string containing a URL that may be used to access the file.
370 371 372
 *   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.
373 374
 *
 * @see http://drupal.org/node/515192
Dries's avatar
 
Dries committed
375
 */
376
function file_create_url($uri) {
377 378 379
  // Allow the URI to be altered, e.g. to serve a file from a CDN or static
  // file server.
  drupal_alter('file_url', $uri);
380

381 382 383
  $scheme = file_uri_scheme($uri);

  if (!$scheme) {
384 385 386 387 388 389 390 391 392 393 394 395
    // 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.
396 397
      // Therefore, return the urlencoded URI with the base URL prepended.
      return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
398
    }
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
  }
  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
414 415 416
}

/**
417
 * Checks that the directory exists and is writable.
418 419 420 421
 *
 * Directories need to have execute permissions to be considered a directory by
 * FTP servers, etc.
 *
422
 * @param $directory
423 424 425
 *   A string reference containing the name of a directory path or URI. A
 *   trailing slash will be trimmed from a path.
 * @param $options
426 427 428
 *   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).
429
 *
430
 * @return
431 432
 *   TRUE if the directory exists (or was created) and is writable. FALSE
 *   otherwise.
Dries's avatar
 
Dries committed
433
 */
434 435 436 437 438
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
439 440 441

  // Check if directory exists.
  if (!is_dir($directory)) {
442 443
    // Let mkdir() recursively create directories and use the default directory
    // permissions.
444 445
    if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
      return drupal_chmod($directory);
Dries's avatar
 
Dries committed
446
    }
447
    return FALSE;
Dries's avatar
 
Dries committed
448
  }
449 450 451 452
  // 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
453 454
  }

455
  return $writable;
Dries's avatar
 
Dries committed
456 457 458
}

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

/**
471
 * Creates a .htaccess file in the given directory.
Dries's avatar
 
Dries committed
472
 *
473
 * @param $directory
474 475 476 477
 *   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
478
 */
479
function file_save_htaccess($directory, $private = TRUE) {
480 481
  if (file_uri_scheme($directory)) {
    $directory = file_stream_wrapper_uri_normalize($directory);
482 483
  }
  else {
484
    $directory = rtrim($directory, '/\\');
485
  }
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
  $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)));
508
    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
509 510 511 512
  }
}

/**
513
 * Loads file objects from the database.
514
 *
515 516 517
 * @param $fids
 *   An array of file IDs.
 * @param $conditions
518 519 520 521 522
 *   (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.
523
 *
524
 * @return
525
 *   An array of file objects, indexed by fid.
526
 *
527 528
 * @todo Remove $conditions in Drupal 8.
 *
529
 * @see hook_file_load()
530
 * @see file_load()
531 532
 * @see entity_load()
 * @see EntityFieldQuery
533
 */
534
function file_load_multiple($fids = array(), $conditions = array()) {
535
  return entity_load('file', $fids, $conditions);
536
}
537

538
/**
539
 * Loads a single file object from the database.
540 541
 *
 * @param $fid
542
 *   A file ID.
543
 *
544 545 546 547 548 549 550 551 552
 * @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);
553 554 555
}

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

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

578 579 580
  module_invoke_all('file_presave', $file);
  module_invoke_all('entity_presave', $file, 'file');

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

594
  unset($file->original);
595 596 597 598
  return $file;
}

/**
599 600 601 602 603 604 605
 * 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,
606 607
 *   the second by object type and the third by the object id. The value
 *   of the third level contains the usage count.
608 609 610 611 612 613 614 615 616 617 618 619
 *
 * @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) {
620
    $references[$usage->module][$usage->type][$usage->id] = $usage->count;
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
  }
  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
643
 *   The type of the object that contains the referenced file.
644
 * @param $id
645
 *   The unique, numeric ID of the object containing the referenced file.
646 647 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
 * @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.
706
  if (!$result && $count > 0) {
707 708 709 710 711 712 713 714
    $query = db_update('file_usage')
      ->condition('module', $module)
      ->condition('fid', $file->fid);
    if ($type && $id) {
      $query
        ->condition('type', $type)
        ->condition('id', $id);
    }
715
    $query->expression('count', 'count - :count', array(':count' => $count));
716 717 718 719 720 721
    $query->execute();
  }
}

/**
 * Copies a file to a new location and adds a file record to the database.
722 723 724 725 726 727 728 729 730 731
 *
 * 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
732 733
 *   temporary file, the resulting file will also be a temporary file. See
 *   file_save_upload() for details on temporary files.
734 735 736 737
 *
 * @param $source
 *   A file object.
 * @param $destination
738
 *   A string containing the destination that $source should be copied to.
739
 *   This must be a stream wrapper URI.
740 741
 * @param $replace
 *   Replace behavior when the destination file already exists:
742 743 744
 *   - 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.
745
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
746
 *       unique.
747
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
748
 *
749 750
 * @return
 *   File object if the copy is successful, or FALSE in the event of an error.
751
 *
752 753 754
 * @see file_unmanaged_copy()
 * @see hook_file_copy()
 */
755
function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
756
  if (!file_valid_uri($destination)) {
757
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
758
      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));
759 760
    }
    else {
761
      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));
762
    }
763
    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');
764 765 766
    return FALSE;
  }

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

    $file = file_save($file);

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

    return $file;
793 794 795 796
  }
  return FALSE;
}

797
/**
798
 * Determines whether the URI has a valid scheme for file API operations.
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
 *
 * 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;
}

819
/**
820
 * Copies a file to a new location without invoking the file API.
Dries's avatar
 
Dries committed
821
 *
822 823 824 825 826 827 828
 * 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.
829 830 831 832
 * - 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
833 834
 *
 * @param $source
835
 *   A string specifying the filepath or URI of the source file.
836
 * @param $destination
837 838 839 840
 *   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://".
841 842 843 844
 * @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
845
 *       unique.
846
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
847
 *
848 849
 * @return
 *   The path to the new file, or FALSE in the event of an error.
850
 *
851
 * @see file_copy()
Dries's avatar
 
Dries committed
852
 */
853
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
854 855 856
  $original_source = $source;
  $original_destination = $destination;

857
  // Assert that the source file actually exists.
858
  if (!file_exists($source)) {
859
    // @todo Replace drupal_set_message() calls with exceptions instead.
860
    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');
861 862 863 864 865 866
    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));
    }
867 868
    return FALSE;
  }
Dries's avatar
 
Dries committed
869

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


876 877 878
  // Prepare the destination directory.
  if (file_prepare_directory($destination)) {
    // The destination is already a directory, so append the source basename.
879
    $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
880 881 882 883 884 885
  }
  else {
    // Perhaps $destination is a dir/file?
    $dirname = drupal_dirname($destination);
    if (!file_prepare_directory($dirname)) {
      // The destination is not valid.
886 887
      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');
888 889 890
      return FALSE;
    }
  }
891

892 893
  // Determine whether we can perform this operation based on overwrite rules.
  $destination = file_destination($destination, $replace);
894
  if ($destination === FALSE) {
895
    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');
896
    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));
897
    return FALSE;
Dries's avatar
 
Dries committed
898
  }
899 900

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

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

  return $destination;
Dries's avatar
 
Dries committed
924 925
}

926
/**
927
 * Constructs a URI to Drupal's default files location given a relative path.
928 929
 */
function file_build_uri($path) {
930
  $uri = file_default_scheme() . '://' . $path;
931 932 933
  return file_stream_wrapper_uri_normalize($uri);
}

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

957
      case FILE_EXISTS_RENAME:
958
        $basename = drupal_basename($destination);
959
        $directory = drupal_dirname($destination);
960 961 962 963
        $destination = file_create_filename($basename, $directory);
        break;

      case FILE_EXISTS_ERROR:
964
        // Error reporting handled by calling function.
965 966 967 968 969 970
        return FALSE;
    }
  }
  return $destination;
}

Dries's avatar
 
Dries committed
971
/**
972
 * Moves a file to a new location and update the file's database entry.
973 974 975 976 977 978 979 980 981 982 983 984
 *
 * 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
985
 *   A string containing the destination that $source should be moved to.
986
 *   This must be a stream wrapper URI.
987 988
 * @param $replace
 *   Replace behavior when the destination file already exists:
989 990 991 992 993
 *   - 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.
994
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
995
 *       unique.
996
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
997
 *
998 999
 * @return
 *   Resulting file object for success, or FALSE in the event of an error.
1000
 *
1001 1002 1003
 * @see file_unmanaged_move()
 * @see hook_file_move()
 */
1004
function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1005
  if (!file_valid_uri($destination)) {
1006
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
1007
      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));
1008 1009
    }
    else {
1010
      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));
1011
    }
1012
    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');
1013 1014 1015
    return FALSE;
  }

1016
  if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
1017 1018
    $delete_source = FALSE;

1019
    $file = clone $source;
1020
    $file->uri = $uri;
1021 1022
    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
1023
      $existing_files = file_load_multiple(array(), array('uri' => $uri));
1024 1025 1026 1027 1028 1029 1030 1031
      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.
1032
    elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
1033
      $file->filename = drupal_basename($destination);
1034
    }
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046

    $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;
1047 1048 1049 1050 1051
  }
  return FALSE;
}

/**