1<?php defined('SYSPATH') OR die('No direct script access.');
2/**
3 * Base session class.
4 *
5 * @package    Kohana
6 * @category   Session
7 * @author     Kohana Team
8 * @copyright  (c) 2008-2012 Kohana Team
9 * @license    http://kohanaframework.org/license
10 */
11abstract class Kohana_Session {
12
13	/**
14	 * @var  string  default session adapter
15	 */
16	public static $default = 'native';
17
18	/**
19	 * @var  array  session instances
20	 */
21	public static $instances = array();
22
23	/**
24	 * Creates a singleton session of the given type. Some session types
25	 * (native, database) also support restarting a session by passing a
26	 * session id as the second parameter.
27	 *
28	 *     $session = Session::instance();
29	 *
30	 * [!!] [Session::write] will automatically be called when the request ends.
31	 *
32	 * @param   string  $type   type of session (native, cookie, etc)
33	 * @param   string  $id     session identifier
34	 * @return  Session
35	 * @uses    Kohana::$config
36	 */
37	public static function instance($type = NULL, $id = NULL)
38	{
39		if ($type === NULL)
40		{
41			// Use the default type
42			$type = Session::$default;
43		}
44
45		if ( ! isset(Session::$instances[$type]))
46		{
47			// Load the configuration for this type
48			$config = Kohana::$config->load('session')->get($type);
49
50			// Set the session class name
51			$class = 'Session_'.ucfirst($type);
52
53			// Create a new session instance
54			Session::$instances[$type] = $session = new $class($config, $id);
55
56			// Write the session at shutdown
57			register_shutdown_function(array($session, 'write'));
58		}
59
60		return Session::$instances[$type];
61	}
62
63	/**
64	 * @var  string  cookie name
65	 */
66	protected $_name = 'session';
67
68	/**
69	 * @var  int  cookie lifetime
70	 */
71	protected $_lifetime = 0;
72
73	/**
74	 * @var  bool  encrypt session data?
75	 */
76	protected $_encrypted = FALSE;
77
78	/**
79	 * @var  array  session data
80	 */
81	protected $_data = array();
82
83	/**
84	 * @var  bool  session destroyed?
85	 */
86	protected $_destroyed = FALSE;
87
88	/**
89	 * Overloads the name, lifetime, and encrypted session settings.
90	 *
91	 * [!!] Sessions can only be created using the [Session::instance] method.
92	 *
93	 * @param   array   $config configuration
94	 * @param   string  $id     session id
95	 * @return  void
96	 * @uses    Session::read
97	 */
98	public function __construct(array $config = NULL, $id = NULL)
99	{
100		if (isset($config['name']))
101		{
102			// Cookie name to store the session id in
103			$this->_name = (string) $config['name'];
104		}
105
106		if (isset($config['lifetime']))
107		{
108			// Cookie lifetime
109			$this->_lifetime = (int) $config['lifetime'];
110		}
111
112		if (isset($config['encrypted']))
113		{
114			if ($config['encrypted'] === TRUE)
115			{
116				// Use the default Encrypt instance
117				$config['encrypted'] = 'default';
118			}
119
120			// Enable or disable encryption of data
121			$this->_encrypted = $config['encrypted'];
122		}
123
124		// Load the session
125		$this->read($id);
126	}
127
128	/**
129	 * Session object is rendered to a serialized string. If encryption is
130	 * enabled, the session will be encrypted. If not, the output string will
131	 * be encoded.
132	 *
133	 *     echo $session;
134	 *
135	 * @return  string
136	 * @uses    Encrypt::encode
137	 */
138	public function __toString()
139	{
140		// Serialize the data array
141		$data = $this->_serialize($this->_data);
142
143		if ($this->_encrypted)
144		{
145			// Encrypt the data using the default key
146			$data = Encrypt::instance($this->_encrypted)->encode($data);
147		}
148		else
149		{
150			// Encode the data
151			$data = $this->_encode($data);
152		}
153
154		return $data;
155	}
156
157	/**
158	 * Returns the current session array. The returned array can also be
159	 * assigned by reference.
160	 *
161	 *     // Get a copy of the current session data
162	 *     $data = $session->as_array();
163	 *
164	 *     // Assign by reference for modification
165	 *     $data =& $session->as_array();
166	 *
167	 * @return  array
168	 */
169	public function & as_array()
170	{
171		return $this->_data;
172	}
173
174	/**
175	 * Get the current session id, if the session supports it.
176	 *
177	 *     $id = $session->id();
178	 *
179	 * [!!] Not all session types have ids.
180	 *
181	 * @return  string
182	 * @since   3.0.8
183	 */
184	public function id()
185	{
186		return NULL;
187	}
188
189	/**
190	 * Get the current session cookie name.
191	 *
192	 *     $name = $session->name();
193	 *
194	 * @return  string
195	 * @since   3.0.8
196	 */
197	public function name()
198	{
199		return $this->_name;
200	}
201
202	/**
203	 * Get a variable from the session array.
204	 *
205	 *     $foo = $session->get('foo');
206	 *
207	 * @param   string  $key        variable name
208	 * @param   mixed   $default    default value to return
209	 * @return  mixed
210	 */
211	public function get($key, $default = NULL)
212	{
213		return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default;
214	}
215
216	/**
217	 * Get and delete a variable from the session array.
218	 *
219	 *     $bar = $session->get_once('bar');
220	 *
221	 * @param   string  $key        variable name
222	 * @param   mixed   $default    default value to return
223	 * @return  mixed
224	 */
225	public function get_once($key, $default = NULL)
226	{
227		$value = $this->get($key, $default);
228
229		unset($this->_data[$key]);
230
231		return $value;
232	}
233
234	/**
235	 * Set a variable in the session array.
236	 *
237	 *     $session->set('foo', 'bar');
238	 *
239	 * @param   string  $key    variable name
240	 * @param   mixed   $value  value
241	 * @return  $this
242	 */
243	public function set($key, $value)
244	{
245		$this->_data[$key] = $value;
246
247		return $this;
248	}
249
250	/**
251	 * Set a variable by reference.
252	 *
253	 *     $session->bind('foo', $foo);
254	 *
255	 * @param   string  $key    variable name
256	 * @param   mixed   $value  referenced value
257	 * @return  $this
258	 */
259	public function bind($key, & $value)
260	{
261		$this->_data[$key] =& $value;
262
263		return $this;
264	}
265
266	/**
267	 * Removes a variable in the session array.
268	 *
269	 *     $session->delete('foo');
270	 *
271	 * @param   string  $key,...    variable name
272	 * @return  $this
273	 */
274	public function delete($key)
275	{
276		$args = func_get_args();
277
278		foreach ($args as $key)
279		{
280			unset($this->_data[$key]);
281		}
282
283		return $this;
284	}
285
286	/**
287	 * Loads existing session data.
288	 *
289	 *     $session->read();
290	 *
291	 * @param   string  $id session id
292	 * @return  void
293	 */
294	public function read($id = NULL)
295	{
296		$data = NULL;
297
298		try
299		{
300			if (is_string($data = $this->_read($id)))
301			{
302				if ($this->_encrypted)
303				{
304					// Decrypt the data using the default key
305					$data = Encrypt::instance($this->_encrypted)->decode($data);
306				}
307				else
308				{
309					// Decode the data
310					$data = $this->_decode($data);
311				}
312
313				// Unserialize the data
314				$data = $this->_unserialize($data);
315			}
316			else
317			{
318				// Ignore these, session is valid, likely no data though.
319			}
320		}
321		catch (Exception $e)
322		{
323			// Error reading the session, usually a corrupt session.
324			throw new Session_Exception('Error reading session data.', NULL, Session_Exception::SESSION_CORRUPT);
325		}
326
327		if (is_array($data))
328		{
329			// Load the data locally
330			$this->_data = $data;
331		}
332	}
333
334	/**
335	 * Generates a new session id and returns it.
336	 *
337	 *     $id = $session->regenerate();
338	 *
339	 * @return  string
340	 */
341	public function regenerate()
342	{
343		return $this->_regenerate();
344	}
345
346	/**
347	 * Sets the last_active timestamp and saves the session.
348	 *
349	 *     $session->write();
350	 *
351	 * [!!] Any errors that occur during session writing will be logged,
352	 * but not displayed, because sessions are written after output has
353	 * been sent.
354	 *
355	 * @return  boolean
356	 * @uses    Kohana::$log
357	 */
358	public function write()
359	{
360		if (headers_sent() OR $this->_destroyed)
361		{
362			// Session cannot be written when the headers are sent or when
363			// the session has been destroyed
364			return FALSE;
365		}
366
367		// Set the last active timestamp
368		$this->_data['last_active'] = time();
369
370		try
371		{
372			return $this->_write();
373		}
374		catch (Exception $e)
375		{
376			// Log & ignore all errors when a write fails
377			Kohana::$log->add(Log::ERROR, Kohana_Exception::text($e))->write();
378
379			return FALSE;
380		}
381	}
382
383	/**
384	 * Completely destroy the current session.
385	 *
386	 *     $success = $session->destroy();
387	 *
388	 * @return  boolean
389	 */
390	public function destroy()
391	{
392		if ($this->_destroyed === FALSE)
393		{
394			if ($this->_destroyed = $this->_destroy())
395			{
396				// The session has been destroyed, clear all data
397				$this->_data = array();
398			}
399		}
400
401		return $this->_destroyed;
402	}
403
404	/**
405	 * Restart the session.
406	 *
407	 *     $success = $session->restart();
408	 *
409	 * @return  boolean
410	 */
411	public function restart()
412	{
413		if ($this->_destroyed === FALSE)
414		{
415			// Wipe out the current session.
416			$this->destroy();
417		}
418
419		// Allow the new session to be saved
420		$this->_destroyed = FALSE;
421
422		return $this->_restart();
423	}
424
425	/**
426	 * Serializes the session data.
427	 *
428	 * @param   array  $data  data
429	 * @return  string
430	 */
431	protected function _serialize($data)
432	{
433		return serialize($data);
434	}
435
436	/**
437	 * Unserializes the session data.
438	 *
439	 * @param   string  $data  data
440	 * @return  array
441	 */
442	protected function _unserialize($data)
443	{
444		return unserialize($data);
445	}
446
447	/**
448	 * Encodes the session data using [base64_encode].
449	 *
450	 * @param   string  $data  data
451	 * @return  string
452	 */
453	protected function _encode($data)
454	{
455		return base64_encode($data);
456	}
457
458	/**
459	 * Decodes the session data using [base64_decode].
460	 *
461	 * @param   string  $data  data
462	 * @return  string
463	 */
464	protected function _decode($data)
465	{
466		return base64_decode($data);
467	}
468
469	/**
470	 * Loads the raw session data string and returns it.
471	 *
472	 * @param   string  $id session id
473	 * @return  string
474	 */
475	abstract protected function _read($id = NULL);
476
477	/**
478	 * Generate a new session id and return it.
479	 *
480	 * @return  string
481	 */
482	abstract protected function _regenerate();
483
484	/**
485	 * Writes the current session.
486	 *
487	 * @return  boolean
488	 */
489	abstract protected function _write();
490
491	/**
492	 * Destroys the current session.
493	 *
494	 * @return  boolean
495	 */
496	abstract protected function _destroy();
497
498	/**
499	 * Restarts the current session.
500	 *
501	 * @return  boolean
502	 */
503	abstract protected function _restart();
504
505}
506