JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
API CHANGE: new DB structure for sessions
[wfpl.git] / session.php
1 <?php
2
3 #  Copyright (C) 2006 Jason Woofenden
4 #
5 #  This program is free software: you can redistribute it and/or modify
6 #  it under the terms of the GNU General Public License as published by
7 #  the Free Software Foundation, either version 3 of the License, or
8 #  (at your option) any later version.
9 #  
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU General Public License for more details.
14 #  
15 #  You should have received a copy of the GNU General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18
19 # The functions in this file assume that you have this database table:
20 # drop table if exists wfpl_sessions;
21 # create table wfpl_sessions (
22 #       id int unique auto_increment,
23 #       session_key varchar(16),
24 #       idle_timeout int,
25 #       expires int,
26 #       expires_max int,
27 #       value text
28 # ) CHARSET=utf8;
29
30 # You'll want to use these:
31 #
32 # session_exists()
33 # session_new('timeout', 'max_len')
34 # session_set('key', 'value')
35 # session_sets(['key': 'value', 'key2': 'val2'])
36 # session_get('key')
37 # session_clear() # removes all set() values
38 # session_clear('key')
39 # session_kill()
40 #
41 # All session data is cached in globals, so:
42 # 1.    don't set large amonuts of data
43 # 2.    session_get() is very fast (no db access)
44
45
46 # generate a new random 16-character string
47 function session_generate_key() {
48         $character_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
49         $id = "                ";
50
51         # PHP 4.2.0 and up seed the random number generator for you.
52         # Lets hope that it seeds with something harder to guess than the clock.
53         for($i = 0; $i < 16; ++$i) {
54                 $id{$i} = $character_set{mt_rand(0, 61)};
55         }
56
57         return $id;
58 }
59
60 # track this user with a session cookie (ie a cookie that goes away when the
61 # user closes the browser). The timestamp is how long to track the session in
62 # the database. Defaults to one day.
63 function session_new($idle_timeout = 86400, $max_timeout = 'same_as_idle') {
64         if ($max_timeout === 'same_as_idle') {
65                 $max_timeout = $idle_timeout;
66         }
67         kill_session();
68
69         $session_key = session_generate_key();
70
71         $now = time();
72         $row = array(
73                 'session_key' => $session_key,
74                 'idle_timeout' => $idle_timeout,
75                 'expires' => $now + $idle_timeout,
76                 'expires_max' => $now + $max_timeout,
77                 'value' => '{}'
78         );
79
80         db_insert_assoc('wfpl_sessions', $row);
81         $session_id = db_auto_id();
82         $GLOBALS['wfpl_session'] = array(
83                 'exists' => true,
84                 'id' => $session_id,
85                 'key' => $session_key,
86                 'idle_timeout' => $row['idle_timeout'],
87                 'expires' => $row['expires'],
88                 'expires_max' => $row['expires_max'],
89                 'value' => array()
90         );
91         session_set_cookie();
92         return $session_key;
93 }
94
95 function session_set_cookie() {
96         if (session_exists()) {
97                 if (!isset($GLOBALS['wfpl_session']['cookie_set'])) {
98                         $GLOBALS['wfpl_session']['cookie_set'] = true;
99                         header('Set-Cookie: session_key=' . $GLOBALS['wfpl_session']['key'] . '; Path=/');
100                 }
101         }
102 }
103
104 # update the idle_timeout
105 function session_touch() {
106         if(!session_exists()) {
107                 return;
108         }
109         # is the session extendable?
110         if ($GLOBALS['wfpl_session']['expires'] < $GLOBALS['wfpl_session']['expires_max']) {
111                 # would this extend the session by at least 10%?
112                 $now = time();
113                 $session_start = $GLOBALS['wfpl_session']['expires'] - $GLOBALS['wfpl_session']['idle_timeout'];
114                 if ($now > $session_start + ceil(0.1 * $GLOBALS['wfpl_session']['idle_timeout'])) {
115                         $expires = max(
116                                 $GLOBALS['wfpl_session']['expires_max'],
117                                 $now + $GLOBALS['wfpl_session']['idle_timeout']
118                         );
119                         db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['wfpl_session']['id']);
120                 }
121         }
122 }
123
124 # delete the current session
125 function kill_session() {
126         if(!session_exists()) {
127             return;
128         }
129         db_delete('wfpl_sessions', 'where id=%i', $GLOBALS['wfpl_session']['id']);
130         $GLOBALS['wfpl_session'] = array('exists' => false);
131 }
132
133 # delete expired sessions from database
134 function session_purge_old() {
135         db_delete('wfpl_sessions', 'where expires < %i', time());
136 }
137
138 # return true if a session exists
139 function session_exists() {
140         if (isset($GLOBALS['wfpl_session'])) {
141                 return $GLOBALS['wfpl_session']['exists'];
142         }
143
144         $GLOBALS['wfpl_session'] = array('exists' => false);
145
146         if(!isset($_COOKIE['session_key'])) {
147                 return false;
148         }
149
150         $session_key = ereg_replace('[^a-zA-Z0-9]', '', $_COOKIE['session_key']);
151
152         if(!strlen($session_key) == 16) {
153                 return false;
154         }
155
156         session_purge_old();
157         $row = db_get_assoc('wfpl_sessions', 'id,idle_timeout,expires,expires_max,value', 'where session_key=%"', $session_key);
158         if($row === false) {
159                 return false;
160         }
161
162         $GLOBALS['wfpl_session']['exists'] = true;
163         $GLOBALS['wfpl_session']['id'] = $row['id'];
164         $GLOBALS['wfpl_session']['idle_timeout'] = $row['idle_timeout'];
165         $GLOBALS['wfpl_session']['expires'] = $row['expires'];
166         $GLOBALS['wfpl_session']['expires_max'] = $row['expires_max'];
167         $GLOBALS['wfpl_session']['key'] = $session_key;
168
169         if ($row['value'] && is_array($parsed = json_decode($row['value'], true))) {
170                 $GLOBALS['wfpl_session']['value'] = $parsed;
171         } else {
172                 $GLOBALS['wfpl_session']['value'] = array();
173         }
174
175         # this session is not idle (extend if it's extendable)
176         session_touch();
177
178         return true;
179 }
180
181
182 # generate a random password using only letters and numbers that look
183 # particularly unique
184 function new_readable_password($length = 8) {
185         $character_set = "ABCDEFHJKLMNPQRTUWXY34789";
186         $code = "";
187
188         # PHP 4.2.0 and up seed the random number generator for you.
189         # Lets hope that it seeds with something harder to guess than the clock.
190         while($length--) {
191                 $code .= $character_set{mt_rand(0, 24)}; # inclusive
192         }
193
194         return $code;
195 }
196
197 # depricated
198 # return username if a session exists and is authenticated
199 function logged_in() {
200         if(!session_exists()) {
201                 return false;
202         }
203
204         return session_get('auth_username');
205 }
206
207
208 # depricated
209 function session_exists_and_authed() {
210         return logged_in();
211 }
212
213
214 # depricated
215 # return true if a session exists and is authenticated
216 function logged_in_as_admin() {
217         if(!session_exists()) {
218                 return false;
219         }
220
221         if(session_get('auth_admin')) {
222                 return true;
223         }
224         return false;
225 }
226
227
228 # find existing session, or make one (name "session_init" was taken)
229 function init_session() {
230         if(!session_exists()) {
231                 session_new();
232         }
233 }
234
235 # internal use only (write session cache to db)
236 function _sync_session() {
237         db_update('wfpl_sessions',
238                 'value', json_encode($GLOBALS['wfpl_session']['value']),
239                 'where id=%i', $GLOBALS['wfpl_session']['id']
240         );
241 }
242
243 # save data into the session
244 # $value can be anything json_encode()able
245 function session_set($name, $value) {
246         init_session();
247         if (isset($GLOBALS['wfpl_session']['value'][$name])) {
248                 if ($GLOBALS['wfpl_session']['value'][$name] === $value) {
249                         return;
250                 }
251         }
252         $GLOBALS['wfpl_session']['value'][$name] = $value;
253         _sync_session();
254 }
255
256 # save data into the session
257 # values can be anything json_encode()able
258 function session_sets($assoc) {
259         init_session();
260         $dirty = false;
261         foreach ($assoc as $name => &$value) {
262                 if (isset($GLOBALS['wfpl_session']['value'][$name])) {
263                         if ($GLOBALS['wfpl_session']['value'][$name] === $value) {
264                                 continue;
265                         }
266                 }
267                 $GLOBALS['wfpl_session']['value'][$name] = $value;
268                 $dirty = true;
269         }
270         if ($dirty) {
271                 _sync_session();
272         }
273 }
274
275 # remove variable from the session
276 # with no args: clear all
277 function session_clear($name = -1) {
278         if(!session_exists()) {
279                 return;
280         }
281         if ($name === -1) {
282                 if (count($GLOBALS['wfpl_session']['value']) > 0) {
283                         $GLOBALS['wfpl_session']['value'] = array();
284                         _sync_session();
285                 }
286         } elseif (isset($GLOBALS['wfpl_session']['value'][$name])) {
287                 unset($GLOBALS['wfpl_session']['value'][$name]);
288                 _sync_session();
289         }
290 }
291
292 # get a variable into the session
293 function session_get($name) {
294         if(!session_exists()) {
295                 return false;
296         }
297         if (isset($GLOBALS['wfpl_session']['value'][$name])) {
298                 return $GLOBALS['wfpl_session']['value'][$name];
299         } else {
300                 return false;
301         }
302 }