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

Contents of /contributions/modules/stream/stream.module

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


Revision 1.8 - (show annotations) (download) (as text)
Sun Jan 11 08:44:38 2009 UTC (10 months, 2 weeks ago) by dopry
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +18 -11 lines
File MIME type: text/x-php
fixed issues with StreamWrapperFilePublic::realpath
found and implemented undocumented stream wrapper method 'stream_lock'
completed tests for copy, rename, unlink, file_put_contents, and file_get_contents
1 <?php
2
3
4 /**
5 * @file
6 * Stream replacement for file.inc
7 *
8 */
9
10 /**
11 * Implementation of hook_boot
12 *
13 * Registers core stream handlers for public and private files.
14 */
15
16 function stream_boot() {
17 $manager = StreamWrapperManager::singleton();
18 $manager->register('public', 'StreamWrapperFilePublic', 'StreamFilePublic');
19 $manager->register('private', 'StreamWrapperFilePrivate', 'StreamFilePrivate');
20 }
21
22 function stream_perm() {
23 return array(
24 'administer stream handlers' => array(
25 'title' => 'Administer stream handlers',
26 'Description' => 'Administer settings for stream handlers.'
27 ),
28 );
29 }
30
31 function stream_menu() {
32 $items = array();
33
34 // menu items that are basically just menu blocks
35 $items['admin/settings/stream'] = array(
36 'title' => 'Stream configuration',
37 'description' => 'Configure stream settings.',
38 'position' => 'right',
39 'weight' => -5,
40 'page callback' => 'stream_settings_overview',
41 'access arguments' => array('access administration pages'),
42 );
43
44 $items['admin/settings/stream/public'] = array(
45 'title' => 'public:// settings',
46 'page callback' => 'drupal_get_form',
47 'page arguments' => array('stream_settings_public_form'),
48 'access arguments' => array('administer stream handlers')
49 );
50 $items['admin/settings/stream/private'] = array(
51 'title' => 'private:// settings',
52 'page callback' => 'drupal_get_form',
53 'page arguments' => array('stream_settings_private_form'),
54 'access arguments' => array('administer stream handlers')
55 );
56 return $items;
57 }
58
59 /**
60 * Menu callback; displays a module's settings page.
61 */
62 function stream_settings_overview() {
63 // Check database setup if necessary
64 if (function_exists('db_check_setup') && empty($_POST)) {
65 db_check_setup();
66 }
67
68 $item = menu_get_item('admin/settings/stream');
69 $content = system_admin_menu_block($item);
70
71 $output = theme('admin_block_content', $content);
72
73 return $output;
74 }
75
76 function stream_settings_public_form() {
77 $form = array();
78 $form['stream_public_path'] = array(
79 '#type' => 'textfield',
80 '#title' => t('public:// file path'),
81 '#default_value' => variable_get('stream_public_path', 'sites/default/files'),
82 '#maxlength' => 255,
83 '#description' => t('The path where public:// files will be stored. This directory must exist and be writable by Drupal. If the download method is set to public, this directory must be relative to the Drupal installation directory and be accessible over the web. If the download method is set to private, this directory should not be accessible over the web. Changing this location will modify all download paths and may cause unexpected problems on an existing site.'),
84 '#after_build' => array('system_check_directory')
85 );
86 return system_settings_form($form);
87 }
88
89 function stream_settings_private_form() {
90 $form = array();
91 $form['stream_private_path'] = array(
92 '#type' => 'textfield',
93 '#title' => t('private:// file path'),
94 '#default_value' => variable_get('stream_private_path', 'sites/default/files-private'),
95 '#maxlength' => 255,
96 '#description' => t('The path where public:// files will be stored. This directory must exist and be writable by Drupal. If the download method is set to public, this directory must be relative to the Drupal installation directory and be accessible over the web. If the download method is set to private, this directory should not be accessible over the web. Changing this location will modify all download paths and may cause unexpected problems on an existing site.'),
97 '#after_build' => array('system_check_directory')
98 );
99
100 return system_settings_form($form);
101 }
102
103 /**
104 * Stream API
105 */
106
107 /**
108 * Debug Logging function.
109 */
110 function stream_debug($message) {
111 if (TRUE) watchdog('stream', $message, array(), WATCHDOG_DEBUG);
112 }
113
114 /**
115 * This class provides a basic interface for registering stream wrappers.
116 * It extends the standard functionality of stream_wrapper_register and
117 * stream_get_wrappers with the ability to look up classnames/protocol
118 * associations and load wrappers classes by protocol. This additional
119 * functionality allows the stream_wrapper_classes to be extended with
120 * Drupal specific stream functionality.
121 */
122
123 class StreamWrapperManager {
124 static private $wrappers = array();
125
126 // private constructor to enforce singleton.
127 private function __construct() { }
128
129 /**
130 * Load the singleton instance of the StreamWrapperManager.
131 * @return object:StreamWrapperManager
132 */
133 public static function singleton() {
134 static $instance = NULL;
135 if (is_null($instance)) {
136 $instance = new StreamWrapperManager();
137 }
138 return $instance;
139 }
140
141 /**
142 * Register a class to handle a stream protocol.
143 * @param string $protocol stream protocol
144 * @param string $classname classname to handle the protocol.
145 * @return bool result of stream_wrapper_register
146 * @see: http://us3.php.net/manual/en/function.stream-wrapper-register.php
147 */
148 function register($protocol, $streamwrapperclass, $streamclass) {
149 self::$wrappers[$protocol]['streamwrapper'] = $streamwrapperclass;
150 self::$wrappers[$protocol]['streamclass'] = $streamclass;
151 return stream_wrapper_register($protocol, $streamwrapperclass);
152 }
153
154 function unregister($protocol) {
155 if (stream_wrapper_unregister($protocol)) {
156 unset(self::$wrappers[$protocol]);
157 return TRUE;
158 }
159 return FALSE;
160 }
161
162 /**
163 * Return the streamwrapper classname for a given protocol.
164 * @param string $protocol stream protocol.
165 * @return mixed string is a protocol has a registered handler or FALSE.
166 */
167 function streamwrapperclass($protocol) {
168 if (empty(self::$wrappers[$protocol]['streamwrapperclass'])) {
169 return FALSE;
170 }
171 return self::$wrappers[$protocol];
172 }
173
174 /**
175 * Return the stream class name for a given protocol.
176 * @param string $protocol stream protocol.
177 * @return mixed string is a protocol has a registered handler or FALSE.
178 */
179 function streamclass($protocol) {
180 if (empty(self::$wrappers[$protocol]['streamclass'])) {
181 return FALSE;
182 }
183 return self::$wrappers[$protocol];
184 }
185
186
187 /**
188 * Return the StreamWrapperManagers wrapper registry.
189 */
190 function wrappers() {
191 return self::$wrappers;
192 }
193 }
194
195 /**
196 * A base StreamWrapper class to extend.
197 */
198 class StreamWrapper {
199
200 public function stream_open($path, $mode, $options, &$opened_path) {
201 return FALSE;
202 }
203
204 public function stream_close() { }
205
206 public function stream_read($count) {
207 return FALSE;
208 }
209
210 public function stream_write($data) {
211 return 0;
212 }
213
214 public function stream_eof() {
215 return TRUE;
216 }
217
218 public function stream_tell() {
219 return 0;
220 }
221
222 public function stream_seek($offset, $whence) {
223 return FALSE;
224 }
225
226 public function stream_flush() {
227 return TRUE;
228 }
229
230 public function stream_stat() {
231 return array();
232 }
233
234 // PHP > 5.3.0
235 public function stream_cast(int $cast_as) {
236 return FALSE;
237 }
238
239 // PHP > 5.3.0
240 public function stream_set_option($option, $arg1, $arg2) {
241 return FALSE;
242 }
243
244 // Do not implement if unsupported.
245 // public function unlink($path) {}
246
247
248 // Do not implement if unsupported.
249 // public function rename($path_from, $path_to) {}
250
251 // Do not implement if unsupported.
252 // public function mkdir($path, $mode, $options) {}
253
254
255 // Do not implement if unsupported.
256 // public function rmdir($path, $options) {}
257
258 public function dir_open($path, $option) {
259 return FALSE;
260 }
261
262 public function url_stat($path, $flags) {
263 return array();
264 }
265
266 public function dir_readdir() {
267 return '';
268 }
269
270 public function dir_rewinddir() {
271 return FALSE;
272 }
273
274 public function dir_closedir() {
275 return TRUE;
276 }
277 }
278
279 /**
280 * A stream wrapper class for working with Local files.
281 */
282 class StreamWrapperFile extends StreamWrapper {
283
284 // A handle to the file opened by stream_open().
285 private $fileHandle;
286
287 /**
288 * Support for fopen(), file_get_contents(), file_put_contents() etc.
289 *
290 * @param $path
291 * A string containing the path to the file to open.
292 * @param $mode
293 * The file mode ("r", "wb" etc.).
294 * @param $options
295 * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
296 * @param &$opened_path
297 * A string containing the path actually opened.
298 * @return
299 * TRUE if file was opened successfully.
300 */
301 public function stream_open($path, $mode, $options, &$opened_path) {
302 stream_debug("stream_open($path, $mode, $options, &$opened_path)");
303 stream_debug("realpath: ". $this->realpath($path));
304 if ($options & STREAM_REPORT_ERRORS) {
305 $this->fileHandle = fopen($this->realpath($path, $mode), $mode);
306 }
307 else {
308 $this->fileHandle = @fopen($this->realpath($path, $mode), $mode);
309 }
310 return (bool)$this->fileHandle;
311 }
312
313 // Undocumented PHP stream wrapper method.
314 function stream_lock($operation) {
315 $args = func_get_args();
316 stream_debug('stream_lock:' . $operation .','. print_r($args,1));
317 if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) {
318 return flock($this->fileHandle, $operation);
319 }
320 return TRUE;
321 }
322
323 /**
324 * Support for fread(), file_get_contents() etc.
325 *
326 * @param $count
327 * Maximum number of bytes to be read.
328 * @return
329 * The string that was read, or FALSE in case of an error.
330 */
331 public function stream_read($count) {
332 stream_debug("stream_read($count)");
333 return fread($this->fileHandle, $count);
334 }
335
336 /**
337 * Support for fwrite(), file_put_contents() etc.
338 *
339 * @param $data
340 * The string to be written.
341 * @return
342 * The number of bytes written.
343 */
344 public function stream_write($data) {
345 stream_debug("stream_write($data)");
346 return fwrite($this->fileHandle, $data);
347 }
348
349 /**
350 * Support for feof().
351 *
352 * @return
353 * TRUE if end-of-file has been reached.
354 */
355 public function stream_eof() {
356 stream_debug("stream_eof()");
357 return feof($this->fileHandle);
358 }
359
360 /**
361 * Support for fseek().
362 *
363 * @param $offset
364 * The byte offset to got to.
365 * @param $whence
366 * SEEK_SET, SEEK_CUR, or SEEK_END.
367 * @return
368 * TRUE on success
369 */
370 public function stream_seek($offset, $whence) {
371 stream_debug("stream_seek($offset, $whence)");
372 return fseek($this->fileHandle, $offset, $whence);
373 }
374
375 /**
376 * Support for fflush().
377 *
378 * @return
379 * TRUE if data was successfully stored (or there was no data to store).
380 */
381 public function stream_flush() {
382 stream_debug("stream_flush()");
383 return fflush($this->fileHandle);
384 }
385
386 /**
387 * Support for ftell().
388 *
389 * @return
390 * The current offset in bytes from the beginning of file.
391 */
392 public function stream_tell() {
393 stream_debug("stream_tell()");
394 return ftell($this->fileHandle);
395 }
396
397 /**
398 * Support for fstat().
399 *
400 * @return
401 * An array with file status, or FALSE in case of an error - see fstat()
402 * for a description of this array.
403 */
404 public function stream_stat() {
405 stream_debug("stream_stat()");
406 return fstat($this->fileHandle);
407 }
408
409 /**
410 * Support for fclose().
411 *
412 * @return
413 * TRUE if stream was successfully closed.
414 */
415 public function stream_close() {
416 stream_debug("stream_close()");
417 return fclose($this->fileHandle);
418 }
419
420 /**
421 * Support for unlink().
422 *
423 * @param $path
424 * A string containing the path to the file to delete.
425 * @return
426 * TRUE if file was successfully deleted.
427 */
428 public function unlink($path) {
429 stream_debug("unlink($path)");
430 return unlink($this->realpath($path));
431 }
432
433 /**
434 * Support for rename().
435 *
436 * @param $fromPath
437 * The path to the file to rename.
438 * @param $toPath
439 * The new path to the file.
440 *
441 * @return
442 * TRUE if file was successfully renamed.
443 */
444 public function rename($fromPath, $toPath) {
445 stream_debug("rename($fromPath, $toPath)");
446 return rename($this->realpath($fromPath), $this->realpath($toPath));
447 }
448
449 /**
450 * Support for mkdir().
451 *
452 * @param $path
453 * A string containing the path to the directory to create.
454 * @param $mode
455 * Permission flags - see mkdir().
456 * @param $options
457 * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
458 * @return
459 * TRUE if directory was successfully created.
460 */
461 public function mkdir($path, $mode, $options) {
462 stream_debug("mkdir($path, $mode, $options)");
463 $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE);
464 if ($options & STREAM_REPORT_ERRORS) {
465 return mkdir($this->realpath($path), $mode, $recursive);
466 }
467 else {
468 return @mkdir($this->realpath($path), $mode, $recursive);
469 }
470 }
471
472 /**
473 * Support for rmdir().
474 *
475 * @param $path
476 * A string containing the path to the directory to delete.
477 * @param $options
478 * A bit mask of STREAM_REPORT_ERRORS.
479 * @return
480 * TRUE if directory was successfully removed.
481 */
482 public function rmdir($path, $options) {
483 stream_debug("rmdir($path, $options)");
484 if ($options & STREAM_REPORT_ERRORS) {
485 return rmdir($this->realpath($path));
486 }
487 else {
488 return @rmdir($this->realpath($path));
489 }
490 }
491
492 /**
493 * Support for stat().
494 *
495 * @param $path
496 * A string containing the path to get information about.
497 * @param $flags
498 * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
499 * @return
500 * An array with file status, or FALSE in case of an error - see fstat()
501 * for a description of this array.
502 */
503 public function url_stat($path, $flags) {
504 stream_debug("url_stat($path, $flags)");
505 return ($flags & STREAM_URL_STAT_QUIET) ? @stat($this->realpath($path)) : stat($this->realpath($path));
506 }
507
508 /**
509 * Support for opendir().
510 *
511 * @param $path
512 * A string containing the path to the directory to open.
513 * @param $options
514 * Unknown (parameter is not documented in PHP Manual).
515 * @return
516 * TRUE on success.
517 */
518 public function dir_opendir($path, $options) {
519 stream_debug("dir_opendir($path, $options)");
520 $this->fileHandle = opendir($this->realpath($path));
521 return (bool)$this->fileHandle;
522 }
523
524 /**
525 * Support for readdir().
526 *
527 * @return
528 * The next filename, or FALSE if there are no more files in the directory.
529 */
530 public function dir_readdir() {
531 stream_debug("dir_readdir()");
532 return readdir($this->fileHandle);
533 }
534
535 /**
536 * Support for rewinddir().
537 *
538 * @return
539 * TRUE on success.
540 */
541 public function dir_rewinddir() {
542 stream_debug("dir_rewinddir()");
543 return rewinddir($this->fileHandle);
544 }
545
546 /**
547 * Support for closedir().
548 *
549 * @return
550 * TRUE on success.
551 */
552 public function dir_closedir() {
553 stream_debug("dir_closedir()");
554 return closedir($this->fileHandle);
555 }
556
557 }
558
559 /**
560 * public:// stream wrapper class.
561 */
562 class StreamWrapperFilePublic extends StreamWrapperFile {
563
564 // A handle to the file opened by stream_open().
565 private $pathkey = 'stream_public_path';
566 private $pathdefault = 'sites/default/files';
567
568 /**
569 * get a realpath for a public stream. Used internally.
570 */
571 function realpath($path) {
572 $parts = parse_url($path);
573 $realpath = realpath(variable_get($this->pathkey, $this->pathdefault)) . '/' . $parts['host'] . $parts['path'];
574
575 // just in case stream_public_path is s3://, ftp://, etc. Don't call PHP's realpath().
576 if (parse_url($realpath, PHP_URL_SCHEME)) {
577 stream_debug("realpath($realpath)");
578 return $realpath;
579 }
580 stream_debug("realpath($realpath)");
581 return $realpath;
582 }
583 }
584
585 /**
586 * private:// stream wrapper class.
587 */
588 class StreamWrapperFilePrivate extends StreamWrapperFilePublic {
589 private $pathkey = 'stream_private_path';
590 private $pathdefault = 'sites/default/files-private';
591 }
592
593
594
595 /**
596 * Base Stream class.
597 *
598 * The drupal_file class represents a physical file stored in Drupal's
599 * 'File System Path'. It is important to understand that copy, move,
600 * delete, etc method affect both the the physical file the object
601 * represents and the database record for the file.
602 *
603 * Example Factory Usage:
604 *
605 * $factory = new StreamFile();
606 * $file = $factory->load_id(87);
607 * $another = $file->load_path('images/logo.png');
608 *
609 * $file = StreamFile::load_id(24);
610 *
611 * $yet_another = $file->load('path', 'uploads/myfile.pdf');
612 *
613 *
614 * if ($yet_another->delete()) unset($yet_another);
615 *
616 */
617
618 class Stream {
619
620 // @see drupal_file::load()
621 protected static $files = array();
622
623 public $fid = 0;
624 public $uid = 1;
625 public $filename = '';
626 public $filepath = '';
627 public $filemime = 'application/octet-stream';
628 public $filesize = 0;
629 public $status = 0;
630 public $timestamp = 0;
631
632 /**
633 * drupal_file constructor.
634 *
635 * @param object $file (optional) stdClass object to initialize self with.
636 */
637 function __construct($file = FALSE) {
638 if (!$file || !is_object($file)) return;
639 // assign all initialized properties to self.
640 foreach ($file as $key => $value) {
641 $this->$key = $value;
642 }
643 }
644
645 function _exists_rename($destination) {
646 if (file_exists($destination)) {
647 $parts = parse_url($destination);
648 $streamdir = $parts['scheme'] . dirname($parts['path']);
649 $basename = basename($parts['path']);
650
651 // Destination file already exists, generate an alternative.
652 $pos = strrpos($basename, '.');
653 if ($pos !== FALSE) {
654 $name = substr($basename, 0, $pos);
655 $ext = substr($basename, $pos);
656 }
657 else {
658 $name = $basename;
659 $ext = '';
660 }
661
662 $counter = 0;
663 do {
664 $destination = $directory . '/' . $name . '_' . $counter++ . $ext;
665 } while (file_exists($destination));
666 }
667
668 return $destination;
669 }
670
671 /**
672 * Create a copy a drupal_file.
673 *
674 * @param string $destination (optional) @see file_copy.
675 * @param int $replace (optional) @see file_destination
676 * @return object|bool drupal_file if the copy is successful, or FALSE
677 * @see file_copy()
678 */
679 function copy($destination, $replace = FILE_EXISTS_RENAME) {
680 if (file_exists($destination)) {
681 if ($replace & FILE_EXISTS_ERROR) {
682 return FALSE;
683 }
684 if ($replace & FILE_EXISTS_RENAME) {
685 $destination = $this->_exists_rename($destination);
686 }
687 }
688
689 if (copy($this->filepath, $destination, $replace)) {
690 $file = clone $this;
691 $file->fid = null;
692 $file->filename = basename($result);
693 $file->filepath = $destination;
694 if ($file->save()) {
695 module_invoke_all('file_copy', $this, $file);
696 return $file;
697 }
698 }
699 return FALSE;
700 }
701
702
703 /**
704 * Delete a file and its database record.
705 *
706 * @param $force
707 * Boolean indicating that the file should be deleted even if
708 * hook_file_references() reports that the file is in use.
709 * @return mixed
710 * TRUE for success, array for reference count, or FALSE in the event
711 * of an error.
712 *
713 * @see hook_file_references()
714 */
715 function delete($force = FALSE) {
716 // If any module returns a value from the reference hook, the
717 // file will not be deleted from Drupal, but file_delete will
718 // return a populated array that tests as TRUE.
719 if (!$force && ($references = module_invoke_all('file_references', $this))) {
720 return $references;
721 }
722
723 // Let other modules clean up on delete.
724 module_invoke_all('file_delete', $this);
725
726 // Make sure the file is deleted before removing its row from the
727 // database, so UIs can still find the file in the database.
728 if (unlink($this->filepath)) {
729 db_query('DELETE FROM {files} WHERE fid = %d', $this->fid);
730 // remove internally used static cache entries.
731 $this->reset_cache('fid::'. $this->fid);
732 $this->reset_cache('filepath::'. $this->filepath);
733 return TRUE;
734 }
735 return FALSE;
736 }
737
738 /**
739 * Return the first matching file in the files table. This is a simple single
740 * object loader it in combination with the static $files variable allows all
741 * drupal file objects to also act as factories and share the same static cache.
742 *
743 * @param string key (required) database column to use in where condition.
744 * @param int|string value (required) the value of the column to use in the where condition.
745 * @return object|bool A Drupal file object or FALSE if a file was not found.
746 * @see drupal_file::load(), drupal_file::load_path()
747 */
748 final function _load($column, $value) {
749 // set a cache id based on key and value so we can statically cache
750 // all simple loads.
751 $cid = $column . '::' . $value;
752 if (empty(self::$files[$cid])) {
753 $file = db_fetch_object(db_query('SELECT f.* FROM {files} f WHERE f.%s = %d', $column, $value));
754 $scheme = parse_url($file, PHP_URL_PATH);
755 $class = StreamWrapperManager::get_classname($scheme);
756 $file = new $class($file);
757 }
758 module_invoke_all('file_load', $file);
759 self::$files[$cid] = $file;
760 // Files are not cloned, because there is in fact only one.
761 return self::$files[$cid];
762 }
763
764 /**
765 * Load a file object from the database by id.
766 *
767 * @param int $id A file id. (required)
768 * @return object|bool A Drupal file object or FALSE if a file was not found.
769 * @see: drupal_file::load()
770 */
771 final function load_id($id) {
772 return $this->_load('fid', $id);
773 }
774
775 /**
776 * Load a file object from the database by path.
777 *
778 * @param string $path A path to a file. (required)
779 * @return object|bool A Drupal file object or FALSE if a file was not found.
780 * @see: drupal_file::load()
781 */
782 final function load_path($path) {
783 return $this->_load('filepath', $path);
784 }
785
786 /**
787 * Move a drupal_file.
788 *
789 * @param string $destination (optional) @see file_copy.
790 * @param int $replace (optional) @see file_destination
791 * @return bool
792 * @see file_copy()
793 */
794 function move($destination, $replace = FILE_EXISTS_RENAME) {
795 if (file_exists($destination)) {
796 if ($replace & FILE_EXISTS_ERROR) {
797 return FALSE;
798 }
799 if ($replace & FILE_EXISTS_RENAME) {
800 $destination = $this->_exists_rename($destination);
801 }
802 }
803
804 if (rename($this->filepath, $destination)) {
805 $orig = clone $this;
806 $this->filename = basename($destination);
807 $this->filepath = $result;
808 if ($this->save()) {
809 module_invoke_all('file_move', $this, $orig);
810 return TRUE;
811 }
812 }
813 return FALSE;
814 }
815
816 /**
817 * Reset the shared static cache.
818 */
819 public function reset_cache($cid = FALSE) {
820 // no cache id, reset the entire cache.
821 if (!$cid) {
822 self::$files = array();
823 }
824 elseif (isset(self::$files[$cid])) {
825 unset(self::$files[$cid]);
826 }
827 }
828
829 /**
830 * Save the current state of a drupal_file in the database.
831 * If the file->fid is empty a new database record will be added.
832 *
833 * @return bool TRUE if save succeeded, FALSE if save failed.
834 */
835 function save() {
836 $this->timestamp = time();
837 $this->filesize = filesize($this->filepath);
838
839 if (empty($this->fid)) {
840 $result = drupal_write_record('files', $this);
841 module_invoke_all('file_insert', $this);
842 }
843 else {
844 $result = drupal_write_record('files', $this, 'fid');
845 module_invoke_all('file_update', $this);
846 }
847 return $result;
848 }
849
850 /**
851 * Mark a file as permanent.
852 *
853 * @return bool
854 */
855 function set_status($bitmask = NULL) {
856 if (is_null($bitmask)) {
857 return $file->status;
858 }
859 elseif (db_query('UPDATE {files} SET status=%d', $bitmask, $this->fid)) {
860 $this->status = $status;
861 module_invoke_all('file_set_status', $this, $status);
862 return TRUE;
863 }
864 return FALSE;
865 }
866
867 /**
868 * Create a URL to the file.
869 */
870 function url() {
871 return '';
872 }
873 }
874
875 class StreamPublic extends Stream {
876 function url() { }
877 function mime() { }
878 }
879
880 class StreamPrivate extends StreamPublic {
881 function url() { }
882 }
883
884 class StreamUpload extends Stream {
885 public $source = '';
886 public $errors;
887
888 function save() {
889 // validate if not already validated, error is validation didn't pass.
890 if (!$this->validate()) {
891 return FALSE;
892 }
893 // rename the file to its original name.
894 parent::save();
895 }
896
897 function validate($validators = array()) {
898 if (!isset($this->errors)) {
899 // Default validation for all uploads.
900 $validators['file_validate_name_length'] = array();
901
902 $this->errors = array();
903 foreach ($validators as $function => $args) {
904 array_unshift($args, $file);
905 $errors = array_merge($errors, call_user_func_array($function, $args));
906 }
907 }
908 return !empty($this->errors);
909 }
910
911 function errors() {
912 return $this->errors;
913 }
914
915 function move($dest) {
916 // validate if not already validated, error is validation didn't pass.
917 if (!$this->fid && !$this->save()) {
918 return FALSE;
919 }
920 // for upload files we changed the filename in the path to a junk string...
921
922 }
923
924 /**
925 * Saves a file upload to a temporary location
926 *
927 * The file will be added to the files table as a temporary file. Temporary
928 * files are periodically cleaned. To make the file permanent file call
929 * it's set_permanent() method.
930 *
931 * @param $source
932 * A string specifying the name of the upload field to save.
933 * @param $validators
934 * An optional, associative array of callback functions used to validate the
935 * file. The keys are function names and the values arrays of callback
936 * parameters which will be passed in after the user and file objects. The
937 * functions should return an array of error messages, an empty array
938 * indicates that the file passed validation. The functions will be called in
939 * the order specified.
940 * @param $destination
941 * A string containing the directory $source should be copied to. If this is
942 * not provided or is not writable, the temporary directory will be used.
943 * @param $replace
944 * A boolean indicating whether an existing file of the same name in the
945 * destination directory should overwritten. A FALSE value will generate a
946 * new, unique filename in the destination directory.
947 * @return
948 * An object containing the file information, or FALSE in the event of an
949 * error.
950 */
951
952 static function save_upload($source) {
953 // check and see if there were any errors.
954 if (drupal_file_upload::upload_error($source)) {
955 return FALSE;
956 }
957
958 // Begin building file object.
959 $file = new stdClass();
960 $file->source = $source;
961 $file->uid = $user->uid;
962 $file->filename = basename($_FILES['files']['name'][$source]);
963 // create a tmp path to use until the file is save to a final location else where.
964 // we just use a random string to defang the file for processing in tmp.
965 $file->filepath = file_destination(uniqid(), file_directory_temp(), FALSE);
966 $file->filemime = $_FILES['files']['type'][$source];
967 $file->filesize = $_FILES['files']['size'][$source];
968
969
970 // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
971 // directory. This overcomes open_basedir restrictions for future file
972 // operations.
973 if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
974 form_set_error($source, t('File upload error. Could not move uploaded file.'));
975 watchdog('file api', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->filepath));
976 return FALSE;
977 }
978 return $file;
979 }
980
981 function upload_error($errno) {
982 // If no file was uploaded there is an error. :)
983 if (empty($_FILES['files']['tmp_name'][$source]) || !is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
984 return t('No file uploaded');
985 }
986
987
988 // @see http://php.net/manual/en/features.file-upload.errors.php
989 switch ($_FILES['files']['error'][$source]) {
990 case UPLOAD_ERR_OK:
991 return FALSE;
992
993 case UPLOAD_ERR_INI_SIZE:
994 case UPLOAD_ERR_FORM_SIZE:
995 return t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(file_upload_max_size())));
996
997 case UPLOAD_ERR_PARTIAL:
998 case UPLOAD_ERR_NO_FILE:
999 return t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source));
1000
1001 case UPLOAD_ERR_NO_TMP_DIR:
1002 return t('The file %file could not be saved, because the PHP upload_tmp_dir does not exist.', array('%file' => $source));
1003
1004 case UPLOAD_ERR_CANT_WRITE:
1005 return t('The file %file could not be saved, because the file could not be written to the disk.', array('%file' => $source));
1006
1007 case UPLOAD_ERR_EXTENSION:
1008 return t('The file %file could not be saved, because the upload was stopped by a php extension.', array('%file' => $source));
1009
1010 // Unknown error
1011 default:
1012 return t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source));
1013 }
1014 }
1015
1016 // Use static load as the entry point to keep the API interface
1017 // consistent.
1018 function load($source) {
1019 if (isset(self::$files[$source])) return self::$files[$source];
1020
1021 // attempt to save the upload.
1022 if ($file = self::save_upload($source)) {
1023 return new StreamPublic($file);
1024 }
1025 }
1026
1027 }
1028
1029

  ViewVC Help
Powered by ViewVC 1.1.2