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