/[drupal]/contributions/modules/hook_file/hook_file.module
ViewVC logotype

Contents of /contributions/modules/hook_file/hook_file.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Sat Jan 3 16:08:44 2009 UTC (10 months, 3 weeks ago) by aaron
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
File MIME type: text/x-php
Experimental backport of file.inc to hook_file.module (aaron).
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Backport of the hook_file patch from Drupal 7 to 6.
7 * API for handling file uploads and server file management.
8 */
9
10
11 /**
12 * @defgroup file File interface
13 * @{
14 * Common file handling functions.
15 *
16 * Fields on the file object:
17 * - fid - File ID
18 * - uid - The {users}.uid of the user who is associated with the file.
19 * - filename - Name of the file with no path components. This may differ from
20 * the basename of the filepath if the file is renamed to avoid overwriting
21 * an existing file.
22 * - filepath - Path of the file relative to Drupal root.
23 * - filemime - The file's MIME type.
24 * - filesize - The size of the file in bytes.
25 * - status - A bitmapped field indicating the status of the file the least
26 * sigifigant bit indicates temporary (1) or permanent (0). Temporary files
27 * older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.
28 * - timestamp - UNIX timestamp for the date the file was added to the database.
29 */
30
31 /**
32 * Flag to indicate that the 'public' file download method is enabled.
33 *
34 * When using this method, files are available from a regular HTTP request,
35 * which provides no additional access restrictions.
36 */
37 // define('FILE_DOWNLOADS_PUBLIC', 1);
38
39 /**
40 * Flag to indicate that the 'private' file download method is enabled.
41 *
42 * When using this method, all file requests are served by Drupal, during which
43 * access-control checking can be performed.
44 */
45 // define('FILE_DOWNLOADS_PRIVATE', 2);
46
47 /**
48 * Flag used by hook_file_check_directory() -- create directory if not present.
49 */
50 // define('FILE_CREATE_DIRECTORY', 1);
51
52 /**
53 * Flag used by hook_file_check_directory() -- file permissions may be changed.
54 */
55 // define('FILE_MODIFY_PERMISSIONS', 2);
56
57 /**
58 * Flag for dealing with existing files: Appends number until name is unique.
59 */
60 // define('FILE_EXISTS_RENAME', 0);
61
62 /**
63 * Flag for dealing with existing files: Replace the existing file.
64 */
65 // define('FILE_EXISTS_REPLACE', 1);
66
67 /**
68 * Flag for dealing with existing files: Do nothing and return FALSE.
69 */
70 // define('FILE_EXISTS_ERROR', 2);
71
72 /**
73 * File status -- File has been temporarily saved to the {files} tables.
74 *
75 * Drupal's file garbage collection will delete the file and remove it from the
76 * files table after a set period of time.
77 */
78 // define('FILE_STATUS_TEMPORARY', 0);
79
80 /**
81 * File status -- File has been permanently saved to the {files} tables.
82 *
83 * If you wish to add custom statuses for use by contrib modules please expand
84 * as binary flags and consider the first 8 bits reserved.
85 * (0,1,2,4,8,16,32,64,128).
86 */
87 // define('FILE_STATUS_PERMANENT', 1);
88
89 /**
90 * Create the download path to a file.
91 *
92 * @param $path A string containing the path of the file to generate URL for.
93 * @return A string containing a URL that can be used to download the file.
94 */
95 function hook_file_create_url($path) {
96 // Strip hook_file_directory_path from $path. We only include relative paths in
97 // URLs.
98 if (strpos($path, hook_file_directory_path() . '/') === 0) {
99 $path = trim(substr($path, strlen(hook_file_directory_path())), '\\/');
100 }
101 switch (variable_get('hook_file_downloads', FILE_DOWNLOADS_PUBLIC)) {
102 case FILE_DOWNLOADS_PUBLIC:
103 return $GLOBALS['base_url'] . '/' . hook_file_directory_path() . '/' . str_replace('\\', '/', $path);
104 case FILE_DOWNLOADS_PRIVATE:
105 return url('system/files/' . $path, array('absolute' => TRUE));
106 }
107 }
108
109 /**
110 * Make sure the destination is a complete path and resides in the file system
111 * directory, if it is not prepend the file system directory.
112 *
113 * @param $destination
114 * A string containing the path to verify. If this value is omitted, Drupal's
115 * 'files' directory will be used.
116 * @return
117 * A string containing the path to file, with file system directory appended
118 * if necessary, or FALSE if the path is invalid (i.e. outside the configured
119 * 'files' or temp directories).
120 */
121 function hook_file_create_path($destination = NULL) {
122 $hook_file_path = hook_file_directory_path();
123 if (is_null($destination)) {
124 return $hook_file_path;
125 }
126 // hook_file_check_location() checks whether the destination is inside the Drupal
127 // files directory.
128 if (hook_file_check_location($destination, $hook_file_path)) {
129 return $destination;
130 }
131 // Check if the destination is instead inside the Drupal temporary files
132 // directory.
133 elseif (hook_file_check_location($destination, hook_file_directory_temp())) {
134 return $destination;
135 }
136 // Not found, try again with prefixed directory path.
137 elseif (hook_file_check_location($hook_file_path . '/' . $destination, $hook_file_path)) {
138 return $hook_file_path . '/' . $destination;
139 }
140 // File not found.
141 return FALSE;
142 }
143
144 /**
145 * Check that the directory exists and is writable.
146 *
147 * Directories need to have execute permissions to be considered a directory by
148 * FTP servers, etc.
149 *
150 * @param $directory
151 * A string containing the name of a directory path.
152 * @param $mode
153 * A bitmask to indicate if the directory should be created if it does
154 * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
155 * (FILE_MODIFY_PERMISSIONS).
156 * @param $form_item
157 * An optional string containing the name of a form item that any errors will
158 * be attached to. This is useful for settings forms that require the user to
159 * specify a writable directory. If it can't be made to work, a form error
160 * will be set preventing them from saving the settings.
161 * @return
162 * FALSE when directory not found, or TRUE when directory exists.
163 */
164 function hook_file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
165 $directory = rtrim($directory, '/\\');
166
167 // Check if directory exists.
168 if (!is_dir($directory)) {
169 if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
170 @chmod($directory, 0775); // Necessary for non-webserver users.
171 }
172 else {
173 if ($form_item) {
174 form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
175 watchdog('file system', 'The directory %directory does not exist.', array('%directory' => $directory), WATCHDOG_ERROR);
176 }
177 return FALSE;
178 }
179 }
180
181 // Check to see if the directory is writable.
182 if (!is_writable($directory)) {
183 // If not able to modify permissions, or if able to, but chmod
184 // fails, return false.
185 if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775))) {
186 if ($form_item) {
187 form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
188 watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
189 }
190 return FALSE;
191 }
192 }
193
194 if ((hook_file_directory_path() == $directory || hook_file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
195 $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
196 if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
197 fclose($fp);
198 chmod($directory . '/.htaccess', 0664);
199 }
200 else {
201 $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
202 form_set_error($form_item, t("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));
203 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);
204 }
205 }
206
207 return TRUE;
208 }
209
210 /**
211 * Checks path to see if it is a directory, or a directory/file.
212 *
213 * @param $path
214 * A string containing a file path. This will be set to the directory's path.
215 * @return
216 * If the directory is not in a Drupal writable directory, FALSE is returned.
217 * Otherwise, the base name of the path is returned.
218 */
219 function hook_file_check_path(&$path) {
220 // Check if path is a directory.
221 if (hook_file_check_directory($path)) {
222 return '';
223 }
224
225 // Check if path is a possible dir/file.
226 $filename = basename($path);
227 $path = dirname($path);
228 if (hook_file_check_directory($path)) {
229 return $filename;
230 }
231
232 return FALSE;
233 }
234
235 /**
236 * Check if a file is really located inside $directory.
237 *
238 * This should be used to make sure a file specified is really located within
239 * the directory to prevent exploits. Note that the file or path being checked
240 * does not actually need to exist yet.
241 *
242 * @code
243 * // Returns FALSE:
244 * hook_file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files');
245 * @endcode
246 *
247 * @param $source
248 * A string set to the file to check.
249 * @param $directory
250 * A string where the file should be located.
251 * @return
252 * FALSE if the path does not exist in the directory; otherwise, the real
253 * path of the source.
254 */
255 function hook_file_check_location($source, $directory = '') {
256 $check = realpath($source);
257 if ($check) {
258 $source = $check;
259 }
260 else {
261 // This file does not yet exist.
262 $source = realpath(dirname($source)) . '/' . basename($source);
263 }
264 $directory = realpath($directory);
265 if ($directory && strpos($source, $directory) !== 0) {
266 return FALSE;
267 }
268 return $source;
269 }
270
271 /**
272 * Load a file object from the database.
273 *
274 * @param $param
275 * Either the id of a file or an array of conditions to match against in the
276 * database query.
277 * @param $reset
278 * Whether to reset the internal hook_file_load cache.
279 * @return
280 * A file object.
281 *
282 * @see hook_hook_file_load()
283 */
284 function hook_file_load($param, $reset = NULL) {
285 static $files = array();
286
287 if ($reset) {
288 $files = array();
289 }
290
291 if (is_numeric($param)) {
292 if (isset($files[(string) $param])) {
293 return is_object($files[$param]) ? clone $files[$param] : $files[$param];
294 }
295 $result = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid', array(':fid' => $param));
296 }
297 elseif (is_array($param)) {
298 // Turn the conditions into a query.
299 $cond = array();
300 $arguments = array();
301 foreach ($param as $key => $value) {
302 $cond[] = 'f.' . db_escape_table($key) . " = '%s'";
303 $arguments[] = $value;
304 }
305 $result = db_query('SELECT f.* FROM {files} f WHERE ' . implode(' AND ', $cond), $arguments);
306 }
307 else {
308 return FALSE;
309 }
310 $file = $result->fetch(PDO::FETCH_OBJ);
311
312 if ($file && $file->fid) {
313 // Allow modules to add or change the file object.
314 module_invoke_all('hook_file_load', $file);
315
316 // Cache the fully loaded value.
317 $files[(string) $file->fid] = clone $file;
318 }
319
320 return $file;
321 }
322
323 /**
324 * Save a file object to the database.
325 *
326 * If the $file->fid is not set a new record will be added. Re-saving an
327 * existing file will not change its status.
328 *
329 * @param $file
330 * A file object returned by hook_file_load().
331 * @return
332 * The updated file object.
333 *
334 * @see hook_hook_file_insert()
335 * @see hook_hook_file_update()
336 */
337 function hook_file_save($file) {
338 $file = (object)$file;
339 $file->timestamp = REQUEST_TIME;
340 $file->filesize = filesize($file->filepath);
341
342 if (empty($file->fid)) {
343 drupal_write_record('files', $file);
344 // Inform modules about the newly added file.
345 module_invoke_all('hook_file_insert', $file);
346 }
347 else {
348 drupal_write_record('files', $file, 'fid');
349 // Inform modules that the file has been updated.
350 module_invoke_all('hook_file_update', $file);
351 }
352
353 return $file;
354 }
355
356 /**
357 * Copy a file to a new location and adds a file record to the database.
358 *
359 * This function should be used when manipulating files that have records
360 * stored in the database. This is a powerful function that in many ways
361 * performs like an advanced version of copy().
362 * - Checks if $source and $destination are valid and readable/writable.
363 * - Checks that $source is not equal to $destination; if they are an error
364 * is reported.
365 * - If file already exists in $destination either the call will error out,
366 * replace the file or rename the file based on the $replace parameter.
367 * - Adds the new file to the files database. If the source file is a
368 * temporary file, the resulting file will also be a temporary file.
369 * @see hook_file_save_upload about temporary files.
370 *
371 * @param $source
372 * A file object.
373 * @param $destination
374 * A string containing the directory $source should be copied to. If this
375 * value is omitted, Drupal's 'files' directory will be used.
376 * @param $replace
377 * Replace behavior when the destination file already exists:
378 * - FILE_EXISTS_REPLACE - Replace the existing file.
379 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
380 * unique.
381 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
382 * @return
383 * File object if the copy is successful, or FALSE in the event of an error.
384 *
385 * @see hook_file_unmanaged_copy()
386 * @see hook_hook_file_copy()
387 */
388 function hook_file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
389 $source = (object)$source;
390
391 if ($filepath = hook_file_unmanaged_copy($source->filepath, $destination, $replace)) {
392 $file = clone $source;
393 $file->fid = NULL;
394 $file->filename = basename($filepath);
395 $file->filepath = $filepath;
396 if ($file = hook_file_save($file)) {
397 // Inform modules that the file has been copied.
398 module_invoke_all('hook_file_copy', $file, $source);
399 return $file;
400 }
401 }
402 return FALSE;
403 }
404
405 /**
406 * Copy a file to a new location without calling any hooks or making any
407 * changes to the database.
408 *
409 * This is a powerful function that in many ways performs like an advanced
410 * version of copy().
411 * - Checks if $source and $destination are valid and readable/writable.
412 * - Checks that $source is not equal to $destination; if they are an error
413 * is reported.
414 * - If file already exists in $destination either the call will error out,
415 * replace the file or rename the file based on the $replace parameter.
416 *
417 * @param $source
418 * A string specifying the file location of the original file.
419 * @param $destination
420 * A string containing the directory $source should be copied to. If this
421 * value is omitted, Drupal's 'files' directory will be used.
422 * @param $replace
423 * Replace behavior when the destination file already exists:
424 * - FILE_EXISTS_REPLACE - Replace the existing file.
425 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
426 * unique.
427 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
428 * @return
429 * The path to the new file, or FALSE in the event of an error.
430 *
431 * @see hook_file_copy()
432 */
433 function hook_file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
434 $source = realpath($source);
435 if (!hook_file_exists($source)) {
436 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' => $source)), 'error');
437 return FALSE;
438 }
439
440 $destination = hook_file_create_path($destination);
441 $directory = $destination;
442 $basename = hook_file_check_path($directory);
443
444 // Make sure we at least have a valid directory.
445 if ($basename === FALSE) {
446 drupal_set_message(t('The specified file %file could not be copied, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $destination)), 'error');
447 return FALSE;
448 }
449
450 // If the destination file is not specified then use the filename of the
451 // source file.
452 $basename = $basename ? $basename : basename($source);
453 $destination = hook_file_destination($directory . '/' . $basename, $replace);
454
455 if ($destination === FALSE) {
456 drupal_set_message(t('The specified file %file could not be copied because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
457 return FALSE;
458 }
459 // Make sure source and destination filenames are not the same, makes no
460 // sense to copy it if they are. In fact copying the file will most likely
461 // result in a 0 byte file. Which is bad. Real bad.
462 if ($source == realpath($destination)) {
463 drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
464 return FALSE;
465 }
466 if (!@copy($source, $destination)) {
467 drupal_set_message(t('The specified file %file could not be copied.', array('%file' => $source)), 'error');
468 return FALSE;
469 }
470
471 // Give everyone read access so that FTP'd users or
472 // non-webserver users can see/read these files,
473 // and give group write permissions so group members
474 // can alter files uploaded by the webserver.
475 @chmod($destination, 0664);
476
477 return $destination;
478 }
479
480 /**
481 * Determines the destination path for a file depending on how replacement of
482 * existing files should be handled.
483 *
484 * @param $destination
485 * A string specifying the desired path.
486 * @param $replace
487 * Replace behavior when the destination file already exists.
488 * - FILE_EXISTS_REPLACE - Replace the existing file.
489 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
490 * unique.
491 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
492 * @return
493 * The destination file path or FALSE if the file already exists and
494 * FILE_EXISTS_ERROR was specified.
495 */
496 function hook_file_destination($destination, $replace) {
497 if (hook_file_exists($destination)) {
498 switch ($replace) {
499 case FILE_EXISTS_REPLACE:
500 // Do nothing here, we want to overwrite the existing file.
501 break;
502
503 case FILE_EXISTS_RENAME:
504 $basename = basename($destination);
505 $directory = dirname($destination);
506 $destination = hook_file_create_filename($basename, $directory);
507 break;
508
509 case FILE_EXISTS_ERROR:
510 drupal_set_message(t('The specified file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error');
511 return FALSE;
512 }
513 }
514 return $destination;
515 }
516
517 /**
518 * Move a file to a new location and update the file's database entry.
519 *
520 * Moving a file is performed by copying the file to the new location and then
521 * deleting the original.
522 * - Checks if $source and $destination are valid and readable/writable.
523 * - Performs a file move if $source is not equal to $destination.
524 * - If file already exists in $destination either the call will error out,
525 * replace the file or rename the file based on the $replace parameter.
526 * - Adds the new file to the files database.
527 *
528 * @param $source
529 * A file object.
530 * @param $destination
531 * A string containing the directory $source should be copied to. If this
532 * value is omitted, Drupal's 'files' directory will be used.
533 * @param $replace
534 * Replace behavior when the destination file already exists:
535 * - FILE_EXISTS_REPLACE - Replace the existing file.
536 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
537 * unique.
538 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
539 * @return
540 * Resulting file object for success, or FALSE in the event of an error.
541 *
542 * @see hook_file_unmanaged_move()
543 * @see hook_hook_file_move()
544 */
545 function hook_file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
546 $source = (object)$source;
547
548 if ($filepath = hook_file_unmanaged_move($source->filepath, $destination, $replace)) {
549 $file = clone $source;
550 $file->filename = basename($filepath);
551 $file->filepath = $filepath;
552 if ($file = hook_file_save($file)) {
553 // Inform modules that the file has been moved.
554 module_invoke_all('hook_file_move', $file, $source);
555 return $file;
556 }
557 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $source->filepath)), 'error');
558 }
559 return FALSE;
560 }
561
562 /**
563 * Move a file to a new location without calling any hooks or making any
564 * changes to the database.
565 *
566 * @param $source
567 * A string specifying the file location of the original file.
568 * @param $destination
569 * A string containing the directory $source should be copied to. If this
570 * value is omitted, Drupal's 'files' directory will be used.
571 * @param $replace
572 * Replace behavior when the destination file already exists:
573 * - FILE_EXISTS_REPLACE - Replace the existing file.
574 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
575 * unique.
576 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
577 * @return
578 * The filepath of the moved file, or FALSE in the event of an error.
579 *
580 * @see hook_file_move()
581 */
582 function hook_file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
583 $filepath = hook_file_unmanaged_copy($source, $destination, $replace);
584 if ($filepath == FALSE || hook_file_unmanaged_delete($source) == FALSE) {
585 return FALSE;
586 }
587 return $filepath;
588 }
589
590 /**
591 * Munge the filename as needed for security purposes.
592 *
593 * For instance the file name "exploit.php.pps" would become "exploit.php_.pps".
594 *
595 * @param $filename
596 * The name of a file to modify.
597 * @param $extensions
598 * A space separated list of extensions that should not be altered.
599 * @param $alerts
600 * Whether alerts (watchdog, drupal_set_message()) should be displayed.
601 * @return
602 * $filename The potentially modified $filename.
603 */
604 function hook_file_munge_filename($filename, $extensions, $alerts = TRUE) {
605 $original = $filename;
606
607 // Allow potentially insecure uploads for very savvy users and admin
608 if (!variable_get('allow_insecure_uploads', 0)) {
609 $whitelist = array_unique(explode(' ', trim($extensions)));
610
611 // Split the filename up by periods. The first part becomes the basename
612 // the last part the final extension.
613 $filename_parts = explode('.', $filename);
614 $new_filename = array_shift($filename_parts); // Remove file basename.
615 $final_extension = array_pop($filename_parts); // Remove final extension.
616
617 // Loop through the middle parts of the name and add an underscore to the
618 // end of each section that could be a file extension but isn't in the list
619 // of allowed extensions.
620 foreach ($filename_parts as $filename_part) {
621 $new_filename .= '.' . $filename_part;
622 if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
623 $new_filename .= '_';
624 }
625 }
626 $filename = $new_filename . '.' . $final_extension;
627
628 if ($alerts && $original != $filename) {
629 drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
630 }
631 }
632
633 return $filename;
634 }
635
636 /**
637 * Undo the effect of upload_munge_filename().
638 *
639 * @param $filename
640 * String with the filename to be unmunged.
641 * @return
642 * An unmunged filename string.
643 */
644 function hook_file_unmunge_filename($filename) {
645 return str_replace('_.', '.', $filename);
646 }
647
648 /**
649 * Create a full file path from a directory and filename.
650 *
651 * If a file with the specified name already exists, an alternative will be
652 * used.
653 *
654 * @param $basename
655 * String filename
656 * @param $directory
657 * String directory
658 * @return
659 * File path consisting of $directory and a unique filename based off
660 * of $basename.
661 */
662 function hook_file_create_filename($basename, $directory) {
663 $destination = $directory . '/' . $basename;
664
665 if (hook_file_exists($destination)) {
666 // Destination file already exists, generate an alternative.
667 $pos = strrpos($basename, '.');
668 if ($pos !== FALSE) {
669 $name = substr($basename, 0, $pos);
670 $ext = substr($basename, $pos);
671 }
672 else {
673 $name = $basename;
674 $ext = '';
675 }
676
677 $counter = 0;
678 do {
679 $destination = $directory . '/' . $name . '_' . $counter++ . $ext;
680 } while (hook_file_exists($destination));
681 }
682
683 return $destination;
684 }
685
686 /**
687 * Delete a file and its database record.
688 *
689 * If the $force parameter is not TRUE hook_hook_file_references() will be called
690 * to determine if the file is being used by any modules. If the file is being
691 * used is the delete will be canceled.
692 *
693 * @param $file
694 * A file object.
695 * @param $force
696 * Boolean indicating that the file should be deleted even if
697 * hook_hook_file_references() reports that the file is in use.
698 * @return mixed
699 * TRUE for success, FALSE in the event of an error, or an array if the file
700 * is being used by another module. The array keys are the module's name and
701 * the values are the number of references.
702 *
703 * @see hook_file_unmanaged_delete()
704 * @see hook_hook_file_references()
705 * @see hook_hook_file_delete()
706 */
707 function hook_file_delete($file, $force = FALSE) {
708 $file = (object)$file;
709
710 // If any module returns a value from the reference hook, the file will not
711 // be deleted from Drupal, but hook_file_delete will return a populated array that
712 // tests as TRUE.
713 if (!$force && ($references = module_invoke_all('hook_file_references', $file))) {
714 return $references;
715 }
716
717 // Let other modules clean up any references to the deleted file.
718 module_invoke_all('hook_file_delete', $file);
719
720 // Make sure the file is deleted before removing its row from the
721 // database, so UIs can still find the file in the database.
722 if (hook_file_unmanaged_delete($file->filepath)) {
723 db_delete('files')->condition('fid', $file->fid)->execute();
724 return TRUE;
725 }
726 return FALSE;
727 }
728
729 /**
730 * Delete a file without calling any hooks or making any changes to the
731 * database.
732 *
733 * This function should be used when the file to be deleted does not have an
734 * entry recorded in the files table.
735 *
736 * @param $path
737 * A string containing a file path.
738 * @return
739 * TRUE for success or path does not exist, or FALSE in the event of an
740 * error.
741 *
742 * @see hook_file_delete()
743 */
744 function hook_file_unmanaged_delete($path) {
745 if (is_dir($path)) {
746 watchdog('file', '%path is a directory and cannot be removed using hook_file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
747 return FALSE;
748 }
749 if (is_file($path)) {
750 return unlink($path);
751 }
752 // Return TRUE for non-existant file, but log that nothing was actually
753 // deleted, as the current state is the indended result.
754 if (!hook_file_exists($path)) {
755 watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE);
756 return TRUE;
757 }
758 // Catch all for everything else: sockets, symbolic links, etc.
759 return FALSE;
760 }
761
762 /**
763 * Determine total disk space used by a single user or the whole filesystem.
764 *
765 * @param $uid
766 * Optional. A user id, specifying NULL returns the total space used by all
767 * non-temporary files.
768 * @param $status
769 * Optional. File Status to return. Combine with a bitwise OR(|) to return
770 * multiple statuses. The default status is FILE_STATUS_PERMANENT.
771 * @return
772 * An integer containing the number of bytes used.
773 */
774 function hook_file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
775 if (!is_null($uid)) {
776 return db_query('SELECT SUM(filesize) FROM {files} WHERE uid = :uid AND status = :status', array(':uid' => $uid, ':status' => $status))->fetchField();
777 }
778 return db_query('SELECT SUM(filesize) FROM {files} WHERE status = :status', array(':status' => $status))->fetchField();
779 }
780
781 /**
782 * Saves a file upload to a new location.
783 *
784 * The file will be added to the files table as a temporary file. Temporary
785 * files are periodically cleaned. To make the file a permanent file call
786 * assign the status and use hook_file_save() to save it.
787 *
788 * @param $source
789 * A string specifying the name of the upload field to save.
790 * @param $validators
791 * An optional, associative array of callback functions used to validate the
792 * file. See @hook_file_validate for a full discussion of the array format.
793 * @param $destination
794 * A string containing the directory $source should be copied to. If this is
795 * not provided or is not writable, the temporary directory will be used.
796 * @param $replace
797 * A boolean indicating whether an existing file of the same name in the
798 * destination directory should overwritten. A false value will generate a
799 * new, unique filename in the destination directory.
800 * @return
801 * An object containing the file information, or FALSE in the event of an
802 * error.
803 */
804 function hook_file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
805 global $user;
806 static $upload_cache;
807
808 // Return cached objects without processing since the file will have
809 // already been processed and the paths in _FILES will be invalid.
810 if (isset($upload_cache[$source])) {
811 return $upload_cache[$source];
812 }
813
814 // Add in our check of the the file name length.
815 $validators['hook_file_validate_name_length'] = array();
816
817
818 // If a file was uploaded, process it.
819 if (isset($_FILES['files']['name'][$source]) && is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
820 // Check for file upload errors and return FALSE if a lower level system
821 // error occurred.
822 switch ($_FILES['files']['error'][$source]) {
823 // @see http://php.net/manual/en/features.file-upload.errors.php
824 case UPLOAD_ERR_OK:
825 break;
826
827 case UPLOAD_ERR_INI_SIZE:
828 case UPLOAD_ERR_FORM_SIZE:
829 drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(hook_file_upload_max_size()))), 'error');
830 return FALSE;
831
832 case UPLOAD_ERR_PARTIAL:
833 case UPLOAD_ERR_NO_FILE:
834 drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
835 return FALSE;
836
837 // Unknown error
838 default:
839 drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error');
840 return FALSE;
841 }
842
843 // Build the list of non-munged extensions.
844 // @todo: this should not be here. we need to figure out the right place.
845 $extensions = '';
846 foreach ($user->roles as $rid => $name) {
847 $extensions .= ' ' . variable_get("upload_extensions_$rid",
848 variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
849 }
850
851 // Begin building file object.
852 $file = new stdClass();
853 $file->uid = $user->uid;
854 $file->status = FILE_STATUS_TEMPORARY;
855 $file->filename = hook_file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
856 $file->filepath = $_FILES['files']['tmp_name'][$source];
857 $file->filemime = hook_file_get_mimetype($file->filename);
858 $file->filesize = $_FILES['files']['size'][$source];
859
860 // Rename potentially executable files, to help prevent exploits.
861 if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
862 $file->filemime = 'text/plain';
863 $file->filepath .= '.txt';
864 $file->filename .= '.txt';
865 }
866
867 // If the destination is not provided, or is not writable, then use the
868 // temporary directory.
869 if (empty($destination) || hook_file_check_path($destination) === FALSE) {
870 $destination = hook_file_directory_temp();
871 }
872
873 $file->source = $source;
874 $file->destination = hook_file_destination(hook_file_create_path($destination . '/' . $file->filename), $replace);
875
876 // Call the validation functions specified by this function's caller.
877 $errors = hook_file_validate($file, $validators);
878
879 // Check for errors.
880 if (!empty($errors)) {
881 $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
882 if (count($errors) > 1) {
883 $message .= theme('item_list', $errors);
884 }
885 else {
886 $message .= ' ' . array_pop($errors);
887 }
888 form_set_error($source, $message);
889 return FALSE;
890 }
891
892 // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
893 // directory. This overcomes open_basedir restrictions for future file
894 // operations.
895 $file->filepath = $file->destination;
896 if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
897 form_set_error($source, t('File upload error. Could not move uploaded file.'));
898 watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->filepath));
899 return FALSE;
900 }
901
902 // If we made it this far it's safe to record this file in the database.
903 if ($file = hook_file_save($file)) {
904 // Add file to the cache.
905 $upload_cache[$source] = $file;
906 return $file;
907 }
908 }
909 return FALSE;
910 }
911
912
913 /**
914 * Check that a file meets the criteria specified by the validators.
915 *
916 * After executing the validator callbacks specified hook_hook_file_validate() will
917 * also be called to allow other modules to report errors about the file.
918 *
919 * @param $file
920 * A Drupal file object.
921 * @param $validators
922 * An optional, associative array of callback functions used to validate the
923 * file. The keys are function names and the values arrays of callback
924 * parameters which will be passed in after the user and file objects. The
925 * functions should return an array of error messages, an empty array
926 * indicates that the file passed validation. The functions will be called in
927 * the order specified.
928 * @return
929 * An array contaning validation error messages.
930 *
931 * @see hook_hook_file_validate()
932 */
933 function hook_file_validate(&$file, $validators = array()) {
934 // Call the validation functions specified by this function's caller.
935 $errors = array();
936 foreach ($validators as $function => $args) {
937 array_unshift($args, $file);
938 $errors = array_merge($errors, call_user_func_array($function, $args));
939 }
940
941 // Let other modules perform validation on the new file.
942 return array_merge($errors, module_invoke_all('hook_file_validate', $file));
943 }
944
945 /**
946 * Check for files with names longer than we can store in the database.
947 *
948 * @param $file
949 * A Drupal file object.
950 * @return
951 * An array. If the file name is too long, it will contain an error message.
952 */
953 function hook_file_validate_name_length($file) {
954 $errors = array();
955
956 if (empty($file->filename)) {
957 $errors[] = t("The file's name is empty. Please give a name to the file.");
958 }
959 if (strlen($file->filename) > 255) {
960 $errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again.");
961 }
962 return $errors;
963 }
964
965 /**
966 * Check that the filename ends with an allowed extension.
967 *
968 * @param $file
969 * A Drupal file object.
970 * @param $extensions
971 * A string with a space separated
972 * @return
973 * An array. If the file extension is not allowed, it will contain an error
974 * message.
975 *
976 * @see hook_hook_file_validate()
977 */
978 function hook_file_validate_extensions($file, $extensions) {
979 global $user;
980
981 $errors = array();
982
983 $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
984 if (!preg_match($regex, $file->filename)) {
985 $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
986 }
987 return $errors;
988 }
989
990 /**
991 * Check that the file's size is below certain limits.
992 *
993 * This check is not enforced for the user #1.
994 *
995 * @param $file
996 * A Drupal file object.
997 * @param $hook_file_limit
998 * An integer specifying the maximum file size in bytes. Zero indicates that
999 * no limit should be enforced.
1000 * @param $user_limit
1001 * An integer specifying the maximum number of bytes the user is allowed.
1002 * Zero indicates that no limit should be enforced.
1003 * @return
1004 * An array. If the file size exceeds limits, it will contain an error
1005 * message.
1006 *
1007 * @see hook_hook_file_validate()
1008 */
1009 function hook_file_validate_size($file, $hook_file_limit = 0, $user_limit = 0) {
1010 global $user;
1011
1012 $errors = array();
1013
1014 // Bypass validation for uid = 1.
1015 if ($user->uid != 1) {
1016 if ($hook_file_limit && $file->filesize > $hook_file_limit) {
1017 $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($hook_file_limit)));
1018 }
1019
1020 if ($user_limit && (hook_file_space_used($user->uid) + $file->filesize) > $user_limit) {
1021 $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)));
1022 }
1023 }
1024 return $errors;
1025 }
1026
1027 /**
1028 * Check that the file is recognized by image_get_info() as an image.
1029 *
1030 * @param $file
1031 * A Drupal file object.
1032 * @return
1033 * An array. If the file is not an image, it will contain an error message.
1034 *
1035 * @see hook_hook_file_validate()
1036 */
1037 function hook_file_validate_is_image(&$file) {
1038 $errors = array();
1039
1040 $info = image_get_info($file->filepath);
1041 if (!$info || empty($info['extension'])) {
1042 $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
1043 }
1044
1045 return $errors;
1046 }
1047
1048 /**
1049 * If the file is an image verify that its dimensions are within the specified
1050 * maximum and minimum dimensions.
1051 *
1052 * Non-image files will be ignored. If a image toolkit is available the image
1053 * will be scalled to fit within the desired maximum dimensions.
1054 *
1055 * @param $file
1056 * A Drupal file object. This function may resize the file affecting its
1057 * size.
1058 * @param $maximum_dimensions
1059 * An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
1060 * an image toolkit is installed the image will be resized down to these
1061 * dimensions. A value of 0 indicates no restriction on size, so resizing
1062 * will be attempted.
1063 * @param $minimum_dimensions
1064 * An optional string in the form WIDTHxHEIGHT. This will check that the
1065 * image meets a minimum size. A value of 0 indicates no restriction.
1066 * @return
1067 * An array. If the file is an image and did not meet the requirements, it
1068 * will contain an error message.
1069 *
1070 * @see hook_hook_file_validate()
1071 */
1072 function hook_file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
1073 $errors = array();
1074
1075 // Check first that the file is an image.
1076 if ($info = image_get_info($file->filepath)) {
1077 if ($maximum_dimensions) {
1078 // Check that it is smaller than the given dimensions.
1079 list($width, $height) = explode('x', $maximum_dimensions);
1080 if ($info['width'] > $width || $info['height'] > $height) {
1081 // Try to resize the image to fit the dimensions.
1082 if (image_get_toolkit() && image_scale($file->filepath, $file->filepath, $width, $height)) {
1083 drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
1084
1085 // Clear the cached filesize and refresh the image information.
1086 clearstatcache();
1087 $info = image_get_info($file->filepath);
1088 $file->filesize = $info['hook_file_size'];
1089 }
1090 else {
1091 $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
1092 }
1093 }
1094 }
1095
1096 if ($minimum_dimensions) {
1097 // Check that it is larger than the given dimensions.
1098 list($width, $height) = explode('x', $minimum_dimensions);
1099 if ($info['width'] < $width || $info['height'] < $height) {
1100 $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions));
1101 }
1102 }
1103 }
1104
1105 return $errors;
1106 }
1107
1108 /**
1109 * Save a string to the specified destination and create a database file entry.
1110 *
1111 * @param $data
1112 * A string containing the contents of the file.
1113 * @param $destination
1114 * A string containing the destination location. If no value is provided
1115 * then a randomly name will be generated and the file saved in Drupal's
1116 * files directory.
1117 * @param $replace
1118 * Replace behavior when the destination file already exists:
1119 * - FILE_EXISTS_REPLACE - Replace the existing file.
1120 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
1121 * unique.
1122 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1123 * @return
1124 * A file object, or FALSE on error.
1125 *
1126 * @see hook_file_unmanaged_save_data()
1127 */
1128 function hook_file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1129 global $user;
1130
1131 if ($filepath = hook_file_unmanaged_save_data($data, $destination, $replace)) {
1132 // Create a file object.
1133 $file = new stdClass();
1134 $file->filepath = $filepath;
1135 $file->filename = basename($file->filepath);
1136 $file->filemime = hook_file_get_mimetype($file->filepath);
1137 $file->uid = $user->uid;
1138 $file->status = FILE_STATUS_PERMANENT;
1139 return hook_file_save($file);
1140 }
1141 return FALSE;
1142 }
1143
1144 /**
1145 * Save a string to the specified destination without calling any hooks or
1146 * making any changes to the database.
1147 *
1148 * This function is identical to hook_file_save_data() except the file will not be
1149 * saved to the files table and none of the hook_file_* hooks will be called.
1150 *
1151 * @param $data
1152 * A string containing the contents of the file.
1153 * @param $destination
1154 * A string containing the destination location. If no value is provided
1155 * then a randomly name will be generated and the file saved in Drupal's
1156 * files directory.
1157 * @param $replace
1158 * Replace behavior when the destination file already exists:
1159 * - FILE_EXISTS_REPLACE - Replace the existing file.
1160 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
1161 * unique.
1162 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1163 * @return
1164 * A string with the path of the resulting file, or FALSE on error.
1165 *
1166 * @see hook_file_save_data()
1167 */
1168 function hook_file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1169 // Write the data to a temporary file.
1170 $temp_name = tempnam(hook_file_directory_temp(), 'file');
1171 if (hook_file_put_contents($temp_name, $data) === FALSE) {
1172 drupal_set_message(t('The file could not be created.'), 'error');
1173 return FALSE;
1174 }
1175
1176 // Move the file to its final destination.
1177 return hook_file_unmanaged_move($temp_name, $destination, $replace);
1178 }
1179
1180 /**
1181 * Transfer file using HTTP to client. Pipes a file through Drupal to the
1182 * client.
1183 *
1184 * @param $source
1185 * String specifying the file path to transfer.
1186 * @param $headers
1187 * An array of HTTP headers to send along with file.
1188 */
1189 function hook_file_transfer($source, $headers) {
1190 if (ob_get_level()) {
1191 ob_end_clean();
1192 }
1193
1194 foreach ($headers as $header) {
1195 // To prevent HTTP header injection, we delete new lines that are
1196 // not followed by a space or a tab.
1197 // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
1198 $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
1199 drupal_set_header($header);
1200 }
1201
1202 $source = hook_file_create_path($source);
1203
1204 // Transfer file in 1024 byte chunks to save memory usage.
1205 if ($fd = fopen($source, 'rb')) {
1206 while (!feof($fd)) {
1207 print fread($fd, 1024);
1208 }
1209 fclose($fd);
1210 }
1211 else {
1212 drupal_not_found();
1213 }
1214 exit();
1215 }
1216
1217 /**
1218 * Menu handler for private file transfers.
1219 *
1220 * Call modules that implement hook_hook_file_download() to find out if a file is
1221 * accessible and what headers it should be transferred with. If a module
1222 * returns -1 drupal_access_denied() will be returned. If one or more modules
1223 * returned headers the download will start with the returned headers. If no
1224 * modules respond drupal_not_found() will be returned.
1225 *
1226 * @see hook_hook_file_download()
1227 */
1228 function hook_file_download() {
1229 // Merge remainder of arguments from GET['q'], into relative file path.
1230 $args = func_get_args();
1231 $filepath = implode('/', $args);
1232
1233 // Maintain compatibility with old ?file=paths saved in node bodies.
1234 if (isset($_GET['file'])) {
1235 $filepath = $_GET['file'];
1236 }
1237
1238 if (hook_file_exists(hook_file_create_path($filepath))) {
1239 // Let other modules provide headers and controls access to the file.
1240 $headers = module_invoke_all('hook_file_download', $filepath);
1241 if (in_array(-1, $headers)) {
1242 return drupal_access_denied();
1243 }
1244 if (count($headers)) {
1245 hook_file_transfer($filepath, $headers);
1246 }
1247 }
1248 return drupal_not_found();
1249 }
1250
1251
1252 /**
1253 * Finds all files that match a given mask in a given directory.
1254 *
1255 * Directories and files beginning with a period are excluded; this
1256 * prevents hidden files and directories (such as SVN working directories)
1257 * from being scanned.
1258 *
1259 * @param $dir
1260 * The base directory for the scan, without trailing slash.
1261 * @param $mask
1262 * The preg_match() regular expression of the files to find.
1263 * @param $nomask
1264 * The preg_match() regular expression of the files to ignore.
1265 * @param $callback
1266 * The callback function to call for each match.
1267 * @param $recurse
1268 * When TRUE, the directory scan will recurse the entire tree
1269 * starting at the provided directory.
1270 * @param $key
1271 * The key to be used for the returned array of files. Possible
1272 * values are "filename", for the path starting with $dir,
1273 * "basename", for the basename of the file, and "name" for the name
1274 * of the file without an extension.
1275 * @param $min_depth
1276 * Minimum depth of directories to return files from.
1277 * @param $depth
1278 * Current depth of recursion. This parameter is only used internally and
1279 * should not be passed.
1280 * @return
1281 * An associative array (keyed on the provided key) of objects with
1282 * "path", "basename", and "name" members corresponding to the
1283 * matching files.
1284 */
1285 function hook_file_scan_directory($dir, $mask, $nomask = '/(\.\.?|CVS)$/', $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) {
1286 $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
1287 $files = array();
1288
1289 if (is_dir($dir) && $handle = opendir($dir)) {
1290 while (FALSE !== ($file = readdir($handle))) {
1291 if (!preg_match($nomask, $file) && $file[0] != '.') {
1292 if (is_dir("$dir/$file") && $recurse) {
1293 // Give priority to files in this folder by merging them in after any subdirectory files.
1294 $files = array_merge(hook_file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
1295 }
1296 elseif ($depth >= $min_depth && preg_match($mask, $file)) {
1297 // Always use this match over anything already set in $files with the
1298 // same $$key.
1299 $filename = "$dir/$file";
1300 $basename = basename($file);
1301 $name = substr($basename, 0, strrpos($basename, '.'));
1302 $files[$$key] = new stdClass();
1303 $files[$$key]->filename = $filename;
1304 $files[$$key]->basename = $basename;
1305 $files[$$key]->name = $name;
1306 if ($callback) {
1307 $callback($filename);
1308 }
1309 }
1310 }
1311 }
1312
1313 closedir($handle);
1314 }
1315
1316 return $files;
1317 }
1318
1319 /**
1320 * Determine the default temporary directory.
1321 *
1322 * @return
1323 * A string containing a temp directory.
1324 */
1325 function hook_file_directory_temp() {
1326 $temporary_directory = variable_get('hook_file_directory_temp', NULL);
1327
1328 if (is_null($temporary_directory)) {
1329 $directories = array();
1330
1331 // Has PHP been set with an upload_tmp_dir?
1332 if (ini_get('upload_tmp_dir')) {
1333 $directories[] = ini_get('upload_tmp_dir');
1334 }
1335
1336 // Operating system specific dirs.
1337 if (substr(PHP_OS, 0, 3) == 'WIN') {
1338 $directories[] = 'c:/windows/temp';
1339 $directories[] = 'c:/winnt/temp';
1340 }
1341 else {
1342 $directories[] = '/tmp';
1343 }
1344
1345 foreach ($directories as $directory) {
1346 if (!$temporary_directory && is_dir($directory)) {
1347 $temporary_directory = $directory;
1348 }
1349 }
1350
1351 // if a directory has been found, use it, otherwise default to 'files/tmp'
1352 $temporary_directory = $temporary_directory ? $temporary_directory : hook_file_directory_path() . '/tmp';
1353 variable_set('hook_file_directory_temp', $temporary_directory);
1354 }
1355
1356 return $temporary_directory;
1357 }
1358
1359 /**
1360 * Determine the default 'files' directory.
1361 *
1362 * @return
1363 * A string containing the path to Drupal's 'files' directory.
1364 */
1365 function hook_file_directory_path()