class-wp-session.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <?php
  2. /**
  3. * WordPress session managment.
  4. *
  5. * Standardizes WordPress session data using database-backed options for storage.
  6. * for storing user session information.
  7. *
  8. * @package WordPress
  9. * @subpackage Session
  10. * @since 3.7.0
  11. */
  12. // Exit if accessed directly
  13. if ( ! defined( 'ABSPATH' ) ) exit;
  14. /*
  15. * MODIFICATIONS
  16. *
  17. * - Remove `set_cooke()` from constructor
  18. * - Give `set_cookie()` public access
  19. * - Manually call `set_cookie()` on form submission
  20. */
  21. /**
  22. * WordPress Session class for managing user session data.
  23. *
  24. * @package WordPress
  25. * @since 3.7.0
  26. */
  27. final class WP_Session extends Recursive_ArrayAccess implements Iterator, Countable {
  28. /**
  29. * ID of the current session.
  30. *
  31. * @var string
  32. */
  33. public $session_id;
  34. /**
  35. * Unix timestamp when session expires.
  36. *
  37. * @var int
  38. */
  39. protected $expires;
  40. /**
  41. * Unix timestamp indicating when the expiration time needs to be reset.
  42. *
  43. * @var int
  44. */
  45. protected $exp_variant;
  46. /**
  47. * Singleton instance.
  48. *
  49. * @var bool|WP_Session
  50. */
  51. private static $instance = false;
  52. /**
  53. * Retrieve the current session instance.
  54. *
  55. * @param bool $session_id Session ID from which to populate data.
  56. *
  57. * @return bool|WP_Session
  58. */
  59. public static function get_instance() {
  60. if ( ! self::$instance ) {
  61. self::$instance = new self();
  62. }
  63. return self::$instance;
  64. }
  65. /**
  66. * Default constructor.
  67. * Will rebuild the session collection from the given session ID if it exists. Otherwise, will
  68. * create a new session with that ID.
  69. *
  70. * @param $session_id
  71. * @uses apply_filters Calls `wp_session_expiration` to determine how long until sessions expire.
  72. */
  73. protected function __construct() {
  74. if ( isset( $_COOKIE[WP_SESSION_COOKIE] ) ) {
  75. $cookie = stripslashes( $_COOKIE[WP_SESSION_COOKIE] );
  76. $cookie_crumbs = explode( '||', $cookie );
  77. if( $this->is_valid_md5( $cookie_crumbs[0] ) ){
  78. $this->session_id = $cookie_crumbs[0];
  79. } else {
  80. $this->session_id = $this->regenerate_id();
  81. }
  82. $this->expires = $cookie_crumbs[1];
  83. $this->exp_variant = $cookie_crumbs[2];
  84. // Update the session expiration if we're past the variant time
  85. if ( time() > $this->exp_variant ) {
  86. $this->set_expiration();
  87. delete_option( "_wp_session_expires_{$this->session_id}" );
  88. add_option( "_wp_session_expires_{$this->session_id}", $this->expires, '', 'no' );
  89. }
  90. } else {
  91. $this->session_id = $this->generate_id();
  92. $this->set_expiration();
  93. }
  94. $this->read_data();
  95. /*
  96. * Only set the cookie manually, on form submission.
  97. */
  98. //$this->set_cookie();
  99. }
  100. /**
  101. * Set both the expiration time and the expiration variant.
  102. *
  103. * If the current time is below the variant, we don't update the session's expiration time. If it's
  104. * greater than the variant, then we update the expiration time in the database. This prevents
  105. * writing to the database on every page load for active sessions and only updates the expiration
  106. * time if we're nearing when the session actually expires.
  107. *
  108. * By default, the expiration time is set to 30 minutes.
  109. * By default, the expiration variant is set to 24 minutes.
  110. *
  111. * As a result, the session expiration time - at a maximum - will only be written to the database once
  112. * every 24 minutes. After 30 minutes, the session will have been expired. No cookie will be sent by
  113. * the browser, and the old session will be queued for deletion by the garbage collector.
  114. *
  115. * @uses apply_filters Calls `wp_session_expiration_variant` to get the max update window for session data.
  116. * @uses apply_filters Calls `wp_session_expiration` to get the standard expiration time for sessions.
  117. */
  118. protected function set_expiration() {
  119. $this->exp_variant = time() + (int) apply_filters( 'wp_session_expiration_variant', 24 * 60 );
  120. $this->expires = time() + (int) apply_filters( 'wp_session_expiration', 30 * 60 );
  121. }
  122. /**
  123. * Set the session cookie
  124. *
  125. * IMPORTANT: Made public
  126. */
  127. public function set_cookie() {
  128. @setcookie( WP_SESSION_COOKIE, $this->session_id . '||' . $this->expires . '||' . $this->exp_variant , $this->expires, COOKIEPATH, COOKIE_DOMAIN );
  129. }
  130. /**
  131. * Generate a cryptographically strong unique ID for the session token.
  132. *
  133. * @return string
  134. */
  135. protected function generate_id() {
  136. require_once( ABSPATH . 'wp-includes/class-phpass.php');
  137. $hasher = new PasswordHash( 8, false );
  138. return md5( $hasher->get_random_bytes( 32 ) );
  139. }
  140. /**
  141. * Read data from a transient for the current session.
  142. *
  143. * Automatically resets the expiration time for the session transient to some time in the future.
  144. *
  145. * @return array
  146. */
  147. protected function read_data() {
  148. $this->container = get_option( "_wp_session_{$this->session_id}", array() );
  149. return $this->container;
  150. }
  151. /**
  152. * Write the data from the current session to the data storage system.
  153. */
  154. public function write_data() {
  155. $option_key = "_wp_session_{$this->session_id}";
  156. // Only write the collection to the DB if it's changed.
  157. if ( $this->dirty ) {
  158. if ( false === get_option( $option_key ) ) {
  159. add_option( "_wp_session_{$this->session_id}", $this->container, '', 'no' );
  160. add_option( "_wp_session_expires_{$this->session_id}", $this->expires, '', 'no' );
  161. } else {
  162. delete_option( "_wp_session_{$this->session_id}" );
  163. add_option( "_wp_session_{$this->session_id}", $this->container, '', 'no' );
  164. }
  165. }
  166. }
  167. /**
  168. * Output the current container contents as a JSON-encoded string.
  169. *
  170. * @return string
  171. */
  172. public function json_out() {
  173. return json_encode( $this->container );
  174. }
  175. /**
  176. * Decodes a JSON string and, if the object is an array, overwrites the session container with its contents.
  177. *
  178. * @param string $data
  179. *
  180. * @return bool
  181. */
  182. public function json_in( $data ) {
  183. $array = json_decode( $data );
  184. if ( is_array( $array ) ) {
  185. $this->container = $array;
  186. return true;
  187. }
  188. return false;
  189. }
  190. /**
  191. * Regenerate the current session's ID.
  192. *
  193. * @param bool $delete_old Flag whether or not to delete the old session data from the server.
  194. */
  195. public function regenerate_id( $delete_old = false ) {
  196. if ( $delete_old ) {
  197. delete_option( "_wp_session_{$this->session_id}" );
  198. }
  199. $this->session_id = $this->generate_id();
  200. $this->set_cookie();
  201. }
  202. /**
  203. * Check if a session has been initialized.
  204. *
  205. * @return bool
  206. */
  207. public function session_started() {
  208. return !!self::$instance;
  209. }
  210. /**
  211. * Return the read-only cache expiration value.
  212. *
  213. * @return int
  214. */
  215. public function cache_expiration() {
  216. return $this->expires;
  217. }
  218. /**
  219. * Flushes all session variables.
  220. */
  221. public function reset() {
  222. $this->container = array();
  223. }
  224. /**
  225. * Checks if is valid md5 string
  226. *
  227. * @param string $md5
  228. * @return int
  229. */
  230. protected function is_valid_md5( $md5 = '' ){
  231. return preg_match( '/^[a-f0-9]{32}$/', $md5 );
  232. }
  233. /*****************************************************************/
  234. /* Iterator Implementation */
  235. /*****************************************************************/
  236. /**
  237. * Current position of the array.
  238. *
  239. * @link http://php.net/manual/en/iterator.current.php
  240. *
  241. * @return mixed
  242. */
  243. public function current() {
  244. return current( $this->container );
  245. }
  246. /**
  247. * Key of the current element.
  248. *
  249. * @link http://php.net/manual/en/iterator.key.php
  250. *
  251. * @return mixed
  252. */
  253. public function key() {
  254. return key( $this->container );
  255. }
  256. /**
  257. * Move the internal point of the container array to the next item
  258. *
  259. * @link http://php.net/manual/en/iterator.next.php
  260. *
  261. * @return void
  262. */
  263. public function next() {
  264. next( $this->container );
  265. }
  266. /**
  267. * Rewind the internal point of the container array.
  268. *
  269. * @link http://php.net/manual/en/iterator.rewind.php
  270. *
  271. * @return void
  272. */
  273. public function rewind() {
  274. reset( $this->container );
  275. }
  276. /**
  277. * Is the current key valid?
  278. *
  279. * @link http://php.net/manual/en/iterator.rewind.php
  280. *
  281. * @return bool
  282. */
  283. public function valid() {
  284. return $this->offsetExists( $this->key() );
  285. }
  286. /*****************************************************************/
  287. /* Countable Implementation */
  288. /*****************************************************************/
  289. /**
  290. * Get the count of elements in the container array.
  291. *
  292. * @link http://php.net/manual/en/countable.count.php
  293. *
  294. * @return int
  295. */
  296. public function count() {
  297. return count( $this->container );
  298. }
  299. }