file.inc 86.5 KB
Newer Older
1
<?php
2

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 15 16
 * 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).
 */
require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc';

17
/**
18
 * @defgroup file File interface
19
 * @{
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.
36 37
 */

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

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

/**
49
 * Flag for dealing with existing files: Appends number until name is unique.
50
 */
51
define('FILE_EXISTS_RENAME', 0);
52 53 54 55

/**
 * Flag for dealing with existing files: Replace the existing file.
 */
56
define('FILE_EXISTS_REPLACE', 1);
57 58 59 60

/**
 * Flag for dealing with existing files: Do nothing and return FALSE.
 */
61
define('FILE_EXISTS_ERROR', 2);
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 71
define('FILE_STATUS_PERMANENT', 1);

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
 * 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
92
 *   $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
93 94 95 96 97 98 99 100 101
 * @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
102
 *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));
103 104
 * @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
  }
  return $uri;
}

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

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

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

377 378 379
  $scheme = file_uri_scheme($uri);

  if (!$scheme) {
380 381 382 383 384 385 386 387 388 389 390 391
    // 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.
392 393
      // Therefore, return the urlencoded URI with the base URL prepended.
      return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
394
    }
395 396
  }
  elseif ($scheme == 'http' || $scheme == 'https') {
397 398
    // Check for HTTP so that we don't have to implement getExternalUrl() for
    // the HTTP wrapper.
399 400 401 402 403 404 405 406 407 408 409
    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;
    }
  }
410 411 412
}

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

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

451
  return $writable;
452 453 454
}

/**
455
 * Creates a .htaccess file in each Drupal files directory if it is missing.
456
 */
457 458
function file_ensure_htaccess() {
  file_create_htaccess('public://', FALSE);
459 460 461
  if (variable_get('file_private_path', FALSE)) {
    file_create_htaccess('private://', TRUE);
  }
462
  file_create_htaccess('temporary://', TRUE);
463 464 465
}

/**
466
 * Creates a .htaccess file in the given directory.
467
 *
468
 * @param $directory
469 470 471 472
 *   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.
David_Rothstein's avatar
David_Rothstein committed
473 474 475
 * @param $force_overwrite
 *   Set to TRUE to attempt to overwrite the existing .htaccess file if one is
 *   already present. Defaults to FALSE.
476
 */
David_Rothstein's avatar
David_Rothstein committed
477
function file_create_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
478 479
  if (file_uri_scheme($directory)) {
    $directory = file_stream_wrapper_uri_normalize($directory);
480 481
  }
  else {
482
    $directory = rtrim($directory, '/\\');
483
  }
484 485
  $htaccess_path =  $directory . '/.htaccess';

David_Rothstein's avatar
David_Rothstein committed
486
  if (file_exists($htaccess_path) && !$force_overwrite) {
487 488 489 490
    // Short circuit if the .htaccess file already exists.
    return;
  }

David_Rothstein's avatar
David_Rothstein committed
491
  $htaccess_lines = file_htaccess_lines($private);
492 493 494 495 496 497 498 499

  // 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)));
    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);
500 501 502
  }
}

David_Rothstein's avatar
David_Rothstein committed
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
/**
 * Returns the standard .htaccess lines that Drupal writes to file directories.
 *
 * @param $private
 *   (Optional) Set to FALSE to return the .htaccess lines for an open and
 *   public directory. The default is TRUE, which returns the .htaccess lines
 *   for a private and protected directory.
 *
 * @return
 *   A string representing the desired contents of the .htaccess file.
 *
 * @see file_create_htaccess()
 */
function file_htaccess_lines($private = TRUE) {
  $lines = <<<EOF
# Turn off all options we don't need.
Options None
Options +FollowSymLinks

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off
</IfModule>
EOF;

  if ($private) {
    $lines = "Deny from all\n\n" . $lines;
  }

  return $lines;
}

542
/**
543
 * Loads file objects from the database.
544
 *
545 546 547
 * @param $fids
 *   An array of file IDs.
 * @param $conditions
548 549 550 551 552
 *   (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.
553
 *
554
 * @return
555
 *   An array of file objects, indexed by fid.
556
 *
557 558
 * @todo Remove $conditions in Drupal 8.
 *
559
 * @see hook_file_load()
560
 * @see file_load()
561 562
 * @see entity_load()
 * @see EntityFieldQuery
563
 */
564
function file_load_multiple($fids = array(), $conditions = array()) {
565
  return entity_load('file', $fids, $conditions);
566
}
567

568
/**
569
 * Loads a single file object from the database.
570 571
 *
 * @param $fid
572
 *   A file ID.
573
 *
574
 * @return
575
 *   An object representing the file, or FALSE if the file was not found.
576 577 578 579 580 581 582
 *
 * @see hook_file_load()
 * @see file_load_multiple()
 */
function file_load($fid) {
  $files = file_load_multiple(array($fid), array());
  return reset($files);
583 584 585
}

/**
586
 * Saves a file object to the database.
587
 *
588
 * If the $file->fid is not set a new record will be added.
589 590 591
 *
 * @param $file
 *   A file object returned by file_load().
592
 *
593 594
 * @return
 *   The updated file object.
595
 *
596 597 598
 * @see hook_file_insert()
 * @see hook_file_update()
 */
599
function file_save(stdClass $file) {
600
  $file->timestamp = REQUEST_TIME;
601
  $file->filesize = filesize($file->uri);
602

603 604 605 606 607
  // Load the stored entity, if any.
  if (!empty($file->fid) && !isset($file->original)) {
    $file->original = entity_load_unchanged('file', $file->fid);
  }

608 609 610
  module_invoke_all('file_presave', $file);
  module_invoke_all('entity_presave', $file, 'file');

611
  if (empty($file->fid)) {
612
    drupal_write_record('file_managed', $file);
613 614
    // Inform modules about the newly added file.
    module_invoke_all('file_insert', $file);
615
    module_invoke_all('entity_insert', $file, 'file');
616 617
  }
  else {
618
    drupal_write_record('file_managed', $file, 'fid');
619 620
    // Inform modules that the file has been updated.
    module_invoke_all('file_update', $file);
621
    module_invoke_all('entity_update', $file, 'file');
622 623
  }

624
  unset($file->original);
625 626 627 628
  return $file;
}

/**
629 630 631 632 633 634 635
 * 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,
636 637
 *   the second by object type and the third by the object id. The value
 *   of the third level contains the usage count.
638 639 640 641 642 643 644 645 646 647 648 649
 *
 * @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) {
650
    $references[$usage->module][$usage->type][$usage->id] = $usage->count;
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
  }
  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
673
 *   The type of the object that contains the referenced file.
674
 * @param $id
675
 *   The unique, numeric ID of the object containing the referenced file.
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 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
 * @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.
736
  if (!$result && $count > 0) {
737 738 739 740 741 742 743 744
    $query = db_update('file_usage')
      ->condition('module', $module)
      ->condition('fid', $file->fid);
    if ($type && $id) {
      $query
        ->condition('type', $type)
        ->condition('id', $id);
    }
745
    $query->expression('count', 'count - :count', array(':count' => $count));
746 747 748 749 750 751
    $query->execute();
  }
}

/**
 * Copies a file to a new location and adds a file record to the database.
752 753 754 755 756 757 758 759 760 761
 *
 * 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
762 763
 *   temporary file, the resulting file will also be a temporary file. See
 *   file_save_upload() for details on temporary files.
764 765 766 767
 *
 * @param $source
 *   A file object.
 * @param $destination
768
 *   A string containing the destination that $source should be copied to.
769
 *   This must be a stream wrapper URI.
770 771
 * @param $replace
 *   Replace behavior when the destination file already exists:
772 773 774
 *   - 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.
775
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
776
 *       unique.
777
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
778
 *
779 780
 * @return
 *   File object if the copy is successful, or FALSE in the event of an error.
781
 *
782 783 784
 * @see file_unmanaged_copy()
 * @see hook_file_copy()
 */
785
function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
786
  if (!file_valid_uri($destination)) {
787 788 789 790 791 792
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
      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));
    }
    else {
      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));
    }
793 794 795 796
    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');
    return FALSE;
  }

797
  if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
798
    $file = clone $source;
799
    $file->fid = NULL;
800
    $file->uri = $uri;
801
    $file->filename = drupal_basename($uri);
802 803
    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
804
      $existing_files = file_load_multiple(array(), array('uri' => $uri));
805 806 807 808 809
      if (count($existing_files)) {
        $existing = reset($existing_files);
        $file->fid = $existing->fid;
        $file->filename = $existing->filename;
      }
810
    }
811 812
    // If we are renaming around an existing file (rather than a directory),
    // use its basename for the filename.
813
    elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
814
      $file->filename = drupal_basename($destination);
815 816 817 818 819 820 821 822
    }

    $file = file_save($file);

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

    return $file;
823 824 825 826
  }
  return FALSE;
}

827
/**
828
 * Determines whether the URI has a valid scheme for file API operations.
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
 *
 * 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;
}

849
/**
850
 * Copies a file to a new location without invoking the file API.
851
 *
852 853 854 855 856 857 858
 * 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.
859 860 861 862
 * - 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
863 864
 *
 * @param $source
865
 *   A string specifying the filepath or URI of the source file.
866
 * @param $destination
867
 *   A URI containing the destination that $source should be copied to. The
868 869
 *   URI may be a bare filepath (without a scheme). If this value is omitted,
 *   Drupal's default files scheme will be used, usually "public://".
870 871 872 873
 * @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
874
 *       unique.
875
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
876
 *
877 878
 * @return
 *   The path to the new file, or FALSE in the event of an error.
879
 *
880
 * @see file_copy()
881
 */
882
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
883 884 885
  $original_source = $source;
  $original_destination = $destination;

886
  // Assert that the source file actually exists.
887
  if (!file_exists($source)) {
888
    // @todo Replace drupal_set_message() calls with exceptions instead.
889
    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');
890 891 892 893 894 895
    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));
    }
896 897
    return FALSE;
  }
898

899 900
  // Build a destination URI if necessary.
  if (!isset($destination)) {
901
    $destination = file_build_uri(drupal_basename($source));
902
  }
903 904


905 906 907
  // Prepare the destination directory.
  if (file_prepare_directory($destination)) {
    // The destination is already a directory, so append the source basename.
908
    $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
909 910 911 912 913 914
  }
  else {
    // Perhaps $destination is a dir/file?
    $dirname = drupal_dirname($destination);
    if (!file_prepare_directory($dirname)) {
      // The destination is not valid.
915
      watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
916
      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');
917 918 919
      return FALSE;
    }
  }
920

921 922
  // Determine whether we can perform this operation based on overwrite rules.
  $destination = file_destination($destination, $replace);
923
  if ($destination === FALSE) {
924
    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');
925
    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, '%directory' => $destination));
926
    return FALSE;
927
  }
928 929

  // Assert that the source and destination filenames are not the same.
930 931 932
  $real_source = drupal_realpath($source);
  $real_destination = drupal_realpath($destination);
  if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
933
    drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
934
    watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
935
    return FALSE;
936
  }
937 938 939
  // Make sure the .htaccess files are present.
  file_ensure_htaccess();
  // Perform the copy operation.
940
  if (!@copy($source, $destination)) {
941 942 943 944 945 946
    // 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;
    }
947
  }
948

949 950
  // Set the permissions on the new file.
  drupal_chmod($destination);
951 952

  return $destination;
953 954
}

955
/**
956
 * Constructs a URI to Drupal's default files location given a relative path.
957 958
 */
function file_build_uri($path) {
959
  $uri = file_default_scheme() . '://' . $path;
960 961 962
  return file_stream_wrapper_uri_normalize($uri);
}

963
/**
964
 * Determines the destination path for a file.
965
 *
966
 * @param $destination
967
 *   A string specifying the desired final URI or filepath.
968 969
 * @param $replace
 *   Replace behavior when the destination file already exists.
970
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
971
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
972
 *       unique.
973
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
974
 *
975
 * @return
976 977
 *   The destination filepath, or FALSE if the file already exists
 *   and FILE_EXISTS_ERROR is specified.
978 979 980 981
 */
function file_destination($destination, $replace) {
  if (file_exists($destination)) {
    switch ($replace) {
982 983 984 985
      case FILE_EXISTS_REPLACE:
        // Do nothing here, we want to overwrite the existing file.
        break;

986
      case FILE_EXISTS_RENAME:
987
        $basename = drupal_basename($destination);
988
        $directory = drupal_dirname($destination);
989 990 991 992
        $destination = file_create_filename($basename, $directory);
        break;

      case FILE_EXISTS_ERROR:
993
        // Error reporting handled by calling function.
994 995 996 997 998 999
        return FALSE;
    }
  }
  return $destination;
}

1000
/**
1001
 * Moves a file to a new location and update the file's database entry.
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
 *
 * 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
1014
 *   A string containing the destination that $source should be moved to.
1015
 *   This must be a stream wrapper URI.
1016 1017
 * @param $replace
 *   Replace behavior when the destination file already exists:
1018 1019 1020 1021 1022
 *   - 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.
1023
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
1024
 *       unique.
1025
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1026
 *
1027 1028
 * @return
 *   Resulting file object for success, or FALSE in the event of an error.
1029
 *
1030 1031 1032
 * @see file_unmanaged_move()
 * @see hook_file_move()
 */
1033
function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1034
  if (!file_valid_uri($destination)) {
1035 1036 1037 1038 1039 1040
    if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
      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));
    }
    else {
      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));
    }
1041 1042 1043 1044
    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');
    return FALSE;
  }

1045
  if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
1046 1047
    $delete_source = FALSE;

1048
    $file = clone $source;
1049
    $file->uri = $uri;
1050 1051
    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
1052
      $existing_files = file_load_multiple(array(), array('uri' => $uri));
1053 1054 1055 1056 1057 1058 1059 1060
      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.
1061
    elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
1062
      $file->filename = drupal_basename($destination);
1063
    }
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075

    $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;
1076 1077 1078 1079 1080
  }
  return FALSE;
}

/**
1081
 * Moves a file to a new location without database changes or hook invocation.
1082
 *
1083
 * @param $source
1084
 *   A string specifying the filepath or URI of the original file.
1085
 * @param $destination
1086 1087 1088
 *   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://".
1089 1090 1091 1092
 * @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
1093
 *       unique.
1094
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1095
 *
1096
 * @return
1097
 *   The URI of the moved file, or FALSE in the event of an error.
1098
 *
1099
 * @see file_move()
1100
 */
1101 1102 1103
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) {
1104
    return FALSE;
1105
  }
1106
  return $filepath;
1107 1108
}

1109
/**
1110
 * Modifies a filename as needed for security purposes.
1111
 *
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
 * Munging a file name prevents unknown file extensions from masking exploit
 * files. When web servers such as Apache decide how to process a URL request,
 * they use the file extension. If the extension is not recognized, Apache
 * skips that extension and uses the previous file extension. For example, if
 * the file being requested is exploit.php.pps, and Apache does not recognize
 * the '.pps' extension, it treats the file as PHP and executes it. To make
 * this file name safe for Apache and prevent it from executing as PHP, the
 * .php extension is "munged" into .php_, making the safe file name
 * exploit.php_.pps.
 *
 * Specifically, this function adds an underscore to all extensions that are
1123
 * between 2 and 5 characters in length, internal to the file name, and not
1124 1125 1126 1127 1128
 * included in $extensions.
 *
 * Function behavior is also controlled by the Drupal variable
 * 'allow_insecure_uploads'. If 'allow_insecure_uploads' evaluates to TRUE, no
 * alterations will be made, if it evaluates to FALSE, the filename is 'munged'.
1129 1130
 *
 * @param $filename
1131
 *   File name to modify.
1132
 * @param $extensions
1133
 *   A space-separated list of extensions that should not be altered.
1134
 * @param $alerts
1135 1136 1137
 *   If TRUE, drupal_set_message() will be called to display a message if the
 *   file name was changed.
 *
1138
 * @return
1139
 *   The potentially modified $filename.
1140 1141 1142 1143 1144 1145
 */
function file_munge_filename($filename, $extensions, $alerts = TRUE) {
  $original = $filename;

  // Allow potentially insecure uploads for very savvy users and admin
  if (!variable_get('allow_insecure_uploads', 0)) {
David_Rothstein's avatar
David_Rothstein committed
1146 1147 1148
    // Remove any null bytes. See http://php.net/manual/en/security.filesystem.nullbytes.php
    $filename = str_replace(chr(0), '', $filename);

1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
    $whitelist = array_unique(explode(' ', trim($extensions)));

    // Split the filename up by periods. The first part becomes the basename
    // the last part the final extension.
    $filename_parts = explode('.', $filename);
    $new_filename = array_shift($filename_parts); // Remove file basename.
    $final_extension = array_pop($filename_parts); // Remove final extension.

    // Loop through the middle parts of the name and add an underscore to the
    // end of each section that could be a file extension but isn't in the list
    // of allowed extensions.
    foreach ($filename_parts as $filename_part) {
1161
      $new_filename .= '.' . $filename_part;
1162 1163 1164 1165
      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
        $new_filename .= '_';
      }
    }
1166
    $filename = $new_filename . '.' . $final_extension;
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176

    if ($alerts && $original != $filename) {
      drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
    }
  }

  return $filename;
}

/**
1177
 * Undoes the effect of file_munge_filename().
1178
 *
1179 1180
 * @param $filename
 *   String with the filename to be unmunged.
1181
 *
1182 1183
 * @return
 *   An unmunged filename string.
1184 1185 1186 1187 1188
 */
function file_unmunge_filename($filename) {
  return str_replace('_.', '.', $filename);
}

1189
/**
1190
 * Creates a full file path from a directory and filename.
1191 1192 1193
 *
 * If a file with the specified name already exists, an alternative will be
 * used.
1194
 *
1195 1196 1197
 * @param $basename
 *   String filename
 * @param $directory
1198
 *   String containing the directory or parent URI.
1199
 *
1200
 * @return
1201 1202
 *   File path consisting of $directory and a unique filename based off
 *   of $basename.
1203
 */
1204
function file_create_filename($basename, $directory) {
1205 1206 1207
  // Strip control characters (ASCII value < 32). Though these are allowed in
  // some filesystems, not many applications handle them well.
  $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
1208 1209 1210 1211
  if (substr(PHP_OS, 0, 3) == 'WIN') {
    // These characters are not allowed in Windows filenames
    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
  }
1212

1213 1214 1215 1216 1217 1218 1219 1220 1221
  // A URI or path may already have a trailing slash or look like "public://".
  if (substr($directory, -1) == '/') {
    $separator = '';
  }
  else {
    $separator = '/';
  }

  $destination = $directory . $separator . $basename;
1222

1223
  if (file_exists($destination)) {
1224
    // Destination file already exists, generate an alternative.
1225 1226
    $pos = strrpos($basename, '.');
    if ($pos !== FALSE) {
1227 1228 1229 1230 1231
      $name = substr($basename, 0, $pos);
      $ext = substr($basename, $pos);
    }
    else {
      $name = $basename;
1232
      $ext = '';
1233 1234 1235 1236
    }

    $counter = 0;
    do {
1237
      $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
1238
    } while (file_exists($destination));
1239 1240
  }

1241
  return $destination;
1242 1243
}

1244
/**
1245
 * Deletes a file and its database record.
1246
 *
1247 1248 1249
 * If the $force parameter is not TRUE, file_usage_list() will be called to
 * determine if the file is being used by any modules. If the file is being
 * used the delete will be canceled.
1250 1251 1252 1253
 *
 * @param $file
 *   A file object.
 * @param $force
1254 1255
 *   Boolean indicating that the file should be deleted even if the file is
 *   reported as in use by the file_usage table.
1256
 *
1257 1258
 * @return mixed
 *   TRUE for success, FALSE in the event of an error, or an array if the file
1259
 *   is being used by any modules.
1260
 *
1261
 * @see file_unmanaged_delete()
1262 1263
 * @see file_usage_list()
 * @see file_usage_delete()
1264 1265
 * @see hook_file_delete()
 */
1266
function file_delete(stdClass $file, $force = FALSE) {
1267
  if (!file_valid_uri($file->uri)) {
1268 1269 1270 1271 1272 1273
    if (($realpath = drupal_realpath($file->uri)) !== FALSE) {
      watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => $realpath));
    }
    else {
      watchdog('file', 'File %file could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri));
    }
1274 1275 1276 1277
    drupal_set_message(t('The specified file %file could not be deleted, because it is not a valid URI. More information is available in the system log.', array('%file' => $file->uri)), 'error');
    return FALSE;
  }

1278 1279 1280 1281
  // If any module still has a usage entry in the file_usage table, the file
  // will not be deleted, but file_delete() will return a populated array
  // that tests as TRUE.
  if (!$force && ($references = file_usage_list($file))) {
1282 1283 1284 1285 1286
    return $references;
  }

  // Let other modules clean up any references to the deleted file.
  module_invoke_all('file_delete', $file);
1287
  module_invoke_all('entity_delete', $file, 'file');
1288 1289 1290

  // Make sure the file is deleted before removing its row from the
  // database, so UIs can still find the file in the database.
1291
  if (file_unmanaged_delete($file->uri)) {
1292
    db_delete('file_managed')->condition('fid', $file->fid)->execute();
1293
    db_delete('file_usage')->condition('fid', $file->fid)->execute();
1294 1295 1296 1297 1298 1299
    return TRUE;
  }
  return FALSE;
}

/**
1300
 * Deletes a file without database changes or hook invocations.
1301 1302 1303
 *
 * This function should be used when the file to be deleted does not have an
 * entry recorded in the files table.
1304
 *
1305
 * @param $path
1306
 *   A string containing a file path or (streamwrapper) URI.
1307
 *
1308 1309 1310
 * @return
 *   TRUE for success or path does not exist, or FALSE in the event of an
 *   error.
1311
 *
1312
 * @see file_delete()
1313
 * @see file_unmanaged_delete_recursive()
1314
 */
1315
function file_unmanaged_delete($path) {
1316
  if (is_dir($path)) {
1317
    watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
1318 1319
    return FALSE;
  }
1320
  if (is_file($path)) {
1321
    return drupal_unlink($path);
1322
  }
1323
  // Return TRUE for non-existent file, but log that nothing was actually
1324
  // deleted, as the current state is the intended result.
1325
  if (!file_exists($path)) {
1326
    watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE);
1327 1328
    return TRUE;
  }
1329 1330 1331
  // We cannot handle anything other than files and directories. Log an error
  // for everything else (sockets, symbolic links, etc).
  watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR);
1332
  return FALSE;
1333 1334
}

1335
/**
1336
 * Deletes all files and directories in the specified filepath recursively.
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
 *
 * If the specified path is a directory then the function will call itself
 * recursively to process the contents. Once the contents have been removed the
 * directory will also be removed.
 *
 * If the specified path is a file then it will be passed to
 * file_unmanaged_delete().
 *
 * Note that this only deletes visible files with write permission.
 *
 * @param $path
1348
 *   A string containing either an URI or a file or directory path.
1349
 *
1350
 * @return