Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
+633
-95
@@ -101,7 +101,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'must_reaccept_privacy' => 32, // 1 means the member needs to re-accept the privacy policy
|
||||
'must_reaccept_terms' => 64, // 1 means the member needs to re-accept the registration terms
|
||||
'email_notifications_once' => 128, // 1 means the member only wants one email per notification item until they revisit the community
|
||||
'disable_notification_sounds' => 256, // 1 means notification sounds are disabled, 0 is enabled
|
||||
'disable_notification_sounds' => 256, /** @deprecated Notification sounds are no longer supported by default */
|
||||
'has_security_answers' => 512,
|
||||
'security_questions_opt_out' => 1024,
|
||||
'ignore_promotions' => 2048,// 1 means that the user will not be checked against group promotion rules. Set by Commerce to prevent "promoting" users incorrectly after a purchase.
|
||||
@@ -354,7 +354,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', "supportAdmin-{$this->member_id}" );
|
||||
}
|
||||
|
||||
|
||||
/* Delete from member map */
|
||||
\IPS\Db::i()->delete( 'core_item_member_map', array( 'map_member_id=?', $this->member_id ) );
|
||||
|
||||
/* Reset statistics */
|
||||
\IPS\Widget::deleteCaches();
|
||||
}
|
||||
@@ -600,7 +603,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* Clear out their profile */
|
||||
if ( \in_array( 'delete', $actions ) )
|
||||
{
|
||||
$this->member_title = '';
|
||||
$this->signature = '';
|
||||
$this->pp_main_photo = NULL;
|
||||
$this->pp_photo_type = NULL;
|
||||
@@ -1065,16 +1067,16 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
public function permissionArray()
|
||||
{
|
||||
$return = $this->groups;
|
||||
|
||||
if ( \IPS\Settings::i()->clubs )
|
||||
{
|
||||
$return[] = "ca"; // Public clubs
|
||||
}
|
||||
|
||||
if ( $this->member_id )
|
||||
{
|
||||
$return[] = "m{$this->member_id}";
|
||||
|
||||
if ( \IPS\Settings::i()->clubs )
|
||||
{
|
||||
$return[] = "ca"; // Public clubs, which is everyone except guests
|
||||
}
|
||||
|
||||
if ( $this->modPermission('can_access_all_clubs') )
|
||||
{
|
||||
$return[] = 'cm';
|
||||
@@ -1237,17 +1239,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return static::photoUrl( $this->_data, $thumb, $email );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Photo Type
|
||||
*
|
||||
* @return string
|
||||
* @deprecated No longer needed as of 4.4, will be removed in a future release
|
||||
*/
|
||||
public function get_pp_photo_type()
|
||||
{
|
||||
return $this->_data['pp_photo_type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Photo Type
|
||||
*
|
||||
@@ -1339,75 +1330,19 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return json_decode( $this->_data['failed_logins'], TRUE ) ?: array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch the ranks - abstracted to a static method for caching
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRanks()
|
||||
{
|
||||
static $ranks = NULL;
|
||||
|
||||
if( $ranks !== NULL )
|
||||
{
|
||||
return $ranks;
|
||||
}
|
||||
|
||||
if ( isset( \IPS\Data\Store::i()->ranks ) )
|
||||
{
|
||||
$ranks = \IPS\Data\Store::i()->ranks;
|
||||
}
|
||||
else
|
||||
{
|
||||
$ranks = iterator_to_array( \IPS\Db::i()->select( '*', 'core_member_ranks', NULL, 'posts DESC' ) );
|
||||
\IPS\Data\Store::i()->ranks = $ranks;
|
||||
}
|
||||
|
||||
return $ranks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get member title
|
||||
*
|
||||
* @return array
|
||||
* @return array
|
||||
* @deprecated Use rank() instead
|
||||
*/
|
||||
public function get_rank()
|
||||
{
|
||||
$title = NULL;
|
||||
$image = NULL;
|
||||
|
||||
/* Does this member have a custom title? */
|
||||
if ( isset( $this->member_title ) )
|
||||
if ( $rank = $this->rank() )
|
||||
{
|
||||
$title = $this->member_title;
|
||||
return [ 'title' => $rank->_title, 'image' => $rank->html() ];
|
||||
}
|
||||
|
||||
foreach( static::getRanks() as $rank )
|
||||
{
|
||||
if ( $this->member_posts >= $rank['posts'] )
|
||||
{
|
||||
/* Pips or Image */
|
||||
if ( $rank['use_icon'] and $rank['icon'] )
|
||||
{
|
||||
$image = "<img src='" . \IPS\File::get( 'core_Theme', $rank['icon'] )->url . "' alt=''>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$image = str_repeat( "<span class='ipsPip'></span>", \intval( $rank['pips'] ) );
|
||||
}
|
||||
|
||||
/* Get member title from rank */
|
||||
if ( !$title )
|
||||
{
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack( 'core_member_rank_' . $rank['id']);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array( 'title' => $title, 'image' => $image );
|
||||
return [ 'title' => NULL, 'image' => NULL ];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1722,6 +1657,21 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_timezone()
|
||||
{
|
||||
if ( \in_array( $this->_data['timezone'], [ 'America/Buenos_Aires', 'America/Catamarca', 'America/Cordoba', 'America/Jujuy', 'America/La_Rioja', 'America/Mendoza', 'America/Rio_Gallegos', 'America/Salta', 'America/San_Juan', 'America/San_Luis', 'America/Tucuman', 'America/Ushuaia' ] ) ) // https://bugs.webkit.org/show_bug.cgi?id=218542
|
||||
{
|
||||
return 'America/Argentina/' . \substr( $this->_data['timezone'], 8 );
|
||||
}
|
||||
|
||||
return $this->_data['timezone'];
|
||||
}
|
||||
|
||||
/* !Photos */
|
||||
|
||||
/**
|
||||
@@ -2219,10 +2169,11 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* HTML link to profile with hovercard
|
||||
*
|
||||
* @param string|NULL $warningRef The reference key for warnings
|
||||
* @param boolean|NULL $groupFormatting Apply the group prefix/suffix to the name?
|
||||
* @param boolean|NULL $groupFormatting Apply the group prefix/suffix to the name?
|
||||
* @param boolean|NULL $anonymous Is this shown with anonymous content?
|
||||
* @return string
|
||||
*/
|
||||
public function link( $warningRef=NULL, $groupFormatting=NULL )
|
||||
public function link( $warningRef=NULL, $groupFormatting=NULL, $anonymous=FALSE )
|
||||
{
|
||||
$groupFormatting = ( $groupFormatting === NULL ) ? ( ( \IPS\Settings::i()->group_formatting == 'legacy' ) ? FALSE : TRUE ) : $groupFormatting;
|
||||
|
||||
@@ -2230,7 +2181,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$warningRef = NULL;
|
||||
}
|
||||
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( $this, $warningRef, $groupFormatting );
|
||||
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( $this, $warningRef, $groupFormatting, $anonymous );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2804,7 +2755,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forcePasswordReset(): void
|
||||
public function forcePasswordReset()
|
||||
{
|
||||
/* If the user is already forced, but hasn't yet, just return */
|
||||
if ( $this->members_bitoptions['password_reset_forced'] )
|
||||
@@ -2882,6 +2833,41 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return $this->notificationsConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached PWA Authorizations
|
||||
*/
|
||||
protected $_pwa = NULL;
|
||||
|
||||
/**
|
||||
* Get members PWA Authorizations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPwaAuths(): array
|
||||
{
|
||||
if ( $this->_pwa === NULL )
|
||||
{
|
||||
$this->_pwa = array();
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_notifications_pwa_keys', array( "`member`=?", $this->member_id ) ) AS $auth )
|
||||
{
|
||||
$this->_pwa[ $auth['id'] ] = $auth;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_pwa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get members PWA Authorizations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function clearPwaAuths()
|
||||
{
|
||||
$this->_pwa = NULL;
|
||||
\IPS\Db::i()->delete( 'core_notifications_pwa_keys', array( "`member`=?", $this->member_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Following?
|
||||
*/
|
||||
@@ -3457,6 +3443,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @apiresponse int profileViews Number of times member's profile has been viewed
|
||||
* @apiresponse string birthday Member birthday in MM/DD/YYYY format (or MM/DD format if no year has been supplied).
|
||||
* @apiresponse [\IPS\core\ProfileFields\Api\FieldGroup] customFields Custom profile fields. For requests using an OAuth Access Token for a particular member, only fields the authorized user can view will be included
|
||||
* @apiresponse [\IPS\core\Achievements\Rank] rank Rank
|
||||
* @apiresponse int achievements_points Points
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL, $otherFields = NULL )
|
||||
{
|
||||
@@ -3504,7 +3492,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$return = array();
|
||||
$return['id'] = $this->member_id;
|
||||
$return['name'] = $this->_name;
|
||||
$return['title'] = $this->member_title;
|
||||
$return['title'] = null;
|
||||
if ( !$authorizedMember )
|
||||
{
|
||||
$return['timeZone'] = $this->timezone;
|
||||
@@ -3532,7 +3520,7 @@ 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;
|
||||
$return['lastActivity'] = ( $this->last_activity AND !$this->isOnlineAnonymously() ) ? \IPS\DateTime::ts( $this->last_activity )->rfc3339() : NULL;
|
||||
|
||||
if( !$authorizedMember )
|
||||
{
|
||||
@@ -3555,6 +3543,9 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
$return['rank'] = $this->rank() ? $this->rank()->apiOutput( $authorizedMember ) : NULL;
|
||||
$return['achievements_points'] = $this->achievements_points;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -3619,11 +3610,13 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function unlockTime()
|
||||
{
|
||||
if ( \IPS\Settings::i()->ipb_bruteforce_attempts and isset( $this->failed_logins[ \IPS\Request::i()->ipAddress() ] ) and \count( $this->failed_logins[ \IPS\Request::i()->ipAddress() ] ) >= \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
$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 and isset( $failedLogins[ \IPS\Request::i()->ipAddress() ] ) and \count( $failedLogins[ \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 = $this->failed_logins[ \IPS\Request::i()->ipAddress() ];
|
||||
$failedLogins = $failedLogins[ \IPS\Request::i()->ipAddress() ];
|
||||
sort( $failedLogins );
|
||||
|
||||
while ( \count( $failedLogins ) > \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
@@ -3646,13 +3639,37 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of failed logins for a given IP address
|
||||
*
|
||||
* @param string $ipAddress IP Address
|
||||
* @return int
|
||||
*/
|
||||
public function failedLoginCount( $ipAddress )
|
||||
{
|
||||
if( $this->member_id )
|
||||
{
|
||||
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();
|
||||
|
||||
return ( isset( $failedLogins[ $ipAddress ] ) ) ? \count( $failedLogins[ $ipAddress ] ) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* !Permissions */
|
||||
|
||||
/**
|
||||
* @var array ACP restriction keys which are hardcoded to FALSE when on CiC (even if admin has no restrictions)
|
||||
*/
|
||||
public static $cicBlockedAcpRestrictions = [ 'core.membersettings.member_history_prune', 'core.settings.datastore', 'core.customization.theme_designers_mode', 'core.membersettings.notifications_prune' ];
|
||||
|
||||
/**
|
||||
* Has access to a restricted ACP area?
|
||||
*
|
||||
@@ -3661,13 +3678,21 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @param string|null $key Restriction Key
|
||||
*/
|
||||
public function hasAcpRestriction( $app, $module=NULL, $key=NULL )
|
||||
{
|
||||
{
|
||||
/* Load our ACP restrictions */
|
||||
$restrictions = $this->acpRestrictions();
|
||||
if ( $restrictions === FALSE )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* CiC Block */
|
||||
$appKey = \is_string( $app ) ? $app : $app->directory;
|
||||
$moduleKey = ( $module === NULL or \is_string( $module ) ) ? $module : $module->key;
|
||||
if ( \IPS\CIC and \in_array( $appKey . ( $moduleKey ? ( ".{$moduleKey}" . ( $key ? ".{$key}" : '' ) ) : '' ), static::$cicBlockedAcpRestrictions ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If we have all permissions, return true */
|
||||
if ( $restrictions === '*' )
|
||||
@@ -3682,7 +3707,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Otherwise, check 'em! */
|
||||
$appKey = \is_string( $app ) ? $app : $app->directory;
|
||||
if ( array_key_exists( $appKey, $restrictions['applications'] ) )
|
||||
{
|
||||
if ( $module === NULL )
|
||||
@@ -3691,7 +3715,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
else
|
||||
{
|
||||
$moduleKey = ( $module === NULL or \is_string( $module ) ) ? $module : $module->key;
|
||||
if ( \in_array( $moduleKey, $restrictions['applications'][ $appKey ] ) )
|
||||
{
|
||||
if ( $key === NULL )
|
||||
@@ -3960,6 +3983,23 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* 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[ $spamCode ] ) )
|
||||
{
|
||||
if ( $days[ $spamCode ] == -1 )
|
||||
{
|
||||
$this->mod_posts = -1;
|
||||
}
|
||||
else if ( $days[ $spamCode ] > 0 )
|
||||
{
|
||||
$this->mod_posts = \IPS\DateTime::ts( time() )->add( new \DateInterval( "P{$days[ $spamCode ]}D" ) )->getTimestamp();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4589,7 +4629,35 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
else
|
||||
{
|
||||
$content->unhide( FALSE );
|
||||
/* Set the value so that checkProfanityFilters() thinks it's good */
|
||||
$column = NULL;
|
||||
if ( isset( $content::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = $content::$databaseColumnMap['hidden'];
|
||||
$oldVal = $content->$column;
|
||||
$content->$column = 0;
|
||||
}
|
||||
elseif ( isset( $content::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = $content::$databaseColumnMap['approved'];
|
||||
$oldVal = $content->$column;
|
||||
$content->$column = 1;
|
||||
}
|
||||
|
||||
/* Run through checkProfanityFilters() */
|
||||
if ( $content->checkProfanityFilters( $content instanceof \IPS\Content\Comment and $content->item()::$firstCommentRequired and $content->isFirst() ) )
|
||||
{
|
||||
$content->sendUnapprovedNotification();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $column )
|
||||
{
|
||||
$content->$column = $oldVal;
|
||||
}
|
||||
|
||||
$content->unhide( FALSE );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
@@ -5429,7 +5497,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function addReferral( $member )
|
||||
{
|
||||
if ( $member->member_id )
|
||||
if ( $this->member_id and $member->member_id )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_referrals', array( 'member_id' => $member->member_id, 'referred_by' => $this->member_id ) );
|
||||
}
|
||||
@@ -5474,4 +5542,474 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* !Achievements */
|
||||
|
||||
/**
|
||||
* Get current rank
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rank(): ?\IPS\core\Achievements\Rank
|
||||
{
|
||||
return \IPS\core\Achievements\Rank::fromPoints( $this->achievements_points );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the current rank was earned
|
||||
*
|
||||
* @return ?\IPS\DateTime
|
||||
*/
|
||||
public function rankEarned(): ?\IPS\DateTime
|
||||
{
|
||||
if ( $rank = $this->rank() )
|
||||
{
|
||||
try
|
||||
{
|
||||
return \IPS\DateTime::ts( \IPS\Db::i()->select( 'datetime', 'core_points_log', [ 'member=? AND new_rank=?', $this->member_id, $rank->id ], 'datetime DESC', 1 )->first() );
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next rank
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function nextRank(): ?\IPS\core\Achievements\Rank
|
||||
{
|
||||
foreach ( \IPS\core\Achievements\Rank::getStore() as $rank )
|
||||
{
|
||||
if ( $rank->points > $this->achievements_points )
|
||||
{
|
||||
return $rank;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
protected $rankHistory = NULL;
|
||||
|
||||
/**
|
||||
* Return rank history
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rankHistory(): array
|
||||
{
|
||||
if ( ! $this->rankHistory )
|
||||
{
|
||||
$return = [ 'earned' => NULL, 'not_earned' => NULL ];
|
||||
$query = iterator_to_array( \IPS\Db::i()->select( 'new_rank, datetime', 'core_points_log', ['member=? and new_rank IS NOT NULL', $this->member_id] )->setKeyField('new_rank')->setValueField('datetime') );
|
||||
$currentRank = $this->rank();
|
||||
|
||||
foreach ( \IPS\core\Achievements\Rank::getStore() as $rank )
|
||||
{
|
||||
if ( $rank->points == 0 )
|
||||
{
|
||||
$return['earned'][] = ['rank' => $rank, 'time' => $this->joined ];
|
||||
}
|
||||
elseif ( $rank->points <= $currentRank->points )
|
||||
{
|
||||
$return['earned'][] = ['rank' => $rank, 'time' => isset( $query[ $rank->id ] ) ? \IPS\DateTime::ts( $query[ $rank->id ] ) : NULL ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['not_earned'][] = ['rank' => $rank];
|
||||
}
|
||||
}
|
||||
|
||||
$this->rankHistory = $return;
|
||||
}
|
||||
|
||||
return $this->rankHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief List of badge IDs that this member has earned
|
||||
*/
|
||||
protected $_badgeIds = NULL;
|
||||
|
||||
/**
|
||||
* Get list of badge IDs that this member has earned
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function badgeIds(): array
|
||||
{
|
||||
if ( $this->_badgeIds === NULL )
|
||||
{
|
||||
$this->_badgeIds = iterator_to_array( \IPS\Db::i()->select( 'badge', 'core_member_badges', [ 'member=?', $this->member_id ] ) );
|
||||
}
|
||||
return $this->_badgeIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterator of badges this member has earned
|
||||
*/
|
||||
public $_badgeCount = NULL;
|
||||
|
||||
/**
|
||||
* Get count of badges that this member has earned
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function badgeCount(): int
|
||||
{
|
||||
if ( $this->_badgeCount === NULL )
|
||||
{
|
||||
$this->_badgeCount = \IPS\Db::i()->select( 'count(*)', 'core_member_badges', [ 'member=?', $this->member_id ] )->first();
|
||||
}
|
||||
return $this->_badgeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent Badges
|
||||
*
|
||||
* @param int|NULL $numberToGet Number to get
|
||||
* @return \IPS\Patterns\ActiveRecordIterator
|
||||
*/
|
||||
public function recentBadges( $numberToGet ): \IPS\Patterns\ActiveRecordIterator
|
||||
{
|
||||
return new \IPS\Patterns\ActiveRecordIterator(
|
||||
\IPS\Db::i()->select( 'core_badges.*, core_member_badges.member, core_member_badges.badge, core_member_badges.datetime, core_member_badges.rule, core_member_badges.action_log, core_member_badges.actor, core_member_badges.recognize', 'core_member_badges', [ 'member=?', $this->member_id ], 'datetime DESC', $numberToGet )
|
||||
->join( 'core_badges', 'core_member_badges.badge=core_badges.id' ),
|
||||
'IPS\core\Achievements\Badge'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array Cache for excluded groups for rules
|
||||
*/
|
||||
protected static $rulesExcluded = NULL;
|
||||
|
||||
/**
|
||||
* Can this member partake in the fun an frivolity of achievements?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canHaveAchievements(): bool
|
||||
{
|
||||
if ( ! \IPS\Settings::i()->achievements_enabled )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( static::$rulesExcluded === NULL )
|
||||
{
|
||||
static::$rulesExcluded = json_decode( \IPS\Settings::i()->rules_exclude_groups, TRUE );;
|
||||
}
|
||||
|
||||
if ( ! \is_array( static::$rulesExcluded ) or ! \count( static::$rulesExcluded ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return ! $this->inGroup( static::$rulesExcluded );
|
||||
}
|
||||
|
||||
/**
|
||||
* User has done something which could earn Points / Badges
|
||||
*
|
||||
* @param string $app Application key for the action being performed
|
||||
* @param string $extension Extension key for the action being performed
|
||||
* @param ?mixed $extra Any additional information to be passed along to the extension (e.g. if a post is being made: the post object)
|
||||
* @param ?\IPS\DateTime $date Optional DateTime object to be used as the date the achievement was earned
|
||||
* @return void
|
||||
*/
|
||||
public function achievementAction( string $app, string $extension, $extra = NULL, $date = NULL )
|
||||
{
|
||||
if ( ! $this->canHaveAchievements() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( \IPS\core\Achievements\Rule::getStore()["{$app}_{$extension}"] ) )
|
||||
{
|
||||
$enabled = FALSE;
|
||||
foreach ( \IPS\core\Achievements\Rule::getStore()["{$app}_{$extension}"] as $ruleId => $ruleData )
|
||||
{
|
||||
if ( $ruleData['enabled'] )
|
||||
{
|
||||
$enabled = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $enabled === TRUE )
|
||||
{
|
||||
$extensionObject = \IPS\Application::load( $app )->extensions( 'core', 'AchievementAction' )[$extension];
|
||||
/* Log it first, because doing so allows us to tell if we have already ran the rules
|
||||
for this action (e.g. if someone unlikes and relikes a post, we don't want to keep
|
||||
doing the actions for that every time: only once) */
|
||||
$logId = \IPS\Db::i()->insert( 'core_achievements_log', [
|
||||
'action' => "{$app}_{$extension}",
|
||||
'identifier' => $extensionObject->identifier( $this, $extra ),
|
||||
'datetime' => ( $date ) ? $date->getTimestamp() : time()
|
||||
], FALSE, TRUE );
|
||||
if ( !$logId )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Work out what we have to dish out */
|
||||
$awards = [];
|
||||
foreach ( \IPS\core\Achievements\Rule::getStore()["{$app}_{$extension}"] as $ruleId => $ruleData )
|
||||
{
|
||||
if ( ! $ruleData['enabled'] )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !$ruleData['filters'] or $extensionObject->filtersMatch( $this, $ruleData['filters'], $extra ) )
|
||||
{
|
||||
if ( $ruleData['points_subject'] or $ruleData['badge_subject'] )
|
||||
{
|
||||
if ( !isset( $awards[ $this->member_id ] ) )
|
||||
{
|
||||
$awards[ $this->member_id ] = [ 'points' => [], 'badges' => [], 'actor' => [] ];
|
||||
}
|
||||
if ( $ruleData['points_subject'] )
|
||||
{
|
||||
$awards[ $this->member_id ]['actor']['subject'] = 'subject';
|
||||
$awards[ $this->member_id ]['points'][ $ruleId ] = $ruleData['points_subject'];
|
||||
}
|
||||
if ( $ruleData['badge_subject'] and !\in_array( $ruleData['badge_subject'], $this->badgeIds() ) )
|
||||
{
|
||||
$awards[ $this->member_id ]['actor']['subject'] = 'subject';
|
||||
$awards[ $this->member_id ]['badges'][ $ruleId ] = $ruleData['badge_subject'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $ruleData['points_other'] or $ruleData['badge_other'] )
|
||||
{
|
||||
foreach ( $extensionObject->awardOther( $extra ) as $member )
|
||||
{
|
||||
/* If the "other" member is also the subject member (e.g. member is replying to their own topic), we
|
||||
only give them the "subject" points */
|
||||
if ( $member->member_id == $this->member_id )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise carry on as usual... */
|
||||
if ( !isset( $awards[ $member->member_id ] ) )
|
||||
{
|
||||
$awards[ $member->member_id ] = [ 'points' => [], 'badges' => [], 'actor' => [] ]; // So given the above comment, why is actor an array? In case we change our minds.
|
||||
}
|
||||
if ( $ruleData['points_other'] )
|
||||
{
|
||||
$awards[ $member->member_id ]['actor']['other'] = 'other';
|
||||
$awards[ $member->member_id ]['points'][ $ruleId ] = $ruleData['points_other'];
|
||||
}
|
||||
if ( $ruleData['badge_subject'] and !\in_array( $ruleData['badge_subject'], $member->badgeIds() ) )
|
||||
{
|
||||
$awards[ $member->member_id ]['actor']['other'] = 'other';
|
||||
$awards[ $member->member_id ]['badges'][ $ruleId ] = $ruleData['badge_other'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have stuff to dish out... */
|
||||
if ( $awards )
|
||||
{
|
||||
foreach ( $awards as $memberId => $memberAwards )
|
||||
{
|
||||
if ( $memberAwards['points'] )
|
||||
{
|
||||
\IPS\Member::load( $memberId )->awardPoints( array_sum( $memberAwards['points'] ), $logId, array_keys( $memberAwards['points'] ), $memberAwards['actor'], 0, $date );
|
||||
}
|
||||
if ( $memberAwards['badges'] )
|
||||
{
|
||||
foreach ( array_unique( $memberAwards['badges'] ) as $ruleId => $badgeId )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Member::load( $memberId )->awardBadge( \IPS\core\Achievements\Badge::load( $badgeId ), $logId, $ruleId, $memberAwards['actor'], 0, $date );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Award Points
|
||||
*
|
||||
* @param int $points Number of points to award
|
||||
* @param int $logId The ID number of the log in core_achievements_log which will tell us specifically what ACTION caused this (eg what post was made)
|
||||
* @param array $ruleIds The ID number of the rules which matched that action which contributed to this number of points being awarded
|
||||
* @param array $actor If member is "subject", "other" or both
|
||||
* @param int $recognize The ID of the recognize table row (optional)
|
||||
* @param ?\IPS\DateTime $date Optional DateTime object to be used as the date the achievement was earned
|
||||
*/
|
||||
public function awardPoints( int $points, int $logId, array $ruleIds, array $actor, int $recognize=0, $date=NULL )
|
||||
{
|
||||
if ( ! $this->canHaveAchievements() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* What are they on now? */
|
||||
$oldPointsBalance = $this->achievements_points;
|
||||
$oldRank = $this->rank();
|
||||
|
||||
/* Increase it */
|
||||
$this->achievements_points += $points;
|
||||
$newPointsBalance = $this->achievements_points;
|
||||
$newRank = $this->rank();
|
||||
if ( $oldRank and $newRank and ( $oldRank->id == $newRank->id ) )
|
||||
{
|
||||
$newRank = NULL;
|
||||
}
|
||||
\IPS\Db::i()->update( 'core_members', "achievements_points = achievements_points + " . \intval( $points ), [ 'member_id=?', $this->member_id ] );
|
||||
|
||||
/* Log it */
|
||||
$logId = \IPS\Db::i()->insert( 'core_points_log', [
|
||||
'member' => $this->member_id,
|
||||
'points' => $points,
|
||||
'datetime' => ( $date ) ? $date->getTimestamp() : time(),
|
||||
'balance' => $newPointsBalance,
|
||||
'rules' => implode( ',', $ruleIds ),
|
||||
'action_log'=> $logId,
|
||||
'actor' => implode( ',', $actor ),
|
||||
'new_rank' => $newRank ? $newRank->id : NULL,
|
||||
'recognize' => $recognize
|
||||
], FALSE, TRUE );
|
||||
|
||||
/* Did that push them over threshold for a new rank? Don't send a notification if rebuilds are in progress, and a random post made live triggers a new badge or award */
|
||||
if ( $newRank and ! \IPS\Settings::i()->achievements_rebuilding )
|
||||
{
|
||||
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_rank', $this, [ $this ], [ $newRank->id ] );
|
||||
$notification->recipients->attach( $this );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
if ( isset( static::$_memberRecognizedPoints[ $this->member_id ] ) )
|
||||
{
|
||||
unset( static::$_memberRecognizedPoints[ $this->member_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Award Badge
|
||||
*
|
||||
* @param \IPS\core\Achievements\Badge $badge The badge to award
|
||||
* @param int $logId The ID number of the log in core_achievements_log which will tell us specifically what ACTION caused this (eg what post was made)
|
||||
* @param int $ruleId The ID number of the rule which matched that action which caused this badge to be awarded
|
||||
* @param array $actor If member is "subject", "other" or both
|
||||
* @param int $recognize The ID of the recognize table row (optional)
|
||||
* @param ?\IPS\DateTime $date Optional DateTime object to be used as the date the achievement was earned
|
||||
*/
|
||||
public function awardBadge( \IPS\core\Achievements\Badge $badge, int $logId, int $ruleId, array $actor, int $recognize=0, $date=NULL )
|
||||
{
|
||||
if ( ! $this->canHaveAchievements() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$insertId = \IPS\Db::i()->insert( 'core_member_badges', [
|
||||
'member' => $this->member_id,
|
||||
'badge' => $badge->id,
|
||||
'datetime' => ( $date ) ? $date->getTimestamp() : time(),
|
||||
'rule' => $ruleId,
|
||||
'action_log' => $logId,
|
||||
'actor' => implode( ',', $actor ),
|
||||
'recognize' => $recognize
|
||||
], FALSE, TRUE );
|
||||
if ( !$insertId )
|
||||
{
|
||||
// Member has already earned this badge
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't send a notification if rebuilds are in progress, and a random post made live triggers a new badge or award */
|
||||
if ( ! \IPS\Settings::i()->achievements_rebuilding )
|
||||
{
|
||||
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_badge', $this, [$badge], [$badge->id] );
|
||||
$notification->recipients->attach( $this );
|
||||
$notification->send();
|
||||
}
|
||||
}
|
||||
|
||||
public static $_memberRecognizedPoints = array();
|
||||
/**
|
||||
* Grab todays recognized points
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function todaysRecognizePoints(): int
|
||||
{
|
||||
if ( ! isset( static::$_memberRecognizedPoints[ $this->member_id ] ) )
|
||||
{
|
||||
$timezone = new \DateTimeZone( \IPS\Settings::i()->reputation_timezone );
|
||||
$today = \IPS\DateTime::create()->setTimezone( $timezone )->setTime( 0, 0 );
|
||||
|
||||
$sum = \IPS\Db::i()->select( 'SUM(r_points)', 'core_member_recognize', [ 'r_member_id=? and r_added >=?', $this->member_id, $today->getTimestamp() ] )->first();
|
||||
|
||||
static::$_memberRecognizedPoints[ $this->member_id ] = $sum !== NULL ? $sum : 0;
|
||||
}
|
||||
|
||||
return static::$_memberRecognizedPoints[ $this->member_id ];
|
||||
}
|
||||
|
||||
public static $_memberRecognizedBadges = array();
|
||||
/**
|
||||
* Grab todays recognized points
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function todaysRecognizeBadges(): int
|
||||
{
|
||||
if ( ! isset( static::$_memberRecognizedBadges[ $this->member_id ] ) )
|
||||
{
|
||||
$timezone = new \DateTimeZone( \IPS\Settings::i()->reputation_timezone );
|
||||
$today = \IPS\DateTime::create()->setTimezone( $timezone )->setTime( 0, 0 );
|
||||
|
||||
$sum = \IPS\Db::i()->select( 'COUNT(*)', 'core_member_recognize', [ 'r_member_id=? and r_badge > 0 and r_added >=?', $this->member_id, $today->getTimestamp() ] )->first();
|
||||
|
||||
static::$_memberRecognizedBadges[ $this->member_id ] = $sum !== NULL ? $sum : 0;
|
||||
}
|
||||
|
||||
return static::$_memberRecognizedBadges[ $this->member_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Count of solutions member has given
|
||||
*/
|
||||
public $_solutionCount = NULL;
|
||||
|
||||
/**
|
||||
* Get count of solutions member has given
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function solutionCount(): int
|
||||
{
|
||||
if ( $this->_solutionCount === NULL )
|
||||
{
|
||||
$this->_solutionCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_solved_index', array( 'member_id=?', $this->member_id ) )->first();
|
||||
}
|
||||
return $this->_solutionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record this log in
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function recordLogin()
|
||||
{
|
||||
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() ) ] );
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user