1: <?php
2: /**
3: * This class provides the interface to the session storage backend.
4: *
5: * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
6: *
7: * See the enclosed file COPYING for license information (LGPL). If you
8: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
9: *
10: * @author Michael Slusarz <slusarz@horde.org>
11: * @category Horde
12: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
13: * @package SessionHandler
14: */
15: class Horde_SessionHandler
16: {
17: /**
18: * If true, indicates the session data has changed.
19: *
20: * @var boolean
21: */
22: public $changed = false;
23:
24: /**
25: * Has a connection been made to the backend?
26: *
27: * @var boolean
28: */
29: protected $_connected = false;
30:
31: /**
32: * A logger instance.
33: *
34: * @var Horde_Log_Logger
35: */
36: protected $_logger;
37:
38: /**
39: * Configuration parameters.
40: *
41: * @var array
42: */
43: protected $_params = array();
44:
45: /**
46: * Initial session data signature.
47: *
48: * @var string
49: */
50: protected $_sig;
51:
52: /**
53: * The storage object.
54: *
55: * @var Horde_SessionHandler_Storage
56: */
57: protected $_storage;
58:
59: /**
60: * Constructor.
61: *
62: * @param Horde_SessionHandler_Storage $storage The storage object.
63: * @param array $params Configuration parameters:
64: * <pre>
65: * logger - (Horde_Log_Logger) A logger instance.
66: * DEFAULT: No logging
67: * no_md5 - (boolean) If true, does not do MD5 signatures of the session
68: * to determine if the session has changed. If true, calling code
69: * is responsible for marking $changed as true when the session
70: * data has changed.
71: * DEFAULT: false
72: * noset - (boolean) If true, don't set the save handler.
73: * DEFAULT: false
74: * parse - (callback) A callback function that parses session
75: * information into an array. Is passed the raw session data
76: * as the only argument; expects either false or an array of
77: * session data as a return.
78: * DEFAULT: No
79: * </pre>
80: */
81: public function __construct(Horde_SessionHandler_Storage $storage,
82: array $params = array())
83: {
84: $params = array_merge($this->_params, $params);
85:
86: if (isset($params['logger'])) {
87: $this->_logger = $params['logger'];
88: unset($params['logger']);
89:
90: $storage->setLogger($this->_logger);
91: }
92:
93: $this->_params = $params;
94: $this->_storage = $storage;
95:
96: if (empty($this->_params['noset'])) {
97: ini_set('session.save_handler', 'user');
98: session_set_save_handler(
99: array($this, 'open'),
100: array($this, 'close'),
101: array($this, 'read'),
102: array($this, 'write'),
103: array($this, 'destroy'),
104: array($this, 'gc')
105: );
106: }
107: }
108:
109: /**
110: * Destructor.
111: */
112: public function __destruct()
113: {
114: /* This is necessary as of PHP 5.0.5 because objects are not available
115: * when the write() handler is called at the end of a session
116: * access. */
117: session_write_close();
118: }
119:
120: /**
121: * Open the backend.
122: *
123: * @param string $save_path The path to the session object.
124: * @param string $session_name The name of the session.
125: *
126: * @return boolean True on success, false otherwise.
127: */
128: public function open($save_path = null, $session_name = null)
129: {
130: if (!$this->_connected) {
131: try {
132: $this->_storage->open($save_path, $session_name);
133: } catch (Horde_SessionHandler_Exception $e) {
134: if ($this->_logger) {
135: $this->_logger->log($e, 'ERR');
136: }
137: return false;
138: }
139:
140: $this->_connected = true;
141: }
142:
143: return true;
144: }
145:
146: /**
147: * Close the backend.
148: *
149: * @return boolean True on success, false otherwise.
150: */
151: public function close()
152: {
153: try {
154: $this->_storage->close();
155: } catch (Horde_SessionHandler_Exception $e) {
156: if ($this->_logger) {
157: $this->_logger->log($e, 'ERR');
158: }
159: return false;
160: }
161:
162: $this->_connected = false;
163: return true;
164: }
165:
166: /**
167: * Read the data for a particular session identifier from the backend.
168: * This method should only be called internally by PHP via
169: * session_set_save_handler().
170: *
171: * @param string $id The session identifier.
172: *
173: * @return string The session data.
174: */
175: public function read($id)
176: {
177: $result = $this->_storage->read($id);
178: if (empty($this->_params['no_md5'])) {
179: $this->_sig = md5($result);
180: }
181: return $result;
182: }
183:
184: /**
185: * Write session data to the backend.
186: * This method should only be called internally by PHP via
187: * session_set_save_handler().
188: *
189: * @param string $id The session identifier.
190: * @param string $session_data The session data.
191: *
192: * @return boolean True on success, false otherwise.
193: */
194: public function write($id, $session_data)
195: {
196: if ($this->changed ||
197: (empty($this->_params['no_md5']) &&
198: ($this->_sig != md5($session_data)))) {
199: return $this->_storage->write($id, $session_data);
200: }
201:
202: if ($this->_logger) {
203: $this->_logger->log('Session data unchanged (id = ' . $id . ')', 'DEBUG');
204: }
205:
206: return true;
207: }
208:
209: /**
210: * Destroy the data for a particular session identifier in the backend.
211: * This method should only be called internally by PHP via
212: * session_set_save_handler().
213: *
214: * @param string $id The session identifier.
215: *
216: * @return boolean True on success, false otherwise.
217: */
218: public function destroy($id)
219: {
220: return $this->_storage->destroy($id);
221: }
222:
223: /**
224: * Garbage collect stale sessions from the backend.
225: * This method should only be called internally by PHP via
226: * session_set_save_handler().
227: *
228: * @param integer $maxlifetime The maximum age of a session.
229: *
230: * @return boolean True on success, false otherwise.
231: */
232: public function gc($maxlifetime = 300)
233: {
234: return $this->_storage->gc($maxlifetime);
235: }
236:
237: /**
238: * Get a list of the valid session identifiers.
239: *
240: * @return array A list of valid session identifiers.
241: * @throws Horde_SessionHandler_Exception
242: */
243: public function getSessionIDs()
244: {
245: return $this->_storage->getSessionIDs();
246: }
247:
248: /**
249: * Returns a list of authenticated users and data about their session.
250: *
251: * @return array For authenticated users, the sessionid as a key and the
252: * session information as value. If no parsing function
253: * was provided, will always return an empty array.
254: * @throws Horde_SessionHandler_Exception
255: */
256: public function getSessionsInfo()
257: {
258: $info = array();
259:
260: if (empty($this->_params['parse']) ||
261: !is_callable($this->_params['parse'])) {
262: return $info;
263: }
264:
265: /* Explicitly do garbage collection call here to make sure session
266: * data is correct. */
267: $this->gc(ini_get('session.gc_maxlifetime'));
268:
269: $sessions = $this->getSessionIDs();
270:
271: $this->_storage->readonly = true;
272:
273: foreach ($sessions as $id) {
274: try {
275: $data = $this->read($id);
276: $this->close();
277: } catch (Horde_SessionHandler_Exception $e) {
278: continue;
279: }
280:
281: $data = call_user_func($this->_params['parse'], $data);
282: if ($data !== false) {
283: $info[$id] = $data;
284: }
285: }
286:
287: $this->_storage->readonly = false;
288:
289: return $info;
290: }
291:
292: }
293: