file_example_session_streams.inc

Provides a demonstration session:// streamwrapper.

This example is nearly fully functional, but has no known practical use. It's an example and demonstration only.

Classes

Namesort descending Description
FileExampleSessionStreamWrapper Example stream wrapper class to handle session:// streams.

File

file_example/file_example_session_streams.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Provides a demonstration session:// streamwrapper.
  5. *
  6. * This example is nearly fully functional, but has no known
  7. * practical use. It's an example and demonstration only.
  8. */
  9. /**
  10. * Example stream wrapper class to handle session:// streams.
  11. *
  12. * This is just an example, as it could have horrible results if much
  13. * information were placed in the $_SESSION variable. However, it does
  14. * demonstrate both the read and write implementation of a stream wrapper.
  15. *
  16. * A "stream" is an important Unix concept for the reading and writing of
  17. * files and other devices. Reading or writing a "stream" just means that you
  18. * open some device, file, internet site, or whatever, and you don't have to
  19. * know at all what it is. All the functions that deal with it are the same.
  20. * You can read/write more from/to the stream, seek a position in the stream,
  21. * or anything else without the code that does it even knowing what kind
  22. * of device it is talking to. This Unix idea is extended into PHP's
  23. * mindset.
  24. *
  25. * The idea of "stream wrapper" is that this can be extended indefinitely.
  26. * The classic example is HTTP: With PHP you can do a
  27. * file_get_contents("http://drupal.org/projects") as if it were a file,
  28. * because the scheme "http" is supported natively in PHP. So Drupal adds
  29. * the public:// and private:// schemes, and contrib modules can add any
  30. * scheme they want to. This example adds the session:// scheme, which allows
  31. * reading and writing the $_SESSION['file_example'] key as if it were a file.
  32. *
  33. * Note that because this implementation uses simple PHP arrays ($_SESSION)
  34. * it is limited to string values, so binary files will not work correctly.
  35. * Only text files can be used.
  36. *
  37. * @ingroup file_example
  38. */
  39. class FileExampleSessionStreamWrapper implements DrupalStreamWrapperInterface {
  40. /**
  41. * Stream context resource.
  42. *
  43. * @var Resource
  44. */
  45. public $context;
  46. /**
  47. * Instance URI (stream).
  48. *
  49. * These streams will be references as 'session://example_target'
  50. *
  51. * @var String
  52. */
  53. protected $uri;
  54. /**
  55. * The content of the stream.
  56. *
  57. * Since this trivial example just uses the $_SESSION variable, this is
  58. * simply a reference to the contents of the related part of
  59. * $_SESSION['file_example'].
  60. */
  61. protected $session_content;
  62. /**
  63. * Pointer to where we are in a directory read.
  64. */
  65. protected $directory_pointer;
  66. /**
  67. * List of keys in a given directory.
  68. */
  69. protected $directory_keys;
  70. /**
  71. * The pointer to the next read or write within the session variable.
  72. */
  73. protected $stream_pointer;
  74. public function __construct() {
  75. $_SESSION['file_example']['.isadir.txt'] = TRUE;
  76. }
  77. /**
  78. * Implements setUri().
  79. */
  80. function setUri($uri) {
  81. $this->uri = $uri;
  82. }
  83. /**
  84. * Implements getUri().
  85. */
  86. function getUri() {
  87. return $this->uri;
  88. }
  89. /**
  90. * Implements getTarget().
  91. *
  92. * The "target" is the portion of the URI to the right of the scheme.
  93. * So in session://example/test.txt, the target is 'example/test.txt'.
  94. */
  95. function getTarget($uri = NULL) {
  96. if (!isset($uri)) {
  97. $uri = $this->uri;
  98. }
  99. list($scheme, $target) = explode('://', $uri, 2);
  100. // Remove erroneous leading or trailing, forward-slashes and backslashes.
  101. // In the session:// scheme, there is never a leading slash on the target.
  102. return trim($target, '\/');
  103. }
  104. /**
  105. * Implements getMimeType().
  106. */
  107. static function getMimeType($uri, $mapping = NULL) {
  108. if (!isset($mapping)) {
  109. // The default file map, defined in file.mimetypes.inc is quite big.
  110. // We only load it when necessary.
  111. include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
  112. $mapping = file_mimetype_mapping();
  113. }
  114. $extension = '';
  115. $file_parts = explode('.', basename($uri));
  116. // Remove the first part: a full filename should not match an extension.
  117. array_shift($file_parts);
  118. // Iterate over the file parts, trying to find a match.
  119. // For my.awesome.image.jpeg, we try:
  120. // - jpeg
  121. // - image.jpeg, and
  122. // - awesome.image.jpeg
  123. while ($additional_part = array_pop($file_parts)) {
  124. $extension = drupal_strtolower($additional_part . ($extension ? '.' . $extension : ''));
  125. if (isset($mapping['extensions'][$extension])) {
  126. return $mapping['mimetypes'][$mapping['extensions'][$extension]];
  127. }
  128. }
  129. return 'application/octet-stream';
  130. }
  131. /**
  132. * Implements getDirectoryPath().
  133. *
  134. * In this case there is no directory string, so return an empty string.
  135. */
  136. public function getDirectoryPath() {
  137. return '';
  138. }
  139. /**
  140. * Overrides getExternalUrl().
  141. *
  142. * We have set up a helper function and menu entry to provide access to this
  143. * key via HTTP; normally it would be accessible some other way.
  144. */
  145. function getExternalUrl() {
  146. $path = $this->getLocalPath();
  147. $url = url('examples/file_example/access_session/' . $path, array('absolute' => TRUE));
  148. return $url;
  149. }
  150. /**
  151. * We have no concept of chmod, so just return TRUE.
  152. */
  153. function chmod($mode) {
  154. return TRUE;
  155. }
  156. /**
  157. * Implements realpath().
  158. */
  159. function realpath() {
  160. return 'session://' . $this->getLocalPath();
  161. }
  162. /**
  163. * Returns the local path.
  164. * Here we aren't doing anything but stashing the "file" in a key in the
  165. * $_SESSION variable, so there's not much to do but to create a "path"
  166. * which is really just a key in the $_SESSION variable. So something
  167. * like 'session://one/two/three.txt' becomes
  168. * $_SESSION['file_example']['one']['two']['three.txt'] and the actual path
  169. * is "one/two/three.txt".
  170. *
  171. * @param $uri
  172. * Optional URI, supplied when doing a move or rename.
  173. */
  174. protected function getLocalPath($uri = NULL) {
  175. if (!isset($uri)) {
  176. $uri = $this->uri;
  177. }
  178. $path = str_replace('session://', '', $uri);
  179. $path = trim($path, '/');
  180. return $path;
  181. }
  182. /**
  183. * Opens a stream, as for fopen(), file_get_contents(), file_put_contents()
  184. *
  185. * @param $uri
  186. * A string containing the URI to the file to open.
  187. * @param $mode
  188. * The file mode ("r", "wb" etc.).
  189. * @param $options
  190. * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
  191. * @param &$opened_path
  192. * A string containing the path actually opened.
  193. *
  194. * @return
  195. * Returns TRUE if file was opened successfully. (Always returns TRUE).
  196. *
  197. * @see http://php.net/manual/en/streamwrapper.stream-open.php
  198. */
  199. public function stream_open($uri, $mode, $options, &$opened_path) {
  200. $this->uri = $uri;
  201. // We make $session_content a reference to the appropriate key in the
  202. // $_SESSION variable. So if the local path were
  203. // /example/test.txt it $session_content would now be a
  204. // reference to $_SESSION['file_example']['example']['test.txt'].
  205. $this->session_content = &$this->uri_to_session_key($uri);
  206. // Reset the stream pointer since this is an open.
  207. $this->stream_pointer = 0;
  208. return TRUE;
  209. }
  210. /**
  211. * Return a reference to the correct $_SESSION key.
  212. *
  213. * @param $uri
  214. * The uri: session://something
  215. * @param $create
  216. * If TRUE, create the key
  217. *
  218. * @return
  219. * A reference to the array at the end of the key-path, or
  220. * FALSE if the path doesn't map to a key-path (and $create is FALSE).
  221. */
  222. protected function &uri_to_session_key($uri, $create = TRUE) {
  223. // Since our uri_to_session_key() method returns a reference, we
  224. // have to set up a failure flag variable.
  225. $fail = FALSE;
  226. $path = $this->getLocalPath($uri);
  227. $path_components = explode('/', $path);
  228. // Set up a reference to the root session:// 'directory.'
  229. $var = &$_SESSION['file_example'];
  230. // Handle case of just session://.
  231. if (count($path_components) < 1) {
  232. return $var;
  233. }
  234. // Walk through the path components and create keys in $_SESSION,
  235. // unless we're told not to create them.
  236. foreach ($path_components as $component) {
  237. if ($create || isset($var[$component])) {
  238. $var = &$var[$component];
  239. }
  240. else {
  241. // This path doesn't exist as keys, either because the
  242. // key doesn't exist, or because we're told not to create it.
  243. return $fail;
  244. }
  245. }
  246. return $var;
  247. }
  248. /**
  249. * Support for flock().
  250. *
  251. * The $_SESSION variable has no locking capability, so return TRUE.
  252. *
  253. * @param $operation
  254. * One of the following:
  255. * - LOCK_SH to acquire a shared lock (reader).
  256. * - LOCK_EX to acquire an exclusive lock (writer).
  257. * - LOCK_UN to release a lock (shared or exclusive).
  258. * - LOCK_NB if you don't want flock() to block while locking (not
  259. * supported on Windows).
  260. *
  261. * @return
  262. * Always returns TRUE at the present time. (no support)
  263. *
  264. * @see http://php.net/manual/en/streamwrapper.stream-lock.php
  265. */
  266. public function stream_lock($operation) {
  267. return TRUE;
  268. }
  269. /**
  270. * Support for fread(), file_get_contents() etc.
  271. *
  272. * @param $count
  273. * Maximum number of bytes to be read.
  274. *
  275. * @return
  276. * The string that was read, or FALSE in case of an error.
  277. *
  278. * @see http://php.net/manual/en/streamwrapper.stream-read.php
  279. */
  280. public function stream_read($count) {
  281. if (is_string($this->session_content)) {
  282. $remaining_chars = drupal_strlen($this->session_content) - $this->stream_pointer;
  283. $number_to_read = min($count, $remaining_chars);
  284. if ($remaining_chars > 0) {
  285. $buffer = drupal_substr($this->session_content, $this->stream_pointer, $number_to_read);
  286. $this->stream_pointer += $number_to_read;
  287. return $buffer;
  288. }
  289. }
  290. return FALSE;
  291. }
  292. /**
  293. * Support for fwrite(), file_put_contents() etc.
  294. *
  295. * @param $data
  296. * The string to be written.
  297. *
  298. * @return
  299. * The number of bytes written (integer).
  300. *
  301. * @see http://php.net/manual/en/streamwrapper.stream-write.php
  302. */
  303. public function stream_write($data) {
  304. // Sanitize the data in a simple way since we're putting it into the
  305. // session variable.
  306. $data = check_plain($data);
  307. $this->session_content = substr_replace($this->session_content, $data, $this->stream_pointer);
  308. $this->stream_pointer += drupal_strlen($data);
  309. return drupal_strlen($data);
  310. }
  311. /**
  312. * Support for feof().
  313. *
  314. * @return
  315. * TRUE if end-of-file has been reached.
  316. *
  317. * @see http://php.net/manual/en/streamwrapper.stream-eof.php
  318. */
  319. public function stream_eof() {
  320. return FALSE;
  321. }
  322. /**
  323. * Support for fseek().
  324. *
  325. * @param $offset
  326. * The byte offset to got to.
  327. * @param $whence
  328. * SEEK_SET, SEEK_CUR, or SEEK_END.
  329. *
  330. * @return
  331. * TRUE on success.
  332. *
  333. * @see http://php.net/manual/en/streamwrapper.stream-seek.php
  334. */
  335. public function stream_seek($offset, $whence) {
  336. if (drupal_strlen($this->session_content) >= $offset) {
  337. $this->stream_pointer = $offset;
  338. return TRUE;
  339. }
  340. return FALSE;
  341. }
  342. /**
  343. * Support for fflush().
  344. *
  345. * @return
  346. * TRUE if data was successfully stored (or there was no data to store).
  347. * This always returns TRUE, as this example provides and needs no
  348. * flush support.
  349. *
  350. * @see http://php.net/manual/en/streamwrapper.stream-flush.php
  351. */
  352. public function stream_flush() {
  353. return TRUE;
  354. }
  355. /**
  356. * Support for ftell().
  357. *
  358. * @return
  359. * The current offset in bytes from the beginning of file.
  360. *
  361. * @see http://php.net/manual/en/streamwrapper.stream-tell.php
  362. */
  363. public function stream_tell() {
  364. return $this->stream_pointer;
  365. }
  366. /**
  367. * Support for fstat().
  368. *
  369. * @return
  370. * An array with file status, or FALSE in case of an error - see fstat()
  371. * for a description of this array.
  372. *
  373. * @see http://php.net/manual/en/streamwrapper.stream-stat.php
  374. */
  375. public function stream_stat() {
  376. return array(
  377. 'size' => drupal_strlen($this->session_content),
  378. );
  379. }
  380. /**
  381. * Support for fclose().
  382. *
  383. * @return
  384. * TRUE if stream was successfully closed.
  385. *
  386. * @see http://php.net/manual/en/streamwrapper.stream-close.php
  387. */
  388. public function stream_close() {
  389. $this->stream_pointer = 0;
  390. unset($this->session_content); // Unassign the reference.
  391. return TRUE;
  392. }
  393. /**
  394. * Support for unlink().
  395. *
  396. * @param $uri
  397. * A string containing the uri to the resource to delete.
  398. *
  399. * @return
  400. * TRUE if resource was successfully deleted.
  401. *
  402. * @see http://php.net/manual/en/streamwrapper.unlink.php
  403. */
  404. public function unlink($uri) {
  405. $path = $this->getLocalPath($uri);
  406. $path_components = preg_split('/\//', $path);
  407. $fail = FALSE;
  408. $unset = '$_SESSION[\'file_example\']';
  409. foreach ($path_components as $component) {
  410. $unset .= '[\'' . $component . '\']';
  411. }
  412. // TODO: Is there a better way to delete from an array?
  413. // drupal_array_get_nested_value() doesn't work because it only returns
  414. // a reference; unsetting a reference only unsets the reference.
  415. eval("unset($unset);");
  416. return TRUE;
  417. }
  418. /**
  419. * Support for rename().
  420. *
  421. * @param $from_uri,
  422. * The uri to the file to rename.
  423. * @param $to_uri
  424. * The new uri for file.
  425. *
  426. * @return
  427. * TRUE if file was successfully renamed.
  428. *
  429. * @see http://php.net/manual/en/streamwrapper.rename.php
  430. */
  431. public function rename($from_uri, $to_uri) {
  432. $from_key = &$this->uri_to_session_key($from_uri);
  433. $to_key = &$this->uri_to_session_key($to_uri);
  434. if (is_dir($to_key) || is_file($to_key)) {
  435. return FALSE;
  436. }
  437. $to_key = $from_key;
  438. unset($from_key);
  439. return TRUE;
  440. }
  441. /**
  442. * Gets the name of the directory from a given path.
  443. *
  444. * @param $uri
  445. * A URI.
  446. *
  447. * @return
  448. * A string containing the directory name.
  449. *
  450. * @see drupal_dirname()
  451. */
  452. public function dirname($uri = NULL) {
  453. list($scheme, $target) = explode('://', $uri, 2);
  454. $target = $this->getTarget($uri);
  455. if (strpos($target, '/')) {
  456. $dirname = preg_replace('@/[^/]*$@', '', $target);
  457. }
  458. else {
  459. $dirname = '';
  460. }
  461. return $scheme . '://' . $dirname;
  462. }
  463. /**
  464. * Support for mkdir().
  465. *
  466. * @param $uri
  467. * A string containing the URI to the directory to create.
  468. * @param $mode
  469. * Permission flags - see mkdir().
  470. * @param $options
  471. * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
  472. *
  473. * @return
  474. * TRUE if directory was successfully created.
  475. *
  476. * @see http://php.net/manual/en/streamwrapper.mkdir.php
  477. */
  478. public function mkdir($uri, $mode, $options) {
  479. // If this already exists, then we can't mkdir.
  480. if (is_dir($uri) || is_file($uri)) {
  481. return FALSE;
  482. }
  483. // Create the key in $_SESSION;
  484. $this->uri_to_session_key($uri, TRUE);
  485. // Place a magic file inside it to differentiate this from an empty file.
  486. $marker_uri = $uri . '/.isadir.txt';
  487. $this->uri_to_session_key($marker_uri, TRUE);
  488. return TRUE;
  489. }
  490. /**
  491. * Support for rmdir().
  492. *
  493. * @param $uri
  494. * A string containing the URI to the directory to delete.
  495. * @param $options
  496. * A bit mask of STREAM_REPORT_ERRORS.
  497. *
  498. * @return
  499. * TRUE if directory was successfully removed.
  500. *
  501. * @see http://php.net/manual/en/streamwrapper.rmdir.php
  502. */
  503. public function rmdir($uri, $options) {
  504. $path = $this->getLocalPath($uri);
  505. $path_components = preg_split('/\//', $path);
  506. $fail = FALSE;
  507. $unset = '$_SESSION[\'file_example\']';
  508. foreach ($path_components as $component) {
  509. $unset .= '[\'' . $component . '\']';
  510. }
  511. // TODO: I really don't like this eval.
  512. debug($unset, 'array element to be unset');
  513. eval("unset($unset);");
  514. return TRUE;
  515. }
  516. /**
  517. * Support for stat().
  518. *
  519. * This important function goes back to the Unix way of doing things.
  520. * In this example almost the entire stat array is irrelevant, but the
  521. * mode is very important. It tells PHP whether we have a file or a
  522. * directory and what the permissions are. All that is packed up in a
  523. * bitmask. This is not normal PHP fodder.
  524. *
  525. * @param $uri
  526. * A string containing the URI to get information about.
  527. * @param $flags
  528. * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
  529. *
  530. * @return
  531. * An array with file status, or FALSE in case of an error - see fstat()
  532. * for a description of this array.
  533. *
  534. * @see http://php.net/manual/en/streamwrapper.url-stat.php
  535. */
  536. public function url_stat($uri, $flags) {
  537. // Get a reference to the $_SESSION key for this URI.
  538. $key = $this->uri_to_session_key($uri, FALSE);
  539. $return = FALSE; // Default to fail.
  540. $mode = 0;
  541. // We will call an array a directory and the root is always an array.
  542. if (is_array($key) && array_key_exists('.isadir.txt', $key)) {
  543. $mode = 0040000; // S_IFDIR means it's a directory.
  544. }
  545. elseif ($key !== FALSE) {
  546. $mode = 0100000; // S_IFREG, means it's a file.
  547. }
  548. if ($mode) {
  549. $size = 0;
  550. if ($mode == 0100000) {
  551. $size = drupal_strlen($key);
  552. }
  553. $mode |= 0777; // There are no protections on this, so all writable.
  554. $return = array(
  555. 'dev' => 0,
  556. 'ino' => 0,
  557. 'mode' => $mode,
  558. 'nlink' => 0,
  559. 'uid' => 0,
  560. 'gid' => 0,
  561. 'rdev' => 0,
  562. 'size' => $size,
  563. 'atime' => 0,
  564. 'mtime' => 0,
  565. 'ctime' => 0,
  566. 'blksize' => 0,
  567. 'blocks' => 0,
  568. );
  569. }
  570. return $return;
  571. }
  572. /**
  573. * Support for opendir().
  574. *
  575. * @param $uri
  576. * A string containing the URI to the directory to open.
  577. * @param $options
  578. * Unknown (parameter is not documented in PHP Manual).
  579. *
  580. * @return
  581. * TRUE on success.
  582. *
  583. * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
  584. */
  585. public function dir_opendir($uri, $options) {
  586. $var = &$this->uri_to_session_key($uri, FALSE);
  587. if ($var === FALSE || !array_key_exists('.isadir.txt', $var)) {
  588. return FALSE;
  589. }
  590. // We grab the list of key names, flip it so that .isadir.txt can easily
  591. // be removed, then flip it back so we can easily walk it as a list.
  592. $this->directory_keys = array_flip(array_keys($var));
  593. unset($this->directory_keys['.isadir.txt']);
  594. $this->directory_keys = array_keys($this->directory_keys);
  595. $this->directory_pointer = 0;
  596. return TRUE;
  597. }
  598. /**
  599. * Support for readdir().
  600. *
  601. * @return
  602. * The next filename, or FALSE if there are no more files in the directory.
  603. *
  604. * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
  605. */
  606. public function dir_readdir() {
  607. if ($this->directory_pointer < count($this->directory_keys)) {
  608. $next = $this->directory_keys[$this->directory_pointer];
  609. $this->directory_pointer++;
  610. return $next;
  611. }
  612. return FALSE;
  613. }
  614. /**
  615. * Support for rewinddir().
  616. *
  617. * @return
  618. * TRUE on success.
  619. *
  620. * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
  621. */
  622. public function dir_rewinddir() {
  623. $this->directory_pointer = 0;
  624. }
  625. /**
  626. * Support for closedir().
  627. *
  628. * @return
  629. * TRUE on success.
  630. *
  631. * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
  632. */
  633. public function dir_closedir() {
  634. $this->directory_pointer = 0;
  635. unset($this->directory_keys);
  636. return TRUE;
  637. }
  638. }