1 | <?php
|
---|
2 | /*
|
---|
3 | V4.93 10 Oct 2006 (c) 2000-2007 John Lim (jlim#natsoft.com.my). All rights reserved.
|
---|
4 | Released under both BSD license and Lesser GPL library license.
|
---|
5 | Whenever there is any discrepancy between the two licenses,
|
---|
6 | the BSD license will take precedence.
|
---|
7 | Set tabs to 4 for best viewing.
|
---|
8 |
|
---|
9 | Latest version of ADODB is available at http://php.weblogs.com/adodb
|
---|
10 | ======================================================================
|
---|
11 |
|
---|
12 | This file provides PHP4 session management using the ADODB database
|
---|
13 | wrapper library.
|
---|
14 |
|
---|
15 | Example
|
---|
16 | =======
|
---|
17 |
|
---|
18 | include('adodb.inc.php');
|
---|
19 | include('adodb-session.php');
|
---|
20 | session_start();
|
---|
21 | session_register('AVAR');
|
---|
22 | $_SESSION['AVAR'] += 1;
|
---|
23 | print "
|
---|
24 | -- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
|
---|
25 |
|
---|
26 | To force non-persistent connections, call adodb_session_open first before session_start():
|
---|
27 |
|
---|
28 | include('adodb.inc.php');
|
---|
29 | include('adodb-session.php');
|
---|
30 | adodb_sess_open(false,false,false);
|
---|
31 | session_start();
|
---|
32 | session_register('AVAR');
|
---|
33 | $_SESSION['AVAR'] += 1;
|
---|
34 | print "
|
---|
35 | -- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>";
|
---|
36 |
|
---|
37 |
|
---|
38 | Installation
|
---|
39 | ============
|
---|
40 | 1. Create this table in your database (syntax might vary depending on your db):
|
---|
41 |
|
---|
42 | create table sessions (
|
---|
43 | SESSKEY char(32) not null,
|
---|
44 | EXPIRY int(11) unsigned not null,
|
---|
45 | EXPIREREF varchar(64),
|
---|
46 | DATA text not null,
|
---|
47 | primary key (sesskey)
|
---|
48 | );
|
---|
49 |
|
---|
50 | For oracle:
|
---|
51 | create table sessions (
|
---|
52 | SESSKEY char(32) not null,
|
---|
53 | EXPIRY DECIMAL(16) not null,
|
---|
54 | EXPIREREF varchar(64),
|
---|
55 | DATA varchar(4000) not null,
|
---|
56 | primary key (sesskey)
|
---|
57 | );
|
---|
58 |
|
---|
59 |
|
---|
60 | 2. Then define the following parameters. You can either modify
|
---|
61 | this file, or define them before this file is included:
|
---|
62 |
|
---|
63 | $ADODB_SESSION_DRIVER='database driver, eg. mysql or ibase';
|
---|
64 | $ADODB_SESSION_CONNECT='server to connect to';
|
---|
65 | $ADODB_SESSION_USER ='user';
|
---|
66 | $ADODB_SESSION_PWD ='password';
|
---|
67 | $ADODB_SESSION_DB ='database';
|
---|
68 | $ADODB_SESSION_TBL = 'sessions'
|
---|
69 |
|
---|
70 | 3. Recommended is PHP 4.1.0 or later. There are documented
|
---|
71 | session bugs in earlier versions of PHP.
|
---|
72 |
|
---|
73 | 4. If you want to receive notifications when a session expires, then
|
---|
74 | you can tag a session with an EXPIREREF, and before the session
|
---|
75 | record is deleted, we can call a function that will pass the EXPIREREF
|
---|
76 | as the first parameter, and the session key as the second parameter.
|
---|
77 |
|
---|
78 | To do this, define a notification function, say NotifyFn:
|
---|
79 |
|
---|
80 | function NotifyFn($expireref, $sesskey)
|
---|
81 | {
|
---|
82 | }
|
---|
83 |
|
---|
84 | Then you need to define a global variable $ADODB_SESSION_EXPIRE_NOTIFY.
|
---|
85 | This is an array with 2 elements, the first being the name of the variable
|
---|
86 | you would like to store in the EXPIREREF field, and the 2nd is the
|
---|
87 | notification function's name.
|
---|
88 |
|
---|
89 | In this example, we want to be notified when a user's session
|
---|
90 | has expired, so we store the user id in the global variable $USERID,
|
---|
91 | store this value in the EXPIREREF field:
|
---|
92 |
|
---|
93 | $ADODB_SESSION_EXPIRE_NOTIFY = array('USERID','NotifyFn');
|
---|
94 |
|
---|
95 | Then when the NotifyFn is called, we are passed the $USERID as the first
|
---|
96 | parameter, eg. NotifyFn($userid, $sesskey).
|
---|
97 | */
|
---|
98 |
|
---|
99 | if (!defined('_ADODB_LAYER')) {
|
---|
100 | include (dirname(__FILE__).'/adodb.inc.php');
|
---|
101 | }
|
---|
102 |
|
---|
103 | if (!defined('ADODB_SESSION')) {
|
---|
104 |
|
---|
105 | define('ADODB_SESSION',1);
|
---|
106 |
|
---|
107 | /* if database time and system time is difference is greater than this, then give warning */
|
---|
108 | define('ADODB_SESSION_SYNCH_SECS',60);
|
---|
109 |
|
---|
110 | /*
|
---|
111 | Thanks Joe Li. See http://phplens.com/lens/lensforum/msgs.php?id=11487&x=1
|
---|
112 | */
|
---|
113 | function adodb_session_regenerate_id()
|
---|
114 | {
|
---|
115 | $conn =& ADODB_Session::_conn();
|
---|
116 | if (!$conn) return false;
|
---|
117 |
|
---|
118 | $old_id = session_id();
|
---|
119 | if (function_exists('session_regenerate_id')) {
|
---|
120 | session_regenerate_id();
|
---|
121 | } else {
|
---|
122 | session_id(md5(uniqid(rand(), true)));
|
---|
123 | $ck = session_get_cookie_params();
|
---|
124 | setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
|
---|
125 | //@session_start();
|
---|
126 | }
|
---|
127 | $new_id = session_id();
|
---|
128 | $ok =& $conn->Execute('UPDATE '. ADODB_Session::table(). ' SET sesskey='. $conn->qstr($new_id). ' WHERE sesskey='.$conn->qstr($old_id));
|
---|
129 |
|
---|
130 | /* it is possible that the update statement fails due to a collision */
|
---|
131 | if (!$ok) {
|
---|
132 | session_id($old_id);
|
---|
133 | if (empty($ck)) $ck = session_get_cookie_params();
|
---|
134 | setcookie(session_name(), session_id(), false, $ck['path'], $ck['domain'], $ck['secure']);
|
---|
135 | return false;
|
---|
136 | }
|
---|
137 |
|
---|
138 | return true;
|
---|
139 | }
|
---|
140 |
|
---|
141 | /****************************************************************************************\
|
---|
142 | Global definitions
|
---|
143 | \****************************************************************************************/
|
---|
144 | GLOBAL $ADODB_SESSION_CONNECT,
|
---|
145 | $ADODB_SESSION_DRIVER,
|
---|
146 | $ADODB_SESSION_USER,
|
---|
147 | $ADODB_SESSION_PWD,
|
---|
148 | $ADODB_SESSION_DB,
|
---|
149 | $ADODB_SESS_CONN,
|
---|
150 | $ADODB_SESS_LIFE,
|
---|
151 | $ADODB_SESS_DEBUG,
|
---|
152 | $ADODB_SESSION_EXPIRE_NOTIFY,
|
---|
153 | $ADODB_SESSION_CRC,
|
---|
154 | $ADODB_SESSION_TBL;
|
---|
155 |
|
---|
156 |
|
---|
157 | $ADODB_SESS_LIFE = ini_get('session.gc_maxlifetime');
|
---|
158 | if ($ADODB_SESS_LIFE <= 1) {
|
---|
159 | // bug in PHP 4.0.3 pl 1 -- how about other versions?
|
---|
160 | //print "<h3>Session Error: PHP.INI setting <i>session.gc_maxlifetime</i>not set: $ADODB_SESS_LIFE</h3>";
|
---|
161 | $ADODB_SESS_LIFE=1440;
|
---|
162 | }
|
---|
163 | $ADODB_SESSION_CRC = false;
|
---|
164 | //$ADODB_SESS_DEBUG = true;
|
---|
165 |
|
---|
166 | //////////////////////////////////
|
---|
167 | /* SET THE FOLLOWING PARAMETERS */
|
---|
168 | //////////////////////////////////
|
---|
169 |
|
---|
170 | if (empty($ADODB_SESSION_DRIVER)) {
|
---|
171 | $ADODB_SESSION_DRIVER='mysql';
|
---|
172 | $ADODB_SESSION_CONNECT='localhost';
|
---|
173 | $ADODB_SESSION_USER ='root';
|
---|
174 | $ADODB_SESSION_PWD ='';
|
---|
175 | $ADODB_SESSION_DB ='xphplens_2';
|
---|
176 | }
|
---|
177 |
|
---|
178 | if (empty($ADODB_SESSION_EXPIRE_NOTIFY)) {
|
---|
179 | $ADODB_SESSION_EXPIRE_NOTIFY = false;
|
---|
180 | }
|
---|
181 | // Made table name configurable - by David Johnson djohnson@inpro.net
|
---|
182 | if (empty($ADODB_SESSION_TBL)){
|
---|
183 | $ADODB_SESSION_TBL = 'sessions';
|
---|
184 | }
|
---|
185 |
|
---|
186 | /*
|
---|
187 | $ADODB_SESS['driver'] = $ADODB_SESSION_DRIVER;
|
---|
188 | $ADODB_SESS['connect'] = $ADODB_SESSION_CONNECT;
|
---|
189 | $ADODB_SESS['user'] = $ADODB_SESSION_USER;
|
---|
190 | $ADODB_SESS['pwd'] = $ADODB_SESSION_PWD;
|
---|
191 | $ADODB_SESS['db'] = $ADODB_SESSION_DB;
|
---|
192 | $ADODB_SESS['life'] = $ADODB_SESS_LIFE;
|
---|
193 | $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
|
---|
194 |
|
---|
195 | $ADODB_SESS['debug'] = $ADODB_SESS_DEBUG;
|
---|
196 | $ADODB_SESS['table'] = $ADODB_SESS_TBL;
|
---|
197 | */
|
---|
198 |
|
---|
199 | /****************************************************************************************\
|
---|
200 | Create the connection to the database.
|
---|
201 |
|
---|
202 | If $ADODB_SESS_CONN already exists, reuse that connection
|
---|
203 | \****************************************************************************************/
|
---|
204 | function adodb_sess_open($save_path, $session_name,$persist=true)
|
---|
205 | {
|
---|
206 | GLOBAL $ADODB_SESS_CONN;
|
---|
207 | if (isset($ADODB_SESS_CONN)) return true;
|
---|
208 |
|
---|
209 | GLOBAL $ADODB_SESSION_CONNECT,
|
---|
210 | $ADODB_SESSION_DRIVER,
|
---|
211 | $ADODB_SESSION_USER,
|
---|
212 | $ADODB_SESSION_PWD,
|
---|
213 | $ADODB_SESSION_DB,
|
---|
214 | $ADODB_SESS_DEBUG;
|
---|
215 |
|
---|
216 | // cannot use & below - do not know why...
|
---|
217 | $ADODB_SESS_CONN = ADONewConnection($ADODB_SESSION_DRIVER);
|
---|
218 | if (!empty($ADODB_SESS_DEBUG)) {
|
---|
219 | $ADODB_SESS_CONN->debug = true;
|
---|
220 | ADOConnection::outp( " conn=$ADODB_SESSION_CONNECT user=$ADODB_SESSION_USER pwd=$ADODB_SESSION_PWD db=$ADODB_SESSION_DB ");
|
---|
221 | }
|
---|
222 | if ($persist) $ok = $ADODB_SESS_CONN->PConnect($ADODB_SESSION_CONNECT,
|
---|
223 | $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
|
---|
224 | else $ok = $ADODB_SESS_CONN->Connect($ADODB_SESSION_CONNECT,
|
---|
225 | $ADODB_SESSION_USER,$ADODB_SESSION_PWD,$ADODB_SESSION_DB);
|
---|
226 |
|
---|
227 | if (!$ok) ADOConnection::outp( "
|
---|
228 | -- Session: connection failed</p>",false);
|
---|
229 | }
|
---|
230 |
|
---|
231 | /****************************************************************************************\
|
---|
232 | Close the connection
|
---|
233 | \****************************************************************************************/
|
---|
234 | function adodb_sess_close()
|
---|
235 | {
|
---|
236 | global $ADODB_SESS_CONN;
|
---|
237 |
|
---|
238 | if ($ADODB_SESS_CONN) $ADODB_SESS_CONN->Close();
|
---|
239 | return true;
|
---|
240 | }
|
---|
241 |
|
---|
242 | /****************************************************************************************\
|
---|
243 | Slurp in the session variables and return the serialized string
|
---|
244 | \****************************************************************************************/
|
---|
245 | function adodb_sess_read($key)
|
---|
246 | {
|
---|
247 | global $ADODB_SESS_CONN,$ADODB_SESSION_TBL,$ADODB_SESSION_CRC;
|
---|
248 |
|
---|
249 | $rs = $ADODB_SESS_CONN->Execute("SELECT data FROM $ADODB_SESSION_TBL WHERE sesskey = '$key' AND expiry >= " . time());
|
---|
250 | if ($rs) {
|
---|
251 | if ($rs->EOF) {
|
---|
252 | $v = '';
|
---|
253 | } else
|
---|
254 | $v = rawurldecode(reset($rs->fields));
|
---|
255 |
|
---|
256 | $rs->Close();
|
---|
257 |
|
---|
258 | // new optimization adodb 2.1
|
---|
259 | $ADODB_SESSION_CRC = strlen($v).crc32($v);
|
---|
260 |
|
---|
261 | return $v;
|
---|
262 | }
|
---|
263 |
|
---|
264 | return ''; // thx to Jorma Tuomainen, webmaster#wizactive.com
|
---|
265 | }
|
---|
266 |
|
---|
267 | /****************************************************************************************\
|
---|
268 | Write the serialized data to a database.
|
---|
269 |
|
---|
270 | If the data has not been modified since adodb_sess_read(), we do not write.
|
---|
271 | \****************************************************************************************/
|
---|
272 | function adodb_sess_write($key, $val)
|
---|
273 | {
|
---|
274 | global
|
---|
275 | $ADODB_SESS_CONN,
|
---|
276 | $ADODB_SESS_LIFE,
|
---|
277 | $ADODB_SESSION_TBL,
|
---|
278 | $ADODB_SESS_DEBUG,
|
---|
279 | $ADODB_SESSION_CRC,
|
---|
280 | $ADODB_SESSION_EXPIRE_NOTIFY;
|
---|
281 |
|
---|
282 | $expiry = time() + $ADODB_SESS_LIFE;
|
---|
283 |
|
---|
284 | // crc32 optimization since adodb 2.1
|
---|
285 | // now we only update expiry date, thx to sebastian thom in adodb 2.32
|
---|
286 | if ($ADODB_SESSION_CRC !== false && $ADODB_SESSION_CRC == strlen($val).crc32($val)) {
|
---|
287 | if ($ADODB_SESS_DEBUG) echo "
|
---|
288 | -- Session: Only updating date - crc32 not changed</p>";
|
---|
289 | $qry = "UPDATE $ADODB_SESSION_TBL SET expiry=$expiry WHERE sesskey='$key' AND expiry >= " . time();
|
---|
290 | $rs = $ADODB_SESS_CONN->Execute($qry);
|
---|
291 | return true;
|
---|
292 | }
|
---|
293 | $val = rawurlencode($val);
|
---|
294 |
|
---|
295 | $arr = array('sesskey' => $key, 'expiry' => $expiry, 'data' => $val);
|
---|
296 | if ($ADODB_SESSION_EXPIRE_NOTIFY) {
|
---|
297 | $var = reset($ADODB_SESSION_EXPIRE_NOTIFY);
|
---|
298 | global $$var;
|
---|
299 | $arr['expireref'] = $$var;
|
---|
300 | }
|
---|
301 | $rs = $ADODB_SESS_CONN->Replace($ADODB_SESSION_TBL,$arr,
|
---|
302 | 'sesskey',$autoQuote = true);
|
---|
303 |
|
---|
304 | if (!$rs) {
|
---|
305 | ADOConnection::outp( '
|
---|
306 | -- Session Replace: '.$ADODB_SESS_CONN->ErrorMsg().'</p>',false);
|
---|
307 | } else {
|
---|
308 | // bug in access driver (could be odbc?) means that info is not commited
|
---|
309 | // properly unless select statement executed in Win2000
|
---|
310 | if ($ADODB_SESS_CONN->databaseType == 'access')
|
---|
311 | $rs = $ADODB_SESS_CONN->Execute("select sesskey from $ADODB_SESSION_TBL WHERE sesskey='$key'");
|
---|
312 | }
|
---|
313 | return !empty($rs);
|
---|
314 | }
|
---|
315 |
|
---|
316 | function adodb_sess_destroy($key)
|
---|
317 | {
|
---|
318 | global $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
|
---|
319 |
|
---|
320 | if ($ADODB_SESSION_EXPIRE_NOTIFY) {
|
---|
321 | reset($ADODB_SESSION_EXPIRE_NOTIFY);
|
---|
322 | $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
|
---|
323 | $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
|
---|
324 | $rs = $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
|
---|
325 | $ADODB_SESS_CONN->SetFetchMode($savem);
|
---|
326 | if ($rs) {
|
---|
327 | $ADODB_SESS_CONN->BeginTrans();
|
---|
328 | while (!$rs->EOF) {
|
---|
329 | $ref = $rs->fields[0];
|
---|
330 | $key = $rs->fields[1];
|
---|
331 | $fn($ref,$key);
|
---|
332 | $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
|
---|
333 | $rs->MoveNext();
|
---|
334 | }
|
---|
335 | $ADODB_SESS_CONN->CommitTrans();
|
---|
336 | }
|
---|
337 | } else {
|
---|
338 | $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE sesskey = '$key'";
|
---|
339 | $rs = $ADODB_SESS_CONN->Execute($qry);
|
---|
340 | }
|
---|
341 | return $rs ? true : false;
|
---|
342 | }
|
---|
343 |
|
---|
344 | function adodb_sess_gc($maxlifetime)
|
---|
345 | {
|
---|
346 | global $ADODB_SESS_DEBUG, $ADODB_SESS_CONN, $ADODB_SESSION_TBL,$ADODB_SESSION_EXPIRE_NOTIFY;
|
---|
347 |
|
---|
348 | if ($ADODB_SESSION_EXPIRE_NOTIFY) {
|
---|
349 | reset($ADODB_SESSION_EXPIRE_NOTIFY);
|
---|
350 | $fn = next($ADODB_SESSION_EXPIRE_NOTIFY);
|
---|
351 | $savem = $ADODB_SESS_CONN->SetFetchMode(ADODB_FETCH_NUM);
|
---|
352 | $t = time();
|
---|
353 | $rs =& $ADODB_SESS_CONN->Execute("SELECT expireref,sesskey FROM $ADODB_SESSION_TBL WHERE expiry < $t");
|
---|
354 | $ADODB_SESS_CONN->SetFetchMode($savem);
|
---|
355 | if ($rs) {
|
---|
356 | $ADODB_SESS_CONN->BeginTrans();
|
---|
357 | while (!$rs->EOF) {
|
---|
358 | $ref = $rs->fields[0];
|
---|
359 | $key = $rs->fields[1];
|
---|
360 | $fn($ref,$key);
|
---|
361 | $del = $ADODB_SESS_CONN->Execute("DELETE FROM $ADODB_SESSION_TBL WHERE sesskey='$key'");
|
---|
362 | $rs->MoveNext();
|
---|
363 | }
|
---|
364 | $rs->Close();
|
---|
365 |
|
---|
366 | $ADODB_SESS_CONN->CommitTrans();
|
---|
367 |
|
---|
368 | }
|
---|
369 | } else {
|
---|
370 | $qry = "DELETE FROM $ADODB_SESSION_TBL WHERE expiry < " . time();
|
---|
371 | $ADODB_SESS_CONN->Execute($qry);
|
---|
372 |
|
---|
373 | if ($ADODB_SESS_DEBUG) ADOConnection::outp("
|
---|
374 | -- <b>Garbage Collection</b>: $qry</p>");
|
---|
375 | }
|
---|
376 | // suggested by Cameron, "GaM3R" <gamr@outworld.cx>
|
---|
377 | if (defined('ADODB_SESSION_OPTIMIZE')) {
|
---|
378 | global $ADODB_SESSION_DRIVER;
|
---|
379 |
|
---|
380 | switch( $ADODB_SESSION_DRIVER ) {
|
---|
381 | case 'mysql':
|
---|
382 | case 'mysqlt':
|
---|
383 | $opt_qry = 'OPTIMIZE TABLE '.$ADODB_SESSION_TBL;
|
---|
384 | break;
|
---|
385 | case 'postgresql':
|
---|
386 | case 'postgresql7':
|
---|
387 | $opt_qry = 'VACUUM '.$ADODB_SESSION_TBL;
|
---|
388 | break;
|
---|
389 | }
|
---|
390 | if (!empty($opt_qry)) {
|
---|
391 | $ADODB_SESS_CONN->Execute($opt_qry);
|
---|
392 | }
|
---|
393 | }
|
---|
394 | if ($ADODB_SESS_CONN->dataProvider === 'oci8') $sql = 'select TO_CHAR('.($ADODB_SESS_CONN->sysTimeStamp).', \'RRRR-MM-DD HH24:MI:SS\') from '. $ADODB_SESSION_TBL;
|
---|
395 | else $sql = 'select '.$ADODB_SESS_CONN->sysTimeStamp.' from '. $ADODB_SESSION_TBL;
|
---|
396 |
|
---|
397 | $rs =& $ADODB_SESS_CONN->SelectLimit($sql,1);
|
---|
398 | if ($rs && !$rs->EOF) {
|
---|
399 |
|
---|
400 | $dbts = reset($rs->fields);
|
---|
401 | $rs->Close();
|
---|
402 | $dbt = $ADODB_SESS_CONN->UnixTimeStamp($dbts);
|
---|
403 | $t = time();
|
---|
404 |
|
---|
405 | if (abs($dbt - $t) >= ADODB_SESSION_SYNCH_SECS) {
|
---|
406 |
|
---|
407 | $msg =
|
---|
408 | __FILE__.": Server time for webserver {$_SERVER['HTTP_HOST']} not in synch with database: database=$dbt ($dbts), webserver=$t (diff=".(abs($dbt-$t)/3600)." hrs)";
|
---|
409 | error_log($msg);
|
---|
410 | if ($ADODB_SESS_DEBUG) ADOConnection::outp("
|
---|
411 | -- $msg</p>");
|
---|
412 | }
|
---|
413 | }
|
---|
414 |
|
---|
415 | return true;
|
---|
416 | }
|
---|
417 |
|
---|
418 | session_module_name('user');
|
---|
419 | session_set_save_handler(
|
---|
420 | "adodb_sess_open",
|
---|
421 | "adodb_sess_close",
|
---|
422 | "adodb_sess_read",
|
---|
423 | "adodb_sess_write",
|
---|
424 | "adodb_sess_destroy",
|
---|
425 | "adodb_sess_gc");
|
---|
426 | }
|
---|
427 |
|
---|
428 | /* TEST SCRIPT -- UNCOMMENT */
|
---|
429 |
|
---|
430 | if (0) {
|
---|
431 |
|
---|
432 | session_start();
|
---|
433 | session_register('AVAR');
|
---|
434 | $_SESSION['AVAR'] += 1;
|
---|
435 | ADOConnection::outp( "
|
---|
436 | -- \$_SESSION['AVAR']={$_SESSION['AVAR']}</p>",false);
|
---|
437 | }
|
---|
438 |
|
---|
439 | ?> |
---|