Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+139 -128
View File
@@ -11,21 +11,37 @@
namespace IPS\Session\Store;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Member;
use IPS\Member\Group;
use IPS\Redis as RedisClass;
use IPS\Session\Front;
use IPS\Session\Store;
use IPS\Settings;
use RedisException;
use function array_slice;
use function count;
use function defined;
use function in_array;
use function is_array;
use function is_null;
use const IPS\TEXT_ENCRYPTION_KEY;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Redis Session Handler
*/
class _Redis extends \IPS\Session\Store
class Redis extends Store
{
/**
* @brief Default expiration for keys in seconds
*/
static protected $ttl = 1800; #30 mins
static protected int $ttl = 1800; #30 mins
/**
* Return the hash we will use to obfuscate the session ID
@@ -34,29 +50,29 @@ class _Redis extends \IPS\Session\Store
*/
public static function getHash(): string
{
if ( \IPS\TEXT_ENCRYPTION_KEY )
if ( TEXT_ENCRYPTION_KEY )
{
return \IPS\TEXT_ENCRYPTION_KEY;
return TEXT_ENCRYPTION_KEY;
}
return \IPS\Settings::i()->sql_pass;
return Settings::i()->sql_pass;
}
/**
* Load the session from the storage engine
*
* @param string $sessionId Session ID
* @param string $sessionId Session ID
* @return array|NULL
*/
public function loadSession( $sessionId )
public function loadSession( string $sessionId ): ?array
{
if ( $result = \IPS\Redis::i()->hGetAll( static::_key( 'session_id_' . md5( $sessionId . static::getHash() ) ) ) )
if ( $result = RedisClass::i()->hGetAll( static::_key( 'session_id_' . md5( $sessionId . static::getHash() ) ) ) )
{
try
{
return \IPS\Redis::i()->decode( $result['data'] );
return RedisClass::i()->decode( $result['data'] );
}
catch( \RedisException $e ){}
catch( RedisException $e ){}
}
return NULL;
@@ -65,73 +81,73 @@ class _Redis extends \IPS\Session\Store
/**
* Update the session storage engine
*
* @param array $data Session data to store
* @param array $data Session data to store
* @return void
*/
public function updateSession( $data )
public function updateSession( array $data ) : void
{
/* Groups are loaded into memory so this does not cause a query */
$group = \IPS\Member\Group::load( $data['member_group'] );
$group = Group::load( $data['member_group'] );
/* Update the specific session */
\IPS\Redis::i()->del( static::_key( 'session_id_' . md5( $data['id'] . static::getHash() ) ) );
\IPS\Redis::i()->hMSet( static::_key( 'session_id_' . md5( $data['id'] . static::getHash() ) ), array(
RedisClass::i()->del( static::_key( 'session_id_' . md5( $data['id'] . static::getHash() ) ) );
RedisClass::i()->hMSet( static::_key( 'session_id_' . md5( $data['id'] . static::getHash() ) ), array(
'member_id' => $data['member_id'],
'member_name' => $data['member_name'],
'seo_name' => $data['seo_name'],
'member_group' => $data['member_group'],
'login_type' => $data['login_type'],
'in_editor' => $data['in_editor'],
'data' => \IPS\Redis::i()->encode( $data )
'data' => RedisClass::i()->encode( $data )
), static::$ttl );
/* Update the list of sessions for the online list [ microtime => sessionID ] */
\IPS\Redis::i()->zAdd( static::_key( 'session_map' ), time(), 'session_id_' . md5( $data['id'] . static::getHash() ), static::$ttl );
RedisClass::i()->zAdd( static::_key( 'session_map' ), time(), 'session_id_' . md5( $data['id'] . static::getHash() ), static::$ttl );
/* Update users list */
if ( $data['uagent_type'] == 'search' )
{
/* Make a unique row based on IP and user-agent to prevent multiple rows for each spider */
\IPS\Redis::i()->zAdd( static::_key( 'session_online_spiders' ), time(), md5( $data['ip_address'] . $data['browser'] ), static::$ttl );
RedisClass::i()->zAdd( static::_key( 'session_online_spiders' ), time(), md5( $data['ip_address'] . $data['browser'] ), static::$ttl );
}
else if ( $data['member_id'] )
{
\IPS\Redis::i()->zAdd( static::_key( 'session_online_users' ), time(), $data['member_id'] . '__' . 'session_id_' . md5( $data['id'] . static::getHash() ), static::$ttl );
RedisClass::i()->zAdd( static::_key( 'session_online_users' ), time(), $data['member_id'] . '__' . 'session_id_' . md5( $data['id'] . static::getHash() ), static::$ttl );
}
else
{
/* A guest may have one ip address but multiple devices, but we don't really need to track that */
\IPS\Redis::i()->zAdd( static::_key( 'session_online_guests' ), time(), md5( $data['ip_address'] ), static::$ttl );
RedisClass::i()->zAdd( static::_key( 'session_online_guests' ), time(), md5( $data['ip_address'] ), static::$ttl );
}
/* Delete old items */
if ( ! \IPS\Redis::i()->get( static::_key( 'session_cleanup' ) ) )
if ( ! RedisClass::i()->get( static::_key( 'session_cleanup' ) ) )
{
/* Do a little clean up */
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_map' ), 0, time() - static::$ttl );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_spiders' ), 0, time() - static::$ttl );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_users' ), 0, time() - static::$ttl );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_guests' ), 0, time() - static::$ttl );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_map' ), 0, time() - static::$ttl );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_spiders' ), 0, time() - static::$ttl );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_users' ), 0, time() - static::$ttl );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_guests' ), 0, time() - static::$ttl );
/* And do it again in 3ish mins */
\IPS\Redis::i()->setEx( static::_key( 'session_cleanup' ), 180, time() );
RedisClass::i()->setEx( static::_key( 'session_cleanup' ), 180, time() );
}
}
/**
* Delete from the session engine
*
* @param string $sessionId Session ID
* @param string $sessionId Session ID
* @return void
*/
public function deleteSession( $sessionId )
public function deleteSession( string $sessionId ) : void
{
$data = $this->loadSession( $sessionId );
\IPS\Redis::i()->del( static::_key( 'session_id_' . md5( $sessionId . static::getHash() ) ) );
\IPS\Redis::i()->zRem( static::_key( 'session_map' ), 'session_id_' . md5( $sessionId . static::getHash() ) );
\IPS\Redis::i()->zRem( static::_key( 'session_online_spiders' ), md5( $data['ip_address'] . $data['browser'] ) );
\IPS\Redis::i()->zRem( static::_key( 'session_online_users' ), $data['member_id'] . '__' . 'session_id_' . md5( $data['id'] . static::getHash() ) );
\IPS\Redis::i()->zRem( static::_key( 'session_online_guests' ), md5( $data['ip_address'] ) );
RedisClass::i()->del( static::_key( 'session_id_' . md5( $sessionId . static::getHash() ) ) );
RedisClass::i()->zRem( static::_key( 'session_map' ), 'session_id_' . md5( $sessionId . static::getHash() ) );
RedisClass::i()->zRem( static::_key( 'session_online_spiders' ), md5( $data['ip_address'] . $data['browser'] ) );
RedisClass::i()->zRem( static::_key( 'session_online_users' ), $data['member_id'] . '__' . 'session_id_' . md5( $data['id'] . static::getHash() ) );
RedisClass::i()->zRem( static::_key( 'session_online_guests' ), md5( $data['ip_address'] ) );
}
/**
@@ -142,33 +158,33 @@ class _Redis extends \IPS\Session\Store
* @param array|NULL $keepSessionIds Array of session ids to keep [optional]
* @return void
*/
public function deleteByMember( int $memberId, string $userAgent=NULL, array $keepSessionIds=NULL )
public function deleteByMember( int $memberId, string $userAgent=NULL, array $keepSessionIds=NULL ) : void
{
if ( ! \is_array( $keepSessionIds ) and ! \is_null( $keepSessionIds ) )
if ( ! is_array( $keepSessionIds ) and ! is_null( $keepSessionIds ) )
{
$keepSessionIds = array( $keepSessionIds );
}
$sessionMap = \IPS\Redis::i()->zRange( static::_key( 'session_online_users' ), 0, -1 );
$sessionMap = RedisClass::i()->zRange( static::_key( 'session_online_users' ), 0, -1 );
foreach( $sessionMap as $index => $redisKey )
{
$key = explode( '__', $redisKey );
$session = \IPS\Redis::i()->hMGet( $key[1], array( 'data' ) );
$session = RedisClass::i()->hMGet( $key[1], array( 'data' ) );
try
{
$sessionMap[ $index ] = \IPS\Redis::i()->decode( $session['data'] );
$sessionMap[ $index ] = RedisClass::i()->decode( $session['data'] );
}
catch( \RedisException $e ){}
catch( RedisException $e ){}
}
foreach( $sessionMap as $session )
{
/* SessionMap may not link to a valid session, remove if that is the case */
if( !\is_array( $session ) )
if( !is_array( $session ) )
{
\IPS\Redis::i()->zRem( static::_key( 'session_map' ), $session );
RedisClass::i()->zRem( static::_key( 'session_map' ), $session );
continue;
}
@@ -183,7 +199,7 @@ class _Redis extends \IPS\Session\Store
$delete = false;
}
if ( $keepSessionIds and \is_array( $keepSessionIds ) and \in_array( $session['id'], $keepSessionIds ) )
if ( $keepSessionIds and in_array( $session['id'], $keepSessionIds ) )
{
$delete = false;
}
@@ -200,9 +216,9 @@ class _Redis extends \IPS\Session\Store
*
* @return array of session IDs
*/
public function getSessionIds()
public function getSessionIds(): array
{
if ( $result = \IPS\Redis::i()->zRangeByScore( static::_key( 'session_map' ), '-inf', '+inf', array( 'withscores' => false ) ) )
if ( $result = RedisClass::i()->zRangeByScore( static::_key( 'session_map' ), '-inf', '+inf', array( 'withscores' => false ) ) )
{
return $result;
}
@@ -213,14 +229,14 @@ class _Redis extends \IPS\Session\Store
/**
* Delete from the session engine
*
* @param int $memberId You can probably figure this out right?
* @param int $memberId You can probably figure this out right?
* @return array|FALSE
*/
public function getLatestMemberSession( $memberId )
public function getLatestMemberSession( int $memberId ): array|FALSE
{
$redis = \IPS\Redis::i()->zRevRangeByScore( static::_key( 'session_online_users' ), '+inf', '-inf', array('withscores' => FALSE, 'alpha' => TRUE ) );
$redis = RedisClass::i()->zRevRangeByScore( static::_key( 'session_online_users' ), '+inf', '-inf', array('withscores' => FALSE, 'alpha' => TRUE ) );
if( \is_array( $redis ) )
if( is_array( $redis ) )
{
foreach ( $redis as $data )
{
@@ -228,13 +244,13 @@ class _Redis extends \IPS\Session\Store
if ( $id == $memberId )
{
if ( $result = \IPS\Redis::i()->hGetAll( static::_key( $sessionKey ) ) )
if ( $result = RedisClass::i()->hGetAll( static::_key( $sessionKey ) ) )
{
try
{
return \IPS\Redis::i()->decode( $result['data'] );
return RedisClass::i()->decode( $result['data'] );
}
catch ( \RedisException $e ) {}
catch ( RedisException $e ) {}
}
}
}
@@ -246,31 +262,31 @@ class _Redis extends \IPS\Session\Store
/**
* Clear sessions - abstracted so it can be called externally without initiating a session
*
* @param int $timeout Sessions older than the number of seconds provided will be deleted
* @param int $timeout Sessions older than the number of seconds provided will be deleted
* @return void
*/
public static function clearSessions( $timeout )
public static function clearSessions( int $timeout ) : void
{
/* Remove the public facing items. This is only called by PHP's session gc so individual sessions do not need removing as they are cleaned by Redis' TTL */
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_map' ), 0, time() - $timeout );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_spiders' ), 0, time() - $timeout );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_users' ), 0, time() - $timeout );
\IPS\Redis::i()->zRemRangeByScore( static::_key( 'session_online_guests' ), 0, time() - $timeout );
\IPS\Redis::i()->del( static::_key( 'session_onlinelist' ) );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_map' ), 0, time() - $timeout );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_spiders' ), 0, time() - $timeout );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_users' ), 0, time() - $timeout );
RedisClass::i()->zRemRangeByScore( static::_key( 'session_online_guests' ), 0, time() - $timeout );
RedisClass::i()->del( static::_key( 'session_onlinelist' ) );
}
/**
* Redis key
*/
protected static $_redisKey;
protected static ?string $_redisKey = NULL;
/**
* Returns a key to be stored with Redis
*
* @param string $key Key suffix
* @param string $key Key suffix
* @return string
*/
protected static function _key( $key )
protected static function _key( string $key ): string
{
/* Only manage the session_onlist key which is prone to corruption. We don't want to wipe out the online list each time this file fails */
if ( $key == 'session_onlinelist' )
@@ -278,11 +294,11 @@ class _Redis extends \IPS\Session\Store
if ( !static::$_redisKey )
{
/* Last access ensures that the data is not stale if we fail back to MySQL and then go back to Redis later */
if ( !( static::$_redisKey = \IPS\Redis::i()->get( 'redisKey_session' ) ) OR ! \IPS\Redis::i()->get( 'redisStore_lastAccess' ) )
if ( !( static::$_redisKey = RedisClass::i()->get( 'redisKey_session' ) ) OR ! RedisClass::i()->get( 'redisStore_lastAccess' ) )
{
static::$_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_session', 604800, static::$_redisKey );
\IPS\Redis::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
RedisClass::i()->setex( 'redisKey_session', 604800, static::$_redisKey );
RedisClass::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
}
}
@@ -299,54 +315,49 @@ class _Redis extends \IPS\Session\Store
*
* @return void
*/
protected static function resetKey()
protected static function resetKey() : void
{
static::$_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_session', 604800, static::$_redisKey );
RedisClass::i()->setex( 'redisKey_session', 604800, static::$_redisKey );
}
/**
* Redis key
*/
protected $fetchAttempt = 0;
/**
* @var array|null cache online user data after fetching it
*/
protected ?array $onlineUsers = null;
protected int $fetchAttempt = 0;
/**
* Fetch all online users (but not spiders)
*
* @param int $flags Bitwise flags
* @param string $sort Sort direction
* @param array|NULL $limit Limit [ offset, limit ]
* @param int $memberGroup Limit by a specific member group ID
* @param boolean $showAnonymous Show anonymously logged in peoples?
* @return array
* @param int $flags Bitwise flags
* @param string $sort Sort direction
* @param array|null $limit Limit [ offset, limit ]
* @param int|null $memberGroup Limit by a specific member group ID
* @param boolean $showAnonymous Show anonymously logged in peoples?
* @return array|int
*/
public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE )
public function getOnlineUsers( int $flags=0, string $sort='desc', array $limit=NULL, int $memberGroup=NULL, bool $showAnonymous=FALSE ): array|int
{
if( $this->onlineUsers === NULL )
try
{
try
{
$this->onlineUsers = \IPS\Redis::i()->lRange( static::_key( 'session_onlinelist' ), 0, -1 );
}
catch ( \RedisException $e )
{
/* Something went wrong, so reset the key to force a new file */
static::resetKey();
}
$results = RedisClass::i()->lRange( static::_key( 'session_onlinelist' ), 0, -1 );
}
if ( ! $this->onlineUsers )
catch ( RedisException $e )
{
/* Something went wrong, so reset the key to force a new file */
static::resetKey();
$results = NULL;
}
if ( ! $results )
{
/* Ensure file is deleted */
try
{
\IPS\Redis::i()->del( static::_key( 'session_onlinelist' ) );
RedisClass::i()->del( static::_key( 'session_onlinelist' ) );
}
catch ( \RedisException $e )
catch ( RedisException $e )
{
/* Something went wrong, so reset the key to force a new file */
static::resetKey();
@@ -354,30 +365,30 @@ class _Redis extends \IPS\Session\Store
$options = array(
'sort' => $sort === 'asc' ? 'asc' : 'desc',
'store' => \IPS\Redis::i()->prefix . static::_key( 'session_onlinelist' ),
'store' => RedisClass::i()->prefix . static::_key( 'session_onlinelist' ),
'alpha' => true,
'by' => 'nosort ' . $sort === 'asc' ? 'asc' : 'desc',
'ttl' => 30, /* This ensures the stored file session_online only lasts for 30 seconds */
'get' => array(
static::_key( \IPS\Redis::i()->prefix ) . '*->member_id',
static::_key( \IPS\Redis::i()->prefix ) . '*->member_name',
static::_key( \IPS\Redis::i()->prefix ) . '*->seo_name',
static::_key( \IPS\Redis::i()->prefix ) . '*->member_group',
static::_key( \IPS\Redis::i()->prefix ) . '*->login_type',
static::_key( \IPS\Redis::i()->prefix ) . '*->data'
static::_key( RedisClass::i()->prefix ) . '*->member_id',
static::_key( RedisClass::i()->prefix ) . '*->member_name',
static::_key( RedisClass::i()->prefix ) . '*->seo_name',
static::_key( RedisClass::i()->prefix ) . '*->member_group',
static::_key( RedisClass::i()->prefix ) . '*->login_type',
static::_key( RedisClass::i()->prefix ) . '*->data'
)
);
\IPS\Redis::i()->sort( static::_key( 'session_map' ), $options );
RedisClass::i()->sort( static::_key( 'session_map' ), $options );
try
{
/* Force expiration in 60 seconds to prevent stale caches hanging around */
\IPS\Redis::i()->expire( static::_key( 'session_onlinelist' ), 60 );
$this->onlineUsers = \IPS\Redis::i()->lRange( static::_key( 'session_onlinelist' ), 0, -1 );
/* Force expiration in 30 seconds to prevent stale caches hanging around */
RedisClass::i()->expire( static::_key( 'session_onlinelist' ), 30 );
$results = RedisClass::i()->lRange( static::_key( 'session_onlinelist' ), 0, -1 );
}
catch ( \RedisException $e )
catch ( RedisException $e )
{
$this->fetchAttempt++;
@@ -387,11 +398,11 @@ class _Redis extends \IPS\Session\Store
if ( $this->fetchAttempt < 2 )
{
/* And try again, but only once more to prevent an infinite loop */
return $this->getOnlineUsers( $flags, $sort, $limit, $memberGroup, $showAnonymous );
return $this->getOnlineUsers($flags, $sort, $limit, $memberGroup, $showAnonymous);
}
else
{
$this->onlineUsers = array();
$results = array();
}
}
}
@@ -403,7 +414,7 @@ class _Redis extends \IPS\Session\Store
$return = array();
$i = 0;
while( $i < \count( $this->onlineUsers ) )
while( $i < count( $results ) )
{
$fields = array();
$data = NULL;
@@ -413,9 +424,9 @@ class _Redis extends \IPS\Session\Store
{
try
{
$data = \IPS\Redis::i()->decode( $this->onlineUsers[ $i++ ] );
$data = RedisClass::i()->decode( $results[ $i++ ] );
}
catch( \RedisException $e )
catch( RedisException $e )
{
$data = NULL;
}
@@ -423,15 +434,15 @@ class _Redis extends \IPS\Session\Store
/* login_type must be cast as an integer or else anonymous state can be lost when adjustSessions() runs */
elseif( $field === 'login_type' )
{
$fields[ $field ] = (int) $this->onlineUsers[ $i++ ];
$fields[ $field ] = (int) $results[ $i++ ];
}
else
{
$fields[ $field ] = $this->onlineUsers[ $i++ ];
$fields[ $field ] = $results[ $i++ ];
}
}
if ( \is_array( $data ) AND \count( $data ) )
if ( is_array( $data ) AND count( $data ) )
{
/* Have we already fetched this member? */
if ( $fields['member_id'] and isset( $return[ $fields['member_id'] ] ) )
@@ -446,12 +457,12 @@ class _Redis extends \IPS\Session\Store
}
/* Ignore spiders */
if ( ! $fields['member_id'] and ! ( $data['login_type'] == \IPS\Session\Front::LOGIN_TYPE_GUEST or $data['login_type'] == \IPS\Session\Front::LOGIN_TYPE_INCOMPLETE ) )
if ( ! $fields['member_id'] and ! ( $data['login_type'] == Front::LOGIN_TYPE_GUEST or $data['login_type'] == Front::LOGIN_TYPE_INCOMPLETE ) )
{
continue;
}
$return[ $fields['member_id'] ? $fields['member_id'] : $data['ip_address'] ] = array_merge( $data, $fields );
$return[ $fields['member_id'] ?: $data['ip_address'] ] = array_merge( $data, $fields );
}
}
@@ -482,7 +493,7 @@ class _Redis extends \IPS\Session\Store
continue;
}
if ( ! $showAnonymous and $data['login_type'] == \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS and ( !\IPS\Member::loggedIn()->member_id OR $data['member_id'] != \IPS\Member::loggedIn()->member_id ) )
if ( ! $showAnonymous and $data['login_type'] == Front::LOGIN_TYPE_ANONYMOUS and ( !Member::loggedIn()->member_id OR $data['member_id'] != Member::loggedIn()->member_id ) )
{
continue;
}
@@ -493,7 +504,7 @@ class _Redis extends \IPS\Session\Store
/* Count only? */
if ( $flags & static::ONLINE_COUNT_ONLY )
{
return \count( $members );
return count( $members );
}
/* Hooray for PHP 7 */
@@ -504,7 +515,7 @@ class _Redis extends \IPS\Session\Store
if ( $limit )
{
return \array_slice( $members, $limit[0], $limit[1], TRUE );
return array_slice( $members, $limit[0], $limit[1], TRUE );
}
return $members;
@@ -516,18 +527,18 @@ class _Redis extends \IPS\Session\Store
/**
* Fetch all members active at a specific location
*
* @param string $app Application directory (core, forums, etc)
* @param string $module Module
* @param string $controller Controller
* @param int $id Current item ID (empty if none)
* @param string $url Current viewing URL
* @param string $app Application directory (core, forums, etc)
* @param string $module Module
* @param string $controller Controller
* @param int $id Current item ID (empty if none)
* @param string $url Current viewing URL
* @return array
*/
public function getOnlineMembersByLocation( $app, $module, $controller, $id, $url )
public function getOnlineMembersByLocation( string $app, string $module, string $controller, ?int $id, string $url ): array
{
$members = array();
foreach( $this->getOnlineUsers( static::ONLINE_MEMBERS, 'desc' ) as $member )
foreach($this->getOnlineUsers(static::ONLINE_MEMBERS, 'desc') as $member )
{
if ( $member['current_appcomponent'] == $app and $member['current_module'] == $module and $member['current_controller'] == $controller and $member['current_id'] == $id )
{