Version 4.7.23
This commit is contained in:
1 parent
7124a02564
commit
25ddeb65d6
1791 files changed
+76990
-44452
No files matched your search
+185
-87
@@ -10,6 +10,11 @@
|
||||
|
||||
namespace IPS;
|
||||
|
||||
use BadFunctionCallException;
|
||||
use IPS\Db;
|
||||
use IPS\Settings;
|
||||
use IPS\Text\Encrypt;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -120,8 +125,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'new_device_email' => 16777216, // Send an email when a new device is used to log in.
|
||||
'no_solved_reenage' => 33554432, // User does not want to get topic re-engagement emails
|
||||
'datalayer_pii_optout' => 67108864, // User has opted in to having their PII collected by datalayer
|
||||
'email_messages_bounce' => 134217728, // User's email keeps returning hard bounces so they need to change it
|
||||
// 268435456 - cookie_optout moved to cookie preferences
|
||||
'email_messages_bounce' => 134217728, // @deprecated
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -153,6 +157,11 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @brief Following anonymously
|
||||
*/
|
||||
const FOLLOW_ANONYMOUS = 2;
|
||||
|
||||
/**
|
||||
* @brief Period for login after inactivity notification
|
||||
*/
|
||||
const LOGIN_INACTIVITY_NOTIFICATION = 'P6M';
|
||||
|
||||
/**
|
||||
* @brief Cached logged in member
|
||||
@@ -305,7 +314,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$this->last_visit = NULL;
|
||||
$this->_data['pp_main_photo'] = NULL;
|
||||
$this->_data['pp_thumb_photo'] = NULL;
|
||||
$this->_data['failed_logins'] = NULL;
|
||||
$this->_data['mfa_details'] = NULL;
|
||||
$this->_data['pp_reputation_points'] = 0;
|
||||
$this->_data['signature'] = '';
|
||||
@@ -474,12 +482,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/* If the email was changed, make sure that the bounce flag is unset so the member no longer sees the error message */
|
||||
if ( isset( $changes['email'] ) )
|
||||
{
|
||||
$this->members_bitoptions['email_messages_bounce'] = 0;
|
||||
}
|
||||
|
||||
parent::save();
|
||||
|
||||
if ( $new )
|
||||
@@ -645,7 +647,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
public function set_member_group_id( $value )
|
||||
{
|
||||
$this->_data['member_group_id'] = (int) $value;
|
||||
$this->_group = NULL;
|
||||
$this->resetGroupsCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,7 +665,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
$this->_data['mgroup_others'] = implode( ',', $groups );
|
||||
$this->_group = NULL;
|
||||
$this->resetGroupsCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1036,7 +1038,17 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @brief Cached groups check
|
||||
*/
|
||||
protected $_groups = NULL;
|
||||
|
||||
|
||||
/**
|
||||
* Reset the group and groups cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function resetGroupsCaches()
|
||||
{
|
||||
$this->_group = NULL;
|
||||
$this->_groups = NULL;
|
||||
}
|
||||
/**
|
||||
* Get an array of the group IDs (including secondary groups) this member belongs to
|
||||
*
|
||||
@@ -1078,7 +1090,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function socialGroups( $bypassCache = FALSE )
|
||||
{
|
||||
if ( $this->_socialGroups === NULL )
|
||||
if ( $this->_socialGroups === NULL OR $bypassCache )
|
||||
{
|
||||
/* If this is a guest, they will not have any social groups - save the query */
|
||||
if( !$this->member_id )
|
||||
@@ -1089,6 +1101,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if( $bypassCache )
|
||||
{
|
||||
/* @note SELECT_FROM_WRITE_SERVER added in Pull #2341 @todo check if needed */
|
||||
$this->_socialGroups = iterator_to_array( \IPS\Db::i()
|
||||
->select( 'group_id', 'core_sys_social_group_members', array( 'member_id=?', $this->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )
|
||||
->setKeyField( 'group_id' )
|
||||
@@ -1141,7 +1154,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
else
|
||||
{
|
||||
$statuses = $moderatorOnly ? ( \IPS\Member\Club::STATUS_MODERATOR . "','" . \IPS\Member\Club::STATUS_LEADER ) : ( \IPS\Member\Club::STATUS_MEMBER . "','" . \IPS\Member\Club::STATUS_MODERATOR . "','" . \IPS\Member\Club::STATUS_LEADER ) ;
|
||||
|
||||
|
||||
/* @note SELECT_FROM_WRITE_SERVER honors $fromWriteServer */
|
||||
$this->_clubs[ \intval( $moderatorOnly ) ] = iterator_to_array( \IPS\Db::i()->select( 'club_id', 'core_clubs_memberships', array( "member_id=? AND status IN('" . $statuses . "')", $this->member_id ), NULL, NULL, NULL, NULL, $fromWriteServer ? \IPS\Db::SELECT_FROM_WRITE_SERVER : 0 ) );
|
||||
}
|
||||
}
|
||||
@@ -1408,16 +1422,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return isset( $this->_data['warn_level'] ) ? (int) $this->_data['warn_level'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed login details
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_failed_logins()
|
||||
{
|
||||
return json_decode( $this->_data['failed_logins'], TRUE ) ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get member title
|
||||
@@ -1626,25 +1630,41 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
return static::_followers( 'member', $this->member_id, $privacy, $frequencyTypes, $date, $limit, $order );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set failed login details
|
||||
* Record a failed login attempt
|
||||
*
|
||||
* @param array $data Data
|
||||
* @return void
|
||||
*/
|
||||
public function set_failed_logins( $data )
|
||||
public function failedLogin()
|
||||
{
|
||||
$this->_data['failed_logins'] = json_encode( $data );
|
||||
|
||||
$highest = 0;
|
||||
foreach ( $data as $ipAddress => $times )
|
||||
\IPS\Db::i()->insert( 'core_login_failures', [
|
||||
'login_member_id' => $this->member_id ?: NULL,
|
||||
'login_date' => ( new \IPS\DateTime )->getTimestamp(),
|
||||
'login_ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'login_email' => $this->member_id ? NULL : $this->email
|
||||
]);
|
||||
|
||||
if( $this->member_id )
|
||||
{
|
||||
if ( $highest < \count( $times ) )
|
||||
{
|
||||
$highest = \count( $times );
|
||||
}
|
||||
$this->recountFailedLogins();
|
||||
}
|
||||
$this->failed_login_count = $highest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recount failed logins.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function recountFailedLogins()
|
||||
{
|
||||
$where = [ [ 'login_date>=?', ( \IPS\Settings::i()->ipb_bruteforce_unlock ? ( new \IPS\DateTime() )->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) )->getTimestamp() : 0 ) ] ];
|
||||
$where[] = [ 'login_ip_address IS NOT NULL AND login_member_id=?', $this->member_id ];
|
||||
|
||||
$failures = iterator_to_array( \IPS\Db::i()->select( 'count(login_ip_address)', 'core_login_failures', $where, NULL, NULL, 'login_ip_address' ) );
|
||||
$this->failed_login_count = count( $failures ) ? max( $failures ) : 0;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2278,7 +2298,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
if( !\IPS\Dispatcher::hasInstance() OR !( \IPS\Member::loggedIn()->isAdmin() OR \IPS\Member::loggedIn()->member_id === $this->member_id ) )
|
||||
{
|
||||
$select = 'member_id';
|
||||
$publicFields = \IPS\Db::i()->select( '*', 'core_pfields_data', array( 'pf_member_hide != ?', 'hide' ) );
|
||||
$publicFields = \IPS\Db::i()->select( '*', 'core_pfields_data', array( 'pf_topic_hide != ?', 'hide' ) );
|
||||
foreach( $publicFields as $field )
|
||||
{
|
||||
if( $field['pf_topic_hide'] == 'all' OR ( $field['pf_topic_hide'] == 'staff' AND ( \IPS\Member::loggedIn()->isAdmin() OR \IPS\Member::loggedIn()->modPermissions() ) )
|
||||
@@ -3502,7 +3522,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @apiresponse string|null profileUrl URL to profile
|
||||
* @clientapiresponse bool validating Whether or not the validating flag is set on the member account
|
||||
* @apiresponse int posts Number of content item submissions member has made
|
||||
* @apiresponse datetime|null lastActivity Last activity date on the site.
|
||||
* @clientapiresponse datetime|null lastActivity Last activity date on the site.
|
||||
* @clientapiresponse datetime|null lastVisit Last distinct visit date on the site.
|
||||
* @clientapiresponse datetime|null lastPost Latest content submission date.
|
||||
* @apiresponse int profileViews Number of times member's profile has been viewed
|
||||
@@ -3598,10 +3618,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$return['validating'] = (bool) $this->members_bitoptions['validating'];
|
||||
}
|
||||
$return['posts'] = $this->member_posts;
|
||||
$return['lastActivity'] = ( $this->last_activity AND !$this->isOnlineAnonymously() ) ? \IPS\DateTime::ts( $this->last_activity )->rfc3339() : NULL;
|
||||
|
||||
|
||||
if( !$authorizedMember )
|
||||
{
|
||||
$return['lastActivity'] = ( $this->last_activity ) ? \IPS\DateTime::ts( $this->last_activity )->rfc3339() : NULL;
|
||||
$return['lastVisit'] = $this->last_visit ? \IPS\DateTime::ts( $this->last_visit )->rfc3339() : NULL;
|
||||
$return['lastPost'] = $this->member_last_post ? \IPS\DateTime::ts( $this->member_last_post )->rfc3339() : NULL;
|
||||
}
|
||||
@@ -3626,7 +3646,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
$return['rank'] = $this->rank() ? $this->rank()->apiOutput( $authorizedMember ) : NULL;
|
||||
$return['achievements_points'] = $this->achievements_points;
|
||||
$return['allowAdminEmails'] = (bool) $this->allow_admin_emails;
|
||||
$return['allowAdminEmails'] = (bool) $this->allow_admin_mails;
|
||||
$return['completed'] = (bool) $this->completed;
|
||||
|
||||
return $return;
|
||||
@@ -3685,6 +3705,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return \IPS\Db::i()->select( 'COUNT(*)', 'core_members_known_devices', array( 'member_id=?', $this->member_id ) )->first();
|
||||
}
|
||||
|
||||
protected array $_failedLoginCache;
|
||||
|
||||
/**
|
||||
* Check if account is locked - returns FALSE if account is unlocked, an \IPS\DateTime object if the account is locked until a certain time, or TRUE if account is locked indefinitely
|
||||
@@ -3693,33 +3715,33 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function unlockTime()
|
||||
{
|
||||
$failedLogins = $this->member_id ? $this->failed_logins : ( ( isset( \IPS\Data\Store::i()->failedLogins[ $this->email ] ) ) ? \IPS\Data\Store::i()->failedLogins[ $this->email ] : array() );
|
||||
if( !\IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( \IPS\Settings::i()->ipb_bruteforce_attempts and isset( $failedLogins[ \IPS\Request::i()->ipAddress() ] ) and \count( $failedLogins[ \IPS\Request::i()->ipAddress() ] ) >= \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
if( !isset( $this->_failedLoginCache[ \IPS\Request::i()->ipAddress() ] ) )
|
||||
{
|
||||
$where = [ [ 'login_date>=? AND login_ip_address=?', ( new \IPS\DateTime() )->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) )->getTimestamp(), \IPS\Request::i()->ipAddress() ] ];
|
||||
if ( $this->member_id )
|
||||
{
|
||||
$where[] = [ 'login_member_id=?', $this->member_id ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ 'login_email=?', $this->email ];
|
||||
}
|
||||
|
||||
$this->_failedLoginCache[ \IPS\Request::i()->ipAddress() ] = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_failures', $where ) );
|
||||
}
|
||||
|
||||
if( count( $this->_failedLoginCache[ \IPS\Request::i()->ipAddress() ] ) > \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
{
|
||||
if ( \IPS\Settings::i()->ipb_bruteforce_period and \IPS\Settings::i()->ipb_bruteforce_unlock )
|
||||
{
|
||||
$failedLogins = $failedLogins[ \IPS\Request::i()->ipAddress() ];
|
||||
sort( $failedLogins );
|
||||
|
||||
while ( \count( $failedLogins ) > \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
{
|
||||
/* We want to remove the oldest logins here so the most recent are checked */
|
||||
array_shift( $failedLogins );
|
||||
}
|
||||
$unlockTime = \IPS\DateTime::ts( array_pop( $failedLogins ) );
|
||||
$unlockTime = $unlockTime->add( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) );
|
||||
|
||||
/* If Unlock Time is in the past, return FALSE to avoid the exception and allow login */
|
||||
if ( $unlockTime->getTimestamp() < time() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Otherwise that is what we're returning */
|
||||
return $unlockTime;
|
||||
return \IPS\DateTime::ts( max( array_column( $this->_failedLoginCache[ \IPS\Request::i()->ipAddress() ], 'login_date' ) ) )->add( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) );
|
||||
}
|
||||
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -3732,18 +3754,24 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @param string $ipAddress IP Address
|
||||
* @return int
|
||||
*/
|
||||
public function failedLoginCount( $ipAddress )
|
||||
public function failedLoginCount( $ipAddress ): int
|
||||
{
|
||||
if( $this->member_id )
|
||||
if( !isset( $this->_failedLoginCache[ $ipAddress ] ) )
|
||||
{
|
||||
return ( isset( $this->failed_logins[ $ipAddress ] ) ) ? \count( $this->failed_logins[ $ipAddress ] ) : 0;
|
||||
}
|
||||
elseif( $this->email )
|
||||
{
|
||||
$failedLogins = ( isset( \IPS\Data\Store::i()->failedLogins[ $this->email ] ) ) ? \IPS\Data\Store::i()->failedLogins[ $this->email ] : array();
|
||||
$where = [ [ 'login_date>=? AND login_ip_address=?', ( new \IPS\DateTime() )->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) )->getTimestamp(), \IPS\Request::i()->ipAddress() ] ];
|
||||
if( $this->member_id )
|
||||
{
|
||||
$where[] = [ 'login_member_id=?', $this->member_id ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ 'login_email=?', $this->email ];
|
||||
}
|
||||
|
||||
return ( isset( $failedLogins[ $ipAddress ] ) ) ? \count( $failedLogins[ $ipAddress ] ) : 0;
|
||||
$this->_failedLoginCache[ $ipAddress ] = \IPS\Db::i()->select( '*', 'core_login_failures', $where )->first();
|
||||
}
|
||||
|
||||
return count( $this->_failedLoginCache[ $ipAddress ] );
|
||||
}
|
||||
|
||||
/* !Permissions */
|
||||
@@ -3880,6 +3908,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function recountNotifications()
|
||||
{
|
||||
/* @note SELECT_FROM_WRITE_SERVER Race condition, need true value from write server as read can be slightly out */
|
||||
$this->notification_cnt = \IPS\Db::i()->select( 'COUNT(*)', 'core_notifications', array( '`member`=? AND read_time IS NULL', $this->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
|
||||
$this->save();
|
||||
}
|
||||
@@ -4129,7 +4158,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$location = \IPS\GeoLocation::getRequesterLocation();
|
||||
}
|
||||
catch( \BadMethodCallException | \IPS\Http\Request\Exception | \RuntimeException | \OutOfRangeException $e )
|
||||
catch( \BadFunctionCallException | \BadMethodCallException | \IPS\Http\Request\Exception | \RuntimeException | \OutOfRangeException $e )
|
||||
{
|
||||
/* If it fails for any reason, don't bother. */
|
||||
\IPS\Log::debug( $e, 'spam-service' );
|
||||
@@ -4419,15 +4448,16 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return;
|
||||
}
|
||||
|
||||
/* Default to member group if none. This shouldn't happen but can and the user error should be prevented. */
|
||||
/* Default to member group if none. This shouldn't happen but can if Redis/MySQL is temporarily unavailable. The next save event will re-check promotion. */
|
||||
try
|
||||
{
|
||||
$primaryGroup = \IPS\Member\Group::load( $this->member_group_id );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$this->member_group_id = \IPS\Settings::i()->member_group;
|
||||
\IPS\Log::debug( "Group promotion found a member (#{$this->member_id}: {$this->name}) with an invalid member group", 'group_promotion' );
|
||||
/* Log the error as part of the failure audit trail */
|
||||
\IPS\Log::log( "Group promotion found a member (#{$this->member_id}: {$this->name} [Group ID {$this->member_group_id}] with an invalid member group", 'group_promotion' );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Just check the primary group, secondary groups should not prevent promoting */
|
||||
@@ -4724,6 +4754,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
/* Insert a record */
|
||||
$vid = md5( $this->members_pass_hash . \IPS\Login::generateRandomString() );
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
\IPS\Db::i()->insert( 'core_validating', array(
|
||||
'vid' => $vid,
|
||||
'member_id' => $this->member_id,
|
||||
@@ -4734,13 +4765,14 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'user_verified' => ( $validationType == 'admin' ) ?: FALSE,
|
||||
'email_sent' => ( $validationType != 'admin' ) ? time() : NULL,
|
||||
'do_not_delete' => $doNotDelete,
|
||||
'ref' => $refUrl ? ( (string) $refUrl ) : NULL
|
||||
'ref' => $refUrl ? ( (string) $refUrl ) : NULL,
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag()
|
||||
) );
|
||||
|
||||
/* Send email for validation */
|
||||
if ( $validationType != 'admin' )
|
||||
{
|
||||
\IPS\Email::buildFromTemplate( 'core', 'registration_validate', array( $this, $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $this );
|
||||
\IPS\Email::buildFromTemplate( 'core', 'registration_validate', array( $this, $vid, $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $this );
|
||||
}
|
||||
|
||||
/* Update core_post_before_registering */
|
||||
@@ -4871,6 +4903,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$moderated = $content::moderateNewItems( $this, $content->containerWrapper(), FALSE );
|
||||
}
|
||||
|
||||
/* Do additional processing */
|
||||
if( method_exists( $this, '_cloudPbrProcess' ) )
|
||||
{
|
||||
$moderated = $this->_cloudPbrProcess( $content, $moderated );
|
||||
}
|
||||
|
||||
if ( $moderated )
|
||||
{
|
||||
@@ -5363,7 +5401,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if ( $this->member_id )
|
||||
{
|
||||
if ( $by === NULL and \IPS\Dispatcher::hasInstance() )
|
||||
/* Set this only if this was not called by the task system, otherwise we'll set the member who triggered the task as moderator */
|
||||
if ( \IPS\Dispatcher::hasInstance() and !\IPS\Dispatcher::i()->inDestructor and $by === NULL )
|
||||
{
|
||||
$by = \IPS\Session::i()->member; // Not \IPS\Member::loggedIn() because if this is an admin logged in as a member, we want to log that the action was done by the admin
|
||||
}
|
||||
@@ -6391,6 +6430,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function recordLogin()
|
||||
{
|
||||
$this->sendLoginAfterInactivityNotification();
|
||||
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_members_logins', [ 'member_id' => $this->member_id, 'member_timestamp' => time(), 'member_date' => date( 'Y-m-d', \IPS\DateTime::ts( time() )->getTimestamp() ) ] );
|
||||
@@ -6398,6 +6439,66 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* If required, send an email notification when signing in after a period of inactivity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendLoginAfterInactivityNotification(): bool
|
||||
{
|
||||
if( !Settings::i()->login_after_inactivity_notification )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$lastLogin = Db::i()->select( 'member_timestamp', 'core_members_logins', [ 'member_id=?', $this->member_id ], 'member_timestamp DESC', 1 )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
/* This is the first login, or they haven't logged in since upgrading from < 106100 */
|
||||
try
|
||||
{
|
||||
$lastLogin = Db::i()->select( 'MIN(upgrade_date)', 'core_upgrade_history', [ 'upgrade_app=? AND upgrade_version_id>=?', 'core', 106100 ] )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
/* This could be a new install, or IN_DEV where upgrade history isn't available */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$lastLoginObj = \IPS\DateTime::ts( $lastLogin );
|
||||
|
||||
/* Likely to be the first login */
|
||||
if( $this->joined > $lastLoginObj )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If this login is within the period, don't send an email */
|
||||
if( $lastLoginObj > \IPS\DateTime::create()->sub( new \DateInterval( static::LOGIN_INACTIVITY_NOTIFICATION ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Device data */
|
||||
$device = \IPS\Member\Device::loadOrCreate( $this, false );
|
||||
try
|
||||
{
|
||||
$location = \IPS\GeoLocation::getRequesterLocation();
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
$location = NULL;
|
||||
}
|
||||
|
||||
\IPS\Email::buildFromTemplate( 'core', 'loginAfterInactivity', array( $this, $device, $location ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $this );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook filters
|
||||
*
|
||||
@@ -6407,7 +6508,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return [
|
||||
'completed' => $this->completed,
|
||||
'allowAdminEmails' => $this->allow_admin_emails,
|
||||
'allowAdminEmails' => $this->allow_admin_mails,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6468,7 +6569,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @param int $time
|
||||
* @return void
|
||||
*/
|
||||
public static function pruneAllLoggedIpAddresses(int $time )
|
||||
public static function pruneAllLoggedIpAddresses( int $time )
|
||||
{
|
||||
foreach ( \IPS\Content::routedClasses( FALSE, TRUE ) as $class )
|
||||
{
|
||||
@@ -6479,17 +6580,14 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
catch( \Exception $ex ) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* PMs and Ratings are treated extra */
|
||||
/* PMs are treated extra */
|
||||
\IPS\Db::i()->update( 'core_message_posts', array( 'msg_ip_address' => '' ), array( "msg_ip_address != '' AND msg_date <= " . $time ) );
|
||||
\IPS\Db::i()->update( 'core_ratings', array( 'ip' => '' ), array( "ip != '' AND rating_date IS NOT NULL AND rating_date <= " . $time ) );
|
||||
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'IpAddresses', FALSE, 'core' ) as $key => $extension )
|
||||
{
|
||||
if( method_exists( $extension, 'pruneIpAddress'))
|
||||
if( method_exists( $extension, 'pruneIpAddresses'))
|
||||
{
|
||||
$extension->pruneIpAddress( $time );
|
||||
$extension->pruneIpAddresses( $time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user