Newer
Older
/**
* @file
* API for handling file uploads and server file management.
*/
Larry Garfield
committed
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\StreamedResponse;
Dries Buytaert
committed
use Drupal\Core\File\File;
Dries Buytaert
committed
/**
* Stream wrapper bit flags that are the basis for composite types.
Dries Buytaert
committed
*
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
* Note that 0x0002 is skipped, because it was the value of a constant that has
* since been removed.
*/
/**
* Stream wrapper bit flag -- a filter that matches all wrappers.
*/
const STREAM_WRAPPERS_ALL = 0x0000;
/**
* Stream wrapper bit flag -- refers to a local file system location.
*/
const STREAM_WRAPPERS_LOCAL = 0x0001;
/**
* Stream wrapper bit flag -- wrapper is readable (almost always true).
*/
const STREAM_WRAPPERS_READ = 0x0004;
/**
* Stream wrapper bit flag -- wrapper is writeable.
*/
const STREAM_WRAPPERS_WRITE = 0x0008;
/**
* Stream wrapper bit flag -- exposed in the UI and potentially web accessible.
*/
const STREAM_WRAPPERS_VISIBLE = 0x0010;
/**
* Composite stream wrapper bit flags that are usually used as the types.
*/
/**
* Stream wrapper type flag -- not visible in the UI or accessible via web,
* but readable and writable. E.g. the temporary directory for uploads.
*/
define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE);
/**
* Stream wrapper type flag -- hidden, readable and writeable using local files.
*/
define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN);
/**
* Stream wrapper type flag -- visible, readable and writeable.
*/
define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE);
/**
* Stream wrapper type flag -- visible and read-only.
*/
define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE);
/**
* Stream wrapper type flag -- the default when 'type' is omitted from
* hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL,
* because PHP grants a greater trust level to local files (for example, they
* can be used in an "include" statement, regardless of the "allow_url_include"
* setting), so stream wrappers need to explicitly opt-in to this.
*/
define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE);
/**
* Stream wrapper type flag -- visible, readable and writeable using local files.
Dries Buytaert
committed
*/
define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL);
Dries Buytaert
committed
Angie Byron
committed
*
Dries Buytaert
committed
* Fields on the file entity:
Dries Buytaert
committed
* - 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
* the basename of the filepath if the file is renamed to avoid overwriting
* an existing file.
Dries Buytaert
committed
* - 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
* bits are reserved for Drupal core. The least significant bit indicates
Dries Buytaert
committed
* temporary (0) or permanent (1). Temporary files older than
* DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs.
Dries Buytaert
committed
* - timestamp: UNIX timestamp for the date the file was added to the database.
* Flag used by file_prepare_directory() -- create directory if not present.
const FILE_CREATE_DIRECTORY = 1;
* Flag used by file_prepare_directory() -- file permissions may be changed.
const FILE_MODIFY_PERMISSIONS = 2;
Angie Byron
committed
* Flag for dealing with existing files: Appends number until name is unique.
const FILE_EXISTS_RENAME = 0;
/**
* Flag for dealing with existing files: Replace the existing file.
*/
const FILE_EXISTS_REPLACE = 1;
/**
* Flag for dealing with existing files: Do nothing and return FALSE.
*/
const FILE_EXISTS_ERROR = 2;
/**
Dries Buytaert
committed
* 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.
const FILE_STATUS_PERMANENT = 1;
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Provides Drupal stream wrapper registry.
Dries Buytaert
committed
*
* 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".
*
Dries Buytaert
committed
* 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
*
Dries Buytaert
committed
* @param $filter
Dries Buytaert
committed
* (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.
Dries Buytaert
committed
*
Dries Buytaert
committed
* @return
Dries Buytaert
committed
* 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.
Dries Buytaert
committed
* @see hook_stream_wrappers()
* @see hook_stream_wrappers_alter()
*/
Dries Buytaert
committed
function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
$wrappers_storage = &drupal_static(__FUNCTION__);
Dries Buytaert
committed
Dries Buytaert
committed
if (!isset($wrappers_storage)) {
Dries Buytaert
committed
$wrappers = module_invoke_all('stream_wrappers');
Dries Buytaert
committed
foreach ($wrappers as $scheme => $info) {
// Add defaults.
$wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
}
Dries Buytaert
committed
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('Drupal\Core\StreamWrapper\StreamWrapperInterface', class_implements($info['class']), TRUE)) {
Dries Buytaert
committed
// 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;
}
Dries Buytaert
committed
if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
stream_wrapper_register($scheme, $info['class']);
Dries Buytaert
committed
stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
Dries Buytaert
committed
}
Dries Buytaert
committed
// 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];
}
Dries Buytaert
committed
}
}
Dries Buytaert
committed
if (!isset($wrappers_storage[$filter])) {
$wrappers_storage[$filter] = array();
foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
// Bit-wise filter.
Dries Buytaert
committed
if (($info['type'] & $filter) == $filter) {
Dries Buytaert
committed
$wrappers_storage[$filter][$scheme] = $info;
}
}
}
return $wrappers_storage[$filter];
Dries Buytaert
committed
}
/**
* Returns the stream wrapper class name for a given scheme.
*
* @param $scheme
* Stream scheme.
Dries Buytaert
committed
* @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".
Dries Buytaert
committed
* @return
* A string containing the name of the scheme, or FALSE if none. For example,
* the URI "public://example.txt" would return "public".
Dries Buytaert
committed
*
* @see file_uri_target()
Dries Buytaert
committed
*/
function file_uri_scheme($uri) {
$position = strpos($uri, '://');
return $position ? substr($uri, 0, $position) : FALSE;
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Checks that the scheme of a stream URI is valid.
Dries Buytaert
committed
*
* 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".
Dries Buytaert
committed
* @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;
}
}
Angie Byron
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Returns the part of a URI after the schema.
Dries Buytaert
committed
*
* @param $uri
* A stream, referenced as "scheme://target".
Dries Buytaert
committed
* @return
* A string containing the target (path), or FALSE if none.
* For example, the URI "public://sample/test.txt" would return
* "sample/test.txt".
Dries Buytaert
committed
*
* @see file_uri_scheme()
Dries Buytaert
committed
*/
function file_uri_target($uri) {
Dries Buytaert
committed
$data = explode('://', $uri, 2);
// Remove erroneous leading or trailing, forward-slashes and backslashes.
return count($data) == 2 ? trim($data[1], '\/') : FALSE;
Dries Buytaert
committed
}
Dries Buytaert
committed
* Gets the default file stream implementation.
*
* @return
* 'public', 'private' or any other file scheme defined as the default.
*/
function file_default_scheme() {
return variable_get('file_default_scheme', 'public');
}
Dries Buytaert
committed
/**
* 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.
* @return
* The normalized URI.
Dries Buytaert
committed
*/
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);
Dries Buytaert
committed
if ($target !== FALSE) {
$uri = $scheme . '://' . $target;
}
Dries Buytaert
committed
}
Angie Byron
committed
else {
// The default scheme is file://
$url = 'file://' . $uri;
}
Dries Buytaert
committed
return $uri;
}
/**
Dries Buytaert
committed
* Returns a reference to the stream wrapper class responsible for a given URI.
Dries Buytaert
committed
*
* 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".
Dries Buytaert
committed
* @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
Dries Buytaert
committed
*/
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)) {
Dries Buytaert
committed
$instance = new $class();
Dries Buytaert
committed
$instance->setUri($uri);
return $instance;
}
else {
return FALSE;
}
}
/**
Dries Buytaert
committed
* Returns a reference to the stream wrapper class responsible for a scheme.
Dries Buytaert
committed
*
* 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.
Dries Buytaert
committed
* @return
* Returns a new stream wrapper object appropriate for the given $scheme.
* For example, for the public scheme a stream wrapper object
Dries Buytaert
committed
* 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)) {
Dries Buytaert
committed
$instance = new $class();
Dries Buytaert
committed
$instance->setUri($scheme . '://');
return $instance;
}
else {
return FALSE;
}
}
Dries Buytaert
committed
* Creates a web-accessible URL for a stream to an external or local file.
* Compatibility: normal paths and stream wrappers.
Dries Buytaert
committed
* There are two kinds of local files:
* - "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).
Dries Buytaert
committed
* - "shipped files", i.e. those outside of the files directory, which ship as
* part of Drupal core or contributed modules or themes.
*
* @param $uri
Dries Buytaert
committed
* The URI to a file for which we need an external URL, or the path to a
* shipped file.
* A string containing a URL that may be used to access the file.
* If the provided string already contains a preceding 'http', 'https', or
* '/', nothing is done and the same string is returned. If a stream wrapper
* could not be found to generate an external URL, then FALSE is returned.
Dries Buytaert
committed
*
* @see http://drupal.org/node/515192
function file_create_url($uri) {
Dries Buytaert
committed
// Allow the URI to be altered, e.g. to serve a file from a CDN or static
// file server.
drupal_alter('file_url', $uri);
Dries Buytaert
committed
$scheme = file_uri_scheme($uri);
if (!$scheme) {
// 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.
// Therefore, return the urlencoded URI with the base URL prepended.
return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
}
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 Buytaert
committed
* Checks that the directory exists and is writable.
*
* Directories need to have execute permissions to be considered a directory by
* FTP servers, etc.
*
* @param $directory
* A string reference containing the name of a directory path or URI. A
* trailing slash will be trimmed from a path.
* @param $options
Dries Buytaert
committed
* 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).
* TRUE if the directory exists (or was created) and is writable. FALSE
* otherwise.
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, '/\\');
}
Angie Byron
committed
// Let mkdir() recursively create directories and use the default directory
// permissions.
if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
return drupal_chmod($directory);
return FALSE;
// 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);
return $writable;
Dries Buytaert
committed
* Creates a .htaccess file in each Drupal files directory if it is missing.
function file_ensure_htaccess() {
Dries Buytaert
committed
file_save_htaccess('public://', FALSE);
Angie Byron
committed
if (variable_get('file_private_path', FALSE)) {
Dries Buytaert
committed
file_save_htaccess('private://', TRUE);
Angie Byron
committed
}
Dries Buytaert
committed
file_save_htaccess('temporary://', TRUE);
Greg Dunlap
committed
file_save_htaccess(config_get_config_directory(), TRUE);
Dries Buytaert
committed
* Creates a .htaccess file in the given directory.
* @param $directory
* 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 Buytaert
committed
function file_save_htaccess($directory, $private = TRUE) {
if (file_uri_scheme($directory)) {
$directory = file_stream_wrapper_uri_normalize($directory);
}
else {
$directory = rtrim($directory, '/\\');
$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)));
Angie Byron
committed
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 Buytaert
committed
* Loads file entities from the database.
* @param array|bool $fids
* An array of file IDs, or FALSE to load all files.
* @param array $conditions
Angie Byron
committed
* (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
* Drupal\entity\EntityFieldQuery to retrieve a list of entity IDs
* loadable by this function.
Dries Buytaert
committed
*
* @return array
Dries Buytaert
committed
* An array of file entities, indexed by fid.
Dries Buytaert
committed
* @todo Remove $conditions in Drupal 8.
*
* @see hook_file_load()
Dries Buytaert
committed
* @see file_load()
Angie Byron
committed
* @see entity_load()
* @see Drupal\entity\EntityFieldQuery
*/
function file_load_multiple($fids = array(), array $conditions = array()) {
return entity_load_multiple('file', $fids, $conditions);
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Loads a single file entity from the database.
Dries Buytaert
committed
*
* @param $fid
Dries Buytaert
committed
* A file ID.
Dries Buytaert
committed
*
Dries Buytaert
committed
* @return Drupal\Core\File\File
* A file entity or FALSE if the file was not found.
Dries Buytaert
committed
*
* @see hook_file_load()
* @see file_load_multiple()
*/
function file_load($fid) {
$files = file_load_multiple(array($fid), array());
return reset($files);
}
/**
Dries Buytaert
committed
* Determines where a file is used.
*
Dries Buytaert
committed
* @param Drupal\Core\File\File $file
* A file entity.
Dries Buytaert
committed
*
* @return
* A nested array with usage data. The first level is keyed by module name,
Dries Buytaert
committed
* the second by object type and the third by the object id. The value
* of the third level contains the usage count.
Dries Buytaert
committed
*
* @see file_usage_add()
* @see file_usage_delete()
*/
Dries Buytaert
committed
function file_usage_list(File $file) {
Dries Buytaert
committed
$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) {
Dries Buytaert
committed
$references[$usage->module][$usage->type][$usage->id] = $usage->count;
Dries Buytaert
committed
}
return $references;
}
/**
* Records that a module is using a file.
*
* 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.
*
Dries Buytaert
committed
* @param Drupal\Core\File\File $file
* A file entity.
Dries Buytaert
committed
* @param $module
* The name of the module using the file.
* @param $type
Angie Byron
committed
* The type of the object that contains the referenced file.
Dries Buytaert
committed
* @param $id
Angie Byron
committed
* The unique, numeric ID of the object containing the referenced file.
Dries Buytaert
committed
* @param $count
* (optional) The number of references to add to the object. Defaults to 1.
*
* @see file_usage_list()
* @see file_usage_delete()
*/
Dries Buytaert
committed
function file_usage_add(File $file, $module, $type, $id, $count = 1) {
Dries Buytaert
committed
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();
catch
committed
// Make sure that a used file is permament.
catch
committed
if ($file->status != FILE_STATUS_PERMANENT) {
$file->status = FILE_STATUS_PERMANENT;
Dries Buytaert
committed
$file->save();
catch
committed
}
Dries Buytaert
committed
}
/**
* Removes a record to indicate that a module is no longer using a file.
*
Dries Buytaert
committed
* @param Drupal\Core\File\File $file
* A file entity.
Dries Buytaert
committed
* @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()
*/
Dries Buytaert
committed
function file_usage_delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) {
Dries Buytaert
committed
// 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.
Dries Buytaert
committed
if (!$result && $count > 0) {
Dries Buytaert
committed
$query = db_update('file_usage')
->condition('module', $module)
->condition('fid', $file->fid);
if ($type && $id) {
$query
->condition('type', $type)
->condition('id', $id);
}
Dries Buytaert
committed
$query->expression('count', 'count - :count', array(':count' => $count));
Dries Buytaert
committed
$query->execute();
}
catch
committed
// If there are no more remaining usages of this file, mark it as temporary,
// which result in a delete through system_cron().
$usage = file_usage_list($file);
if (empty($usage)) {
$file->status = 0;
Dries Buytaert
committed
$file->save();
catch
committed
}
Dries Buytaert
committed
}
/**
* Copies a file to a new location and adds a file record to the database.
*
* 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
Dries Buytaert
committed
* temporary file, the resulting file will also be a temporary file. See
* file_save_upload() for details on temporary files.
Dries Buytaert
committed
* @param Drupal\Core\File\File $source
* A file entity.
* @param $destination
* A string containing the destination that $source should be copied to.
* This must be a stream wrapper URI.
* @param $replace
* Replace behavior when the destination file already exists:
* - 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.
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Dries Buytaert
committed
*
* @return
* File object if the copy is successful, or FALSE in the event of an error.
* @see file_unmanaged_copy()
* @see hook_file_copy()
*/
Dries Buytaert
committed
function file_copy(File $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Angie Byron
committed
if (!file_valid_uri($destination)) {
if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
Dries Buytaert
committed
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 {
Dries Buytaert
committed
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));
}
Dries Buytaert
committed
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');
Angie Byron
committed
return FALSE;
}
if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
$file = clone $source;
$file->fid = NULL;
$file->uri = $uri;
$file->filename = drupal_basename($uri);
// If we are replacing an existing file re-use its database record.
if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = file_load_multiple(array(), array('uri' => $uri));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
$file->filename = $existing->filename;
}
// If we are renaming around an existing file (rather than a directory),
// use its basename for the filename.
elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
$file->filename = drupal_basename($destination);
}
Dries Buytaert
committed
$file->save();
// Inform modules that the file has been copied.
module_invoke_all('file_copy', $file, $source);
return $file;
}
return FALSE;
}
Angie Byron
committed
/**
Dries Buytaert
committed
* Determines whether the URI has a valid scheme for file API operations.
Angie Byron
committed
*
* 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;
}
/**
Dries Buytaert
committed
* Copies a file to a new location without invoking the file API.
Angie Byron
committed
* 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.
* - 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
Angie Byron
committed
*
* @param $source
Angie Byron
committed
* A string specifying the filepath or URI of the source file.
Angie Byron
committed
* @param $destination
Angie Byron
committed
* 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://".
Angie Byron
committed
* @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
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
Angie Byron
committed
* @return
* The path to the new file, or FALSE in the event of an error.
* @see file_copy()
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
Dries Buytaert
committed
$original_source = $source;
$original_destination = $destination;
// Assert that the source file actually exists.
Angie Byron
committed
if (!file_exists($source)) {
// @todo Replace drupal_set_message() calls with exceptions instead.
Dries Buytaert
committed
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');
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));
}
Angie Byron
committed
return FALSE;
}
// Build a destination URI if necessary.
if (!isset($destination)) {
$destination = file_build_uri(drupal_basename($source));
}
// Prepare the destination directory.
if (file_prepare_directory($destination)) {
// The destination is already a directory, so append the source basename.
$destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
}
else {
// Perhaps $destination is a dir/file?
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.
Dries Buytaert
committed
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');
return FALSE;
}
}
// Determine whether we can perform this operation based on overwrite rules.
$destination = file_destination($destination, $replace);
Angie Byron
committed
if ($destination === FALSE) {
Angie Byron
committed
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');
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));
Angie Byron
committed
return FALSE;
// Assert that the source and destination filenames are not the same.
$real_source = drupal_realpath($source);
$real_destination = drupal_realpath($destination);
if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
Angie Byron
committed
drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
Angie Byron
committed
watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
Angie Byron
committed
return FALSE;
// Make sure the .htaccess files are present.
file_ensure_htaccess();
// Perform the copy operation.
Angie Byron
committed
if (!@copy($source, $destination)) {
// 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;
}
// Set the permissions on the new file.
drupal_chmod($destination);
Angie Byron
committed
return $destination;
/**
Dries Buytaert
committed
* Constructs a URI to Drupal's default files location given a relative path.
*/
function file_build_uri($path) {
$uri = file_default_scheme() . '://' . $path;
return file_stream_wrapper_uri_normalize($uri);
}
/**
Dries Buytaert
committed
* Determines the destination path for a file.
*
* @param $destination
* A string specifying the desired final URI or filepath.
* @param $replace
* Replace behavior when the destination file already exists.
Angie Byron
committed
* - FILE_EXISTS_REPLACE - Replace the existing file.
* - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
* unique.
* - FILE_EXISTS_ERROR - Do nothing and return FALSE.
* The destination filepath, or FALSE if the file already exists
* and FILE_EXISTS_ERROR is specified.
*/
function file_destination($destination, $replace) {
if (file_exists($destination)) {
switch ($replace) {
Angie Byron
committed
case FILE_EXISTS_REPLACE:
// Do nothing here, we want to overwrite the existing file.
break;
case FILE_EXISTS_RENAME:
$basename = drupal_basename($destination);
$directory = drupal_dirname($destination);
$destination = file_create_filename($basename, $directory);
break;
case FILE_EXISTS_ERROR:
// Error reporting handled by calling function.
return FALSE;
}
}
return $destination;