Version 4.7.13
This commit is contained in:
1 parent
fd22d99e69
commit
7124a02564
954 files changed
+29277
-59308
No files matched your search
+334
-12
@@ -120,6 +120,8 @@ 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
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -133,7 +135,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'last_visit',
|
||||
'last_activity',
|
||||
'profilesync_lastsync',
|
||||
|
||||
];
|
||||
|
||||
/* !Follow */
|
||||
@@ -203,7 +204,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( !static::$loggedInMember->member_id and isset( \IPS\Request::i()->cookie['ipsTimezone'] ) )
|
||||
{
|
||||
static::$loggedInMember->timezone = \IPS\DateTime::getFixedTimezone( \IPS\Request::i()->cookie['ipsTimezone'] );
|
||||
/* Check for valid timezone identifier */
|
||||
$tz = \IPS\DateTime::getFixedTimezone( \IPS\Request::i()->cookie['ipsTimezone'] );
|
||||
if( \in_array( $tz, \DateTimeZone::listIdentifiers() ) )
|
||||
{
|
||||
static::$loggedInMember->timezone = $tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +337,47 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow optional cookies
|
||||
*
|
||||
* @param bool $allow
|
||||
* @return bool
|
||||
*/
|
||||
public function setAllowOptionalCookies( bool $allow = TRUE ): bool
|
||||
{
|
||||
$expire = ( new \IPS\DateTime )->add( new \DateInterval('P6M' ) );
|
||||
\IPS\Request::i()->setCookie( 'cookie_consent', 1, $expire, FALSE );
|
||||
|
||||
if( !$allow )
|
||||
{
|
||||
/* Remove optional cookies */
|
||||
foreach( \IPS\Request::i()->cookie as $cookieName => $value )
|
||||
{
|
||||
if( !\in_array( $cookieName, \IPS\Request::i()->getEssentialCookies() ) )
|
||||
{
|
||||
\IPS\Request::i()->setCookie( $cookieName, NULL);
|
||||
}
|
||||
}
|
||||
\IPS\Request::i()->setCookie( 'cookie_consent_optional', NULL );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Request::i()->setCookie( 'cookie_consent_optional', 1, $expire, FALSE );
|
||||
}
|
||||
|
||||
return $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are optional cookies allowed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_optionalCookiesAllowed(): bool
|
||||
{
|
||||
return isset( \IPS\Request::i()->cookie[ 'cookie_consent' ] ) AND isset( \IPS\Request::i()->cookie[ 'cookie_consent_optional' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
@@ -411,6 +458,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* @note The completed column is added in the 4.4.0 Beta 1 routine, so we need to verify the upgrade has been performed otherwise
|
||||
the auto-upgrader can replace this file before the column is added, and an SQL error can prevent the admin from completing the
|
||||
auto-upgrade process */
|
||||
$fireRegistrationCompletedWebhook = FALSE;
|
||||
if ( \IPS\Application::load( 'core' )->long_version > 104000 )
|
||||
{
|
||||
if( $this->completed AND ( !$this->name OR !$this->email ) )
|
||||
@@ -421,22 +469,32 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
elseif( !$this->completed AND $this->name AND $this->email )
|
||||
{
|
||||
$this->completed = TRUE;
|
||||
\IPS\Api\Webhook::fire( 'member_registration_complete', $this, $this->webhookFilters() );
|
||||
$fireRegistrationCompletedWebhook = TRUE;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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 )
|
||||
{
|
||||
$this->memberSync( 'onCreateAccount' );
|
||||
|
||||
/* Profile Fields */
|
||||
\IPS\Db::i()->insert( 'core_pfields_content', array( 'member_id' => $this->member_id ), TRUE );
|
||||
|
||||
$this->memberSync( 'onCreateAccount' );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( $fireRegistrationCompletedWebhook )
|
||||
{
|
||||
\IPS\Api\Webhook::fire( 'member_registration_complete', $this, $this->webhookFilters() );
|
||||
}
|
||||
/* Run member sync, but not if the only change is the last_activity timestamp */
|
||||
if( \count( $this->changedCustomFields ) > 0 OR ( \count( $changes ) > 0 AND !( \count( $changes ) === 1 AND isset( $changes['last_activity'] ) ) ) )
|
||||
{
|
||||
@@ -784,6 +842,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$this->members_bitoptions['validating'] = FALSE;
|
||||
}
|
||||
|
||||
/* Revoke oAuth tokens */
|
||||
if( $value )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'member_id=?', $this->member_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2011,7 +2075,14 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return $this->_lang;
|
||||
}
|
||||
|
||||
|
||||
/* If in API use the lang set by dispatcher */
|
||||
if ( \IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) AND \IPS\Dispatcher::i()->controllerLocation === 'api' )
|
||||
{
|
||||
$this->_lang = \IPS\Dispatcher::i()->_setLanguage();
|
||||
return $this->_lang;
|
||||
}
|
||||
|
||||
/* If in setup, create a "dummy" language */
|
||||
if ( \IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) AND \IPS\Dispatcher::i()->controllerLocation === 'setup' AND \IPS\Dispatcher::i()->setupLocation === 'install' )
|
||||
{
|
||||
@@ -3418,7 +3489,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @apiresponse string title Member title
|
||||
* @clientapiresponse string timezone Member timezone
|
||||
* @apiresponse string formattedName Username with group formatting
|
||||
* @apiresponse string ipAddress IP address used during registration
|
||||
* @apiresponse \IPS\Member\Group primaryGroup Primary group
|
||||
* @clientapiresponse [\IPS\Member\Group] secondaryGroups Secondary groups
|
||||
* @clientapiresponse string email Email address
|
||||
@@ -3445,7 +3515,17 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL, $otherFields = NULL )
|
||||
{
|
||||
$group = \IPS\Member\Group::load( $this->_data['member_group_id'] );
|
||||
try
|
||||
{
|
||||
$group = \IPS\Member\Group::load( $this->_data['member_group_id'] );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
\IPS\Log::log( "{$this->name} has an invalid group ({$this->_data['member_group_id']}) during API call. Resetting.", 'api_invalid_group' );
|
||||
$group = \IPS\Member\Group::load( \IPS\Settings::i()->member_group ); // Intentially no catch here as that means things are really broken.
|
||||
$this->member_group_id = \IPS\Settings::i()->member_group;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
$secondaryGroups = array();
|
||||
foreach ( array_filter( array_map( "intval", explode( ',', $this->_data['mgroup_others'] ) ) ) as $secondaryGroupId )
|
||||
@@ -3471,7 +3551,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
foreach( $profileFields as $field )
|
||||
{
|
||||
if ( !$authorizedMember or
|
||||
if ( !$authorizedMember or
|
||||
$field['pf_contains_pii'] == 1 AND ( $authorizedMember->member_id == $this->member_id ) or
|
||||
$field['pf_member_hide'] == 'all' or
|
||||
( $field['pf_member_hide'] == 'owner' and ( $authorizedMember->member_id == $this->member_id OR $authorizedMember->modPermissions() OR $authorizedMember->isAdmin() ) ) or
|
||||
( $field['pf_member_hide'] == 'staff' and ( $authorizedMember->modPermissions() OR $authorizedMember->isAdmin() ) )
|
||||
@@ -3914,9 +3995,11 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @param string $type Request type
|
||||
* @param string $emailAddress Email address to check, NULL for existing email address
|
||||
* @param int $spamCode If set, will modify by reference with the raw value from the spam service
|
||||
* @param bool $disposable If set, will modify by reference if the user used a disposable email.
|
||||
* @param bool $geoBlock If set, will modify by reference if the user is in a moderated / blocked country.
|
||||
* @return int|NULL Action code based on spam service response, or NULL for no action
|
||||
*/
|
||||
public function spamService( $type='register', $emailAddress=NULL, &$spamCode=NULL )
|
||||
public function spamService( $type='register', $emailAddress=NULL, &$spamCode=NULL, &$disposable=FALSE, &$geoBlock=FALSE )
|
||||
{
|
||||
$email = $emailAddress ?: $this->email;
|
||||
|
||||
@@ -4004,10 +4087,156 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* GeoLocation */
|
||||
if ( $type === 'register' AND $action == 1 )
|
||||
{
|
||||
$action = $this->geoSpamCheck( $geoBlock );
|
||||
}
|
||||
|
||||
/* If the normal spam service and geolocation didn't pick up anything, check for a disposable email. */
|
||||
if ( $type === 'register' AND $action == 1 )
|
||||
{
|
||||
$action = $this->disposableEmailCheck( $emailAddress, $disposable );
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeoLocation Block Check
|
||||
*
|
||||
* @param bool $geoBlock If set, will modify by reference if the user is in a moderated / blocked country.
|
||||
* @return int|NULL
|
||||
*/
|
||||
public function geoSpamCheck( &$geoBlock ): ?int
|
||||
{
|
||||
/* If Geo is enabled, don't bother. */
|
||||
if ( !\IPS\Settings::i()->ipsgeoip )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* See we have anything configured. If we don't, don't bother. */
|
||||
$settings = \IPS\Settings::i()->spam_geo_settings ? json_decode( \IPS\Settings::i()->spam_geo_settings, true ) : array();
|
||||
|
||||
if ( !\count( $settings ) )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$location = \IPS\GeoLocation::getRequesterLocation();
|
||||
}
|
||||
catch( \BadMethodCallException | \IPS\Http\Request\Exception | \RuntimeException | \OutOfRangeException $e )
|
||||
{
|
||||
/* If it fails for any reason, don't bother. */
|
||||
\IPS\Log::debug( $e, 'spam-service' );
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* All good, start checking. */
|
||||
if ( !isset( $settings[ $location->country ] ) )
|
||||
{
|
||||
/* Not in the list, we're good. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* See what action we're taking. */
|
||||
switch( $settings[$location->country] )
|
||||
{
|
||||
case 'moderate':
|
||||
\IPS\Settings::i()->reg_auth_type = 'admin';
|
||||
$geoBlock = TRUE;
|
||||
return 2;
|
||||
|
||||
case 'block':
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for disposable email address domain
|
||||
*
|
||||
* @param string $emailAddress Email to check, or NULL for current.
|
||||
* @param bool $disposable If set, will modify by reference if the user used a disposable email.
|
||||
* @return int|NULL
|
||||
*/
|
||||
public function disposableEmailCheck( $emailAddress, &$disposable=FALSE ): ?int
|
||||
{
|
||||
$email = $emailAddress ?: $this->email;
|
||||
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::ips( 'spam/disposable' )->request()->login( \IPS\Settings::i()->ipb_reg_number, '' )->post( array(
|
||||
'email' => $email,
|
||||
) );
|
||||
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \DomainException( print_r( $response, TRUE ) );
|
||||
}
|
||||
|
||||
$result = (bool) $response->decodeJson()['result'];
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::debug( $e, 'spam-service' );
|
||||
$result = false;
|
||||
}
|
||||
|
||||
$action = NULL;
|
||||
if ( $result )
|
||||
{
|
||||
$disposable = TRUE;
|
||||
|
||||
$action = \IPS\Settings::i()->spam_service_disposable;
|
||||
|
||||
/* Perform Action */
|
||||
switch( $action )
|
||||
{
|
||||
/* Proceed with registration */
|
||||
case 1:
|
||||
break;
|
||||
|
||||
/* Flag for admin approval */
|
||||
case 2:
|
||||
\IPS\Settings::i()->reg_auth_type = 'admin';
|
||||
break;
|
||||
|
||||
/* Approve the account, but ban it */
|
||||
case 3:
|
||||
$this->temp_ban = -1;
|
||||
$this->members_bitoptions['bw_is_spammer'] = TRUE;
|
||||
break;
|
||||
|
||||
/* Deny registration - we return the code and the controller is expected to show an error */
|
||||
case 4:
|
||||
break;
|
||||
|
||||
/* Moderate posts */
|
||||
case 5:
|
||||
$days = json_decode( \IPS\Settings::i()->spam_service_days, TRUE );
|
||||
|
||||
if ( isset( $days['disposable'] ) )
|
||||
{
|
||||
if ( $days['disposable'] == -1 )
|
||||
{
|
||||
$this->mod_posts = -1;
|
||||
}
|
||||
else if ( $days['disposable'] > 0 )
|
||||
{
|
||||
$this->mod_posts = \IPS\DateTime::ts( time() )->add( new \DateInterval( "P{$days['disposable']}D" ) )->getTimestamp();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Member Sync
|
||||
*
|
||||
@@ -4597,6 +4826,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* Reset the flag */
|
||||
$this->members_bitoptions['validating'] = FALSE;
|
||||
$this->save();
|
||||
|
||||
\IPS\Api\Webhook::fire( 'member_registration_complete', $this, $this->webhookFilters() );
|
||||
|
||||
/* Sync */
|
||||
$this->memberSync( 'onValidate' );
|
||||
@@ -5931,7 +6162,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
foreach( $members as $data )
|
||||
{
|
||||
list( $type, $memberId ) = explode( '-', $data );
|
||||
[ $type, $memberId ] = explode( '-', $data );
|
||||
$insertId = \IPS\Db::i()->insert( 'core_achievements_log_milestones', [
|
||||
'milestone_member_id' => $memberId,
|
||||
'milestone_rule' => $ruleId,
|
||||
@@ -6190,4 +6421,95 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the member's personal information
|
||||
*
|
||||
* @return \IPS\Xml\SimpleXML
|
||||
*/
|
||||
public function getPiiData(): \IPS\Xml\SimpleXML
|
||||
{
|
||||
/* Init */
|
||||
$xml = \IPS\Xml\SimpleXML::create('member_export');
|
||||
$xml->addAttribute( 'created', \IPS\DateTime::ts( time() )->rfc3339() );
|
||||
|
||||
/* Get the data */
|
||||
foreach( \IPS\Application::allExtensions( 'core', 'MemberExportPersonalInformation', TRUE, 'core' ) AS $key => $ext )
|
||||
{
|
||||
if ( $data = $ext->getData( $this ) and \is_array( $data ) and \count( $data ) )
|
||||
{
|
||||
$child = $xml->addChild( $key );
|
||||
foreach( $data as $k => $v )
|
||||
{
|
||||
$child->addChild( $k, $v );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there a deletion request pending?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_isDeletionPending(): bool
|
||||
{
|
||||
$where = [];
|
||||
$where[] = ['member_id=?', $this->member_id];
|
||||
$where[] = [ \IPS\Db::i()->in( 'action',[\IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE, \IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE_VALIDATION ] ) ];
|
||||
|
||||
return !\IPS\Db::i()->select( 'count(*)', \IPS\Member\PrivacyAction::$databaseTable, $where )->first() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all logged IP addresses which were logged after a certain time
|
||||
*
|
||||
* @param int $time
|
||||
* @return void
|
||||
*/
|
||||
public static function pruneAllLoggedIpAddresses(int $time )
|
||||
{
|
||||
foreach ( \IPS\Content::routedClasses( FALSE, TRUE ) as $class )
|
||||
{
|
||||
try
|
||||
{
|
||||
$class::pruneIpAddresses( \IPS\Settings::i()->ip_address_prune );
|
||||
}
|
||||
catch( \Exception $ex ) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* PMs and Ratings 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'))
|
||||
{
|
||||
$extension->pruneIpAddress( $time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this member request that his account gets deleted via the frontend
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canUseAccountDeletion(): bool
|
||||
{
|
||||
if( \IPS\Settings::i()->right_to_be_forgotten_type != 'on' )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if( \IPS\Member::loggedIn()->isAdmin() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user