cms/drupal/includes/file.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * API for handling file uploads and server file management.
       
     6  */
       
     7 
       
     8 /**
       
     9  * Manually include stream wrapper code.
       
    10  *
       
    11  * Stream wrapper code is included here because there are cases where
       
    12  * File API is needed before a bootstrap, or in an alternate order (e.g.
       
    13  * maintenance theme).
       
    14  */
       
    15 require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc';
       
    16 
       
    17 /**
       
    18  * @defgroup file File interface
       
    19  * @{
       
    20  * Common file handling functions.
       
    21  *
       
    22  * Fields on the file object:
       
    23  * - fid: File ID
       
    24  * - uid: The {users}.uid of the user who is associated with the file.
       
    25  * - filename: Name of the file with no path components. This may differ from
       
    26  *   the basename of the filepath if the file is renamed to avoid overwriting
       
    27  *   an existing file.
       
    28  * - uri: URI of the file.
       
    29  * - filemime: The file's MIME type.
       
    30  * - filesize: The size of the file in bytes.
       
    31  * - 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  *   temporary (0) or permanent (1). Temporary files older than
       
    34  *   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  * Flag for dealing with existing files: Replace the existing file.
       
    55  */
       
    56 define('FILE_EXISTS_REPLACE', 1);
       
    57 
       
    58 /**
       
    59  * Flag for dealing with existing files: Do nothing and return FALSE.
       
    60  */
       
    61 define('FILE_EXISTS_ERROR', 2);
       
    62 
       
    63 /**
       
    64  * Indicates that the file is permanent and should not be deleted.
       
    65  *
       
    66  * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
       
    67  * during cron runs, but permanent files will not be removed during the file
       
    68  * garbage collection process.
       
    69  */
       
    70 define('FILE_STATUS_PERMANENT', 1);
       
    71 
       
    72 /**
       
    73  * Provides Drupal stream wrapper registry.
       
    74  *
       
    75  * A stream wrapper is an abstraction of a file system that allows Drupal to
       
    76  * use the same set of methods to access both local files and remote resources.
       
    77  *
       
    78  * Provide a facility for managing and querying user-defined stream wrappers
       
    79  * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
       
    80  * registered to handle a stream, which we need to be able to find the handler
       
    81  * for class instantiation.
       
    82  *
       
    83  * If a module registers a scheme that is already registered with PHP, the
       
    84  * existing scheme will be unregistered and replaced with the specified class.
       
    85  *
       
    86  * A stream is referenced as "scheme://target".
       
    87  *
       
    88  * The optional $filter parameter can be used to retrieve only the stream
       
    89  * wrappers that are appropriate for particular usage. For example, this returns
       
    90  * only stream wrappers that use local file storage:
       
    91  * @code
       
    92  *   $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
       
    93  * @endcode
       
    94  *
       
    95  * The $filter parameter can only filter to types containing a particular flag.
       
    96  * In some cases, you may want to filter to types that do not contain a
       
    97  * particular flag. For example, you may want to retrieve all stream wrappers
       
    98  * that are not writable, or all stream wrappers that are not local. PHP's
       
    99  * array_diff_key() function can be used to help with this. For example, this
       
   100  * returns only stream wrappers that do not use local file storage:
       
   101  * @code
       
   102  *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));
       
   103  * @endcode
       
   104  *
       
   105  * @param $filter
       
   106  *   (Optional) Filters out all types except those with an on bit for each on
       
   107  *   bit in $filter. For example, if $filter is STREAM_WRAPPERS_WRITE_VISIBLE,
       
   108  *   which is equal to (STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE |
       
   109  *   STREAM_WRAPPERS_VISIBLE), then only stream wrappers with all three of these
       
   110  *   bits set are returned. Defaults to STREAM_WRAPPERS_ALL, which returns all
       
   111  *   registered stream wrappers.
       
   112  *
       
   113  * @return
       
   114  *   An array keyed by scheme, with values containing an array of information
       
   115  *   about the stream wrapper, as returned by hook_stream_wrappers(). If $filter
       
   116  *   is omitted or set to STREAM_WRAPPERS_ALL, the entire Drupal stream wrapper
       
   117  *   registry is returned. Otherwise only the stream wrappers whose 'type'
       
   118  *   bitmask has an on bit for each bit specified in $filter are returned.
       
   119  *
       
   120  * @see hook_stream_wrappers()
       
   121  * @see hook_stream_wrappers_alter()
       
   122  */
       
   123 function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
       
   124   $wrappers_storage = &drupal_static(__FUNCTION__);
       
   125 
       
   126   if (!isset($wrappers_storage)) {
       
   127     $wrappers = module_invoke_all('stream_wrappers');
       
   128     foreach ($wrappers as $scheme => $info) {
       
   129       // Add defaults.
       
   130       $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
       
   131     }
       
   132     drupal_alter('stream_wrappers', $wrappers);
       
   133     $existing = stream_get_wrappers();
       
   134     foreach ($wrappers as $scheme => $info) {
       
   135       // We only register classes that implement our interface.
       
   136       if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) {
       
   137         // Record whether we are overriding an existing scheme.
       
   138         if (in_array($scheme, $existing, TRUE)) {
       
   139           $wrappers[$scheme]['override'] = TRUE;
       
   140           stream_wrapper_unregister($scheme);
       
   141         }
       
   142         else {
       
   143           $wrappers[$scheme]['override'] = FALSE;
       
   144         }
       
   145         if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
       
   146           stream_wrapper_register($scheme, $info['class']);
       
   147         }
       
   148         else {
       
   149           stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
       
   150         }
       
   151       }
       
   152       // Pre-populate the static cache with the filters most typically used.
       
   153       $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme];
       
   154       if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) {
       
   155         $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme];
       
   156       }
       
   157     }
       
   158   }
       
   159 
       
   160   if (!isset($wrappers_storage[$filter])) {
       
   161     $wrappers_storage[$filter] = array();
       
   162     foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
       
   163       // Bit-wise filter.
       
   164       if (($info['type'] & $filter) == $filter) {
       
   165         $wrappers_storage[$filter][$scheme] = $info;
       
   166       }
       
   167     }
       
   168   }
       
   169 
       
   170   return $wrappers_storage[$filter];
       
   171 }
       
   172 
       
   173 /**
       
   174  * Returns the stream wrapper class name for a given scheme.
       
   175  *
       
   176  * @param $scheme
       
   177  *   Stream scheme.
       
   178  *
       
   179  * @return
       
   180  *   Return string if a scheme has a registered handler, or FALSE.
       
   181  */
       
   182 function file_stream_wrapper_get_class($scheme) {
       
   183   $wrappers = file_get_stream_wrappers();
       
   184   return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
       
   185 }
       
   186 
       
   187 /**
       
   188  * Returns the scheme of a URI (e.g. a stream).
       
   189  *
       
   190  * @param $uri
       
   191  *   A stream, referenced as "scheme://target".
       
   192  *
       
   193  * @return
       
   194  *   A string containing the name of the scheme, or FALSE if none. For example,
       
   195  *   the URI "public://example.txt" would return "public".
       
   196  *
       
   197  * @see file_uri_target()
       
   198  */
       
   199 function file_uri_scheme($uri) {
       
   200   $position = strpos($uri, '://');
       
   201   return $position ? substr($uri, 0, $position) : FALSE;
       
   202 }
       
   203 
       
   204 /**
       
   205  * Checks that the scheme of a stream URI is valid.
       
   206  *
       
   207  * Confirms that there is a registered stream handler for the provided scheme
       
   208  * and that it is callable. This is useful if you want to confirm a valid
       
   209  * scheme without creating a new instance of the registered handler.
       
   210  *
       
   211  * @param $scheme
       
   212  *   A URI scheme, a stream is referenced as "scheme://target".
       
   213  *
       
   214  * @return
       
   215  *   Returns TRUE if the string is the name of a validated stream,
       
   216  *   or FALSE if the scheme does not have a registered handler.
       
   217  */
       
   218 function file_stream_wrapper_valid_scheme($scheme) {
       
   219   // Does the scheme have a registered handler that is callable?
       
   220   $class = file_stream_wrapper_get_class($scheme);
       
   221   if (class_exists($class)) {
       
   222     return TRUE;
       
   223   }
       
   224   else {
       
   225     return FALSE;
       
   226   }
       
   227 }
       
   228 
       
   229 
       
   230 /**
       
   231  * Returns the part of a URI after the schema.
       
   232  *
       
   233  * @param $uri
       
   234  *   A stream, referenced as "scheme://target".
       
   235  *
       
   236  * @return
       
   237  *   A string containing the target (path), or FALSE if none.
       
   238  *   For example, the URI "public://sample/test.txt" would return
       
   239  *   "sample/test.txt".
       
   240  *
       
   241  * @see file_uri_scheme()
       
   242  */
       
   243 function file_uri_target($uri) {
       
   244   $data = explode('://', $uri, 2);
       
   245 
       
   246   // Remove erroneous leading or trailing, forward-slashes and backslashes.
       
   247   return count($data) == 2 ? trim($data[1], '\/') : FALSE;
       
   248 }
       
   249 
       
   250 /**
       
   251  * Gets the default file stream implementation.
       
   252  *
       
   253  * @return
       
   254  *   'public', 'private' or any other file scheme defined as the default.
       
   255  */
       
   256 function file_default_scheme() {
       
   257   return variable_get('file_default_scheme', 'public');
       
   258 }
       
   259 
       
   260 /**
       
   261  * Normalizes a URI by making it syntactically correct.
       
   262  *
       
   263  * A stream is referenced as "scheme://target".
       
   264  *
       
   265  * The following actions are taken:
       
   266  * - Remove trailing slashes from target
       
   267  * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
       
   268  *
       
   269  * @param $uri
       
   270  *   String reference containing the URI to normalize.
       
   271  *
       
   272  * @return
       
   273  *   The normalized URI.
       
   274  */
       
   275 function file_stream_wrapper_uri_normalize($uri) {
       
   276   // Inline file_uri_scheme() function call for performance reasons.
       
   277   $position = strpos($uri, '://');
       
   278   $scheme = $position ? substr($uri, 0, $position) : FALSE;
       
   279 
       
   280   if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
       
   281     $target = file_uri_target($uri);
       
   282 
       
   283     if ($target !== FALSE) {
       
   284       $uri = $scheme . '://' . $target;
       
   285     }
       
   286   }
       
   287   return $uri;
       
   288 }
       
   289 
       
   290 /**
       
   291  * Returns a reference to the stream wrapper class responsible for a given URI.
       
   292  *
       
   293  * The scheme determines the stream wrapper class that should be
       
   294  * used by consulting the stream wrapper registry.
       
   295  *
       
   296  * @param $uri
       
   297  *   A stream, referenced as "scheme://target".
       
   298  *
       
   299  * @return
       
   300  *   Returns a new stream wrapper object appropriate for the given URI or FALSE
       
   301  *   if no registered handler could be found. For example, a URI of
       
   302  *   "private://example.txt" would return a new private stream wrapper object
       
   303  *   (DrupalPrivateStreamWrapper).
       
   304  */
       
   305 function file_stream_wrapper_get_instance_by_uri($uri) {
       
   306   $scheme = file_uri_scheme($uri);
       
   307   $class = file_stream_wrapper_get_class($scheme);
       
   308   if (class_exists($class)) {
       
   309     $instance = new $class();
       
   310     $instance->setUri($uri);
       
   311     return $instance;
       
   312   }
       
   313   else {
       
   314     return FALSE;
       
   315   }
       
   316 }
       
   317 
       
   318 /**
       
   319  * Returns a reference to the stream wrapper class responsible for a scheme.
       
   320  *
       
   321  * This helper method returns a stream instance using a scheme. That is, the
       
   322  * passed string does not contain a "://". For example, "public" is a scheme
       
   323  * but "public://" is a URI (stream). This is because the later contains both
       
   324  * a scheme and target despite target being empty.
       
   325  *
       
   326  * Note: the instance URI will be initialized to "scheme://" so that you can
       
   327  * make the customary method calls as if you had retrieved an instance by URI.
       
   328  *
       
   329  * @param $scheme
       
   330  *   If the stream was "public://target", "public" would be the scheme.
       
   331  *
       
   332  * @return
       
   333  *   Returns a new stream wrapper object appropriate for the given $scheme.
       
   334  *   For example, for the public scheme a stream wrapper object
       
   335  *   (DrupalPublicStreamWrapper).
       
   336  *   FALSE is returned if no registered handler could be found.
       
   337  */
       
   338 function file_stream_wrapper_get_instance_by_scheme($scheme) {
       
   339   $class = file_stream_wrapper_get_class($scheme);
       
   340   if (class_exists($class)) {
       
   341     $instance = new $class();
       
   342     $instance->setUri($scheme . '://');
       
   343     return $instance;
       
   344   }
       
   345   else {
       
   346     return FALSE;
       
   347   }
       
   348 }
       
   349 
       
   350 /**
       
   351  * Creates a web-accessible URL for a stream to an external or local file.
       
   352  *
       
   353  * Compatibility: normal paths and stream wrappers.
       
   354  *
       
   355  * There are two kinds of local files:
       
   356  * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
       
   357  *   These are files that have either been uploaded by users or were generated
       
   358  *   automatically (for example through CSS aggregation).
       
   359  * - "shipped files", i.e. those outside of the files directory, which ship as
       
   360  *   part of Drupal core or contributed modules or themes.
       
   361  *
       
   362  * @param $uri
       
   363  *   The URI to a file for which we need an external URL, or the path to a
       
   364  *   shipped file.
       
   365  *
       
   366  * @return
       
   367  *   A string containing a URL that may be used to access the file.
       
   368  *   If the provided string already contains a preceding 'http', 'https', or
       
   369  *   '/', nothing is done and the same string is returned. If a stream wrapper
       
   370  *   could not be found to generate an external URL, then FALSE is returned.
       
   371  *
       
   372  * @see http://drupal.org/node/515192
       
   373  */
       
   374 function file_create_url($uri) {
       
   375   // Allow the URI to be altered, e.g. to serve a file from a CDN or static
       
   376   // file server.
       
   377   drupal_alter('file_url', $uri);
       
   378 
       
   379   $scheme = file_uri_scheme($uri);
       
   380 
       
   381   if (!$scheme) {
       
   382     // Allow for:
       
   383     // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
       
   384     // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
       
   385     //   http://example.com/bar.jpg by the browser when viewing a page over
       
   386     //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
       
   387     // Both types of relative URIs are characterized by a leading slash, hence
       
   388     // we can use a single check.
       
   389     if (drupal_substr($uri, 0, 1) == '/') {
       
   390       return $uri;
       
   391     }
       
   392     else {
       
   393       // If this is not a properly formatted stream, then it is a shipped file.
       
   394       // Therefore, return the urlencoded URI with the base URL prepended.
       
   395       return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
       
   396     }
       
   397   }
       
   398   elseif ($scheme == 'http' || $scheme == 'https') {
       
   399     // Check for HTTP so that we don't have to implement getExternalUrl() for
       
   400     // the HTTP wrapper.
       
   401     return $uri;
       
   402   }
       
   403   else {
       
   404     // Attempt to return an external URL using the appropriate wrapper.
       
   405     if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
       
   406       return $wrapper->getExternalUrl();
       
   407     }
       
   408     else {
       
   409       return FALSE;
       
   410     }
       
   411   }
       
   412 }
       
   413 
       
   414 /**
       
   415  * Checks that the directory exists and is writable.
       
   416  *
       
   417  * Directories need to have execute permissions to be considered a directory by
       
   418  * FTP servers, etc.
       
   419  *
       
   420  * @param $directory
       
   421  *   A string reference containing the name of a directory path or URI. A
       
   422  *   trailing slash will be trimmed from a path.
       
   423  * @param $options
       
   424  *   A bitmask to indicate if the directory should be created if it does
       
   425  *   not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
       
   426  *   (FILE_MODIFY_PERMISSIONS).
       
   427  *
       
   428  * @return
       
   429  *   TRUE if the directory exists (or was created) and is writable. FALSE
       
   430  *   otherwise.
       
   431  */
       
   432 function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
       
   433   if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) {
       
   434     // Only trim if we're not dealing with a stream.
       
   435     $directory = rtrim($directory, '/\\');
       
   436   }
       
   437 
       
   438   // Check if directory exists.
       
   439   if (!is_dir($directory)) {
       
   440     // Let mkdir() recursively create directories and use the default directory
       
   441     // permissions.
       
   442     if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) {
       
   443       return drupal_chmod($directory);
       
   444     }
       
   445     return FALSE;
       
   446   }
       
   447   // The directory exists, so check to see if it is writable.
       
   448   $writable = is_writable($directory);
       
   449   if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
       
   450     return drupal_chmod($directory);
       
   451   }
       
   452 
       
   453   return $writable;
       
   454 }
       
   455 
       
   456 /**
       
   457  * Creates a .htaccess file in each Drupal files directory if it is missing.
       
   458  */
       
   459 function file_ensure_htaccess() {
       
   460   file_create_htaccess('public://', FALSE);
       
   461   if (variable_get('file_private_path', FALSE)) {
       
   462     file_create_htaccess('private://', TRUE);
       
   463   }
       
   464   file_create_htaccess('temporary://', TRUE);
       
   465 }
       
   466 
       
   467 /**
       
   468  * Creates a .htaccess file in the given directory.
       
   469  *
       
   470  * @param $directory
       
   471  *   The directory.
       
   472  * @param $private
       
   473  *   FALSE indicates that $directory should be an open and public directory.
       
   474  *   The default is TRUE which indicates a private and protected directory.
       
   475  * @param $force_overwrite
       
   476  *   Set to TRUE to attempt to overwrite the existing .htaccess file if one is
       
   477  *   already present. Defaults to FALSE.
       
   478  */
       
   479 function file_create_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
       
   480   if (file_uri_scheme($directory)) {
       
   481     $directory = file_stream_wrapper_uri_normalize($directory);
       
   482   }
       
   483   else {
       
   484     $directory = rtrim($directory, '/\\');
       
   485   }
       
   486   $htaccess_path =  $directory . '/.htaccess';
       
   487 
       
   488   if (file_exists($htaccess_path) && !$force_overwrite) {
       
   489     // Short circuit if the .htaccess file already exists.
       
   490     return;
       
   491   }
       
   492 
       
   493   $htaccess_lines = file_htaccess_lines($private);
       
   494 
       
   495   // Write the .htaccess file.
       
   496   if (file_put_contents($htaccess_path, $htaccess_lines)) {
       
   497     drupal_chmod($htaccess_path, 0444);
       
   498   }
       
   499   else {
       
   500     $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
       
   501     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);
       
   502   }
       
   503 }
       
   504 
       
   505 /**
       
   506  * Returns the standard .htaccess lines that Drupal writes to file directories.
       
   507  *
       
   508  * @param $private
       
   509  *   (Optional) Set to FALSE to return the .htaccess lines for an open and
       
   510  *   public directory. The default is TRUE, which returns the .htaccess lines
       
   511  *   for a private and protected directory.
       
   512  *
       
   513  * @return
       
   514  *   A string representing the desired contents of the .htaccess file.
       
   515  *
       
   516  * @see file_create_htaccess()
       
   517  */
       
   518 function file_htaccess_lines($private = TRUE) {
       
   519   $lines = <<<EOF
       
   520 # Turn off all options we don't need.
       
   521 Options None
       
   522 Options +FollowSymLinks
       
   523 
       
   524 # Set the catch-all handler to prevent scripts from being executed.
       
   525 SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
       
   526 <Files *>
       
   527   # Override the handler again if we're run later in the evaluation list.
       
   528   SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
       
   529 </Files>
       
   530 
       
   531 # If we know how to do it safely, disable the PHP engine entirely.
       
   532 <IfModule mod_php5.c>
       
   533   php_flag engine off
       
   534 </IfModule>
       
   535 EOF;
       
   536 
       
   537   if ($private) {
       
   538     $lines = <<<EOF
       
   539 # Deny all requests from Apache 2.4+.
       
   540 <IfModule mod_authz_core.c>
       
   541   Require all denied
       
   542 </IfModule>
       
   543 
       
   544 # Deny all requests from Apache 2.0-2.2.
       
   545 <IfModule !mod_authz_core.c>
       
   546   Deny from all
       
   547 </IfModule>
       
   548 EOF
       
   549     . "\n\n" . $lines;
       
   550   }
       
   551 
       
   552   return $lines;
       
   553 }
       
   554 
       
   555 /**
       
   556  * Loads file objects from the database.
       
   557  *
       
   558  * @param $fids
       
   559  *   An array of file IDs.
       
   560  * @param $conditions
       
   561  *   (deprecated) An associative array of conditions on the {file_managed}
       
   562  *   table, where the keys are the database fields and the values are the
       
   563  *   values those fields must have. Instead, it is preferable to use
       
   564  *   EntityFieldQuery to retrieve a list of entity IDs loadable by
       
   565  *   this function.
       
   566  *
       
   567  * @return
       
   568  *   An array of file objects, indexed by fid.
       
   569  *
       
   570  * @todo Remove $conditions in Drupal 8.
       
   571  *
       
   572  * @see hook_file_load()
       
   573  * @see file_load()
       
   574  * @see entity_load()
       
   575  * @see EntityFieldQuery
       
   576  */
       
   577 function file_load_multiple($fids = array(), $conditions = array()) {
       
   578   return entity_load('file', $fids, $conditions);
       
   579 }
       
   580 
       
   581 /**
       
   582  * Loads a single file object from the database.
       
   583  *
       
   584  * @param $fid
       
   585  *   A file ID.
       
   586  *
       
   587  * @return
       
   588  *   An object representing the file, or FALSE if the file was not found.
       
   589  *
       
   590  * @see hook_file_load()
       
   591  * @see file_load_multiple()
       
   592  */
       
   593 function file_load($fid) {
       
   594   $files = file_load_multiple(array($fid), array());
       
   595   return reset($files);
       
   596 }
       
   597 
       
   598 /**
       
   599  * Saves a file object to the database.
       
   600  *
       
   601  * If the $file->fid is not set a new record will be added.
       
   602  *
       
   603  * @param $file
       
   604  *   A file object returned by file_load().
       
   605  *
       
   606  * @return
       
   607  *   The updated file object.
       
   608  *
       
   609  * @see hook_file_insert()
       
   610  * @see hook_file_update()
       
   611  */
       
   612 function file_save(stdClass $file) {
       
   613   $file->timestamp = REQUEST_TIME;
       
   614   $file->filesize = filesize($file->uri);
       
   615 
       
   616   // Load the stored entity, if any.
       
   617   if (!empty($file->fid) && !isset($file->original)) {
       
   618     $file->original = entity_load_unchanged('file', $file->fid);
       
   619   }
       
   620 
       
   621   module_invoke_all('file_presave', $file);
       
   622   module_invoke_all('entity_presave', $file, 'file');
       
   623 
       
   624   if (empty($file->fid)) {
       
   625     drupal_write_record('file_managed', $file);
       
   626     // Inform modules about the newly added file.
       
   627     module_invoke_all('file_insert', $file);
       
   628     module_invoke_all('entity_insert', $file, 'file');
       
   629   }
       
   630   else {
       
   631     drupal_write_record('file_managed', $file, 'fid');
       
   632     // Inform modules that the file has been updated.
       
   633     module_invoke_all('file_update', $file);
       
   634     module_invoke_all('entity_update', $file, 'file');
       
   635   }
       
   636 
       
   637   // Clear internal properties.
       
   638   unset($file->original);
       
   639   // Clear the static loading cache.
       
   640   entity_get_controller('file')->resetCache(array($file->fid));
       
   641 
       
   642   return $file;
       
   643 }
       
   644 
       
   645 /**
       
   646  * Determines where a file is used.
       
   647  *
       
   648  * @param $file
       
   649  *   A file object.
       
   650  *
       
   651  * @return
       
   652  *   A nested array with usage data. The first level is keyed by module name,
       
   653  *   the second by object type and the third by the object id. The value
       
   654  *   of the third level contains the usage count.
       
   655  *
       
   656  * @see file_usage_add()
       
   657  * @see file_usage_delete()
       
   658  */
       
   659 function file_usage_list(stdClass $file) {
       
   660   $result = db_select('file_usage', 'f')
       
   661     ->fields('f', array('module', 'type', 'id', 'count'))
       
   662     ->condition('fid', $file->fid)
       
   663     ->condition('count', 0, '>')
       
   664     ->execute();
       
   665   $references = array();
       
   666   foreach ($result as $usage) {
       
   667     $references[$usage->module][$usage->type][$usage->id] = $usage->count;
       
   668   }
       
   669   return $references;
       
   670 }
       
   671 
       
   672 /**
       
   673  * Records that a module is using a file.
       
   674  *
       
   675  * This usage information will be queried during file_delete() to ensure that
       
   676  * a file is not in use before it is physically removed from disk.
       
   677  *
       
   678  * Examples:
       
   679  * - A module that associates files with nodes, so $type would be
       
   680  *   'node' and $id would be the node's nid. Files for all revisions are stored
       
   681  *   within a single nid.
       
   682  * - The User module associates an image with a user, so $type would be 'user'
       
   683  *   and the $id would be the user's uid.
       
   684  *
       
   685  * @param $file
       
   686  *   A file object.
       
   687  * @param $module
       
   688  *   The name of the module using the file.
       
   689  * @param $type
       
   690  *   The type of the object that contains the referenced file.
       
   691  * @param $id
       
   692  *   The unique, numeric ID of the object containing the referenced file.
       
   693  * @param $count
       
   694  *   (optional) The number of references to add to the object. Defaults to 1.
       
   695  *
       
   696  * @see file_usage_list()
       
   697  * @see file_usage_delete()
       
   698  */
       
   699 function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
       
   700   db_merge('file_usage')
       
   701     ->key(array(
       
   702       'fid' => $file->fid,
       
   703       'module' => $module,
       
   704       'type' => $type,
       
   705       'id' => $id,
       
   706     ))
       
   707     ->fields(array('count' => $count))
       
   708     ->expression('count', 'count + :count', array(':count' => $count))
       
   709     ->execute();
       
   710 }
       
   711 
       
   712 /**
       
   713  * Removes a record to indicate that a module is no longer using a file.
       
   714  *
       
   715  * The file_delete() function is typically called after removing a file usage
       
   716  * to remove the record from the file_managed table and delete the file itself.
       
   717  *
       
   718  * @param $file
       
   719  *   A file object.
       
   720  * @param $module
       
   721  *   The name of the module using the file.
       
   722  * @param $type
       
   723  *   (optional) The type of the object that contains the referenced file. May
       
   724  *   be omitted if all module references to a file are being deleted.
       
   725  * @param $id
       
   726  *   (optional) The unique, numeric ID of the object containing the referenced
       
   727  *   file. May be omitted if all module references to a file are being deleted.
       
   728  * @param $count
       
   729  *   (optional) The number of references to delete from the object. Defaults to
       
   730  *   1. 0 may be specified to delete all references to the file within a
       
   731  *   specific object.
       
   732  *
       
   733  * @see file_usage_add()
       
   734  * @see file_usage_list()
       
   735  * @see file_delete()
       
   736  */
       
   737 function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) {
       
   738   // Delete rows that have a exact or less value to prevent empty rows.
       
   739   $query = db_delete('file_usage')
       
   740     ->condition('module', $module)
       
   741     ->condition('fid', $file->fid);
       
   742   if ($type && $id) {
       
   743     $query
       
   744       ->condition('type', $type)
       
   745       ->condition('id', $id);
       
   746   }
       
   747   if ($count) {
       
   748     $query->condition('count', $count, '<=');
       
   749   }
       
   750   $result = $query->execute();
       
   751 
       
   752   // If the row has more than the specified count decrement it by that number.
       
   753   if (!$result && $count > 0) {
       
   754     $query = db_update('file_usage')
       
   755       ->condition('module', $module)
       
   756       ->condition('fid', $file->fid);
       
   757     if ($type && $id) {
       
   758       $query
       
   759         ->condition('type', $type)
       
   760         ->condition('id', $id);
       
   761     }
       
   762     $query->expression('count', 'count - :count', array(':count' => $count));
       
   763     $query->execute();
       
   764   }
       
   765 }
       
   766 
       
   767 /**
       
   768  * Copies a file to a new location and adds a file record to the database.
       
   769  *
       
   770  * This function should be used when manipulating files that have records
       
   771  * stored in the database. This is a powerful function that in many ways
       
   772  * performs like an advanced version of copy().
       
   773  * - Checks if $source and $destination are valid and readable/writable.
       
   774  * - If file already exists in $destination either the call will error out,
       
   775  *   replace the file or rename the file based on the $replace parameter.
       
   776  * - If the $source and $destination are equal, the behavior depends on the
       
   777  *   $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
       
   778  *   will rename the file until the $destination is unique.
       
   779  * - Adds the new file to the files database. If the source file is a
       
   780  *   temporary file, the resulting file will also be a temporary file. See
       
   781  *   file_save_upload() for details on temporary files.
       
   782  *
       
   783  * @param $source
       
   784  *   A file object.
       
   785  * @param $destination
       
   786  *   A string containing the destination that $source should be copied to.
       
   787  *   This must be a stream wrapper URI.
       
   788  * @param $replace
       
   789  *   Replace behavior when the destination file already exists:
       
   790  *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
       
   791  *       the destination name exists then its database entry will be updated. If
       
   792  *       no database entry is found then a new one will be created.
       
   793  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
   794  *       unique.
       
   795  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
   796  *
       
   797  * @return
       
   798  *   File object if the copy is successful, or FALSE in the event of an error.
       
   799  *
       
   800  * @see file_unmanaged_copy()
       
   801  * @see hook_file_copy()
       
   802  */
       
   803 function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
   804   if (!file_valid_uri($destination)) {
       
   805     if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
       
   806       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));
       
   807     }
       
   808     else {
       
   809       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));
       
   810     }
       
   811     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');
       
   812     return FALSE;
       
   813   }
       
   814 
       
   815   if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
       
   816     $file = clone $source;
       
   817     $file->fid = NULL;
       
   818     $file->uri = $uri;
       
   819     $file->filename = drupal_basename($uri);
       
   820     // If we are replacing an existing file re-use its database record.
       
   821     if ($replace == FILE_EXISTS_REPLACE) {
       
   822       $existing_files = file_load_multiple(array(), array('uri' => $uri));
       
   823       if (count($existing_files)) {
       
   824         $existing = reset($existing_files);
       
   825         $file->fid = $existing->fid;
       
   826         $file->filename = $existing->filename;
       
   827       }
       
   828     }
       
   829     // If we are renaming around an existing file (rather than a directory),
       
   830     // use its basename for the filename.
       
   831     elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
       
   832       $file->filename = drupal_basename($destination);
       
   833     }
       
   834 
       
   835     $file = file_save($file);
       
   836 
       
   837     // Inform modules that the file has been copied.
       
   838     module_invoke_all('file_copy', $file, $source);
       
   839 
       
   840     return $file;
       
   841   }
       
   842   return FALSE;
       
   843 }
       
   844 
       
   845 /**
       
   846  * Determines whether the URI has a valid scheme for file API operations.
       
   847  *
       
   848  * There must be a scheme and it must be a Drupal-provided scheme like
       
   849  * 'public', 'private', 'temporary', or an extension provided with
       
   850  * hook_stream_wrappers().
       
   851  *
       
   852  * @param $uri
       
   853  *   The URI to be tested.
       
   854  *
       
   855  * @return
       
   856  *   TRUE if the URI is allowed.
       
   857  */
       
   858 function file_valid_uri($uri) {
       
   859   // Assert that the URI has an allowed scheme. Barepaths are not allowed.
       
   860   $uri_scheme = file_uri_scheme($uri);
       
   861   if (empty($uri_scheme) || !file_stream_wrapper_valid_scheme($uri_scheme)) {
       
   862     return FALSE;
       
   863   }
       
   864   return TRUE;
       
   865 }
       
   866 
       
   867 /**
       
   868  * Copies a file to a new location without invoking the file API.
       
   869  *
       
   870  * This is a powerful function that in many ways performs like an advanced
       
   871  * version of copy().
       
   872  * - Checks if $source and $destination are valid and readable/writable.
       
   873  * - If file already exists in $destination either the call will error out,
       
   874  *   replace the file or rename the file based on the $replace parameter.
       
   875  * - If the $source and $destination are equal, the behavior depends on the
       
   876  *   $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
       
   877  *   will rename the file until the $destination is unique.
       
   878  * - Provides a fallback using realpaths if the move fails using stream
       
   879  *   wrappers. This can occur because PHP's copy() function does not properly
       
   880  *   support streams if safe_mode or open_basedir are enabled. See
       
   881  *   https://bugs.php.net/bug.php?id=60456
       
   882  *
       
   883  * @param $source
       
   884  *   A string specifying the filepath or URI of the source file.
       
   885  * @param $destination
       
   886  *   A URI containing the destination that $source should be copied to. The
       
   887  *   URI may be a bare filepath (without a scheme). If this value is omitted,
       
   888  *   Drupal's default files scheme will be used, usually "public://".
       
   889  * @param $replace
       
   890  *   Replace behavior when the destination file already exists:
       
   891  *   - FILE_EXISTS_REPLACE - Replace the existing file.
       
   892  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
   893  *       unique.
       
   894  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
   895  *
       
   896  * @return
       
   897  *   The path to the new file, or FALSE in the event of an error.
       
   898  *
       
   899  * @see file_copy()
       
   900  */
       
   901 function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
   902   $original_source = $source;
       
   903 
       
   904   // Assert that the source file actually exists.
       
   905   if (!file_exists($source)) {
       
   906     // @todo Replace drupal_set_message() calls with exceptions instead.
       
   907     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');
       
   908     if (($realpath = drupal_realpath($original_source)) !== FALSE) {
       
   909       watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
       
   910     }
       
   911     else {
       
   912       watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
       
   913     }
       
   914     return FALSE;
       
   915   }
       
   916 
       
   917   // Build a destination URI if necessary.
       
   918   if (!isset($destination)) {
       
   919     $destination = file_build_uri(drupal_basename($source));
       
   920   }
       
   921 
       
   922 
       
   923   // Prepare the destination directory.
       
   924   if (file_prepare_directory($destination)) {
       
   925     // The destination is already a directory, so append the source basename.
       
   926     $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
       
   927   }
       
   928   else {
       
   929     // Perhaps $destination is a dir/file?
       
   930     $dirname = drupal_dirname($destination);
       
   931     if (!file_prepare_directory($dirname)) {
       
   932       // The destination is not valid.
       
   933       watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
       
   934       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');
       
   935       return FALSE;
       
   936     }
       
   937   }
       
   938 
       
   939   // Determine whether we can perform this operation based on overwrite rules.
       
   940   $destination = file_destination($destination, $replace);
       
   941   if ($destination === FALSE) {
       
   942     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');
       
   943     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));
       
   944     return FALSE;
       
   945   }
       
   946 
       
   947   // Assert that the source and destination filenames are not the same.
       
   948   $real_source = drupal_realpath($source);
       
   949   $real_destination = drupal_realpath($destination);
       
   950   if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
       
   951     drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
       
   952     watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
       
   953     return FALSE;
       
   954   }
       
   955   // Make sure the .htaccess files are present.
       
   956   file_ensure_htaccess();
       
   957   // Perform the copy operation.
       
   958   if (!@copy($source, $destination)) {
       
   959     // If the copy failed and realpaths exist, retry the operation using them
       
   960     // instead.
       
   961     if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
       
   962       watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
       
   963       return FALSE;
       
   964     }
       
   965   }
       
   966 
       
   967   // Set the permissions on the new file.
       
   968   drupal_chmod($destination);
       
   969 
       
   970   return $destination;
       
   971 }
       
   972 
       
   973 /**
       
   974  * Constructs a URI to Drupal's default files location given a relative path.
       
   975  */
       
   976 function file_build_uri($path) {
       
   977   $uri = file_default_scheme() . '://' . $path;
       
   978   return file_stream_wrapper_uri_normalize($uri);
       
   979 }
       
   980 
       
   981 /**
       
   982  * Determines the destination path for a file.
       
   983  *
       
   984  * @param $destination
       
   985  *   A string specifying the desired final URI or filepath.
       
   986  * @param $replace
       
   987  *   Replace behavior when the destination file already exists.
       
   988  *   - FILE_EXISTS_REPLACE - Replace the existing file.
       
   989  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
   990  *       unique.
       
   991  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
   992  *
       
   993  * @return
       
   994  *   The destination filepath, or FALSE if the file already exists
       
   995  *   and FILE_EXISTS_ERROR is specified.
       
   996  */
       
   997 function file_destination($destination, $replace) {
       
   998   if (file_exists($destination)) {
       
   999     switch ($replace) {
       
  1000       case FILE_EXISTS_REPLACE:
       
  1001         // Do nothing here, we want to overwrite the existing file.
       
  1002         break;
       
  1003 
       
  1004       case FILE_EXISTS_RENAME:
       
  1005         $basename = drupal_basename($destination);
       
  1006         $directory = drupal_dirname($destination);
       
  1007         $destination = file_create_filename($basename, $directory);
       
  1008         break;
       
  1009 
       
  1010       case FILE_EXISTS_ERROR:
       
  1011         // Error reporting handled by calling function.
       
  1012         return FALSE;
       
  1013     }
       
  1014   }
       
  1015   return $destination;
       
  1016 }
       
  1017 
       
  1018 /**
       
  1019  * Moves a file to a new location and update the file's database entry.
       
  1020  *
       
  1021  * Moving a file is performed by copying the file to the new location and then
       
  1022  * deleting the original.
       
  1023  * - Checks if $source and $destination are valid and readable/writable.
       
  1024  * - Performs a file move if $source is not equal to $destination.
       
  1025  * - If file already exists in $destination either the call will error out,
       
  1026  *   replace the file or rename the file based on the $replace parameter.
       
  1027  * - Adds the new file to the files database.
       
  1028  *
       
  1029  * @param $source
       
  1030  *   A file object.
       
  1031  * @param $destination
       
  1032  *   A string containing the destination that $source should be moved to.
       
  1033  *   This must be a stream wrapper URI.
       
  1034  * @param $replace
       
  1035  *   Replace behavior when the destination file already exists:
       
  1036  *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
       
  1037  *       the destination name exists then its database entry will be updated and
       
  1038  *       file_delete() called on the source file after hook_file_move is called.
       
  1039  *       If no database entry is found then the source files record will be
       
  1040  *       updated.
       
  1041  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
  1042  *       unique.
       
  1043  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
  1044  *
       
  1045  * @return
       
  1046  *   Resulting file object for success, or FALSE in the event of an error.
       
  1047  *
       
  1048  * @see file_unmanaged_move()
       
  1049  * @see hook_file_move()
       
  1050  */
       
  1051 function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
  1052   if (!file_valid_uri($destination)) {
       
  1053     if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
       
  1054       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));
       
  1055     }
       
  1056     else {
       
  1057       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));
       
  1058     }
       
  1059     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');
       
  1060     return FALSE;
       
  1061   }
       
  1062 
       
  1063   if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
       
  1064     $delete_source = FALSE;
       
  1065 
       
  1066     $file = clone $source;
       
  1067     $file->uri = $uri;
       
  1068     // If we are replacing an existing file re-use its database record.
       
  1069     if ($replace == FILE_EXISTS_REPLACE) {
       
  1070       $existing_files = file_load_multiple(array(), array('uri' => $uri));
       
  1071       if (count($existing_files)) {
       
  1072         $existing = reset($existing_files);
       
  1073         $delete_source = TRUE;
       
  1074         $file->fid = $existing->fid;
       
  1075       }
       
  1076     }
       
  1077     // If we are renaming around an existing file (rather than a directory),
       
  1078     // use its basename for the filename.
       
  1079     elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
       
  1080       $file->filename = drupal_basename($destination);
       
  1081     }
       
  1082 
       
  1083     $file = file_save($file);
       
  1084 
       
  1085     // Inform modules that the file has been moved.
       
  1086     module_invoke_all('file_move', $file, $source);
       
  1087 
       
  1088     if ($delete_source) {
       
  1089       // Try a soft delete to remove original if it's not in use elsewhere.
       
  1090       file_delete($source);
       
  1091     }
       
  1092 
       
  1093     return $file;
       
  1094   }
       
  1095   return FALSE;
       
  1096 }
       
  1097 
       
  1098 /**
       
  1099  * Moves a file to a new location without database changes or hook invocation.
       
  1100  *
       
  1101  * @param $source
       
  1102  *   A string specifying the filepath or URI of the original file.
       
  1103  * @param $destination
       
  1104  *   A string containing the destination that $source should be moved to.
       
  1105  *   This must be a stream wrapper URI. If this value is omitted, Drupal's
       
  1106  *   default files scheme will be used, usually "public://".
       
  1107  * @param $replace
       
  1108  *   Replace behavior when the destination file already exists:
       
  1109  *   - FILE_EXISTS_REPLACE - Replace the existing file.
       
  1110  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
  1111  *       unique.
       
  1112  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
  1113  *
       
  1114  * @return
       
  1115  *   The URI of the moved file, or FALSE in the event of an error.
       
  1116  *
       
  1117  * @see file_move()
       
  1118  */
       
  1119 function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
  1120   $filepath = file_unmanaged_copy($source, $destination, $replace);
       
  1121   if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
       
  1122     return FALSE;
       
  1123   }
       
  1124   return $filepath;
       
  1125 }
       
  1126 
       
  1127 /**
       
  1128  * Modifies a filename as needed for security purposes.
       
  1129  *
       
  1130  * Munging a file name prevents unknown file extensions from masking exploit
       
  1131  * files. When web servers such as Apache decide how to process a URL request,
       
  1132  * they use the file extension. If the extension is not recognized, Apache
       
  1133  * skips that extension and uses the previous file extension. For example, if
       
  1134  * the file being requested is exploit.php.pps, and Apache does not recognize
       
  1135  * the '.pps' extension, it treats the file as PHP and executes it. To make
       
  1136  * this file name safe for Apache and prevent it from executing as PHP, the
       
  1137  * .php extension is "munged" into .php_, making the safe file name
       
  1138  * exploit.php_.pps.
       
  1139  *
       
  1140  * Specifically, this function adds an underscore to all extensions that are
       
  1141  * between 2 and 5 characters in length, internal to the file name, and not
       
  1142  * included in $extensions.
       
  1143  *
       
  1144  * Function behavior is also controlled by the Drupal variable
       
  1145  * 'allow_insecure_uploads'. If 'allow_insecure_uploads' evaluates to TRUE, no
       
  1146  * alterations will be made, if it evaluates to FALSE, the filename is 'munged'.
       
  1147  *
       
  1148  * @param $filename
       
  1149  *   File name to modify.
       
  1150  * @param $extensions
       
  1151  *   A space-separated list of extensions that should not be altered.
       
  1152  * @param $alerts
       
  1153  *   If TRUE, drupal_set_message() will be called to display a message if the
       
  1154  *   file name was changed.
       
  1155  *
       
  1156  * @return
       
  1157  *   The potentially modified $filename.
       
  1158  */
       
  1159 function file_munge_filename($filename, $extensions, $alerts = TRUE) {
       
  1160   $original = $filename;
       
  1161 
       
  1162   // Allow potentially insecure uploads for very savvy users and admin
       
  1163   if (!variable_get('allow_insecure_uploads', 0)) {
       
  1164     // Remove any null bytes. See http://php.net/manual/security.filesystem.nullbytes.php
       
  1165     $filename = str_replace(chr(0), '', $filename);
       
  1166 
       
  1167     $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
       
  1168 
       
  1169     // Split the filename up by periods. The first part becomes the basename
       
  1170     // the last part the final extension.
       
  1171     $filename_parts = explode('.', $filename);
       
  1172     $new_filename = array_shift($filename_parts); // Remove file basename.
       
  1173     $final_extension = array_pop($filename_parts); // Remove final extension.
       
  1174 
       
  1175     // Loop through the middle parts of the name and add an underscore to the
       
  1176     // end of each section that could be a file extension but isn't in the list
       
  1177     // of allowed extensions.
       
  1178     foreach ($filename_parts as $filename_part) {
       
  1179       $new_filename .= '.' . $filename_part;
       
  1180       if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
       
  1181         $new_filename .= '_';
       
  1182       }
       
  1183     }
       
  1184     $filename = $new_filename . '.' . $final_extension;
       
  1185 
       
  1186     if ($alerts && $original != $filename) {
       
  1187       drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
       
  1188     }
       
  1189   }
       
  1190 
       
  1191   return $filename;
       
  1192 }
       
  1193 
       
  1194 /**
       
  1195  * Undoes the effect of file_munge_filename().
       
  1196  *
       
  1197  * @param $filename
       
  1198  *   String with the filename to be unmunged.
       
  1199  *
       
  1200  * @return
       
  1201  *   An unmunged filename string.
       
  1202  */
       
  1203 function file_unmunge_filename($filename) {
       
  1204   return str_replace('_.', '.', $filename);
       
  1205 }
       
  1206 
       
  1207 /**
       
  1208  * Creates a full file path from a directory and filename.
       
  1209  *
       
  1210  * If a file with the specified name already exists, an alternative will be
       
  1211  * used.
       
  1212  *
       
  1213  * @param $basename
       
  1214  *   String filename
       
  1215  * @param $directory
       
  1216  *   String containing the directory or parent URI.
       
  1217  *
       
  1218  * @return
       
  1219  *   File path consisting of $directory and a unique filename based off
       
  1220  *   of $basename.
       
  1221  */
       
  1222 function file_create_filename($basename, $directory) {
       
  1223   // Strip control characters (ASCII value < 32). Though these are allowed in
       
  1224   // some filesystems, not many applications handle them well.
       
  1225   $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
       
  1226   if (substr(PHP_OS, 0, 3) == 'WIN') {
       
  1227     // These characters are not allowed in Windows filenames
       
  1228     $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
       
  1229   }
       
  1230 
       
  1231   // A URI or path may already have a trailing slash or look like "public://".
       
  1232   if (substr($directory, -1) == '/') {
       
  1233     $separator = '';
       
  1234   }
       
  1235   else {
       
  1236     $separator = '/';
       
  1237   }
       
  1238 
       
  1239   $destination = $directory . $separator . $basename;
       
  1240 
       
  1241   if (file_exists($destination)) {
       
  1242     // Destination file already exists, generate an alternative.
       
  1243     $pos = strrpos($basename, '.');
       
  1244     if ($pos !== FALSE) {
       
  1245       $name = substr($basename, 0, $pos);
       
  1246       $ext = substr($basename, $pos);
       
  1247     }
       
  1248     else {
       
  1249       $name = $basename;
       
  1250       $ext = '';
       
  1251     }
       
  1252 
       
  1253     $counter = 0;
       
  1254     do {
       
  1255       $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
       
  1256     } while (file_exists($destination));
       
  1257   }
       
  1258 
       
  1259   return $destination;
       
  1260 }
       
  1261 
       
  1262 /**
       
  1263  * Deletes a file and its database record.
       
  1264  *
       
  1265  * If the $force parameter is not TRUE, file_usage_list() will be called to
       
  1266  * determine if the file is being used by any modules. If the file is being
       
  1267  * used the delete will be canceled.
       
  1268  *
       
  1269  * @param $file
       
  1270  *   A file object.
       
  1271  * @param $force
       
  1272  *   Boolean indicating that the file should be deleted even if the file is
       
  1273  *   reported as in use by the file_usage table.
       
  1274  *
       
  1275  * @return mixed
       
  1276  *   TRUE for success, FALSE in the event of an error, or an array if the file
       
  1277  *   is being used by any modules.
       
  1278  *
       
  1279  * @see file_unmanaged_delete()
       
  1280  * @see file_usage_list()
       
  1281  * @see file_usage_delete()
       
  1282  * @see hook_file_delete()
       
  1283  */
       
  1284 function file_delete(stdClass $file, $force = FALSE) {
       
  1285   if (!file_valid_uri($file->uri)) {
       
  1286     if (($realpath = drupal_realpath($file->uri)) !== FALSE) {
       
  1287       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));
       
  1288     }
       
  1289     else {
       
  1290       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));
       
  1291     }
       
  1292     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');
       
  1293     return FALSE;
       
  1294   }
       
  1295 
       
  1296   // If any module still has a usage entry in the file_usage table, the file
       
  1297   // will not be deleted, but file_delete() will return a populated array
       
  1298   // that tests as TRUE.
       
  1299   if (!$force && ($references = file_usage_list($file))) {
       
  1300     return $references;
       
  1301   }
       
  1302 
       
  1303   // Let other modules clean up any references to the deleted file.
       
  1304   module_invoke_all('file_delete', $file);
       
  1305   module_invoke_all('entity_delete', $file, 'file');
       
  1306 
       
  1307   // Make sure the file is deleted before removing its row from the
       
  1308   // database, so UIs can still find the file in the database.
       
  1309   if (file_unmanaged_delete($file->uri)) {
       
  1310     db_delete('file_managed')->condition('fid', $file->fid)->execute();
       
  1311     db_delete('file_usage')->condition('fid', $file->fid)->execute();
       
  1312     entity_get_controller('file')->resetCache();
       
  1313     return TRUE;
       
  1314   }
       
  1315   return FALSE;
       
  1316 }
       
  1317 
       
  1318 /**
       
  1319  * Deletes a file without database changes or hook invocations.
       
  1320  *
       
  1321  * This function should be used when the file to be deleted does not have an
       
  1322  * entry recorded in the files table.
       
  1323  *
       
  1324  * @param $path
       
  1325  *   A string containing a file path or (streamwrapper) URI.
       
  1326  *
       
  1327  * @return
       
  1328  *   TRUE for success or path does not exist, or FALSE in the event of an
       
  1329  *   error.
       
  1330  *
       
  1331  * @see file_delete()
       
  1332  * @see file_unmanaged_delete_recursive()
       
  1333  */
       
  1334 function file_unmanaged_delete($path) {
       
  1335   if (is_dir($path)) {
       
  1336     watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
       
  1337     return FALSE;
       
  1338   }
       
  1339   if (is_file($path)) {
       
  1340     return drupal_unlink($path);
       
  1341   }
       
  1342   // Return TRUE for non-existent file, but log that nothing was actually
       
  1343   // deleted, as the current state is the intended result.
       
  1344   if (!file_exists($path)) {
       
  1345     watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE);
       
  1346     return TRUE;
       
  1347   }
       
  1348   // We cannot handle anything other than files and directories. Log an error
       
  1349   // for everything else (sockets, symbolic links, etc).
       
  1350   watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR);
       
  1351   return FALSE;
       
  1352 }
       
  1353 
       
  1354 /**
       
  1355  * Deletes all files and directories in the specified filepath recursively.
       
  1356  *
       
  1357  * If the specified path is a directory then the function will call itself
       
  1358  * recursively to process the contents. Once the contents have been removed the
       
  1359  * directory will also be removed.
       
  1360  *
       
  1361  * If the specified path is a file then it will be passed to
       
  1362  * file_unmanaged_delete().
       
  1363  *
       
  1364  * Note that this only deletes visible files with write permission.
       
  1365  *
       
  1366  * @param $path
       
  1367  *   A string containing either an URI or a file or directory path.
       
  1368  *
       
  1369  * @return
       
  1370  *   TRUE for success or if path does not exist, FALSE in the event of an
       
  1371  *   error.
       
  1372  *
       
  1373  * @see file_unmanaged_delete()
       
  1374  */
       
  1375 function file_unmanaged_delete_recursive($path) {
       
  1376   if (is_dir($path)) {
       
  1377     $dir = dir($path);
       
  1378     while (($entry = $dir->read()) !== FALSE) {
       
  1379       if ($entry == '.' || $entry == '..') {
       
  1380         continue;
       
  1381       }
       
  1382       $entry_path = $path . '/' . $entry;
       
  1383       file_unmanaged_delete_recursive($entry_path);
       
  1384     }
       
  1385     $dir->close();
       
  1386 
       
  1387     return drupal_rmdir($path);
       
  1388   }
       
  1389   return file_unmanaged_delete($path);
       
  1390 }
       
  1391 
       
  1392 /**
       
  1393  * Determines total disk space used by a single user or the whole filesystem.
       
  1394  *
       
  1395  * @param $uid
       
  1396  *   Optional. A user id, specifying NULL returns the total space used by all
       
  1397  *   non-temporary files.
       
  1398  * @param $status
       
  1399  *   Optional. The file status to consider. The default is to only
       
  1400  *   consider files in status FILE_STATUS_PERMANENT.
       
  1401  *
       
  1402  * @return
       
  1403  *   An integer containing the number of bytes used.
       
  1404  */
       
  1405 function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
       
  1406   $query = db_select('file_managed', 'f');
       
  1407   $query->condition('f.status', $status);
       
  1408   $query->addExpression('SUM(f.filesize)', 'filesize');
       
  1409   if (isset($uid)) {
       
  1410     $query->condition('f.uid', $uid);
       
  1411   }
       
  1412   return $query->execute()->fetchField();
       
  1413 }
       
  1414 
       
  1415 /**
       
  1416  * Saves a file upload to a new location.
       
  1417  *
       
  1418  * The file will be added to the {file_managed} table as a temporary file.
       
  1419  * Temporary files are periodically cleaned. To make the file a permanent file,
       
  1420  * assign the status and use file_save() to save the changes.
       
  1421  *
       
  1422  * @param $form_field_name
       
  1423  *   A string that is the associative array key of the upload form element in
       
  1424  *   the form array.
       
  1425  * @param $validators
       
  1426  *   An optional, associative array of callback functions used to validate the
       
  1427  *   file. See file_validate() for a full discussion of the array format.
       
  1428  *   If no extension validator is provided it will default to a limited safe
       
  1429  *   list of extensions which is as follows: "jpg jpeg gif png txt
       
  1430  *   doc xls pdf ppt pps odt ods odp". To allow all extensions you must
       
  1431  *   explicitly set the 'file_validate_extensions' validator to an empty array
       
  1432  *   (Beware: this is not safe and should only be allowed for trusted users, if
       
  1433  *   at all).
       
  1434  * @param $destination
       
  1435  *   A string containing the URI that the file should be copied to. This must
       
  1436  *   be a stream wrapper URI. If this value is omitted, Drupal's temporary
       
  1437  *   files scheme will be used ("temporary://").
       
  1438  * @param $replace
       
  1439  *   Replace behavior when the destination file already exists:
       
  1440  *   - FILE_EXISTS_REPLACE: Replace the existing file.
       
  1441  *   - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
       
  1442  *     unique.
       
  1443  *   - FILE_EXISTS_ERROR: Do nothing and return FALSE.
       
  1444  *
       
  1445  * @return
       
  1446  *   An object containing the file information if the upload succeeded, FALSE
       
  1447  *   in the event of an error, or NULL if no file was uploaded. The
       
  1448  *   documentation for the "File interface" group, which you can find under
       
  1449  *   Related topics, or the header at the top of this file, documents the
       
  1450  *   components of a file object. In addition to the standard components,
       
  1451  *   this function adds:
       
  1452  *   - source: Path to the file before it is moved.
       
  1453  *   - destination: Path to the file after it is moved (same as 'uri').
       
  1454  */
       
  1455 function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
       
  1456   global $user;
       
  1457   static $upload_cache;
       
  1458 
       
  1459   // Return cached objects without processing since the file will have
       
  1460   // already been processed and the paths in _FILES will be invalid.
       
  1461   if (isset($upload_cache[$form_field_name])) {
       
  1462     return $upload_cache[$form_field_name];
       
  1463   }
       
  1464 
       
  1465   // Make sure there's an upload to process.
       
  1466   if (empty($_FILES['files']['name'][$form_field_name])) {
       
  1467     return NULL;
       
  1468   }
       
  1469 
       
  1470   // Check for file upload errors and return FALSE if a lower level system
       
  1471   // error occurred. For a complete list of errors:
       
  1472   // See http://php.net/manual/features.file-upload.errors.php.
       
  1473   switch ($_FILES['files']['error'][$form_field_name]) {
       
  1474     case UPLOAD_ERR_INI_SIZE:
       
  1475     case UPLOAD_ERR_FORM_SIZE:
       
  1476       drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$form_field_name], '%maxsize' => format_size(file_upload_max_size()))), 'error');
       
  1477       return FALSE;
       
  1478 
       
  1479     case UPLOAD_ERR_PARTIAL:
       
  1480     case UPLOAD_ERR_NO_FILE:
       
  1481       drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $_FILES['files']['name'][$form_field_name])), 'error');
       
  1482       return FALSE;
       
  1483 
       
  1484     case UPLOAD_ERR_OK:
       
  1485       // Final check that this is a valid upload, if it isn't, use the
       
  1486       // default error handler.
       
  1487       if (is_uploaded_file($_FILES['files']['tmp_name'][$form_field_name])) {
       
  1488         break;
       
  1489       }
       
  1490 
       
  1491     // Unknown error
       
  1492     default:
       
  1493       drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$form_field_name])), 'error');
       
  1494       return FALSE;
       
  1495   }
       
  1496 
       
  1497   // Begin building file object.
       
  1498   $file = new stdClass();
       
  1499   $file->uid      = $user->uid;
       
  1500   $file->status   = 0;
       
  1501   $file->filename = trim(drupal_basename($_FILES['files']['name'][$form_field_name]), '.');
       
  1502   $file->uri      = $_FILES['files']['tmp_name'][$form_field_name];
       
  1503   $file->filemime = file_get_mimetype($file->filename);
       
  1504   $file->filesize = $_FILES['files']['size'][$form_field_name];
       
  1505 
       
  1506   $extensions = '';
       
  1507   if (isset($validators['file_validate_extensions'])) {
       
  1508     if (isset($validators['file_validate_extensions'][0])) {
       
  1509       // Build the list of non-munged extensions if the caller provided them.
       
  1510       $extensions = $validators['file_validate_extensions'][0];
       
  1511     }
       
  1512     else {
       
  1513       // If 'file_validate_extensions' is set and the list is empty then the
       
  1514       // caller wants to allow any extension. In this case we have to remove the
       
  1515       // validator or else it will reject all extensions.
       
  1516       unset($validators['file_validate_extensions']);
       
  1517     }
       
  1518   }
       
  1519   else {
       
  1520     // No validator was provided, so add one using the default list.
       
  1521     // Build a default non-munged safe list for file_munge_filename().
       
  1522     $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
       
  1523     $validators['file_validate_extensions'] = array();
       
  1524     $validators['file_validate_extensions'][0] = $extensions;
       
  1525   }
       
  1526 
       
  1527   if (!empty($extensions)) {
       
  1528     // Munge the filename to protect against possible malicious extension hiding
       
  1529     // within an unknown file type (ie: filename.html.foo).
       
  1530     $file->filename = file_munge_filename($file->filename, $extensions);
       
  1531   }
       
  1532 
       
  1533   // Rename potentially executable files, to help prevent exploits (i.e. will
       
  1534   // rename filename.php.foo and filename.php to filename.php.foo.txt and
       
  1535   // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
       
  1536   // evaluates to TRUE.
       
  1537   if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
       
  1538     $file->filemime = 'text/plain';
       
  1539     $file->uri .= '.txt';
       
  1540     $file->filename .= '.txt';
       
  1541     // The .txt extension may not be in the allowed list of extensions. We have
       
  1542     // to add it here or else the file upload will fail.
       
  1543     if (!empty($extensions)) {
       
  1544       $validators['file_validate_extensions'][0] .= ' txt';
       
  1545       drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename)));
       
  1546     }
       
  1547   }
       
  1548 
       
  1549   // If the destination is not provided, use the temporary directory.
       
  1550   if (empty($destination)) {
       
  1551     $destination = 'temporary://';
       
  1552   }
       
  1553 
       
  1554   // Assert that the destination contains a valid stream.
       
  1555   $destination_scheme = file_uri_scheme($destination);
       
  1556   if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
       
  1557     drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
       
  1558     return FALSE;
       
  1559   }
       
  1560 
       
  1561   $file->source = $form_field_name;
       
  1562   // A URI may already have a trailing slash or look like "public://".
       
  1563   if (substr($destination, -1) != '/') {
       
  1564     $destination .= '/';
       
  1565   }
       
  1566   $file->destination = file_destination($destination . $file->filename, $replace);
       
  1567   // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
       
  1568   // there's an existing file so we need to bail.
       
  1569   if ($file->destination === FALSE) {
       
  1570     drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error');
       
  1571     return FALSE;
       
  1572   }
       
  1573 
       
  1574   // Add in our check of the file name length.
       
  1575   $validators['file_validate_name_length'] = array();
       
  1576 
       
  1577   // Call the validation functions specified by this function's caller.
       
  1578   $errors = file_validate($file, $validators);
       
  1579 
       
  1580   // Check for errors.
       
  1581   if (!empty($errors)) {
       
  1582     $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
       
  1583     if (count($errors) > 1) {
       
  1584       $message .= theme('item_list', array('items' => $errors));
       
  1585     }
       
  1586     else {
       
  1587       $message .= ' ' . array_pop($errors);
       
  1588     }
       
  1589     form_set_error($form_field_name, $message);
       
  1590     return FALSE;
       
  1591   }
       
  1592 
       
  1593   // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
       
  1594   // directory. This overcomes open_basedir restrictions for future file
       
  1595   // operations.
       
  1596   $file->uri = $file->destination;
       
  1597   if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$form_field_name], $file->uri)) {
       
  1598     form_set_error($form_field_name, t('File upload error. Could not move uploaded file.'));
       
  1599     watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
       
  1600     return FALSE;
       
  1601   }
       
  1602 
       
  1603   // Set the permissions on the new file.
       
  1604   drupal_chmod($file->uri);
       
  1605 
       
  1606   // If we are replacing an existing file re-use its database record.
       
  1607   if ($replace == FILE_EXISTS_REPLACE) {
       
  1608     $existing_files = file_load_multiple(array(), array('uri' => $file->uri));
       
  1609     if (count($existing_files)) {
       
  1610       $existing = reset($existing_files);
       
  1611       $file->fid = $existing->fid;
       
  1612     }
       
  1613   }
       
  1614 
       
  1615   // If we made it this far it's safe to record this file in the database.
       
  1616   if ($file = file_save($file)) {
       
  1617     // Track non-public files in the session if they were uploaded by an
       
  1618     // anonymous user. This allows modules such as the File module to only
       
  1619     // grant view access to the specific anonymous user who uploaded the file.
       
  1620     // See file_file_download().
       
  1621     // The 'file_public_schema' variable is used to allow other publicly
       
  1622     // accessible file schemes to be treated the same as the public:// scheme
       
  1623     // provided by Drupal core and to avoid adding unnecessary data to the
       
  1624     // session (and the resulting bypass of the page cache) in those cases. For
       
  1625     // security reasons, only schemes that are completely publicly accessible,
       
  1626     // with no download restrictions, should be added to this variable. See
       
  1627     // file_managed_file_value().
       
  1628     if (!$user->uid && !in_array($destination_scheme, variable_get('file_public_schema', array('public')))) {
       
  1629       $_SESSION['anonymous_allowed_file_ids'][$file->fid] = $file->fid;
       
  1630     }
       
  1631     // Add file to the cache.
       
  1632     $upload_cache[$form_field_name] = $file;
       
  1633     return $file;
       
  1634   }
       
  1635   return FALSE;
       
  1636 }
       
  1637 
       
  1638 /**
       
  1639  * Moves an uploaded file to a new location.
       
  1640  *
       
  1641  * PHP's move_uploaded_file() does not properly support streams if safe_mode
       
  1642  * or open_basedir are enabled, so this function fills that gap.
       
  1643  *
       
  1644  * Compatibility: normal paths and stream wrappers.
       
  1645  *
       
  1646  * @param $filename
       
  1647  *   The filename of the uploaded file.
       
  1648  * @param $uri
       
  1649  *   A string containing the destination URI of the file.
       
  1650  *
       
  1651  * @return
       
  1652  *   TRUE on success, or FALSE on failure.
       
  1653  *
       
  1654  * @see move_uploaded_file()
       
  1655  * @see http://drupal.org/node/515192
       
  1656  * @ingroup php_wrappers
       
  1657  */
       
  1658 function drupal_move_uploaded_file($filename, $uri) {
       
  1659   $result = @move_uploaded_file($filename, $uri);
       
  1660   // PHP's move_uploaded_file() does not properly support streams if safe_mode
       
  1661   // or open_basedir are enabled so if the move failed, try finding a real path
       
  1662   // and retry the move operation.
       
  1663   if (!$result) {
       
  1664     if ($realpath = drupal_realpath($uri)) {
       
  1665       $result = move_uploaded_file($filename, $realpath);
       
  1666     }
       
  1667     else {
       
  1668       $result = move_uploaded_file($filename, $uri);
       
  1669     }
       
  1670   }
       
  1671 
       
  1672   return $result;
       
  1673 }
       
  1674 
       
  1675 /**
       
  1676  * Checks that a file meets the criteria specified by the validators.
       
  1677  *
       
  1678  * After executing the validator callbacks specified hook_file_validate() will
       
  1679  * also be called to allow other modules to report errors about the file.
       
  1680  *
       
  1681  * @param $file
       
  1682  *   A Drupal file object.
       
  1683  * @param $validators
       
  1684  *   An optional, associative array of callback functions used to validate the
       
  1685  *   file. The keys are function names and the values arrays of callback
       
  1686  *   parameters which will be passed in after the file object. The
       
  1687  *   functions should return an array of error messages; an empty array
       
  1688  *   indicates that the file passed validation. The functions will be called in
       
  1689  *   the order specified.
       
  1690  *
       
  1691  * @return
       
  1692  *   An array containing validation error messages.
       
  1693  *
       
  1694  * @see hook_file_validate()
       
  1695  */
       
  1696 function file_validate(stdClass &$file, $validators = array()) {
       
  1697   // Call the validation functions specified by this function's caller.
       
  1698   $errors = array();
       
  1699   foreach ($validators as $function => $args) {
       
  1700     if (function_exists($function)) {
       
  1701       array_unshift($args, $file);
       
  1702       $errors = array_merge($errors, call_user_func_array($function, $args));
       
  1703     }
       
  1704   }
       
  1705 
       
  1706   // Let other modules perform validation on the new file.
       
  1707   return array_merge($errors, module_invoke_all('file_validate', $file));
       
  1708 }
       
  1709 
       
  1710 /**
       
  1711  * Checks for files with names longer than we can store in the database.
       
  1712  *
       
  1713  * @param $file
       
  1714  *   A Drupal file object.
       
  1715  *
       
  1716  * @return
       
  1717  *   An array. If the file name is too long, it will contain an error message.
       
  1718  */
       
  1719 function file_validate_name_length(stdClass $file) {
       
  1720   $errors = array();
       
  1721 
       
  1722   if (empty($file->filename)) {
       
  1723     $errors[] = t("The file's name is empty. Please give a name to the file.");
       
  1724   }
       
  1725   if (strlen($file->filename) > 240) {
       
  1726     $errors[] = t("The file's name exceeds the 240 characters limit. Please rename the file and try again.");
       
  1727   }
       
  1728   return $errors;
       
  1729 }
       
  1730 
       
  1731 /**
       
  1732  * Checks that the filename ends with an allowed extension.
       
  1733  *
       
  1734  * @param $file
       
  1735  *   A Drupal file object.
       
  1736  * @param $extensions
       
  1737  *   A string with a space separated list of allowed extensions.
       
  1738  *
       
  1739  * @return
       
  1740  *   An array. If the file extension is not allowed, it will contain an error
       
  1741  *   message.
       
  1742  *
       
  1743  * @see hook_file_validate()
       
  1744  */
       
  1745 function file_validate_extensions(stdClass $file, $extensions) {
       
  1746   $errors = array();
       
  1747 
       
  1748   $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
       
  1749   if (!preg_match($regex, $file->filename)) {
       
  1750     $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
       
  1751   }
       
  1752   return $errors;
       
  1753 }
       
  1754 
       
  1755 /**
       
  1756  * Checks that the file's size is below certain limits.
       
  1757  *
       
  1758  * @param $file
       
  1759  *   A Drupal file object.
       
  1760  * @param $file_limit
       
  1761  *   An integer specifying the maximum file size in bytes. Zero indicates that
       
  1762  *   no limit should be enforced.
       
  1763  * @param $user_limit
       
  1764  *   An integer specifying the maximum number of bytes the user is allowed.
       
  1765  *   Zero indicates that no limit should be enforced.
       
  1766  *
       
  1767  * @return
       
  1768  *   An array. If the file size exceeds limits, it will contain an error
       
  1769  *   message.
       
  1770  *
       
  1771  * @see hook_file_validate()
       
  1772  */
       
  1773 function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) {
       
  1774   global $user;
       
  1775   $errors = array();
       
  1776 
       
  1777   if ($file_limit && $file->filesize > $file_limit) {
       
  1778     $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
       
  1779   }
       
  1780 
       
  1781   // Save a query by only calling file_space_used() when a limit is provided.
       
  1782   if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) {
       
  1783     $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
       
  1784   }
       
  1785 
       
  1786   return $errors;
       
  1787 }
       
  1788 
       
  1789 /**
       
  1790  * Checks that the file is recognized by image_get_info() as an image.
       
  1791  *
       
  1792  * @param $file
       
  1793  *   A Drupal file object.
       
  1794  *
       
  1795  * @return
       
  1796  *   An array. If the file is not an image, it will contain an error message.
       
  1797  *
       
  1798  * @see hook_file_validate()
       
  1799  */
       
  1800 function file_validate_is_image(stdClass $file) {
       
  1801   $errors = array();
       
  1802 
       
  1803   $info = image_get_info($file->uri);
       
  1804   if (!$info || empty($info['extension'])) {
       
  1805     $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
       
  1806   }
       
  1807 
       
  1808   return $errors;
       
  1809 }
       
  1810 
       
  1811 /**
       
  1812  * Verifies that image dimensions are within the specified maximum and minimum.
       
  1813  *
       
  1814  * Non-image files will be ignored. If an image toolkit is available the image
       
  1815  * will be scaled to fit within the desired maximum dimensions.
       
  1816  *
       
  1817  * @param $file
       
  1818  *   A Drupal file object. This function may resize the file affecting its
       
  1819  *   size.
       
  1820  * @param $maximum_dimensions
       
  1821  *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
       
  1822  *   an image toolkit is installed the image will be resized down to these
       
  1823  *   dimensions. A value of 0 indicates no restriction on size, so resizing
       
  1824  *   will be attempted.
       
  1825  * @param $minimum_dimensions
       
  1826  *   An optional string in the form WIDTHxHEIGHT. This will check that the
       
  1827  *   image meets a minimum size. A value of 0 indicates no restriction.
       
  1828  *
       
  1829  * @return
       
  1830  *   An array. If the file is an image and did not meet the requirements, it
       
  1831  *   will contain an error message.
       
  1832  *
       
  1833  * @see hook_file_validate()
       
  1834  */
       
  1835 function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
       
  1836   $errors = array();
       
  1837 
       
  1838   // Check first that the file is an image.
       
  1839   if ($info = image_get_info($file->uri)) {
       
  1840     if ($maximum_dimensions) {
       
  1841       // Check that it is smaller than the given dimensions.
       
  1842       list($width, $height) = explode('x', $maximum_dimensions);
       
  1843       if ($info['width'] > $width || $info['height'] > $height) {
       
  1844         // Try to resize the image to fit the dimensions.
       
  1845         if ($image = image_load($file->uri)) {
       
  1846           image_scale($image, $width, $height);
       
  1847           image_save($image);
       
  1848           $file->filesize = $image->info['file_size'];
       
  1849           drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
       
  1850         }
       
  1851         else {
       
  1852           $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
       
  1853         }
       
  1854       }
       
  1855     }
       
  1856 
       
  1857     if ($minimum_dimensions) {
       
  1858       // Check that it is larger than the given dimensions.
       
  1859       list($width, $height) = explode('x', $minimum_dimensions);
       
  1860       if ($info['width'] < $width || $info['height'] < $height) {
       
  1861         $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions));
       
  1862       }
       
  1863     }
       
  1864   }
       
  1865 
       
  1866   return $errors;
       
  1867 }
       
  1868 
       
  1869 /**
       
  1870  * Saves a file to the specified destination and creates a database entry.
       
  1871  *
       
  1872  * @param $data
       
  1873  *   A string containing the contents of the file.
       
  1874  * @param $destination
       
  1875  *   A string containing the destination URI. This must be a stream wrapper URI.
       
  1876  *   If no value is provided, a randomized name will be generated and the file
       
  1877  *   will be saved using Drupal's default files scheme, usually "public://".
       
  1878  * @param $replace
       
  1879  *   Replace behavior when the destination file already exists:
       
  1880  *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
       
  1881  *       the destination name exists then its database entry will be updated. If
       
  1882  *       no database entry is found then a new one will be created.
       
  1883  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
  1884  *       unique.
       
  1885  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
  1886  *
       
  1887  * @return
       
  1888  *   A file object, or FALSE on error.
       
  1889  *
       
  1890  * @see file_unmanaged_save_data()
       
  1891  */
       
  1892 function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
  1893   global $user;
       
  1894 
       
  1895   if (empty($destination)) {
       
  1896     $destination = file_default_scheme() . '://';
       
  1897   }
       
  1898   if (!file_valid_uri($destination)) {
       
  1899     watchdog('file', 'The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', array('%destination' => $destination));
       
  1900     drupal_set_message(t('The data could not be saved, because the destination is invalid. More information is available in the system log.'), 'error');
       
  1901     return FALSE;
       
  1902   }
       
  1903 
       
  1904   if ($uri = file_unmanaged_save_data($data, $destination, $replace)) {
       
  1905     // Create a file object.
       
  1906     $file = new stdClass();
       
  1907     $file->fid = NULL;
       
  1908     $file->uri = $uri;
       
  1909     $file->filename = drupal_basename($uri);
       
  1910     $file->filemime = file_get_mimetype($file->uri);
       
  1911     $file->uid      = $user->uid;
       
  1912     $file->status   = FILE_STATUS_PERMANENT;
       
  1913     // If we are replacing an existing file re-use its database record.
       
  1914     if ($replace == FILE_EXISTS_REPLACE) {
       
  1915       $existing_files = file_load_multiple(array(), array('uri' => $uri));
       
  1916       if (count($existing_files)) {
       
  1917         $existing = reset($existing_files);
       
  1918         $file->fid = $existing->fid;
       
  1919         $file->filename = $existing->filename;
       
  1920       }
       
  1921     }
       
  1922     // If we are renaming around an existing file (rather than a directory),
       
  1923     // use its basename for the filename.
       
  1924     elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) {
       
  1925       $file->filename = drupal_basename($destination);
       
  1926     }
       
  1927 
       
  1928     return file_save($file);
       
  1929   }
       
  1930   return FALSE;
       
  1931 }
       
  1932 
       
  1933 /**
       
  1934  * Saves a string to the specified destination without invoking file API.
       
  1935  *
       
  1936  * This function is identical to file_save_data() except the file will not be
       
  1937  * saved to the {file_managed} table and none of the file_* hooks will be
       
  1938  * called.
       
  1939  *
       
  1940  * @param $data
       
  1941  *   A string containing the contents of the file.
       
  1942  * @param $destination
       
  1943  *   A string containing the destination location. This must be a stream wrapper
       
  1944  *   URI. If no value is provided, a randomized name will be generated and the
       
  1945  *   file will be saved using Drupal's default files scheme, usually
       
  1946  *   "public://".
       
  1947  * @param $replace
       
  1948  *   Replace behavior when the destination file already exists:
       
  1949  *   - FILE_EXISTS_REPLACE - Replace the existing file.
       
  1950  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
       
  1951  *                          unique.
       
  1952  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
       
  1953  *
       
  1954  * @return
       
  1955  *   A string with the path of the resulting file, or FALSE on error.
       
  1956  *
       
  1957  * @see file_save_data()
       
  1958  */
       
  1959 function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
       
  1960   // Write the data to a temporary file.
       
  1961   $temp_name = drupal_tempnam('temporary://', 'file');
       
  1962   if (file_put_contents($temp_name, $data) === FALSE) {
       
  1963     drupal_set_message(t('The file could not be created.'), 'error');
       
  1964     return FALSE;
       
  1965   }
       
  1966 
       
  1967   // Move the file to its final destination.
       
  1968   return file_unmanaged_move($temp_name, $destination, $replace);
       
  1969 }
       
  1970 
       
  1971 /**
       
  1972  * Transfers a file to the client using HTTP.
       
  1973  *
       
  1974  * Pipes a file through Drupal to the client.
       
  1975  *
       
  1976  * @param $uri
       
  1977  *   String specifying the file URI to transfer.
       
  1978  * @param $headers
       
  1979  *   An array of HTTP headers to send along with file.
       
  1980  */
       
  1981 function file_transfer($uri, $headers) {
       
  1982   if (ob_get_level()) {
       
  1983     ob_end_clean();
       
  1984   }
       
  1985 
       
  1986   foreach ($headers as $name => $value) {
       
  1987     drupal_add_http_header($name, $value);
       
  1988   }
       
  1989   drupal_send_headers();
       
  1990   $scheme = file_uri_scheme($uri);
       
  1991   // Transfer file in 1024 byte chunks to save memory usage.
       
  1992   if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) {
       
  1993     while (!feof($fd)) {
       
  1994       print fread($fd, 1024);
       
  1995     }
       
  1996     fclose($fd);
       
  1997   }
       
  1998   else {
       
  1999     drupal_not_found();
       
  2000   }
       
  2001   drupal_exit();
       
  2002 }
       
  2003 
       
  2004 /**
       
  2005  * Menu handler for private file transfers.
       
  2006  *
       
  2007  * Call modules that implement hook_file_download() to find out if a file is
       
  2008  * accessible and what headers it should be transferred with. If one or more
       
  2009  * modules returned headers the download will start with the returned headers.
       
  2010  * If a module returns -1 drupal_access_denied() will be returned. If the file
       
  2011  * exists but no modules responded drupal_access_denied() will be returned.
       
  2012  * If the file does not exist drupal_not_found() will be returned.
       
  2013  *
       
  2014  * @see system_menu()
       
  2015  */
       
  2016 function file_download() {
       
  2017   // Merge remainder of arguments from GET['q'], into relative file path.
       
  2018   $args = func_get_args();
       
  2019   $scheme = array_shift($args);
       
  2020   $target = implode('/', $args);
       
  2021   $uri = $scheme . '://' . $target;
       
  2022   if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
       
  2023     $headers = file_download_headers($uri);
       
  2024     if (count($headers)) {
       
  2025       file_transfer($uri, $headers);
       
  2026     }
       
  2027     drupal_access_denied();
       
  2028   }
       
  2029   else {
       
  2030     drupal_not_found();
       
  2031   }
       
  2032   drupal_exit();
       
  2033 }
       
  2034 
       
  2035 /**
       
  2036  * Retrieves headers for a private file download.
       
  2037  *
       
  2038  * Calls all module implementations of hook_file_download() to retrieve headers
       
  2039  * for files by the module that originally provided the file. The presence of
       
  2040  * returned headers indicates the current user has access to the file.
       
  2041  *
       
  2042  * @param $uri
       
  2043  *   The URI for the file whose headers should be retrieved.
       
  2044  *
       
  2045  * @return
       
  2046  *   If access is allowed, headers for the file, suitable for passing to
       
  2047  *   file_transfer(). If access is not allowed, an empty array will be returned.
       
  2048  *
       
  2049  * @see file_transfer()
       
  2050  * @see file_download_access()
       
  2051  * @see hook_file_download()
       
  2052  */
       
  2053 function file_download_headers($uri) {
       
  2054   // Let other modules provide headers and control access to the file.
       
  2055   // module_invoke_all() uses array_merge_recursive() which merges header
       
  2056   // values into a new array. To avoid that and allow modules to override
       
  2057   // headers instead, use array_merge() to merge the returned arrays.
       
  2058   $headers = array();
       
  2059   foreach (module_implements('file_download') as $module) {
       
  2060     $function = $module . '_file_download';
       
  2061     $result = $function($uri);
       
  2062     if ($result == -1) {
       
  2063       // Throw away the headers received so far.
       
  2064       $headers = array();
       
  2065       break;
       
  2066     }
       
  2067     if (isset($result) && is_array($result)) {
       
  2068       $headers = array_merge($headers, $result);
       
  2069     }
       
  2070   }
       
  2071   return $headers;
       
  2072 }
       
  2073 
       
  2074 /**
       
  2075  * Checks that the current user has access to a particular file.
       
  2076  *
       
  2077  * The return value of this function hinges on the return value from
       
  2078  * file_download_headers(), which is the function responsible for collecting
       
  2079  * access information through hook_file_download().
       
  2080  *
       
  2081  * If immediately transferring the file to the browser and the headers will
       
  2082  * need to be retrieved, the return value of file_download_headers() should be
       
  2083  * used to determine access directly, so that access checks will not be run
       
  2084  * twice.
       
  2085  *
       
  2086  * @param $uri
       
  2087  *   The URI for the file whose access should be retrieved.
       
  2088  *
       
  2089  * @return
       
  2090  *   Boolean TRUE if access is allowed. FALSE if access is not allowed.
       
  2091  *
       
  2092  * @see file_download_headers()
       
  2093  * @see hook_file_download()
       
  2094  */
       
  2095 function file_download_access($uri) {
       
  2096   return count(file_download_headers($uri)) > 0;
       
  2097 }
       
  2098 
       
  2099 /**
       
  2100  * Finds all files that match a given mask in a given directory.
       
  2101  *
       
  2102  * Directories and files beginning with a period are excluded; this
       
  2103  * prevents hidden files and directories (such as SVN working directories)
       
  2104  * from being scanned.
       
  2105  *
       
  2106  * @param $dir
       
  2107  *   The base directory or URI to scan, without trailing slash.
       
  2108  * @param $mask
       
  2109  *   The preg_match() regular expression of the files to find.
       
  2110  * @param $options
       
  2111  *   An associative array of additional options, with the following elements:
       
  2112  *   - 'nomask': The preg_match() regular expression of the files to ignore.
       
  2113  *     Defaults to '/(\.\.?|CVS)$/'.
       
  2114  *   - 'callback': The callback function to call for each match. There is no
       
  2115  *     default callback.
       
  2116  *   - 'recurse': When TRUE, the directory scan will recurse the entire tree
       
  2117  *     starting at the provided directory. Defaults to TRUE.
       
  2118  *   - 'key': The key to be used for the returned associative array of files.
       
  2119  *     Possible values are 'uri', for the file's URI; 'filename', for the
       
  2120  *     basename of the file; and 'name' for the name of the file without the
       
  2121  *     extension. Defaults to 'uri'.
       
  2122  *   - 'min_depth': Minimum depth of directories to return files from. Defaults
       
  2123  *     to 0.
       
  2124  * @param $depth
       
  2125  *   Current depth of recursion. This parameter is only used internally and
       
  2126  *   should not be passed in.
       
  2127  *
       
  2128  * @return
       
  2129  *   An associative array (keyed on the chosen key) of objects with 'uri',
       
  2130  *   'filename', and 'name' members corresponding to the matching files.
       
  2131  */
       
  2132 function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
       
  2133   // Merge in defaults.
       
  2134   $options += array(
       
  2135     'nomask' => '/(\.\.?|CVS)$/',
       
  2136     'callback' => 0,
       
  2137     'recurse' => TRUE,
       
  2138     'key' => 'uri',
       
  2139     'min_depth' => 0,
       
  2140   );
       
  2141 
       
  2142   $options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri';
       
  2143   $files = array();
       
  2144   if (is_dir($dir) && $handle = opendir($dir)) {
       
  2145     while (FALSE !== ($filename = readdir($handle))) {
       
  2146       if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
       
  2147         $uri = "$dir/$filename";
       
  2148         $uri = file_stream_wrapper_uri_normalize($uri);
       
  2149         if (is_dir($uri) && $options['recurse']) {
       
  2150           // Give priority to files in this folder by merging them in after any subdirectory files.
       
  2151           $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
       
  2152         }
       
  2153         elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
       
  2154           // Always use this match over anything already set in $files with the
       
  2155           // same $$options['key'].
       
  2156           $file = new stdClass();
       
  2157           $file->uri = $uri;
       
  2158           $file->filename = $filename;
       
  2159           $file->name = pathinfo($filename, PATHINFO_FILENAME);
       
  2160           $key = $options['key'];
       
  2161           $files[$file->$key] = $file;
       
  2162           if ($options['callback']) {
       
  2163             $options['callback']($uri);
       
  2164           }
       
  2165         }
       
  2166       }
       
  2167     }
       
  2168 
       
  2169     closedir($handle);
       
  2170   }
       
  2171 
       
  2172   return $files;
       
  2173 }
       
  2174 
       
  2175 /**
       
  2176  * Determines the maximum file upload size by querying the PHP settings.
       
  2177  *
       
  2178  * @return
       
  2179  *   A file size limit in bytes based on the PHP upload_max_filesize and
       
  2180  *   post_max_size
       
  2181  */
       
  2182 function file_upload_max_size() {
       
  2183   static $max_size = -1;
       
  2184 
       
  2185   if ($max_size < 0) {
       
  2186     // Start with post_max_size.
       
  2187     $max_size = parse_size(ini_get('post_max_size'));
       
  2188 
       
  2189     // If upload_max_size is less, then reduce. Except if upload_max_size is
       
  2190     // zero, which indicates no limit.
       
  2191     $upload_max = parse_size(ini_get('upload_max_filesize'));
       
  2192     if ($upload_max > 0 && $upload_max < $max_size) {
       
  2193       $max_size = $upload_max;
       
  2194     }
       
  2195   }
       
  2196   return $max_size;
       
  2197 }
       
  2198 
       
  2199 /**
       
  2200  * Determines an Internet Media Type or MIME type from a filename.
       
  2201  *
       
  2202  * @param $uri
       
  2203  *   A string containing the URI, path, or filename.
       
  2204  * @param $mapping
       
  2205  *   An optional map of extensions to their mimetypes, in the form:
       
  2206  *    - 'mimetypes': a list of mimetypes, keyed by an identifier,
       
  2207  *    - 'extensions': the mapping itself, an associative array in which
       
  2208  *      the key is the extension (lowercase) and the value is the mimetype
       
  2209  *      identifier. If $mapping is NULL file_mimetype_mapping() is called.
       
  2210  *
       
  2211  * @return
       
  2212  *   The internet media type registered for the extension or
       
  2213  *   application/octet-stream for unknown extensions.
       
  2214  *
       
  2215  * @see file_default_mimetype_mapping()
       
  2216  */
       
  2217 function file_get_mimetype($uri, $mapping = NULL) {
       
  2218   if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
       
  2219     return $wrapper->getMimeType($uri, $mapping);
       
  2220   }
       
  2221   else {
       
  2222     // getMimeType() is not implementation specific, so we can directly
       
  2223     // call it without an instance.
       
  2224     return DrupalLocalStreamWrapper::getMimeType($uri, $mapping);
       
  2225   }
       
  2226 }
       
  2227 
       
  2228 /**
       
  2229  * Sets the permissions on a file or directory.
       
  2230  *
       
  2231  * This function will use the 'file_chmod_directory' and 'file_chmod_file'
       
  2232  * variables for the default modes for directories and uploaded/generated
       
  2233  * files. By default these will give everyone read access so that users
       
  2234  * accessing the files with a user account without the webserver group (e.g.
       
  2235  * via FTP) can read these files, and give group write permissions so webserver
       
  2236  * group members (e.g. a vhost account) can alter files uploaded and owned by
       
  2237  * the webserver.
       
  2238  *
       
  2239  * PHP's chmod does not support stream wrappers so we use our wrapper
       
  2240  * implementation which interfaces with chmod() by default. Contrib wrappers
       
  2241  * may override this behavior in their implementations as needed.
       
  2242  *
       
  2243  * @param $uri
       
  2244  *   A string containing a URI file, or directory path.
       
  2245  * @param $mode
       
  2246  *   Integer value for the permissions. Consult PHP chmod() documentation for
       
  2247  *   more information.
       
  2248  *
       
  2249  * @return
       
  2250  *   TRUE for success, FALSE in the event of an error.
       
  2251  *
       
  2252  * @ingroup php_wrappers
       
  2253  */
       
  2254 function drupal_chmod($uri, $mode = NULL) {
       
  2255   if (!isset($mode)) {
       
  2256     if (is_dir($uri)) {
       
  2257       $mode = variable_get('file_chmod_directory', 0775);
       
  2258     }
       
  2259     else {
       
  2260       $mode = variable_get('file_chmod_file', 0664);
       
  2261     }
       
  2262   }
       
  2263 
       
  2264   // If this URI is a stream, pass it off to the appropriate stream wrapper.
       
  2265   // Otherwise, attempt PHP's chmod. This allows use of drupal_chmod even
       
  2266   // for unmanaged files outside of the stream wrapper interface.
       
  2267   if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
       
  2268     if ($wrapper->chmod($mode)) {
       
  2269       return TRUE;
       
  2270     }
       
  2271   }
       
  2272   else {
       
  2273     if (@chmod($uri, $mode)) {
       
  2274       return TRUE;
       
  2275     }
       
  2276   }
       
  2277 
       
  2278   watchdog('file', 'The file permissions could not be set on %uri.', array('%uri' => $uri), WATCHDOG_ERROR);
       
  2279   return FALSE;
       
  2280 }
       
  2281 
       
  2282 /**
       
  2283  * Deletes a file.
       
  2284  *
       
  2285  * PHP's unlink() is broken on Windows, as it can fail to remove a file
       
  2286  * when it has a read-only flag set.
       
  2287  *
       
  2288  * @param $uri
       
  2289  *   A URI or pathname.
       
  2290  * @param $context
       
  2291  *   Refer to http://php.net/manual/ref.stream.php
       
  2292  *
       
  2293  * @return
       
  2294  *   Boolean TRUE on success, or FALSE on failure.
       
  2295  *
       
  2296  * @see unlink()
       
  2297  * @ingroup php_wrappers
       
  2298  */
       
  2299 function drupal_unlink($uri, $context = NULL) {
       
  2300   $scheme = file_uri_scheme($uri);
       
  2301   if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) {
       
  2302     chmod($uri, 0600);
       
  2303   }
       
  2304   if ($context) {
       
  2305     return unlink($uri, $context);
       
  2306   }
       
  2307   else {
       
  2308     return unlink($uri);
       
  2309   }
       
  2310 }
       
  2311 
       
  2312 /**
       
  2313  * Resolves the absolute filepath of a local URI or filepath.
       
  2314  *
       
  2315  * The use of drupal_realpath() is discouraged, because it does not work for
       
  2316  * remote URIs. Except in rare cases, URIs should not be manually resolved.
       
  2317  *
       
  2318  * Only use this function if you know that the stream wrapper in the URI uses
       
  2319  * the local file system, and you need to pass an absolute path to a function
       
  2320  * that is incompatible with stream URIs.
       
  2321  *
       
  2322  * @param string $uri
       
  2323  *   A stream wrapper URI or a filepath, possibly including one or more symbolic
       
  2324  *   links.
       
  2325  *
       
  2326  * @return string|false
       
  2327  *   The absolute local filepath (with no symbolic links), or FALSE on failure.
       
  2328  *
       
  2329  * @see DrupalStreamWrapperInterface::realpath()
       
  2330  * @see http://php.net/manual/function.realpath.php
       
  2331  * @ingroup php_wrappers
       
  2332  */
       
  2333 function drupal_realpath($uri) {
       
  2334   // If this URI is a stream, pass it off to the appropriate stream wrapper.
       
  2335   // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even
       
  2336   // for unmanaged files outside of the stream wrapper interface.
       
  2337   if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
       
  2338     return $wrapper->realpath();
       
  2339   }
       
  2340   // Check that the URI has a value. There is a bug in PHP 5.2 on *BSD systems
       
  2341   // that makes realpath not return FALSE as expected when passing an empty
       
  2342   // variable.
       
  2343   // @todo Remove when Drupal drops support for PHP 5.2.
       
  2344   elseif (!empty($uri)) {
       
  2345     return realpath($uri);
       
  2346   }
       
  2347   return FALSE;
       
  2348 }
       
  2349 
       
  2350 /**
       
  2351  * Gets the name of the directory from a given path.
       
  2352  *
       
  2353  * PHP's dirname() does not properly pass streams, so this function fills
       
  2354  * that gap. It is backwards compatible with normal paths and will use
       
  2355  * PHP's dirname() as a fallback.
       
  2356  *
       
  2357  * Compatibility: normal paths and stream wrappers.
       
  2358  *
       
  2359  * @param $uri
       
  2360  *   A URI or path.
       
  2361  *
       
  2362  * @return
       
  2363  *   A string containing the directory name.
       
  2364  *
       
  2365  * @see dirname()
       
  2366  * @see http://drupal.org/node/515192
       
  2367  * @ingroup php_wrappers
       
  2368  */
       
  2369 function drupal_dirname($uri) {
       
  2370   $scheme = file_uri_scheme($uri);
       
  2371 
       
  2372   if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
       
  2373     return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri);
       
  2374   }
       
  2375   else {
       
  2376     return dirname($uri);
       
  2377   }
       
  2378 }
       
  2379 
       
  2380 /**
       
  2381  * Gets the filename from a given path.
       
  2382  *
       
  2383  * PHP's basename() does not properly support streams or filenames beginning
       
  2384  * with a non-US-ASCII character.
       
  2385  *
       
  2386  * @see http://bugs.php.net/bug.php?id=37738
       
  2387  * @see basename()
       
  2388  *
       
  2389  * @ingroup php_wrappers
       
  2390  */
       
  2391 function drupal_basename($uri, $suffix = NULL) {
       
  2392   $separators = '/';
       
  2393   if (DIRECTORY_SEPARATOR != '/') {
       
  2394     // For Windows OS add special separator.
       
  2395     $separators .= DIRECTORY_SEPARATOR;
       
  2396   }
       
  2397   // Remove right-most slashes when $uri points to directory.
       
  2398   $uri = rtrim($uri, $separators);
       
  2399   // Returns the trailing part of the $uri starting after one of the directory
       
  2400   // separators.
       
  2401   $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : '';
       
  2402   // Cuts off a suffix from the filename.
       
  2403   if ($suffix) {
       
  2404     $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
       
  2405   }
       
  2406   return $filename;
       
  2407 }
       
  2408 
       
  2409 /**
       
  2410  * Creates a directory using Drupal's default mode.
       
  2411  *
       
  2412  * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode
       
  2413  * is not provided, this function will make sure that Drupal's is used.
       
  2414  *
       
  2415  * Compatibility: normal paths and stream wrappers.
       
  2416  *
       
  2417  * @param $uri
       
  2418  *   A URI or pathname.
       
  2419  * @param $mode
       
  2420  *   By default the Drupal mode is used.
       
  2421  * @param $recursive
       
  2422  *   Default to FALSE.
       
  2423  * @param $context
       
  2424  *   Refer to http://php.net/manual/ref.stream.php
       
  2425  *
       
  2426  * @return
       
  2427  *   Boolean TRUE on success, or FALSE on failure.
       
  2428  *
       
  2429  * @see mkdir()
       
  2430  * @see http://drupal.org/node/515192
       
  2431  * @ingroup php_wrappers
       
  2432  */
       
  2433 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
       
  2434   if (!isset($mode)) {
       
  2435     $mode = variable_get('file_chmod_directory', 0775);
       
  2436   }
       
  2437 
       
  2438   if (!isset($context)) {
       
  2439     return mkdir($uri, $mode, $recursive);
       
  2440   }
       
  2441   else {
       
  2442     return mkdir($uri, $mode, $recursive, $context);
       
  2443   }
       
  2444 }
       
  2445 
       
  2446 /**
       
  2447  * Removes a directory.
       
  2448  *
       
  2449  * PHP's rmdir() is broken on Windows, as it can fail to remove a directory
       
  2450  * when it has a read-only flag set.
       
  2451  *
       
  2452  * @param $uri
       
  2453  *   A URI or pathname.
       
  2454  * @param $context
       
  2455  *   Refer to http://php.net/manual/ref.stream.php
       
  2456  *
       
  2457  * @return
       
  2458  *   Boolean TRUE on success, or FALSE on failure.
       
  2459  *
       
  2460  * @see rmdir()
       
  2461  * @ingroup php_wrappers
       
  2462  */
       
  2463 function drupal_rmdir($uri, $context = NULL) {
       
  2464   $scheme = file_uri_scheme($uri);
       
  2465   if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) {
       
  2466     chmod($uri, 0700);
       
  2467   }
       
  2468   if ($context) {
       
  2469     return rmdir($uri, $context);
       
  2470   }
       
  2471   else {
       
  2472     return rmdir($uri);
       
  2473   }
       
  2474 }
       
  2475 
       
  2476 /**
       
  2477  * Creates a file with a unique filename in the specified directory.
       
  2478  *
       
  2479  * PHP's tempnam() does not return a URI like we want. This function
       
  2480  * will return a URI if given a URI, or it will return a filepath if
       
  2481  * given a filepath.
       
  2482  *
       
  2483  * Compatibility: normal paths and stream wrappers.
       
  2484  *
       
  2485  * @param $directory
       
  2486  *   The directory where the temporary filename will be created.
       
  2487  * @param $prefix
       
  2488  *   The prefix of the generated temporary filename.
       
  2489  *   Note: Windows uses only the first three characters of prefix.
       
  2490  *
       
  2491  * @return
       
  2492  *   The new temporary filename, or FALSE on failure.
       
  2493  *
       
  2494  * @see tempnam()
       
  2495  * @see http://drupal.org/node/515192
       
  2496  * @ingroup php_wrappers
       
  2497  */
       
  2498 function drupal_tempnam($directory, $prefix) {
       
  2499   $scheme = file_uri_scheme($directory);
       
  2500 
       
  2501   if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
       
  2502     $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
       
  2503 
       
  2504     if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) {
       
  2505       return $scheme . '://' . drupal_basename($filename);
       
  2506     }
       
  2507     else {
       
  2508       return FALSE;
       
  2509     }
       
  2510   }
       
  2511   else {
       
  2512     // Handle as a normal tempnam() call.
       
  2513     return tempnam($directory, $prefix);
       
  2514   }
       
  2515 }
       
  2516 
       
  2517 /**
       
  2518  * Gets the path of system-appropriate temporary directory.
       
  2519  */
       
  2520 function file_directory_temp() {
       
  2521   $temporary_directory = variable_get('file_temporary_path', NULL);
       
  2522 
       
  2523   if (empty($temporary_directory)) {
       
  2524     $directories = array();
       
  2525 
       
  2526     // Has PHP been set with an upload_tmp_dir?
       
  2527     if (ini_get('upload_tmp_dir')) {
       
  2528       $directories[] = ini_get('upload_tmp_dir');
       
  2529     }
       
  2530 
       
  2531     // Operating system specific dirs.
       
  2532     if (substr(PHP_OS, 0, 3) == 'WIN') {
       
  2533       $directories[] = 'c:\\windows\\temp';
       
  2534       $directories[] = 'c:\\winnt\\temp';
       
  2535     }
       
  2536     else {
       
  2537       $directories[] = '/tmp';
       
  2538     }
       
  2539     // PHP may be able to find an alternative tmp directory.
       
  2540     // This function exists in PHP 5 >= 5.2.1, but Drupal
       
  2541     // requires PHP 5 >= 5.2.0, so we check for it.
       
  2542     if (function_exists('sys_get_temp_dir')) {
       
  2543       $directories[] = sys_get_temp_dir();
       
  2544     }
       
  2545 
       
  2546     foreach ($directories as $directory) {
       
  2547       if (is_dir($directory) && is_writable($directory)) {
       
  2548         $temporary_directory = $directory;
       
  2549         break;
       
  2550       }
       
  2551     }
       
  2552 
       
  2553     if (empty($temporary_directory)) {
       
  2554       // If no directory has been found default to 'files/tmp'.
       
  2555       $temporary_directory = variable_get('file_public_path', conf_path() . '/files') . '/tmp';
       
  2556 
       
  2557       // Windows accepts paths with either slash (/) or backslash (\), but will
       
  2558       // not accept a path which contains both a slash and a backslash. Since
       
  2559       // the 'file_public_path' variable may have either format, we sanitize
       
  2560       // everything to use slash which is supported on all platforms.
       
  2561       $temporary_directory = str_replace('\\', '/', $temporary_directory);
       
  2562     }
       
  2563     // Save the path of the discovered directory.
       
  2564     variable_set('file_temporary_path', $temporary_directory);
       
  2565   }
       
  2566 
       
  2567   return $temporary_directory;
       
  2568 }
       
  2569 
       
  2570 /**
       
  2571  * Examines a file object and returns appropriate content headers for download.
       
  2572  *
       
  2573  * @param $file
       
  2574  *   A file object.
       
  2575  *
       
  2576  * @return
       
  2577  *   An associative array of headers, as expected by file_transfer().
       
  2578  */
       
  2579 function file_get_content_headers($file) {
       
  2580   $type = mime_header_encode($file->filemime);
       
  2581 
       
  2582   return array(
       
  2583     'Content-Type' => $type,
       
  2584     'Content-Length' => $file->filesize,
       
  2585     'Cache-Control' => 'private',
       
  2586   );
       
  2587 }
       
  2588 
       
  2589 /**
       
  2590  * @} End of "defgroup file".
       
  2591  */