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