PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/administrator/components/com_virtuemart/classes/ps_session.php

https://bitbucket.org/dgough/annamaria-daneswood-25102012
PHP | 602 lines | 361 code | 74 blank | 167 comment | 128 complexity | b36137586a456416f76d213c6b56d31e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. if( !defined( '_VALID_MOS' ) && !defined( '_JEXEC' ) ) die( 'Direct Access to '.basename(__FILE__).' is not allowed.' );
  3. /**
  4. *
  5. * @version $Id: ps_session.php 1452 2008-07-08 17:21:42Z soeren_nb $
  6. * @package VirtueMart
  7. * @subpackage classes
  8. * @copyright Copyright (C) 2004-2008 soeren - All rights reserved.
  9. * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
  10. * VirtueMart is free software. This version may have been modified pursuant
  11. * to the GNU General Public License, and as distributed it includes or
  12. * is derivative of works licensed under the GNU General Public License or
  13. * other free or open source software licenses.
  14. * See /administrator/components/com_virtuemart/COPYRIGHT.php for copyright notices and details.
  15. *
  16. * http://virtuemart.net
  17. */
  18. /**
  19. * This class handles the session initialization, restart
  20. * and the re-init of a session after redirection to a Shared SSL domain
  21. *
  22. */
  23. class ps_session {
  24. var $component_name;
  25. var $_session_name = 'virtuemart';
  26. /**
  27. * Initialize the Session environment for VirtueMart
  28. *
  29. */
  30. function ps_session() {
  31. $this->component_name = 'option='.VM_COMPONENT_NAME;
  32. $this->initSession();
  33. }
  34. /**
  35. * Initiate the Session
  36. *
  37. */
  38. function initSession() {
  39. global $vmLogger, $mainframe, $mosConfig_absolute_path, $VM_LANG;
  40. // We only care for the session if it is not started!
  41. if( empty( $_SESSION ) || session_id() == '') {
  42. if(ini_get('session.session_save_handler') == 'files') {
  43. // Check if the session_save_path is writable
  44. $this->checkSessionSavePath();
  45. }
  46. session_name( $this->_session_name );
  47. if( @$_REQUEST['option'] == 'com_virtuemart' ) {
  48. ob_start();// Fix for the issue that printed the shop contents BEFORE the page begin
  49. }
  50. @session_start();
  51. if( !empty($_SESSION) && !empty($_COOKIE[$this->_session_name])) {
  52. $vmLogger->debug( 'A Session called '.$this->_session_name.' (ID: '.session_id().') was successfully started!' );
  53. }
  54. else {
  55. }
  56. }
  57. else {
  58. if( empty( $_COOKIE['virtuemart']) && !vmIsJoomla('1.5', '>=')) {
  59. $_COOKIE['virtuemart'] = $this->getSessionId();
  60. if( USE_AS_CATALOGUE == '' ) {
  61. $vmLogger->debug( 'A Cookie had to be set to keep the session (there was none - does your Browser keep the Cookie?) although a Session already has been started! If you see this message on each page load, your browser doesn\'t accept Cookies from this site.' );
  62. }
  63. }
  64. $vmLogger->debug( 'Using existing Session '.session_name().', ID: '.session_id().'.');
  65. }
  66. // Cookie Check
  67. // Introduced to check if the user-agent accepts cookies
  68. if( @$_REQUEST['option'] == 'com_virtuemart' && empty($_GET['martID'])
  69. && USE_AS_CATALOGUE != '1' && VM_ENABLE_COOKIE_CHECK == '1' && !vmIsAdminMode() ) {
  70. $this->doCookieCheck();
  71. }
  72. }
  73. /**
  74. * Checks if the user-agent accepts cookies
  75. * @since VirtueMart 1.0.7
  76. * @author soeren
  77. */
  78. function doCookieCheck() {
  79. global $mm_action_url, $VM_LANG;
  80. $doCheck = vmGet( $_REQUEST, 'vmcchk', 0 );
  81. $isOK = vmGet( $_SESSION, 'VMCHECK' );
  82. if( $doCheck && $isOK != 'OK' ) {
  83. $GLOBALS['vmLogger']->info( $VM_LANG->_('VM_SESSION_COOKIES_NOT_ACCEPTED_TIP',false) );
  84. }
  85. elseif( empty( $isOK )) {
  86. $_SESSION['VMCHECK'] = 'OK';
  87. vmRedirect( $this->url( $mm_action_url . 'index.php?' . vmGet($_SERVER,'QUERY_STRING').'&vmcchk=1', true, false ));
  88. }
  89. }
  90. /**
  91. * Returns the Joomla/Mambo Session ID
  92. * @static
  93. */
  94. function getSessionId() {
  95. global $mainframe;
  96. // Joomla >= 1.0.8
  97. if( is_callable( array( 'mosMainframe', 'sessionCookieName'))) {
  98. // Session Cookie `name`
  99. $sessionCookieName = mosMainFrame::sessionCookieName();
  100. // Get Session Cookie `value`
  101. $sessionCookie = vmGet( $_COOKIE, $sessionCookieName, null );
  102. // Session ID / `value`
  103. return mosMainFrame::sessionCookieValue( $sessionCookie );
  104. }
  105. // Mambo 4.6
  106. elseif( is_callable( array('mosSession', 'getCurrent' ))) {
  107. $session =& mosSession::getCurrent();
  108. return $session->session_id;
  109. }
  110. // Mambo <= 4.5.2.3 and Joomla <= 1.0.7
  111. elseif( !empty( $mainframe->_session->session_id )) {
  112. // Set the sessioncookie if its missing
  113. // this is needed for joomla sites only
  114. return $mainframe->_session->session_id;
  115. }
  116. else {
  117. return session_id();
  118. }
  119. }
  120. /**
  121. * This function returns a base64_encoded string:
  122. * VMsessionId|JsessionID
  123. *
  124. */
  125. function getMartId() {
  126. global $vmuser, $mosConfig_secret;
  127. // Get the Joomla! / Mambo session ID
  128. $sessionId = ps_session::getSessionId();
  129. $userNameSeed = '';
  130. if( $vmuser->id ) {
  131. $userNameSeed = '|'.md5( $vmuser->username . $vmuser->password . $mosConfig_secret );
  132. if( is_callable(array('mosMainFrame', 'remCookieName_User'))) {
  133. if( !empty( $GLOBALS['real_mosConfig_live_site'] ) && empty( $_REQUEST['real_mosConfig_live_site'])) {
  134. $GLOBALS['mosConfig_live_site'] = $GLOBALS['real_mosConfig_live_site'];
  135. }
  136. $userNameSeed .= '|' . vmGet( $_COOKIE, mosMainFrame::remCookieName_User(), '' );
  137. }
  138. }
  139. $martID = base64_encode( vmCreateHash($_COOKIE[$this->_session_name] . $sessionId) . $userNameSeed );
  140. return $martID;
  141. }
  142. /**
  143. * Saves the session contents to a file (so they the session can be continued later on) and does a redirect
  144. *
  145. * @param boolean $toSecure Redirect to a https or http domain?
  146. */
  147. function saveSessionAndRedirect( $toSecure = true ) {
  148. $martID = $this->getMartId();
  149. $sessionFile = IMAGEPATH. md5( $martID ).'.sess';
  150. $session_contents = session_encode();
  151. if( file_exists( ADMINPATH.'install.copy.php')) {
  152. require_once( ADMINPATH.'install.copy.php');
  153. }
  154. file_put_contents( $sessionFile, $session_contents );
  155. $url = $toSecure ? SECUREURL : URL;
  156. // Redirect and send the Cookie Values within the variable martID
  157. vmRedirect( $this->url( $url . basename($_SERVER['PHP_SELF']).'?'.vmGet($_SERVER,'QUERY_STRING')."&martID=$martID&redirected=1", true, false, true ) );
  158. }
  159. /**
  160. * It does what the name says. It starts a session again (with a certain ID when a $sid is given)
  161. *
  162. * @param string $sid
  163. */
  164. function restartSession( $sid = '') {
  165. // Save the session data and close the session
  166. session_write_close();
  167. // Prepare the new session
  168. if( $sid != '' ) {
  169. session_id( $sid );
  170. }
  171. session_name( $this->_session_name );
  172. // Start the new Session.
  173. session_start();
  174. }
  175. function emptySession() {
  176. global $mainframe;
  177. $_SESSION = array();
  178. $_COOKIE[$this->_session_name] = md5( $this->getSessionId() );
  179. }
  180. /**
  181. * This is a solution for the Shared SSL problem
  182. * We have to copy some cookies from the Main Mambo site domain into
  183. * the shared SSL domain (only when necessary!)
  184. *
  185. * The function is called on each page load.
  186. */
  187. function prepare_SSL_Session() {
  188. global $mainframe, $my, $database, $mosConfig_secret, $_VERSION, $page, $VM_MODULES_FORCE_HTTPS;
  189. if( vmIsAdminMode() && vmIsJoomla('1.0')) {
  190. return;
  191. }
  192. $ssl_redirect = vmGet( $_GET, "ssl_redirect", 0 );
  193. $redirected = vmGet( $_GET, "redirected", 0 );
  194. $martID = vmGet( $_GET, 'martID', '' );
  195. $ssl_domain = "";
  196. if (!empty( $VM_MODULES_FORCE_HTTPS )) {
  197. $pagearr = explode( '.', $page );
  198. $module = $pagearr[0];
  199. // When NOT in https mode, but the called page is part of a shop module that is
  200. // forced to use https, we prepare the redirection to https here
  201. if( array_search( $module, $VM_MODULES_FORCE_HTTPS ) !== false
  202. && !vmIsHttpsMode()
  203. && $this->check_Shared_SSL( $ssl_domain )
  204. ) {
  205. $ssl_redirect = 1;
  206. }
  207. }
  208. // Generally redirect to HTTP (from HTTPS) when it is not necessary? (speed up the pageload)
  209. if( VM_GENERALLY_PREVENT_HTTPS == '1'
  210. && vmIsHttpsMode() && $redirected != 1
  211. && $ssl_redirect == 0 && !vmIsAdminMode()
  212. && URL != SECUREURL
  213. && @$_REQUEST['option']=='com_virtuemart') {
  214. $pagearr = explode( '.', $page );
  215. $module = $pagearr[0];
  216. // When it is not necessary to stay in https mode, we leave it here
  217. if( array_search( $module, $VM_MODULES_FORCE_HTTPS ) === false ) {
  218. if( $this->check_Shared_SSL($ssl_domain)) {
  219. $this->saveSessionAndRedirect( false );
  220. }
  221. vmRedirect( $this->url( URL.basename( $_SERVER['PHP_SELF']).'?'.vmGet($_SERVER,'QUERY_STRING').'&redirected=1', true, false, true ));
  222. }
  223. }
  224. /**
  225. * This is the first part of the Function:
  226. * We check if the function must be called at all
  227. * Usually this is only called once: Before we go to the checkout.
  228. * The variable ssl_redirect=1 is appended to the URL, just for this function knows
  229. * is must be active! This has nothing to do with SSL / Shared SSL or whatever
  230. */
  231. if( $ssl_redirect == 1 ) {
  232. $_SERVER['QUERY_STRING'] = str_replace('&ssl_redirect=1', '', vmGet($_SERVER, 'QUERY_STRING' ) );
  233. // check_Shared_SSL compares the normal http domain name
  234. // and the https Domain Name. If both do not match, we move on
  235. // else we leave this function.
  236. if( $this->check_Shared_SSL( $ssl_domain ) && !vmIsHttpsMode() && $redirected == 0) {
  237. $this->saveSessionAndRedirect( true );
  238. }
  239. // do nothing but redirect
  240. elseif( !vmIsHttpsMode() && $redirected == 0 ) {
  241. vmRedirect( $this->url(SECUREURL . basename($_SERVER['PHP_SELF'])."?".vmGet($_SERVER,'QUERY_STRING').'&redirected=1', true, false, true ) );
  242. }
  243. }
  244. /**
  245. * This is part two of the function
  246. * If the redirect (see 4/5 lines above) was successful
  247. * and the Store uses Shared SSL, we have the variable martID
  248. * So let's copy the Session contents ton the new domain and start the session again
  249. * othwerwise: do nothing.
  250. */
  251. if( !empty( $martID ) ) {
  252. if( $this->check_Shared_SSL( $ssl_domain ) ) {
  253. // We now need to copy the Session Data to the SSL Domain
  254. if( $martID ) {
  255. require_once( ADMINPATH.'install.copy.php');
  256. $sessionFile = IMAGEPATH. md5( $martID ).'.sess';
  257. // Read the contents of the session file
  258. $session_data = file_get_contents( $sessionFile );
  259. // Delete it for security and disk space reasons
  260. unlink( $sessionFile );
  261. // Read the session data into $_SESSION
  262. // From now on, we can use all the data in $_SESSION
  263. session_decode( $session_data );
  264. $check = base64_decode( $martID );
  265. $checkValArr = explode( "|", $check );
  266. if( defined('_JEXEC') ) {
  267. //TODO
  268. }
  269. elseif( class_exists('mambocore')) {
  270. //TODO
  271. }
  272. elseif( $GLOBALS['_VERSION']->RELEASE == '1.0' && (int)$GLOBALS['_VERSION']->DEV_LEVEL >= 13) {
  273. if( !empty( $GLOBALS['real_mosConfig_live_site'] ) && empty( $_REQUEST['real_mosConfig_live_site'])) {
  274. $GLOBALS['mosConfig_live_site'] = $GLOBALS['real_mosConfig_live_site'];
  275. }
  276. if( !empty( $checkValArr[2] )) {
  277. // Joomla! >= 1.0.13 can be cheated to log in a user who has previsously logged in and checked the "Remember me" box
  278. setcookie( mosmainframe::remCookieName_User(), $checkValArr[2], false, '/' );
  279. // there's no need to call "$mainframe->login"
  280. }
  281. } else {
  282. // Check if the user was logged in in the http domain
  283. // and is not yet logged in at the Shared SSL domain
  284. if( isset( $checkValArr[1] ) && !$my->id ) {
  285. // user should expect to be logged in,
  286. // we can use the values from $_SESSION['auth'] now
  287. $username = $database->getEscaped( trim( $_SESSION['auth']['user_name'] ) );
  288. if( !empty( $username )) {
  289. $database->setQuery('SELECT username, password FROM `#__users` WHERE `username` = \''.$username.'\';');
  290. $database->loadObject( $user );
  291. if( is_object( $user )) {
  292. // a last security check using the transmitted md5 hash and the rebuilt hash
  293. $check = md5( $user->username . $user->password . $mosConfig_secret );
  294. if( $check === $checkValArr[1] ) {
  295. // Log the user in with his username
  296. $mainframe->login( $user->username, $user->password );
  297. }
  298. }
  299. }
  300. }
  301. }
  302. session_write_close();
  303. // Prevent the martID from being displayed in the URL
  304. if( !empty( $_GET['martID'] )) {
  305. $query_string = substr_replace( vmGet($_SERVER,'QUERY_STRING'), '', strpos( vmGet($_SERVER,'QUERY_STRING'), '&martID'));
  306. $url = vmIsHttpsMode() ? SECUREURL : URL;
  307. vmRedirect( $this->url( $url . "index.php?$query_string&cartReset=N&redirected=1", true, false, true) );
  308. }
  309. }
  310. }
  311. }
  312. }
  313. /**
  314. * This function compares the store URL with the SECUREURL
  315. * and returns the result
  316. *
  317. * @param string $ssl_domain The SSL domain (empty string to be filled here)
  318. * @return boolean True when we have to do a SSL redirect (for Shared SSL)
  319. */
  320. function check_Shared_SSL( &$ssl_domain ) {
  321. if( URL == SECUREURL ) {
  322. $ssl_domain = str_replace("http://", "", URL );
  323. $ssl_redirect = false;
  324. return $ssl_redirect;
  325. }
  326. // Extract the Domain Names without the Protocol
  327. $domain = str_replace("http://", "", URL );
  328. $ssl_domain = str_replace("https://", "", SECUREURL );
  329. // If SSL and normal Domain do not match,
  330. // we assume that you use Shared SSL
  331. if( $ssl_domain != $domain ) {
  332. $ssl_redirect = true;
  333. }
  334. else {
  335. $ssl_redirect = false;
  336. }
  337. return $ssl_redirect;
  338. }
  339. /**
  340. * Correct the session save path if necessary
  341. * or generate an error if the save path can't be corrected
  342. *
  343. * @return mixed
  344. */
  345. function checkSessionSavePath() {
  346. global $mosConfig_absolute_path, $VM_LANG, $vmLogger;
  347. if( !@is_writable( session_save_path()) ) {
  348. // If the session save path is not writable this can have different
  349. // reasons. One reason is that the open_basedir directive is set, but
  350. // doesn't include the session_save_path
  351. $open_basedir = @ini_get('open_basedir'); // Get the list of allowed directories
  352. if( !empty($open_basedir)) {
  353. switch( substr( strtoupper( PHP_OS ), 0, 3 ) ) {
  354. case "WIN":
  355. $basedirs = explode(';', $open_basedir );
  356. break;
  357. case "MAC": // fallthrough
  358. case "DAR": // Does PHP_OS return 'Macintosh' or 'Darwin' ?
  359. default: // change nothing
  360. $basedirs = explode(':', $open_basedir );
  361. break;
  362. break;
  363. }
  364. $session_save_path_is_allowed_directory = false;
  365. foreach ( $basedirs as $basedir ) {
  366. $basedir_strlen = strlen( $basedir );
  367. // If the session save path is a subdirectory of a directory allowed by open_basedir
  368. // we need to do further investigation
  369. if( strtolower( substr( session_save_path(), 0, $basedir_strlen )) == $basedir ) {
  370. $session_save_path_is_allowed_directory = true;
  371. }
  372. }
  373. if( !$session_save_path_is_allowed_directory) {
  374. // PHP Sessions can be stored in a session save path which is not
  375. // allowed through open_basedir!
  376. return true;
  377. }
  378. }
  379. $try_these_paths = array( 'Cache Path' => $mosConfig_absolute_path. '/cache',
  380. 'Media Directory' => $mosConfig_absolute_path.'/media',
  381. 'Shop Image Directory' => IMAGEPATH );
  382. foreach( $try_these_paths as $name => $session_save_path ) {
  383. if( @is_writable( $session_save_path )) {
  384. $vmLogger->debug( sprintf( $VM_LANG->_('VM_SESSION_SAVEPATH_UNWRITABLE_TMPFIX',false), session_save_path(), $name));
  385. session_save_path( $session_save_path );
  386. break;
  387. }
  388. }
  389. }
  390. // If the path is STILL not writable, generate an error
  391. if( !@is_writable( session_save_path()) ) {
  392. $vmLogger->err( $VM_LANG->_('VM_SESSION_SAVEPATH_UNWRITABLE',false) );
  393. }
  394. }
  395. /**
  396. * Gets the Itemid for the com_virtuemart Component
  397. * and stores it in a global Variable
  398. *
  399. * @return int Itemid
  400. */
  401. function getShopItemid() {
  402. if( empty( $_REQUEST['shopItemid'] )) {
  403. $db = new ps_DB;
  404. $db->query( "SELECT id FROM #__menu WHERE link='index.php?option=com_virtuemart' AND published=1");
  405. if( $db->next_record() ) {
  406. $_REQUEST['shopItemid'] = $db->f("id");
  407. }
  408. else {
  409. if( !empty( $_REQUEST['Itemid'] )) {
  410. $_REQUEST['shopItemid'] = intval( $_REQUEST['Itemid'] );
  411. }
  412. else {
  413. $_REQUEST['shopItemid'] = 1;
  414. }
  415. }
  416. }
  417. return intval($_REQUEST['shopItemid']);
  418. }
  419. /**
  420. * Prints a reformatted URL
  421. *
  422. * @param string $text
  423. */
  424. function purl($text, $createAbsoluteURI=false, $encodeAmpersands=true, $ignoreSEF=false) {
  425. echo $this->url( $text, $createAbsoluteURI, $encodeAmpersands, $ignoreSEF );
  426. }
  427. /**
  428. * This reformats an URL, appends "option=com_virtuemart" and "Itemid=XX"
  429. * where XX is the Id of an entry in the table mos_menu with "link: option=com_virtuemart"
  430. * It also calls sefRelToAbs to apply SEF formatting
  431. *
  432. * @param string $text THE URL
  433. * @param boolean False: Create a URI like /joomla/index.php?....; True: Create a URI like http://www.domain.com/index.php?....
  434. * @return string The reformatted URL
  435. */
  436. function url($text, $createAbsoluteURI=false, $encodeAmpersands=true, $ignoreSEF=false ) {
  437. global $mm_action_url, $page, $mainframe;
  438. if( !defined( '_VM_IS_BACKEND' )) {
  439. $Itemid = "&Itemid=".$this->getShopItemid();
  440. }
  441. else {
  442. $Itemid = '';
  443. }
  444. switch ($text) {
  445. case SECUREURL:
  446. $text = SECUREURL.basename( $_SERVER['SCRIPT_NAME'] )."?".$this->component_name.$Itemid;
  447. break;
  448. case URL:
  449. $text = URL.basename( $_SERVER['SCRIPT_NAME'] )."?".$this->component_name.$Itemid;
  450. break;
  451. default:
  452. $limiter = strpos($text, '?');
  453. if( !stristr( $text, $_SERVER['SCRIPT_NAME']) && $limiter === false ) {
  454. $text = '?'.$text;
  455. }
  456. $appendix = "";
  457. // now append "&option=com_virtuemart&Itemid=XX"
  458. if (!strstr($text, "option=")) {
  459. $appendix .= "&" . $this->component_name;
  460. }
  461. $appendix .= $Itemid;
  462. $script = basename( substr( $text, 0, $limiter ));
  463. if( $script == '' ) {
  464. $script = basename( $_SERVER['SCRIPT_NAME'] );
  465. }
  466. if (!defined( '_VM_IS_BACKEND' )) {
  467. if( $script == 'index3.php') {
  468. $script = 'index2.php'; // index3.php is not available in the frontend!
  469. }
  470. $appendix = $script.substr($text, $limiter, strlen($text)).$appendix;
  471. if( class_exists('JRoute') && !$ignoreSEF && $mainframe->getCfg('sef') ) {
  472. $appendix = JRoute::_( str_replace( $script.'&', $script.'?', $appendix ) );
  473. }
  474. else if( function_exists('sefRelToAbs') && !$ignoreSEF && !defined( '_JLEGACY' ) ) {
  475. $appendix = sefRelToAbs( str_replace( $script.'&', $script.'?', $appendix ) );
  476. }
  477. if( $createAbsoluteURI && substr($appendix,0,4)!='http' && ($ignoreSEF || !$mainframe->getCfg('sef')) ) {
  478. $appendix = URL . $appendix;
  479. }
  480. }
  481. elseif( $_SERVER['SERVER_PORT'] == 443 ) {
  482. //$script = strstr($_SERVER['PHP_SELF'], 'index2.php') ? 'index2.php' : 'index3.php';
  483. $appendix = SECUREURL."administrator/$script".substr($text, $limiter, strlen($text)-1).$appendix;
  484. }
  485. else {
  486. //$script = strstr($_SERVER['PHP_SELF'], 'index2.php') ? 'index2.php' : 'index3.php';
  487. $appendix = URL."administrator/$script".substr($text, $limiter, strlen($text)-1).$appendix;
  488. }
  489. if( vmIsAdminMode() && strstr($text, 'func') !== false ) {
  490. $appendix .= '&vmtoken='.vmSpoofValue($this->getSessionId());
  491. }
  492. if ( stristr($text, SECUREURL)) {
  493. $appendix = str_replace(URL, SECUREURL, $appendix);
  494. }
  495. elseif( stristr($text, URL) && $createAbsoluteURI ) {
  496. $appendix = str_replace(SECUREURL, URL, $appendix);
  497. }
  498. $text = $appendix;
  499. break;
  500. }
  501. if( $encodeAmpersands ) {
  502. $text = vmAmpReplace( $text );
  503. } else {
  504. $text = str_replace( '&amp;', '&', $text );
  505. }
  506. return $text;
  507. }
  508. /**
  509. * Formerly printed the session id into a hidden field
  510. * @deprecated
  511. * @return boolean
  512. */
  513. function hidden_session() {
  514. return true;
  515. }
  516. function initRecentProducts() {
  517. global $recentproducts, $sess;
  518. // Register the recentproducts
  519. if (empty($_SESSION['recent'])) {
  520. $recentproducts = array();
  521. $recentproducts['idx'] = 0;
  522. $_SESSION['recent'] = $recentproducts;
  523. return $recentproducts;
  524. }
  525. return $_SESSION['recent'];
  526. }
  527. } // end of class session
  528. ?>