JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
enc_htmlbrtab: don't double spaces
[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 # start a new session, tracked by a browser "session cookie".
61 #
62 # args:
63 #    $idle_timeout (seconds) session ends after this much inactivity (or up to 10% less)
64 #    $max_length (seconds) session ends after this long, regardless of activity
65 function session_new($idle_timeout = 129600 /* 36 hours */, $max_length = 604800 /* 1 week */) {
66         kill_session();
67
68         $session_key = session_generate_key();
69
70         $now = time();
71         $row = array(
72                 'session_key' => $session_key,
73                 'idle_timeout' => $idle_timeout,
74                 'expires' => $now + $idle_timeout,
75                 'expires_max' => $now + $max_length,
76                 'value' => ''
77         );
78
79         db_insert_assoc('wfpl_sessions', $row);
80         $session_id = db_auto_id();
81         $GLOBALS['wfpl_session'] = array(
82                 'exists' => true,
83                 'id' => $session_id,
84                 'key' => $session_key,
85                 'idle_timeout' => $idle_timeout,
86                 'expires' => $now + $idle_timeout,
87                 'expires_max' => $now + $max_length,
88                 'value' => array()
89         );
90         session_set_cookie();
91         return $session_key;
92 }
93
94 function session_set_cookie() {
95         if (session_exists()) {
96                 if (!isset($GLOBALS['wfpl_session']['cookie_set'])) {
97                         $GLOBALS['wfpl_session']['cookie_set'] = true;
98                         header('Set-Cookie: session_key=' . $GLOBALS['wfpl_session']['key'] . '; Path=/');
99                 }
100         }
101 }
102
103 # this is a helper function. See session_new()
104 function session_touch() {
105         if(!session_exists()) {
106                 return;
107         }
108         # is the session extendable?
109         if ($GLOBALS['wfpl_session']['expires'] < $GLOBALS['wfpl_session']['expires_max']) {
110                 # would this extend the session by at least 10%?
111                 $now = time();
112                 $last_activity = $GLOBALS['wfpl_session']['expires'] - $GLOBALS['wfpl_session']['idle_timeout'];
113                 # don't db_update if only a tiny fraction of the idle timeout has passed
114                 $db_threshold = ceil(0.1 * $GLOBALS['wfpl_session']['idle_timeout']);
115                 if ($now > $last_activity + $db_threshold) {
116                         $expires = min(
117                                 $GLOBALS['wfpl_session']['expires_max'],
118                                 $now + $GLOBALS['wfpl_session']['idle_timeout']
119                         );
120                         db_update('wfpl_sessions', 'expires', $expires, 'where id=%i', $GLOBALS['wfpl_session']['id']);
121                         $GLOBALS['wfpl_session']['expires'] = $expires;
122                 }
123         }
124 }
125
126 # delete the current session
127 function kill_session() {
128         if(!session_exists()) {
129             return;
130         }
131         db_delete('wfpl_sessions', 'where id=%i', $GLOBALS['wfpl_session']['id']);
132         $GLOBALS['wfpl_session'] = array('exists' => false);
133 }
134
135 # delete expired sessions from database
136 function session_purge_old() {
137         db_delete('wfpl_sessions', 'where expires < %i', time());
138 }
139
140 # return true if a session exists
141 function session_exists() {
142         if (isset($GLOBALS['wfpl_session'])) {
143                 return $GLOBALS['wfpl_session']['exists'];
144         }
145
146         $GLOBALS['wfpl_session'] = array('exists' => false);
147
148         if(!isset($_COOKIE['session_key'])) {
149                 return false;
150         }
151
152         $session_key = preg_replace('|[^a-z0-9]|i', '', $_COOKIE['session_key']);
153
154         if(!strlen($session_key) == 16) {
155                 return false;
156         }
157
158         $row = db_get_assoc('wfpl_sessions', 'id,idle_timeout,expires,expires_max,value', 'where session_key=%"', $session_key);
159         if($row === false) {
160                 return false;
161         }
162         $now = time();
163         if ($now >= (int) $row['expires']) {
164                 session_purge_old();
165                 return false;
166         }
167
168         $GLOBALS['wfpl_session']['exists'] = true;
169         $GLOBALS['wfpl_session']['id'] = $row['id'];
170         $GLOBALS['wfpl_session']['idle_timeout'] = (int) $row['idle_timeout'];
171         $GLOBALS['wfpl_session']['expires'] = (int) $row['expires'];
172         $GLOBALS['wfpl_session']['expires_max'] = (int) $row['expires_max'];
173         $GLOBALS['wfpl_session']['key'] = $session_key;
174
175         if (strlen($row['value']) && is_array($parsed = json_decode($row['value'], true))) {
176                 $GLOBALS['wfpl_session']['value'] = $parsed;
177         } else {
178                 $GLOBALS['wfpl_session']['value'] = array();
179         }
180
181         # mark session as not idle
182         session_touch();
183
184         return true;
185 }
186
187
188 # generate a random password using only letters and numbers that look
189 # particularly unique
190 function new_readable_password($length = 8) {
191         $character_set = "ABCDEFHJKLMNPQRTUWXY34789";
192         $code = "";
193
194         # PHP 4.2.0 and up seed the random number generator for you.
195         # Lets hope that it seeds with something harder to guess than the clock.
196         while($length--) {
197                 $code .= $character_set{mt_rand(0, 24)}; # inclusive
198         }
199
200         return $code;
201 }
202
203 # depricated
204 # return username if a session exists and is authenticated
205 function logged_in() {
206         if(!session_exists()) {
207                 return false;
208         }
209
210         return session_get('auth_username');
211 }
212
213
214 # depricated
215 function session_exists_and_authed() {
216         return logged_in();
217 }
218
219
220 # depricated
221 # return true if a session exists and is authenticated
222 function logged_in_as_admin() {
223         if(!session_exists()) {
224                 return false;
225         }
226
227         if(session_get('auth_admin')) {
228                 return true;
229         }
230         return false;
231 }
232
233
234 # find existing session, or make one (name "session_init" was taken)
235 function init_session() {
236         if(!session_exists()) {
237                 session_new();
238         }
239 }
240
241 # internal use only (write session cache to db)
242 function _sync_session() {
243         if (count($GLOBALS['wfpl_session']['value']) > 0) {
244                 $value = json_encode($GLOBALS['wfpl_session']['value']);
245         } else {
246                 $value = '';
247         }
248         db_update('wfpl_sessions', 'value', $value, 'where id=%i', $GLOBALS['wfpl_session']['id']);
249 }
250
251 # save data into the session
252 # $value can be anything json_encode()able
253 function session_set($name, $value) {
254         init_session();
255         if (isset($GLOBALS['wfpl_session']['value'][$name])) {
256                 if ($GLOBALS['wfpl_session']['value'][$name] === $value) {
257                         return;
258                 }
259         }
260         $GLOBALS['wfpl_session']['value'][$name] = $value;
261         _sync_session();
262 }
263
264 # save data into the session
265 # values can be anything json_encode()able
266 function session_sets($assoc) {
267         init_session();
268         $dirty = false;
269         foreach ($assoc as $name => &$value) {
270                 if (isset($GLOBALS['wfpl_session']['value'][$name])) {
271                         if ($GLOBALS['wfpl_session']['value'][$name] === $value) {
272                                 continue;
273                         }
274                 }
275                 $GLOBALS['wfpl_session']['value'][$name] = $value;
276                 $dirty = true;
277         }
278         if ($dirty) {
279                 _sync_session();
280         }
281 }
282
283 # remove variable from the session
284 # with no args: clear all
285 function session_clear($name = -1) {
286         if(!session_exists()) {
287                 return;
288         }
289         if ($name === -1) {
290                 if (count($GLOBALS['wfpl_session']['value']) > 0) {
291                         $GLOBALS['wfpl_session']['value'] = array();
292                         _sync_session();
293                 }
294         } elseif (isset($GLOBALS['wfpl_session']['value'][$name])) {
295                 unset($GLOBALS['wfpl_session']['value'][$name]);
296                 _sync_session();
297         }
298 }
299
300 # get a variable into the session
301 function session_get($name) {
302         if(!session_exists()) {
303                 return false;
304         }
305         if (isset($GLOBALS['wfpl_session']['value'][$name])) {
306                 return $GLOBALS['wfpl_session']['value'][$name];
307         } else {
308                 return false;
309         }
310 }