Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

+633 -95
View File
@@ -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 ) { }
}
}