file_example_session_streams.inc

  1. examples
    1. 7 file_example/file_example_session_streams.inc
    2. 8 file_example/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

NameDescription
FileExampleSessionStreamWrapperExample 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. * TRUE if the key exists (which it will if $create was set)
  220. */
  221. protected function &uri_to_session_key($uri, $create = TRUE) {
  222. $path = $this->getLocalPath($uri);
  223. $path_components = preg_split('/\//', $path);
  224. $fail = FALSE;
  225. $var = &$_SESSION['file_example'];
  226. // Handle case of just session://.
  227. if (drupal_strlen($path) == 0) {
  228. return $var;
  229. }
  230. foreach ($path_components as $component) {
  231. if ($create || isset($var[$component])) {
  232. $var = &$var[$component];
  233. }
  234. else {
  235. return $fail;
  236. }
  237. }
  238. return $var;
  239. }
  240. /**
  241. * Support for flock().
  242. *
  243. * The $_SESSION variable has no locking capability, so return TRUE.
  244. *
  245. * @param $operation
  246. * One of the following:
  247. * - LOCK_SH to acquire a shared lock (reader).
  248. * - LOCK_EX to acquire an exclusive lock (writer).
  249. * - LOCK_UN to release a lock (shared or exclusive).
  250. * - LOCK_NB if you don't want flock() to block while locking (not
  251. * supported on Windows).
  252. *
  253. * @return
  254. * Always returns TRUE at the present time. (no support)
  255. *
  256. * @see http://php.net/manual/en/streamwrapper.stream-lock.php
  257. */
  258. public function stream_lock($operation) {
  259. return TRUE;
  260. }
  261. /**
  262. * Support for fread(), file_get_contents() etc.
  263. *
  264. * @param $count
  265. * Maximum number of bytes to be read.
  266. *
  267. * @return
  268. * The string that was read, or FALSE in case of an error.
  269. *
  270. * @see http://php.net/manual/en/streamwrapper.stream-read.php
  271. */
  272. public function stream_read($count) {
  273. if (is_string($this->session_content)) {
  274. $remaining_chars = drupal_strlen($this->session_content) - $this->stream_pointer;
  275. $number_to_read = min($count, $remaining_chars);
  276. if ($remaining_chars > 0) {
  277. $buffer = drupal_substr($this->session_content, $this->stream_pointer, $number_to_read);
  278. $this->stream_pointer += $number_to_read;
  279. return $buffer;
  280. }
  281. }
  282. return FALSE;
  283. }
  284. /**
  285. * Support for fwrite(), file_put_contents() etc.
  286. *
  287. * @param $data
  288. * The string to be written.
  289. *
  290. * @return
  291. * The number of bytes written (integer).
  292. *
  293. * @see http://php.net/manual/en/streamwrapper.stream-write.php
  294. */
  295. public function stream_write($data) {
  296. // Sanitize the data in a simple way since we're putting it into the
  297. // session variable.
  298. $data = check_plain($data);
  299. $this->session_content = substr_replace($this->session_content, $data, $this->stream_pointer);
  300. $this->stream_pointer += drupal_strlen($data);
  301. return drupal_strlen($data);
  302. }
  303. /**
  304. * Support for feof().
  305. *
  306. * @return
  307. * TRUE if end-of-file has been reached.
  308. *
  309. * @see http://php.net/manual/en/streamwrapper.stream-eof.php
  310. */
  311. public function stream_eof() {
  312. return FALSE;
  313. }
  314. /**
  315. * Support for fseek().
  316. *
  317. * @param $offset
  318. * The byte offset to got to.
  319. * @param $whence
  320. * SEEK_SET, SEEK_CUR, or SEEK_END.
  321. *
  322. * @return
  323. * TRUE on success.
  324. *
  325. * @see http://php.net/manual/en/streamwrapper.stream-seek.php
  326. */
  327. public function stream_seek($offset, $whence) {
  328. if (drupal_strlen($this->session_content) >= $offset) {
  329. $this->stream_pointer = $offset;
  330. return TRUE;
  331. }
  332. return FALSE;
  333. }
  334. /**
  335. * Support for fflush().
  336. *
  337. * @return
  338. * TRUE if data was successfully stored (or there was no data to store).
  339. * This always returns TRUE, as this example provides and needs no
  340. * flush support.
  341. *
  342. * @see http://php.net/manual/en/streamwrapper.stream-flush.php
  343. */
  344. public function stream_flush() {
  345. return TRUE;
  346. }
  347. /**
  348. * Support for ftell().
  349. *
  350. * @return
  351. * The current offset in bytes from the beginning of file.
  352. *
  353. * @see http://php.net/manual/en/streamwrapper.stream-tell.php
  354. */
  355. public function stream_tell() {
  356. return $this->stream_pointer;
  357. }
  358. /**
  359. * Support for fstat().
  360. *
  361. * @return
  362. * An array with file status, or FALSE in case of an error - see fstat()
  363. * for a description of this array.
  364. *
  365. * @see http://php.net/manual/en/streamwrapper.stream-stat.php
  366. */
  367. public function stream_stat() {
  368. return array(
  369. 'size' => drupal_strlen($this->session_content),
  370. );
  371. }
  372. /**
  373. * Support for fclose().
  374. *
  375. * @return
  376. * TRUE if stream was successfully closed.
  377. *
  378. * @see http://php.net/manual/en/streamwrapper.stream-close.php
  379. */
  380. public function stream_close() {
  381. $this->stream_pointer = 0;
  382. unset($this->session_content); // Unassign the reference.
  383. return TRUE;
  384. }
  385. /**
  386. * Support for unlink().
  387. *
  388. * @param $uri
  389. * A string containing the uri to the resource to delete.
  390. *
  391. * @return
  392. * TRUE if resource was successfully deleted.
  393. *
  394. * @see http://php.net/manual/en/streamwrapper.unlink.php
  395. */
  396. public function unlink($uri) {
  397. $path = $this->getLocalPath($uri);
  398. $path_components = preg_split('/\//', $path);
  399. $fail = FALSE;
  400. $unset = '$_SESSION[\'file_example\']';
  401. foreach ($path_components as $component) {
  402. $unset .= '[\'' . $component . '\']';
  403. }
  404. // TODO: Is there a better way to delete from an array?
  405. // drupal_array_get_nested_value() doesn't work because it only returns
  406. // a reference; unsetting a reference only unsets the reference.
  407. eval("unset($unset);");
  408. return TRUE;
  409. }
  410. /**
  411. * Support for rename().
  412. *
  413. * @param $from_uri,
  414. * The uri to the file to rename.
  415. * @param $to_uri
  416. * The new uri for file.
  417. *
  418. * @return
  419. * TRUE if file was successfully renamed.
  420. *
  421. * @see http://php.net/manual/en/streamwrapper.rename.php
  422. */
  423. public function rename($from_uri, $to_uri) {
  424. $from_key = &$this->uri_to_session_key($from_uri);
  425. $to_key = &$this->uri_to_session_key($to_uri);
  426. if (is_dir($to_key) || is_file($to_key)) {
  427. return FALSE;
  428. }
  429. $to_key = $from_key;
  430. unset($from_key);
  431. return TRUE;
  432. }
  433. /**
  434. * Gets the name of the directory from a given path.
  435. *
  436. * @param $uri
  437. * A URI.
  438. *
  439. * @return
  440. * A string containing the directory name.
  441. *
  442. * @see drupal_dirname()
  443. */
  444. public function dirname($uri = NULL) {
  445. list($scheme, $target) = explode('://', $uri, 2);
  446. $target = $this->getTarget($uri);
  447. if (strpos($target, '/')) {
  448. $dirname = preg_replace('@/[^/]*$@', '', $target);
  449. }
  450. else {
  451. $dirname = '';
  452. }
  453. return $scheme . '://' . $dirname;
  454. }
  455. /**
  456. * Support for mkdir().
  457. *
  458. * @param $uri
  459. * A string containing the URI to the directory to create.
  460. * @param $mode
  461. * Permission flags - see mkdir().
  462. * @param $options
  463. * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
  464. *
  465. * @return
  466. * TRUE if directory was successfully created.
  467. *
  468. * @see http://php.net/manual/en/streamwrapper.mkdir.php
  469. */
  470. public function mkdir($uri, $mode, $options) {
  471. // If this already exists, then we can't mkdir.
  472. if (is_dir($uri) || is_file($uri)) {
  473. return FALSE;
  474. }
  475. // Create the key in $_SESSION;
  476. $this->uri_to_session_key($uri, TRUE);
  477. // Place a magic file inside it to differentiate this from an empty file.
  478. $marker_uri = $uri . '/.isadir.txt';
  479. $this->uri_to_session_key($marker_uri, TRUE);
  480. return TRUE;
  481. }
  482. /**
  483. * Support for rmdir().
  484. *
  485. * @param $uri
  486. * A string containing the URI to the directory to delete.
  487. * @param $options
  488. * A bit mask of STREAM_REPORT_ERRORS.
  489. *
  490. * @return
  491. * TRUE if directory was successfully removed.
  492. *
  493. * @see http://php.net/manual/en/streamwrapper.rmdir.php
  494. */
  495. public function rmdir($uri, $options) {
  496. $path = $this->getLocalPath($uri);
  497. $path_components = preg_split('/\//', $path);
  498. $fail = FALSE;
  499. $unset = '$_SESSION[\'file_example\']';
  500. foreach ($path_components as $component) {
  501. $unset .= '[\'' . $component . '\']';
  502. }
  503. // TODO: I really don't like this eval.
  504. debug($unset, 'array element to be unset');
  505. eval("unset($unset);");
  506. return TRUE;
  507. }
  508. /**
  509. * Support for stat().
  510. *
  511. * This important function goes back to the Unix way of doing things.
  512. * In this example almost the entire stat array is irrelevant, but the
  513. * mode is very important. It tells PHP whether we have a file or a
  514. * directory and what the permissions are. All that is packed up in a
  515. * bitmask. This is not normal PHP fodder.
  516. *
  517. * @param $uri
  518. * A string containing the URI to get information about.
  519. * @param $flags
  520. * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
  521. *
  522. * @return
  523. * An array with file status, or FALSE in case of an error - see fstat()
  524. * for a description of this array.
  525. *
  526. * @see http://php.net/manual/en/streamwrapper.url-stat.php
  527. */
  528. public function url_stat($uri, $flags) {
  529. // PHP does not necessarily construct the object before all operations,
  530. // so here we make sure that the startup work is done.
  531. $this->__construct();
  532. $key = $this->uri_to_session_key($uri, FALSE);
  533. $return = FALSE; // Default to fail.
  534. $mode = 0;
  535. // We will call an array a directory and the root is always an array.
  536. if (is_array($key) && array_key_exists('.isadir.txt', $key)) {
  537. $mode = 0040000; // S_IFDIR means it's a directory.
  538. }
  539. elseif ($key !== FALSE) {
  540. $mode = 0100000; // S_IFREG, means it's a file.
  541. }
  542. if ($mode) {
  543. $size = 0;
  544. if ($mode == 0100000) {
  545. $size = drupal_strlen($key);
  546. }
  547. $mode |= 0777; // There are no protections on this, so all writable.
  548. $return = array(
  549. 'dev' => 0,
  550. 'ino' => 0,
  551. 'mode' => $mode,
  552. 'nlink' => 0,
  553. 'uid' => 0,
  554. 'gid' => 0,
  555. 'rdev' => 0,
  556. 'size' => $size,
  557. 'atime' => 0,
  558. 'mtime' => 0,
  559. 'ctime' => 0,
  560. 'blksize' => 0,
  561. 'blocks' => 0,
  562. );
  563. }
  564. return $return;
  565. }
  566. /**
  567. * Support for opendir().
  568. *
  569. * @param $uri
  570. * A string containing the URI to the directory to open.
  571. * @param $options
  572. * Unknown (parameter is not documented in PHP Manual).
  573. *
  574. * @return
  575. * TRUE on success.
  576. *
  577. * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
  578. */
  579. public function dir_opendir($uri, $options) {
  580. $var = &$this->uri_to_session_key($uri, FALSE);
  581. if ($var === FALSE || !array_key_exists('.isadir.txt', $var)) {
  582. return FALSE;
  583. }
  584. // We grab the list of key names, flip it so that .isadir.txt can easily
  585. // be removed, then flip it back so we can easily walk it as a list.
  586. $this->directory_keys = array_flip(array_keys($var));
  587. unset($this->directory_keys['.isadir.txt']);
  588. $this->directory_keys = array_keys($this->directory_keys);
  589. $this->directory_pointer = 0;
  590. return TRUE;
  591. }
  592. /**
  593. * Support for readdir().
  594. *
  595. * @return
  596. * The next filename, or FALSE if there are no more files in the directory.
  597. *
  598. * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
  599. */
  600. public function dir_readdir() {
  601. if ($this->directory_pointer < count($this->directory_keys)) {
  602. $next = $this->directory_keys[$this->directory_pointer];
  603. $this->directory_pointer++;
  604. return $next;
  605. }
  606. return FALSE;
  607. }
  608. /**
  609. * Support for rewinddir().
  610. *
  611. * @return
  612. * TRUE on success.
  613. *
  614. * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
  615. */
  616. public function dir_rewinddir() {
  617. $this->directory_pointer = 0;
  618. }
  619. /**
  620. * Support for closedir().
  621. *
  622. * @return
  623. * TRUE on success.
  624. *
  625. * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
  626. */
  627. public function dir_closedir() {
  628. $this->directory_pointer = 0;
  629. unset($this->directory_keys);
  630. return TRUE;
  631. }
  632. }
Login or register to post comments