PageRenderTime 63ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/fSession.php

https://bitbucket.org/dsqmoore/flourish
PHP | 884 lines | 453 code | 120 blank | 311 comment | 62 complexity | 1624f43a64151a5dc34f22f818c77dd3 MD5 | raw file
  1. <?php
  2. /**
  3. * Wraps the session control functions and the `$_SESSION` superglobal for a more consistent and safer API
  4. *
  5. * A `Cannot send session cache limiter` warning will be triggered if ::open(),
  6. * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output has
  7. * been sent to the browser. To prevent such a warning, explicitly call ::open()
  8. * before generating any output.
  9. *
  10. * @copyright Copyright (c) 2007-2011 Will Bond, others
  11. * @author Will Bond [wb] <will@flourishlib.com>
  12. * @author Alex Leeds [al] <alex@kingleeds.com>
  13. * @license http://flourishlib.com/license
  14. *
  15. * @package Flourish
  16. * @link http://flourishlib.com/fSession
  17. *
  18. * @version 1.0.0b20
  19. * @changes 1.0.0b20 Fixed bugs with ::reset() introduced in 1.0.0b19 [wb, 2011-08-23]
  20. * @changes 1.0.0b19 Fixed some session warning messages for PHP 5.1.6 [wb, 2011-07-29]
  21. * @changes 1.0.0b18 Added support for storing session data in memcache, redis and databases using fCache and ::setBackend() [wb, 2011-06-21]
  22. * @changes 1.0.0b17 Updated ::ignoreSubdomain() to use `$_SERVER['HTTP_HOST']` when `$_SERVER['SERVER_NAME']` is not set [wb, 2011-02-01]
  23. * @changes 1.0.0b16 Changed ::delete() to return the value of the key being deleted [wb, 2010-09-19]
  24. * @changes 1.0.0b15 Added documentation about `[sub-key]` syntax [wb, 2010-09-12]
  25. * @changes 1.0.0b14 Backwards Compatibility Break - ::add(), ::delete(), ::get() and ::set() now interpret `[` and `]` as array shorthand and thus they can not be used in keys - added `$beginning` parameter to ::add(), added ::remove() method [wb, 2010-09-12]
  26. * @changes 1.0.0b13 Fixed a bug that prevented working with existing sessions since they did not have the `fSession::expires` key [wb, 2010-08-24]
  27. * @changes 1.0.0b12 Changed ::enablePersistence() to always regenerate the session ID, which ensures the function works even if the ID has already been regenerated by fAuthorizaton [wb, 2010-08-21]
  28. * @changes 1.0.0b11 Updated the class to make sure ::enablePersistence() is called after ::ignoreSubdomain(), ::setLength() and ::setPath() [wb, 2010-05-29]
  29. * @changes 1.0.0b10 Fixed some documentation bugs [wb, 2010-03-03]
  30. * @changes 1.0.0b9 Fixed a bug in ::destroy() where sessions weren't always being properly destroyed [wb, 2009-12-08]
  31. * @changes 1.0.0b8 Fixed a bug that made the unit tests fail on PHP 5.1 [wb, 2009-10-27]
  32. * @changes 1.0.0b7 Backwards Compatibility Break - Removed the `$prefix` parameter from the methods ::delete(), ::get() and ::set() - added the methods ::add(), ::enablePersistence(), ::regenerateID() [wb+al, 2009-10-23]
  33. * @changes 1.0.0b6 Backwards Compatibility Break - the first parameter of ::clear() was removed, use ::delete() instead [wb, 2009-05-08]
  34. * @changes 1.0.0b5 Added documentation about session cache limiter warnings [wb, 2009-05-04]
  35. * @changes 1.0.0b4 The class now works with existing sessions [wb, 2009-05-04]
  36. * @changes 1.0.0b3 Fixed ::clear() to properly handle when `$key` is `NULL` [wb, 2009-02-05]
  37. * @changes 1.0.0b2 Made ::open() public, fixed some consistency issues with setting session options through the class [wb, 2009-01-06]
  38. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  39. */
  40. class fSession
  41. {
  42. // The following constants allow for nice looking callbacks to static methods
  43. const add = 'fSession::add';
  44. const clear = 'fSession::clear';
  45. const close = 'fSession::close';
  46. const closeCache = 'fSession::closeCache';
  47. const delete = 'fSession::delete';
  48. const destroy = 'fSession::destroy';
  49. const destroyCache = 'fSession::destroyCache';
  50. const enablePersistence = 'fSession::enablePersistence';
  51. const gcCache = 'fSession::gcCache';
  52. const get = 'fSession::get';
  53. const ignoreSubdomain = 'fSession::ignoreSubdomain';
  54. const open = 'fSession::open';
  55. const openCache = 'fSession::openCache';
  56. const readCache = 'fSession::readCache';
  57. const regenerateID = 'fSession::regenerateID';
  58. const reset = 'fSession::reset';
  59. const set = 'fSession::set';
  60. const setBackend = 'fSession::setBackend';
  61. const setLength = 'fSession::setLength';
  62. const setPath = 'fSession::setPath';
  63. const writeCache = 'fSession::writeCache';
  64. /**
  65. * The fCache backend to use for the session
  66. *
  67. * @var fCache
  68. */
  69. static private $backend = NULL;
  70. /**
  71. * The key prefix to use when saving the session to an fCache
  72. *
  73. * @var string
  74. */
  75. static private $key_prefix = '';
  76. /**
  77. * The length for a normal session
  78. *
  79. * @var integer
  80. */
  81. static private $normal_timespan = NULL;
  82. /**
  83. * The name of the old session module to revent to when fSession is closed
  84. *
  85. * @var string
  86. */
  87. static private $old_session_module_name = NULL;
  88. /**
  89. * If the session is open
  90. *
  91. * @var boolean
  92. */
  93. static private $open = FALSE;
  94. /**
  95. * The length for a persistent session cookie - one that survives browser restarts
  96. *
  97. * @var integer
  98. */
  99. static private $persistent_timespan = NULL;
  100. /**
  101. * If the session ID was regenerated during this script
  102. *
  103. * @var boolean
  104. */
  105. static private $regenerated = FALSE;
  106. /**
  107. * Adds a value to an already-existing array value, or to a new array value
  108. *
  109. * @param string $key The name to access the array under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  110. * @param mixed $value The value to add to the array
  111. * @param boolean $beginning If the value should be added to the beginning
  112. * @return void
  113. */
  114. static public function add($key, $value, $beginning=FALSE)
  115. {
  116. self::open();
  117. $tip =& $_SESSION;
  118. if ($bracket_pos = strpos($key, '[')) {
  119. $original_key = $key;
  120. $array_dereference = substr($key, $bracket_pos);
  121. $key = substr($key, 0, $bracket_pos);
  122. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  123. $array_keys = array_map('current', $array_keys);
  124. array_unshift($array_keys, $key);
  125. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  126. if (!isset($tip[$array_key])) {
  127. $tip[$array_key] = array();
  128. } elseif (!is_array($tip[$array_key])) {
  129. throw new fProgrammerException(
  130. '%1$s was called for the key, %2$s, which is not an array',
  131. __CLASS__ . '::add()',
  132. $original_key
  133. );
  134. }
  135. $tip =& $tip[$array_key];
  136. }
  137. $key = end($array_keys);
  138. }
  139. if (!isset($tip[$key])) {
  140. $tip[$key] = array();
  141. } elseif (!is_array($tip[$key])) {
  142. throw new fProgrammerException(
  143. '%1$s was called for the key, %2$s, which is not an array',
  144. __CLASS__ . '::add()',
  145. $key
  146. );
  147. }
  148. if ($beginning) {
  149. array_unshift($tip[$key], $value);
  150. } else {
  151. $tip[$key][] = $value;
  152. }
  153. }
  154. /**
  155. * Removes all session values with the provided prefix
  156. *
  157. * This method will not remove session variables used by this class, which
  158. * are prefixed with `fSession::`.
  159. *
  160. * @param string $prefix The prefix to clear all session values for
  161. * @return void
  162. */
  163. static public function clear($prefix=NULL)
  164. {
  165. self::open();
  166. $session_type = $_SESSION['fSession::type'];
  167. $session_expires = $_SESSION['fSession::expires'];
  168. if ($prefix) {
  169. foreach ($_SESSION as $key => $value) {
  170. if (strpos($key, $prefix) === 0) {
  171. unset($_SESSION[$key]);
  172. }
  173. }
  174. } else {
  175. $_SESSION = array();
  176. }
  177. $_SESSION['fSession::type'] = $session_type;
  178. $_SESSION['fSession::expires'] = $session_expires;
  179. }
  180. /**
  181. * Closes the session for writing, allowing other pages to open the session
  182. *
  183. * @return void
  184. */
  185. static public function close()
  186. {
  187. if (!self::$open) { return; }
  188. session_write_close();
  189. unset($_SESSION);
  190. self::$open = FALSE;
  191. if (self::$old_session_module_name) {
  192. session_module_name(self::$old_session_module_name);
  193. }
  194. }
  195. /**
  196. * Callback to close the session
  197. *
  198. * @internal
  199. *
  200. * @return boolean If the operation succeeded
  201. */
  202. static public function closeCache()
  203. {
  204. return TRUE;
  205. }
  206. /**
  207. * Deletes a value from the session
  208. *
  209. * @param string $key The key of the value to delete - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  210. * @param mixed $default_value The value to return if the `$key` is not set
  211. * @return mixed The value of the `$key` that was deleted
  212. */
  213. static public function delete($key, $default_value=NULL)
  214. {
  215. self::open();
  216. $value = $default_value;
  217. if ($bracket_pos = strpos($key, '[')) {
  218. $original_key = $key;
  219. $array_dereference = substr($key, $bracket_pos);
  220. $key = substr($key, 0, $bracket_pos);
  221. if (!isset($_SESSION[$key])) {
  222. return $value;
  223. }
  224. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  225. $array_keys = array_map('current', $array_keys);
  226. $tip =& $_SESSION[$key];
  227. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  228. if (!isset($tip[$array_key])) {
  229. return $value;
  230. } elseif (!is_array($tip[$array_key])) {
  231. throw new fProgrammerException(
  232. '%1$s was called for an element, %2$s, which is not an array',
  233. __CLASS__ . '::delete()',
  234. $original_key
  235. );
  236. }
  237. $tip =& $tip[$array_key];
  238. }
  239. $key = end($array_keys);
  240. } else {
  241. $tip =& $_SESSION;
  242. }
  243. if (isset($tip[$key])) {
  244. $value = $tip[$key];
  245. unset($tip[$key]);
  246. }
  247. return $value;
  248. }
  249. /**
  250. * Destroys the session, removing all values
  251. *
  252. * @return void
  253. */
  254. static public function destroy()
  255. {
  256. self::open();
  257. $_SESSION = array();
  258. if (isset($_COOKIE[session_name()])) {
  259. $params = session_get_cookie_params();
  260. setcookie(session_name(), '', time()-43200, $params['path'], $params['domain'], $params['secure']);
  261. }
  262. session_destroy();
  263. self::regenerateID();
  264. }
  265. /**
  266. * Callback to destroy a session
  267. *
  268. * @internal
  269. *
  270. * @param string $id The session to destroy
  271. * @return boolean If the operation succeeded
  272. */
  273. static public function destroyCache($id)
  274. {
  275. return self::$backend->delete(self::$key_prefix . $id);
  276. }
  277. /**
  278. * Changed the session to use a time-based cookie instead of a session-based cookie
  279. *
  280. * The length of the time-based cookie is controlled by ::setLength(). When
  281. * this method is called, a time-based cookie is used to store the session
  282. * ID. This means the session can persist browser restarts. Normally, a
  283. * session-based cookie is used, which is wiped when a browser restart
  284. * occurs.
  285. *
  286. * This method should be called during the login process and will normally
  287. * be controlled by a checkbox or similar where the user can indicate if
  288. * they want to stay logged in for an extended period of time.
  289. *
  290. * @return void
  291. */
  292. static public function enablePersistence()
  293. {
  294. if (self::$persistent_timespan === NULL) {
  295. throw new fProgrammerException(
  296. 'The method %1$s must be called with the %2$s parameter before calling %3$s',
  297. __CLASS__ . '::setLength()',
  298. '$persistent_timespan',
  299. __CLASS__ . '::enablePersistence()'
  300. );
  301. }
  302. $current_params = session_get_cookie_params();
  303. $params = array(
  304. self::$persistent_timespan,
  305. $current_params['path'],
  306. $current_params['domain'],
  307. $current_params['secure']
  308. );
  309. call_user_func_array('session_set_cookie_params', $params);
  310. self::open();
  311. $_SESSION['fSession::type'] = 'persistent';
  312. session_regenerate_id();
  313. self::$regenerated = TRUE;
  314. }
  315. /**
  316. * Callback to garbage-collect the session cache
  317. *
  318. * @internal
  319. *
  320. * @return boolean If the operation succeeded
  321. */
  322. static public function gcCache()
  323. {
  324. self::$backend->clean();
  325. return TRUE;
  326. }
  327. /**
  328. * Gets data from the `$_SESSION` superglobal
  329. *
  330. * @param string $key The name to get the value for - array elements can be accessed via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  331. * @param mixed $default_value The default value to use if the requested key is not set
  332. * @return mixed The data element requested
  333. */
  334. static public function get($key, $default_value=NULL)
  335. {
  336. self::open();
  337. $array_dereference = NULL;
  338. if ($bracket_pos = strpos($key, '[')) {
  339. $array_dereference = substr($key, $bracket_pos);
  340. $key = substr($key, 0, $bracket_pos);
  341. }
  342. if (!isset($_SESSION[$key])) {
  343. return $default_value;
  344. }
  345. $value = $_SESSION[$key];
  346. if ($array_dereference) {
  347. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  348. $array_keys = array_map('current', $array_keys);
  349. foreach ($array_keys as $array_key) {
  350. if (!is_array($value) || !isset($value[$array_key])) {
  351. $value = $default_value;
  352. break;
  353. }
  354. $value = $value[$array_key];
  355. }
  356. }
  357. return $value;
  358. }
  359. /**
  360. * Sets the session to run on the main domain, not just the specific subdomain currently being accessed
  361. *
  362. * This method should be called after any calls to
  363. * [http://php.net/session_set_cookie_params `session_set_cookie_params()`].
  364. *
  365. * @return void
  366. */
  367. static public function ignoreSubdomain()
  368. {
  369. if (self::$open || isset($_SESSION)) {
  370. throw new fProgrammerException(
  371. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  372. __CLASS__ . '::ignoreSubdomain()',
  373. __CLASS__ . '::add()',
  374. __CLASS__ . '::clear()',
  375. __CLASS__ . '::enablePersistence()',
  376. __CLASS__ . '::get()',
  377. __CLASS__ . '::open()',
  378. __CLASS__ . '::set()',
  379. 'session_start()'
  380. );
  381. }
  382. $current_params = session_get_cookie_params();
  383. if (isset($_SERVER['SERVER_NAME'])) {
  384. $domain = $_SERVER['SERVER_NAME'];
  385. } elseif (isset($_SERVER['HTTP_HOST'])) {
  386. $domain = $_SERVER['HTTP_HOST'];
  387. } else {
  388. throw new fEnvironmentException(
  389. 'The domain name could not be found in %1$s or %2$s. Please set one of these keys to use %3$s.',
  390. '$_SERVER[\'SERVER_NAME\']',
  391. '$_SERVER[\'HTTP_HOST\']',
  392. __CLASS__ . '::ignoreSubdomain()'
  393. );
  394. }
  395. $params = array(
  396. $current_params['lifetime'],
  397. $current_params['path'],
  398. preg_replace('#.*?([a-z0-9\\-]+\.[a-z]+)$#iD', '.\1', $domain),
  399. $current_params['secure']
  400. );
  401. call_user_func_array('session_set_cookie_params', $params);
  402. }
  403. /**
  404. * Opens the session for writing, is automatically called by ::clear(), ::get() and ::set()
  405. *
  406. * A `Cannot send session cache limiter` warning will be triggered if this,
  407. * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output
  408. * has been sent to the browser. To prevent such a warning, explicitly call
  409. * this method before generating any output.
  410. *
  411. * @param boolean $cookie_only_session_id If the session id should only be allowed via cookie - this is a security issue and should only be set to `FALSE` when absolutely necessary
  412. * @return void
  413. */
  414. static public function open($cookie_only_session_id=TRUE)
  415. {
  416. if (self::$open) { return; }
  417. self::$open = TRUE;
  418. if (self::$normal_timespan === NULL) {
  419. self::$normal_timespan = ini_get('session.gc_maxlifetime');
  420. }
  421. if (self::$backend && isset($_SESSION) && session_module_name() != 'user') {
  422. throw new fProgrammerException(
  423. 'A custom backend was provided by %1$s, however the session has already been started, so it can not be used',
  424. __CLASS__ . '::setBackend()'
  425. );
  426. }
  427. // If the session is already open, we just piggy-back without setting options
  428. if (!isset($_SESSION)) {
  429. if ($cookie_only_session_id) {
  430. ini_set('session.use_cookies', 1);
  431. ini_set('session.use_only_cookies', 1);
  432. }
  433. // If we are using a custom backend we have to set the session handler
  434. if (self::$backend && session_module_name() != 'user') {
  435. session_set_save_handler(
  436. array('fSession', 'openCache'),
  437. array('fSession', 'closeCache'),
  438. array('fSession', 'readCache'),
  439. array('fSession', 'writeCache'),
  440. array('fSession', 'destroyCache'),
  441. array('fSession', 'gcCache')
  442. );
  443. }
  444. session_start();
  445. }
  446. // If the session has existed for too long, reset it
  447. if (isset($_SESSION['fSession::expires']) && $_SESSION['fSession::expires'] < $_SERVER['REQUEST_TIME']) {
  448. $_SESSION = array();
  449. self::regenerateID();
  450. }
  451. if (!isset($_SESSION['fSession::type'])) {
  452. $_SESSION['fSession::type'] = 'normal';
  453. }
  454. // We store the expiration time for a session to allow for both normal and persistent sessions
  455. if ($_SESSION['fSession::type'] == 'persistent' && self::$persistent_timespan) {
  456. $_SESSION['fSession::expires'] = $_SERVER['REQUEST_TIME'] + self::$persistent_timespan;
  457. } else {
  458. $_SESSION['fSession::expires'] = $_SERVER['REQUEST_TIME'] + self::$normal_timespan;
  459. }
  460. }
  461. /**
  462. * Callback to open the session
  463. *
  464. * @internal
  465. *
  466. * @return boolean If the operation succeeded
  467. */
  468. static public function openCache()
  469. {
  470. return TRUE;
  471. }
  472. /**
  473. * Callback to read a session's values
  474. *
  475. * @internal
  476. *
  477. * @param string $id The session to read
  478. * @return string The session's serialized data
  479. */
  480. static public function readCache($id)
  481. {
  482. return self::$backend->get(self::$key_prefix . $id, '');
  483. }
  484. /**
  485. * Regenerates the session ID, but only once per script execution
  486. *
  487. * @internal
  488. *
  489. * @return void
  490. */
  491. static public function regenerateID()
  492. {
  493. if (!self::$regenerated){
  494. session_regenerate_id();
  495. self::$regenerated = TRUE;
  496. }
  497. }
  498. /**
  499. * Removes and returns the value from the end of an array value
  500. *
  501. * @param string $key The name of the element to remove the value from - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  502. * @param boolean $beginning If the value should be removed to the beginning
  503. * @return mixed The value that was removed
  504. */
  505. static public function remove($key, $beginning=FALSE)
  506. {
  507. self::open();
  508. $tip =& $_SESSION;
  509. if ($bracket_pos = strpos($key, '[')) {
  510. $original_key = $key;
  511. $array_dereference = substr($key, $bracket_pos);
  512. $key = substr($key, 0, $bracket_pos);
  513. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  514. $array_keys = array_map('current', $array_keys);
  515. array_unshift($array_keys, $key);
  516. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  517. if (!isset($tip[$array_key])) {
  518. return NULL;
  519. } elseif (!is_array($tip[$array_key])) {
  520. throw new fProgrammerException(
  521. '%1$s was called for the key, %2$s, which is not an array',
  522. __CLASS__ . '::remove()',
  523. $original_key
  524. );
  525. }
  526. $tip =& $tip[$array_key];
  527. }
  528. $key = end($array_keys);
  529. }
  530. if (!isset($tip[$key])) {
  531. return NULL;
  532. } elseif (!is_array($tip[$key])) {
  533. throw new fProgrammerException(
  534. '%1$s was called for the key, %2$s, which is not an array',
  535. __CLASS__ . '::remove()',
  536. $key
  537. );
  538. }
  539. if ($beginning) {
  540. return array_shift($tip[$key]);
  541. }
  542. return array_pop($tip[$key]);
  543. }
  544. /**
  545. * Resets the configuration of the class
  546. *
  547. * @internal
  548. *
  549. * @return void
  550. */
  551. static public function reset()
  552. {
  553. self::$normal_timespan = NULL;
  554. self::$persistent_timespan = NULL;
  555. self::$regenerated = FALSE;
  556. self::destroy();
  557. self::close();
  558. self::$backend = NULL;
  559. self::$key_prefix = '';
  560. }
  561. /**
  562. * Sets data to the `$_SESSION` superglobal
  563. *
  564. * @param string $key The name to save the value under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  565. * @param mixed $value The value to store
  566. * @return void
  567. */
  568. static public function set($key, $value)
  569. {
  570. self::open();
  571. $tip =& $_SESSION;
  572. if ($bracket_pos = strpos($key, '[')) {
  573. $array_dereference = substr($key, $bracket_pos);
  574. $key = substr($key, 0, $bracket_pos);
  575. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  576. $array_keys = array_map('current', $array_keys);
  577. array_unshift($array_keys, $key);
  578. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  579. if (!isset($tip[$array_key]) || !is_array($tip[$array_key])) {
  580. $tip[$array_key] = array();
  581. }
  582. $tip =& $tip[$array_key];
  583. }
  584. $tip[end($array_keys)] = $value;
  585. } else {
  586. $tip[$key] = $value;
  587. }
  588. }
  589. /**
  590. * Sets an fCache object to store sessions in
  591. *
  592. * While any type of fCache backend should technically work, it would be
  593. * unwise to use the `file` and `directory` types. The `file` caching
  594. * backend stores all values in a single file, which would quickly become a
  595. * performance bottleneck and could cause data loss with many concurrent
  596. * users. The `directory` caching backend would not make sense since it is
  597. * the same general functionality as the default session handler, but it
  598. * would be slightly slower since it is written in PHP and not C.
  599. *
  600. * It is recommended to set the `serializer` and `unserializer` `$config`
  601. * settings on the fCache object to `string` for the best performance and
  602. * minimal storage space.
  603. *
  604. * For better performance, check out using the built-in session handlers
  605. * that are bundled with the following extensions:
  606. *
  607. * - [http://php.net/memcached.sessions memcached]
  608. * - [http://php.net/memcache.examples-overview#example-3596 memcache]
  609. * - [https://github.com/nicolasff/phpredis redis]
  610. *
  611. * The [http://pecl.php.net/package/igbinary igbinary] extension can
  612. * provide even more of a performance boost by storing serialized data in
  613. * binary format instead of as text.
  614. *
  615. * @param fCache $backend An fCache object to store session values in
  616. * @param string $key_prefix A prefix to add to all session IDs before storing them in the cache
  617. * @return void
  618. */
  619. static public function setBackend($backend, $key_prefix='')
  620. {
  621. if (self::$open || isset($_SESSION)) {
  622. throw new fProgrammerException(
  623. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  624. __CLASS__ . '::setLength()',
  625. __CLASS__ . '::add()',
  626. __CLASS__ . '::clear()',
  627. __CLASS__ . '::enablePersistence()',
  628. __CLASS__ . '::get()',
  629. __CLASS__ . '::open()',
  630. __CLASS__ . '::set()',
  631. 'session_start()'
  632. );
  633. }
  634. self::$old_session_module_name = session_module_name();
  635. self::$backend = $backend;
  636. self::$key_prefix = $key_prefix;
  637. session_set_save_handler(
  638. array('fSession', 'openCache'),
  639. array('fSession', 'closeCache'),
  640. array('fSession', 'readCache'),
  641. array('fSession', 'writeCache'),
  642. array('fSession', 'destroyCache'),
  643. array('fSession', 'gcCache')
  644. );
  645. // This ensures the session is closed before the fCache object is destructed
  646. register_shutdown_function(array('fSession', 'close'));
  647. }
  648. /**
  649. * Sets the minimum length of a session - PHP might not clean up the session data right away once this timespan has elapsed
  650. *
  651. * Please be sure to set a custom session path via ::setPath() to ensure
  652. * another site on the server does not garbage collect the session files
  653. * from this site!
  654. *
  655. * Both of the timespan can accept either a integer timespan in seconds,
  656. * or an english description of a timespan (e.g. `'30 minutes'`, `'1 hour'`,
  657. * `'1 day 2 hours'`).
  658. *
  659. * @param string|integer $normal_timespan The normal, session-based cookie, length for the session
  660. * @param string|integer $persistent_timespan The persistent, timed-based cookie, length for the session - this is enabled by calling ::enabledPersistence() during login
  661. * @return void
  662. */
  663. static public function setLength($normal_timespan, $persistent_timespan=NULL)
  664. {
  665. if (self::$open || isset($_SESSION)) {
  666. throw new fProgrammerException(
  667. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  668. __CLASS__ . '::setLength()',
  669. __CLASS__ . '::add()',
  670. __CLASS__ . '::clear()',
  671. __CLASS__ . '::enablePersistence()',
  672. __CLASS__ . '::get()',
  673. __CLASS__ . '::open()',
  674. __CLASS__ . '::set()',
  675. 'session_start()'
  676. );
  677. }
  678. $seconds = (!is_numeric($normal_timespan)) ? strtotime($normal_timespan) - time() : $normal_timespan;
  679. self::$normal_timespan = $seconds;
  680. if ($persistent_timespan) {
  681. $seconds = (!is_numeric($persistent_timespan)) ? strtotime($persistent_timespan) - time() : $persistent_timespan;
  682. self::$persistent_timespan = $seconds;
  683. }
  684. ini_set('session.gc_maxlifetime', $seconds);
  685. }
  686. /**
  687. * Sets the path to store session files in
  688. *
  689. * This method should always be called with a non-standard directory
  690. * whenever ::setLength() is called to ensure that another site on the
  691. * server does not garbage collect the session files for this site.
  692. *
  693. * Standard session directories usually include `/tmp` and `/var/tmp`.
  694. *
  695. * @param string|fDirectory $directory The directory to store session files in
  696. * @return void
  697. */
  698. static public function setPath($directory)
  699. {
  700. if (self::$open || isset($_SESSION)) {
  701. throw new fProgrammerException(
  702. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  703. __CLASS__ . '::setPath()',
  704. __CLASS__ . '::add()',
  705. __CLASS__ . '::clear()',
  706. __CLASS__ . '::enablePersistence()',
  707. __CLASS__ . '::get()',
  708. __CLASS__ . '::open()',
  709. __CLASS__ . '::set()',
  710. 'session_start()'
  711. );
  712. }
  713. if (!$directory instanceof fDirectory) {
  714. $directory = new fDirectory($directory);
  715. }
  716. if (!$directory->isWritable()) {
  717. throw new fEnvironmentException(
  718. 'The directory specified, %s, is not writable',
  719. $directory->getPath()
  720. );
  721. }
  722. session_save_path($directory->getPath());
  723. }
  724. /**
  725. * Callback to write a session's values
  726. *
  727. * @internal
  728. *
  729. * @param string $id The session to write
  730. * @param string $values The serialized values
  731. * @return string The session's serialized data
  732. */
  733. static public function writeCache($id, $values)
  734. {
  735. return self::$backend->set(self::$key_prefix . $id, $values);
  736. }
  737. /**
  738. * Forces use as a static class
  739. *
  740. * @return fSession
  741. */
  742. private function __construct() { }
  743. }
  744. /**
  745. * Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others
  746. *
  747. * Permission is hereby granted, free of charge, to any person obtaining a copy
  748. * of this software and associated documentation files (the "Software"), to deal
  749. * in the Software without restriction, including without limitation the rights
  750. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  751. * copies of the Software, and to permit persons to whom the Software is
  752. * furnished to do so, subject to the following conditions:
  753. *
  754. * The above copyright notice and this permission notice shall be included in
  755. * all copies or substantial portions of the Software.
  756. *
  757. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  758. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  759. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  760. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  761. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  762. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  763. * THE SOFTWARE.
  764. */