PageRenderTime 43ms CodeModel.GetById 32ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/symphony/lib/core/class.session.php

https://github.com/vlad-ghita/symphony-2
PHP | 257 lines | 108 code | 27 blank | 122 comment | 17 complexity | 445e4d979dd48184e30c6747290cd884 MD5 | raw file
  1<?php
  2
  3	/**
  4	 * @package core
  5	 */
  6
  7	 /**
  8	  * The Session class is a handler for all Session related logic in PHP. The functions
  9	  * map directly to all handler functions as defined by session_set_save_handler in
 10	  * PHP. In Symphony, this function is used in conjunction with the `Cookie` class.
 11	  * Based on: http://php.net/manual/en/function.session-set-save-handler.php#81761
 12	  * by klose at openriverbed dot de which was based on
 13	  * http://php.net/manual/en/function.session-set-save-handler.php#79706 by
 14	  * maria at junkies dot jp
 15	  *
 16	  * @link http://php.net/manual/en/function.session-set-save-handler.php
 17	  */
 18	require_once(CORE . '/class.cacheable.php');
 19
 20	Class Session{
 21
 22		/**
 23		 * If a Session has been created, this will be true, otherwise false
 24		 *
 25		 * @var boolean
 26		 */
 27		private static $_initialized = false;
 28
 29		/**
 30		 * Starts a Session object, only if one doesn't already exist. This function maps
 31		 * the Session Handler functions to this classes methods by reading the default
 32		 * information from the PHP ini file.
 33		 *
 34		 * @link http://php.net/manual/en/function.session-set-save-handler.php
 35		 * @link http://php.net/manual/en/function.session-set-cookie-params.php
 36		 * @param integer $lifetime
 37		 *  How long a Session is valid for, by default this is 0, which means it
 38		 *  never expires
 39		 * @param string $path
 40		 *  The path the cookie is valid for on the domain
 41		 * @param string $domain
 42		 *  The domain this cookie is valid for
 43		 * @param boolean $httpOnly
 44		 *  Whether this cookie can be read by Javascript. By default the cookie
 45		 *  cannot be read by Javascript
 46		 * @param boolean $secure
 47		 *  Whether this cookie should only be sent on secure servers. By default this is
 48		 *  false, which means the cookie can be sent over HTTP and HTTPS
 49		 * @throws Exception
 50		 * @return string|boolean
 51		 *  Returns the Session ID on success, or false on error.
 52		 */
 53		public static function start($lifetime = 0, $path = '/', $domain = NULL, $httpOnly = true, $secure = false) {
 54
 55			if (!self::$_initialized) {
 56				if(!is_object(Symphony::Database()) || !Symphony::Database()->isConnected()) return false;
 57
 58				if (session_id() == '') {
 59					ini_set('session.save_handler', 'user');
 60					ini_set('session.gc_maxlifetime', $lifetime);
 61					ini_set('session.gc_probability', '1');
 62					ini_set('session.gc_divisor', Symphony::Configuration()->get('session_gc_divisor', 'symphony'));
 63				}
 64
 65				session_set_save_handler(
 66					array('Session', 'open'),
 67					array('Session', 'close'),
 68					array('Session', 'read'),
 69					array('Session', 'write'),
 70					array('Session', 'destroy'),
 71					array('Session', 'gc')
 72				);
 73
 74				session_set_cookie_params($lifetime, $path, ($domain ? $domain : self::getDomain()), $secure, $httpOnly);
 75
 76				if(session_id() == ''){
 77					if(headers_sent()){
 78						throw new Exception('Headers already sent. Cannot start session.');
 79					}
 80					register_shutdown_function('session_write_close');
 81					session_start();
 82				}
 83
 84				self::$_initialized = true;
 85			}
 86
 87			return session_id();
 88		}
 89
 90		/**
 91		 * Returns the current domain for the Session to be saved to, if the installation
 92		 * is on localhost, this returns null and just allows PHP to take care of setting
 93		 * the valid domain for the Session, otherwise it will return the non-www version
 94		 * of the domain host.
 95		 *
 96		 * @return string|null
 97		 *  Null if on localhost, or HTTP_HOST is not set, a string of the domain name sans
 98		 *  www otherwise
 99		 */
100		public static function getDomain() {
101			if(isset($_SERVER['HTTP_HOST'])){
102
103				if(preg_match('/(localhost|127\.0\.0\.1)/', $_SERVER['HTTP_HOST']) || $_SERVER['SERVER_ADDR'] == '127.0.0.1'){
104					return null; // prevent problems on local setups
105				}
106
107				return preg_replace('/(^www\.|:\d+$)/i', NULL, $_SERVER['HTTP_HOST']);
108
109			}
110
111			return null;
112		}
113
114		/**
115		 * Allows the Session to open without any further logic.
116		 *
117		 * @return boolean
118		 *  Always returns true
119		 */
120		public static function open() {
121			return true;
122		}
123
124		/**
125		 * Allows the Session to close without any further logic. Acts as a
126		 * destructor function for the Session.
127		 *
128		 * @return boolean
129		 *  Always returns true
130		 */
131		public static function close() {
132			return true;
133		}
134
135		/**
136		 * Given an ID, and some data, save it into `tbl_sessions`. This uses
137		 * the ID as a unique key, and will override any existing data. If the
138		 * `$data` is deemed to be empty, no row will be saved in the database
139		 * unless there is an existing row.
140		 *
141		 * @param string $id
142		 *  The ID of the Session, usually a hash
143		 * @param string $data
144		 *  The Session information, usually a serialized object of
145		 * `$_SESSION[Cookie->_index]`
146		 * @throws DatabaseException
147		 * @return boolean
148		 *  True if the Session information was saved successfully, false otherwise
149		 */
150		public static function write($id, $data) {
151			// Only prevent this record from saving if there isn't already a record
152			// in the database. This prevents empty Sessions from being created, but
153			// allows them to be nulled.
154			$session_data = Session::read($id);
155			if(is_null($session_data)) {
156				$empty = true;
157				$unserialized_data = Session::unserialize_session($session_data);
158				foreach ($unserialized_data as $d) {
159					if (!empty($d)) {
160						$empty = false;
161					}
162				}
163
164				if ($empty) {
165					return false;
166				}
167			}
168
169			$fields = array(
170				'session' => $id,
171				'session_expires' => time(),
172				'session_data' => $data
173			);
174			return Symphony::Database()->insert($fields, 'tbl_sessions', true);
175		}
176
177		/**
178		 * Given raw session data return the unserialized array.
179		 * Used to check if the session is really empty before writing.
180		 *
181		 * @since Symphony 2.3.3
182		 * @param string $data
183		 *  The serialized session data
184		 * @return string
185		 *  The unserialised session data
186		 */
187		private static function unserialize_session($data) {
188			$hasBuffer = isset($_SESSION);
189			$buffer = $_SESSION;
190			session_decode($data);
191			$session = $_SESSION;
192			if($hasBuffer) {
193				$_SESSION = $buffer;
194			}
195			else {
196				unset($_SESSION);
197			}
198
199			return $session;
200		}
201
202		/**
203		 * Given a session's ID, return it's row from `tbl_sessions`
204		 *
205		 * @param string $id
206		 *  The identifier for the Session to fetch
207		 * @return string
208		 *  The serialised session data
209		 */
210		public static function read($id) {
211			return Symphony::Database()->fetchVar(
212				'session_data', 0,
213				sprintf(
214					"SELECT `session_data` FROM `tbl_sessions` WHERE `session` = '%s' LIMIT 1",
215					Symphony::Database()->cleanValue($id)
216				)
217			);
218		}
219
220		/**
221		 * Given a session's ID, remove it's row from `tbl_sessions`
222		 *
223		 * @param string $id
224		 *  The identifier for the Session to destroy
225		 * @throws DatabaseException
226		 * @return boolean
227		 *  True if the Session was deleted successfully, false otherwise
228		 */
229		public static function destroy($id) {
230			return Symphony::Database()->query(
231				sprintf(
232					"DELETE FROM `tbl_sessions` WHERE `session` = '%s'",
233					Symphony::Database()->cleanValue($id)
234				)
235			);
236		}
237
238		/**
239		 * The garbage collector, which removes all empty Sessions, or any
240		 * Sessions that have expired. This has a 10% chance of firing based
241		 * off the `gc_probability`/`gc_divisor`.
242		 *
243		 * @param integer $max
244		 *  The max session lifetime.
245		 * @throws DatabaseException
246		 * @return boolean
247		 *  True on Session deletion, false if an error occurs
248		 */
249		public static function gc($max) {
250			return Symphony::Database()->query(
251				sprintf(
252					"DELETE FROM `tbl_sessions` WHERE `session_expires` <= '%s'",
253					Symphony::Database()->cleanValue(time() - $max)
254				)
255			);
256		}
257	}