| 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 |
|