1<?php 2/** 3 * Copyright 2013-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @category Horde 9 * @copyright 2013-2017 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package SessionHandler 12 */ 13 14/** 15 * Horde_HashTable SessionHandler driver. 16 * 17 * @author Michael Slusarz <slusarz@horde.org> 18 * @category Horde 19 * @copyright 2013-2017 Horde LLC 20 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 21 * @package SessionHandler 22 * @since 2.2.0 23 */ 24class Horde_SessionHandler_Storage_Hashtable extends Horde_SessionHandler_Storage 25{ 26 /** 27 * HashTable object. 28 * 29 * @var Horde_HashTable 30 */ 31 protected $_hash; 32 33 /** 34 * Current session ID. 35 * 36 * @var string 37 */ 38 protected $_id; 39 40 /** 41 * The ID used for session tracking. 42 * 43 * @var string 44 */ 45 protected $_trackID = 'horde_sessions_track_ht'; 46 47 /** 48 * Constructor. 49 * 50 * @param array $params Parameters: 51 * <pre> 52 * - hashtable: (Horde_HashTable) [REQUIRED] A Horde_HashTable object. 53 * - track: (boolean) Track active sessions? 54 * </pre> 55 */ 56 public function __construct(array $params = array()) 57 { 58 if (empty($params['hashtable'])) { 59 throw new InvalidArgumentException('Missing hashtable parameter.'); 60 } 61 62 if (!$params['hashtable']->locking) { 63 throw new InvalidArgumentException('HashTable object must support locking.'); 64 } 65 66 $this->_hash = $params['hashtable']; 67 unset($params['hashtable']); 68 69 parent::__construct($params); 70 71 if (!empty($this->_params['track']) && (!rand(0, 999))) { 72 register_shutdown_function(array($this, 'trackGC')); 73 } 74 } 75 76 /** 77 */ 78 public function open($save_path = null, $session_name = null) 79 { 80 } 81 82 /** 83 */ 84 public function close() 85 { 86 if (isset($this->_id)) { 87 $this->_hash->unlock($this->_id); 88 } 89 } 90 91 /** 92 */ 93 public function read($id) 94 { 95 if (!$this->readonly) { 96 $this->_hash->lock($id); 97 } 98 99 if (($result = $this->_hash->get($id)) === false) { 100 if (!$this->readonly) { 101 $this->_hash->unlock($id); 102 } 103 104 $result = ''; 105 } elseif (!$this->readonly) { 106 $this->_id = $id; 107 } 108 109 return $result; 110 } 111 112 /** 113 */ 114 public function write($id, $session_data) 115 { 116 $base = array_filter(array( 117 'timeout' => ini_get('session.gc_maxlifetime') 118 )); 119 120 if (!empty($this->_params['track'])) { 121 // Do a replace - the only time it should fail is if we are 122 // writing a session for the first time. If that is the case, 123 // update the session tracker. 124 $res = $this->_hash->set($id, $session_data, array_merge($base, array( 125 'replace' => true, 126 ))); 127 $track = !$res; 128 } else { 129 $res = $track = false; 130 } 131 132 if (!$res && !$this->_hash->set($id, $session_data, $base)) { 133 return false; 134 } 135 136 if ($track) { 137 $this->_hash->lock($this->_trackID); 138 $ids = $this->_getTrackIds(); 139 $ids[$id] = 1; 140 $this->_hash->set($this->_trackID, json_encode($ids)); 141 $this->_hash->unlock($this->_trackID); 142 } 143 144 return true; 145 } 146 147 /** 148 */ 149 public function destroy($id) 150 { 151 $res = $this->_hash->delete($id); 152 $this->_hash->unlock($id); 153 154 if ($res === false) { 155 return false; 156 } 157 158 if (!empty($this->_params['track'])) { 159 $this->_hash->lock($this->_trackID); 160 if ($ids = $this->_getTrackIds()) { 161 unset($ids[$id]); 162 $this->_hash->set($this->_trackID, json_encode($ids)); 163 } 164 $this->_hash->unlock($this->_trackID); 165 } 166 167 return true; 168 } 169 170 /** 171 */ 172 public function gc($maxlifetime = 300) 173 { 174 return true; 175 } 176 177 /** 178 */ 179 public function getSessionIDs() 180 { 181 if (empty($this->_params['track'])) { 182 throw new Horde_SessionHandler_Exception('Memcache session tracking not enabled.'); 183 } 184 185 $this->trackGC(); 186 187 return array_keys($this->_getTrackIds()); 188 } 189 190 /** 191 * Do garbage collection for session tracking information. 192 */ 193 public function trackGC() 194 { 195 try { 196 $this->_hash->lock($this->_trackID); 197 198 if ($ids = $this->_getTrackIds()) { 199 $alter = false; 200 201 foreach (array_keys($ids) as $key) { 202 if (!$this->_hash->exists($key)) { 203 unset($ids[$key]); 204 $alter = true; 205 } 206 } 207 208 if ($alter) { 209 $this->_hash->set($this->_trackID, json_encode($ids)); 210 } 211 } 212 213 $this->_hash->unlock($this->_trackID); 214 } catch (Horde_HashTable_Exception $e) { 215 } 216 } 217 218 /** 219 * Get the tracking IDs. 220 * 221 * @return array Tracking IDs. 222 */ 223 protected function _getTrackIds() 224 { 225 if ((($ids = $this->_hash->get($this->_trackID)) === false) || 226 !($ids = json_decode($ids, true))) { 227 $ids = array(); 228 } 229 230 return $ids; 231 } 232 233} 234