file_example_session_streams.inc

You are here

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