Invision Power Services, Inc. * @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc. * @license http://www.invisionpower.com/legal/standards/ * @package IPS Social Suite * @since 11 Mar 2013 * @version SVN_VERSION_NUMBER */ namespace IPS\Session; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Front Session Handler */ class _Front extends \IPS\Session { const LOGIN_TYPE_MEMBER = 0; const LOGIN_TYPE_ANONYMOUS = 1; const LOGIN_TYPE_GUEST = 2; const LOGIN_TYPE_SPIDER = 3; /** * Guess if the user is logged in * * This is a lightweight check that does not rely on other classes. It is only intended * to be used by the guest caching mechanism so that it can check if the user is logged * in before other classes are initiated. * * This method MUST NOT be used for other purposes as it IS NOT COMPLETELY ACCURATE. * * @return bool */ public static function loggedIn() { return isset( \IPS\Request::i()->cookie['member_id'] ) and \IPS\Request::i()->cookie['member_id']; } /** * @brief Session Data */ protected $data = array(); /** * @brief Needs saving? */ protected $save = TRUE; /** * Open Session * * @param string $savePath Save path * @param string $sessionName Session Name * @return void */ public function open( $savePath, $sessionName ) { return TRUE; } /** * Read Session * * @param string $sessionId Session ID * @return string */ public function read( $sessionId ) { $session = NULL; /* Get user agent info */ $this->userAgent = \IPS\Http\Useragent::parse(); /* Check the cache */ $key = "session_{$sessionId}"; if ( isset( \IPS\Data\Cache::i()->$key ) ) { $session = \IPS\Data\Cache::i()->$key; } /* Not in cache, check the database */ else { try { /* If it looks like we're logged in, join the member row to save a query later */ if ( static::loggedIn() ) { $session = \IPS\Db::i()->select( '*', 'core_sessions', array( 'id=?', $sessionId ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_MULTIDIMENSIONAL_JOINS )->join( 'core_members', 'core_members.member_id=core_sessions.member_id' )->first(); if ( $session['core_members']['member_id'] ) { \IPS\Member::constructFromData( $session['core_members'], FALSE ); } $session = $session['core_sessions']; } /* If we're not logged in, just look at the session */ else { /* Spiders match by IP and useragent */ if ( $this->userAgent->spider ) { $session = \IPS\Db::i()->select( '*', 'core_sessions', array( 'id=? OR ( ip_address=? AND browser=? )', $sessionId, \IPS\Request::i()->ipAddress(), $_SERVER['HTTP_USER_AGENT'] ) )->first(); $sessionId = $session['id']; } /* Normal users don't */ else { $session = \IPS\Db::i()->select( '*', 'core_sessions', array( 'id=?', $sessionId ) )->first(); } } } catch ( \UnderflowException $e ) { } } /* Only use sessions with matching IP address */ if( \IPS\Settings::i()->match_ipaddress and $session['ip_address'] != \IPS\Request::i()->ipAddress() ) { $session = NULL; } /* Store this so plugins can access */ $this->sessionData = $session; /* Got one? */ if ( $session ) { /* If this is a guest and the "running time" on this is less than 30 seconds ago, or if a member and less than 15 seconds ago, we don't need a database write */ if ( ( !$session['member_id'] and $session['running_time'] > ( time() - 30 ) ) or ( $session['member_id'] and $session['running_time'] > ( time() - 15 ) ) ) { $this->save = FALSE; } /* Set member */ try { $this->member = \IPS\Member::load( (int) $session['member_id'] ); } catch ( \OutOfRangeException $e ) { $this->member = new \IPS\Member; } } /* We might be able to get the member from a cookie */ else { $this->member = new \IPS\Member; } /* If we don't have a member, check the cookies */ if ( !$this->member->member_id and isset( \IPS\Request::i()->cookie['member_id'] ) and isset( \IPS\Request::i()->cookie['pass_hash'] ) ) { try { $member = \IPS\Member::load( (int) \IPS\Request::i()->cookie['member_id'] ); if ( $member->member_login_key === \IPS\Request::i()->cookie['pass_hash'] ) { $this->member = $member; /* Renew those cookies */ $expire = new \IPS\DateTime; $expire->add( new \DateInterval( 'P7D' ) ); \IPS\Request::i()->setCookie( 'member_id', $member->member_id, $expire ); \IPS\Request::i()->setCookie( 'pass_hash', $member->member_login_key, $expire ); } else { $this->member = new \IPS\Member; \IPS\Request::i()->setCookie( 'member_id', NULL ); \IPS\Request::i()->setCookie( 'pass_hash', NULL ); } } catch ( \OutOfRangeException $e ) { $this->member = new \IPS\Member; \IPS\Request::i()->setCookie( 'member_id', NULL ); \IPS\Request::i()->setCookie( 'pass_hash', NULL ); } } /* Work out the type */ if ( $this->member->member_id ) { if ( ( $session and $session['login_type'] === static::LOGIN_TYPE_ANONYMOUS ) or isset( \IPS\Request::i()->cookie['anon_login'] ) and \IPS\Request::i()->cookie['anon_login'] ) { $type = static::LOGIN_TYPE_ANONYMOUS; } else { $type = static::LOGIN_TYPE_MEMBER; } } else { $type = $this->userAgent->spider ? static::LOGIN_TYPE_SPIDER : static::LOGIN_TYPE_GUEST; } /* Set data */ $this->data = array( 'id' => $sessionId, 'member_name' => $this->member->member_id ? $this->member->name : '', 'seo_name' => $this->member->member_id ? ( $this->member->members_seo_name ?: '' ) : '', 'member_id' => $this->member->member_id ?: 0, 'ip_address' => \IPS\Request::i()->ipAddress(), 'browser' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '', 'running_time' => time(), 'login_type' => $type, 'member_group' => ($this->member->member_id) ? $this->member->member_group_id : \IPS\Settings::i()->guest_group, 'current_appcomponent' => '', 'current_module' => '', 'current_controller' => NULL, 'current_id' => intval( \IPS\Request::i()->id ), 'uagent_key' => $this->userAgent->useragentKey, 'uagent_version' => $this->userAgent->useragentVersion ?: '', 'uagent_type' => $this->userAgent->spider ? 'search' : 'browser', 'search_thread_id' => $session ? $session['search_thread_id'] : 0, 'search_thread_time' => $session ? $session['search_thread_time'] : 0, 'data' => $session ? $session['data'] : NULL, 'location_url' => $session ? $session['location_url'] : NULL, 'location_lang' => $session ? $session['location_lang'] : NULL, 'location_data' => $session ? $session['location_data'] : NULL, 'location_permissions' => $session ? $session['location_permissions'] : NULL, 'theme_id' => $session ? $session['theme_id'] : 0, ); /* Is this a spider? */ if( $this->userAgent->spider ) { /* Is this Facebook? Do we need to treat them as a user of a different group? */ if( $this->userAgent->useragentKey == 'facebook' ) { if( \IPS\core\ShareLinks\Service::load( 'facebook', 'share_key' )->enabled ) { if( $this->userAgent->facebookIpVerified( \IPS\Request::i()->ipAddress() ) AND \IPS\Settings::i()->fbc_bot_group != \IPS\Settings::i()->guest_group ) { $this->member->member_group_id = \IPS\Settings::i()->fbc_bot_group; } } } } return $this->data['data']; } /** * Set Session Member * * @param \IPS\Member $member Member object * @return void */ public function setMember( $member ) { parent::setMember( $member ); /* Make sure login key has been set */ $member->checkLoginKey(); /* Set the cookies */ $expire = new \IPS\DateTime; $expire->add( new \DateInterval( 'P7D' ) ); \IPS\Request::i()->setCookie( 'member_id', $member->member_id, $expire ); \IPS\Request::i()->setCookie( 'pass_hash', $member->member_login_key, $expire ); /* Make sure session handler saves during write() */ $this->save = TRUE; } /** * Write Session * * @param string $sessionId Session ID * @param string $data Session Data * @return void */ public function write( $sessionId, $data ) { if ( $data !== $this->data['data'] or $this->data['member_id'] != $this->member->member_id ) { $this->save = TRUE; } $this->data['member_name'] = $this->member->member_id ? $this->member->name : ''; $this->data['member_id'] = $this->member->member_id ?: NULL; $this->data['data'] = $data; $this->setLocationData(); $key = "session_{$sessionId}"; \IPS\Data\Cache::i()->$key = $this->data; if ( $this->save === TRUE and ( !empty( \IPS\Request::i()->cookie ) or $this->userAgent->spider or $this->member->member_id ) ) // If a guest and cookies are disabled we do not write to database to prevent duplicate sessions unless it's a search engine, which we deal with separately { \IPS\Db::i()->replace( 'core_sessions', $this->data, TRUE ); } } /** * Set the search start * * @return void */ public function startSearch() { $this->data['search_thread_id'] = \IPS\Db::i()->thread_id; $this->data['search_thread_time'] = time(); } /** * Set the search end * * @return void */ public function endSearch() { $this->data['search_thread_id'] = 0; $this->data['search_thread_time'] = 0; } /** * Set a theme ID * * @param int $themeId The theme id, of course * @return void */ public function setTheme( $themeId ) { if( !\IPS\Dispatcher::hasInstance() OR \IPS\Request::i()->isAjax() ) { return; } $this->data['theme_id'] = $themeId; $this->save = TRUE; } /** * Get the theme ID * * @return int */ public function getTheme() { if ( isset( $this->data['theme_id'] ) and $this->data['theme_id'] ) { return $this->data['theme_id']; } return NULL; } /** * Set basic location data * * @return void */ public function setLocationData() { if( !\IPS\Dispatcher::hasInstance() OR \IPS\Request::i()->isAjax() ) { return; } $this->data['current_appcomponent'] = \IPS\Dispatcher::i()->application ? \IPS\Dispatcher::i()->application->directory : ''; $this->data['current_module'] = \IPS\Dispatcher::i()->module ? \IPS\Dispatcher::i()->module->key : ''; $this->data['current_controller'] = \IPS\Dispatcher::i()->controller; $this->data['current_id'] = intval( \IPS\Request::i()->id ); } /** * Set the session location * * @param \IPS\Http\Url $url URL * @param array $groupIds Permission data * @param string $lang Language string * @param array $data Language data. Keys are the words, value is a boolean indicating if it's a language key (TRUE) or should be displayed as-is (FALSE) * @return void */ public function setLocation( \IPS\Http\Url $url, $groupIds, $lang, $data=array() ) { if( !\IPS\Dispatcher::hasInstance() OR \IPS\Request::i()->isAjax() ) { return; } $this->data['location_url'] = (string) $url; $this->data['location_lang'] = $lang; $this->data['location_data'] = json_encode( $data ); $this->data['current_id'] = intval( \IPS\Request::i()->id ); if ( !$this->data['current_appcomponent'] ) { $this->setLocationData(); } $groupIds = is_string( $groupIds ) ? explode( ',', $groupIds ) : ( $groupIds ?: NULL ); $app = \IPS\Application::load( $this->data['current_appcomponent'] ); if ( !$app->enabled ) { $groupIds = $groupIds ? array_intersect( $groupIds, explode( ',', $app->disabled_groups ) ) : explode( ',', $app->disabled_groups ); } $modulePermissions = \IPS\Application\Module::get( $this->data['current_appcomponent'], $this->data['current_module'], 'front' )->permissions(); if ( $modulePermissions['perm_view'] !== '*' ) { $groupIds = $groupIds ? array_intersect( $groupIds, explode( ',', $modulePermissions['perm_view'] ) ) : explode( ',', $modulePermissions['perm_view'] ); } $this->data['location_permissions'] = ( $groupIds !== NULL ) ? ( is_string( $groupIds ) ? $groupIds : implode( ',', $groupIds ) ) : NULL; } /** * Get the session location * * @param array $row Row from sessions * @return string|null */ public static function getLocation( $row ) { $location = NULL; if( !$row['location_lang'] ) { return $location; } if ( $row['location_permissions'] === NULL or $row['location_permissions'] === '*' or \IPS\Member::loggedIn()->inGroup( explode( ',', $row['location_permissions'] ), TRUE ) ) { $sprintf = array(); $data = json_decode( $row['location_data'], TRUE ); if ( !empty( $data ) ) { foreach ( $data as $key => $parse ) { $value = htmlspecialchars( $parse ? \IPS\Member::loggedIn()->language()->get( $key ) : $key, \IPS\HTMLENTITIES, 'UTF-8', FALSE ); $sprintf[] = $value; } } $location = \IPS\Member::loggedIn()->language()->addToStack( htmlspecialchars( $row['location_lang'], \IPS\HTMLENTITIES, 'UTF-8', FALSE ), FALSE, array( 'htmlsprintf' => $sprintf ) ); $location = "" . $location . ""; } return $location; } /** * Set the session as anonymous * * @return void */ public function setAnon() { $this->data['login_type'] = static::LOGIN_TYPE_ANONYMOUS; } /** * Set the session as anonymous * * @return void */ public function getAnon() { return (bool) $this->data['login_type'] == static::LOGIN_TYPE_ANONYMOUS; } /** * Close Session * * @return bool */ public function close() { return TRUE; } /** * Destroy Session * * @param string $sessionId Session ID * @return bool */ public function destroy( $sessionId ) { $key = "session_{$sessionId}"; unset( \IPS\Data\Cache::i()->$key ); if ( isset( $_SESSION['wizardKey'] ) ) { $dataKey = $_SESSION['wizardKey']; unset( \IPS\Data\Store::i()->$dataKey ); } \IPS\Db::i()->delete( 'core_sessions', array( 'id=?', $sessionId ) ); return TRUE; } /** * Garbage Collection * * @param int $lifetime Unix timestamp of the oldest session to keep * @return bool */ public function gc( $lifetime ) { foreach ( \IPS\Db::i()->select( '*', 'core_sessions', array( 'running_time$key ); } \IPS\Db::i()->delete( 'core_sessions', array( 'running_time