Version 4.7.0
This commit is contained in:
1 parent
517a5e1f70
commit
8ba7d43898
1933 files changed
+65613
-11332
No files matched your search
+56
-13
@@ -722,6 +722,15 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
$this->sendModeratorApprovalNotification();
|
||||
}
|
||||
|
||||
if( $new and $this->approved )
|
||||
{
|
||||
\IPS\Api\Webhook::fire( 'club_created', $this );
|
||||
}
|
||||
else if( !$new AND isset( $this->changed['approved'] ) )
|
||||
{
|
||||
\IPS\Api\Webhook::fire( 'club_created', $this );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -803,6 +812,8 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
public function coverPhoto( $getOverlay=TRUE, $position='full' )
|
||||
{
|
||||
$photo = new \IPS\Helpers\CoverPhoto;
|
||||
|
||||
$photo->maxSize = \IPS\Settings::i()->club_max_cover;
|
||||
if ( $this->cover_photo )
|
||||
{
|
||||
$photo->file = \IPS\File::get( 'core_Clubs', $this->cover_photo );
|
||||
@@ -1129,7 +1140,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
* Add a member
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param bool $status Status
|
||||
* @param string $status Status
|
||||
* @param bool $update Update membership if already a member?
|
||||
* @param \IPS\Member|NULL $addedBy The leader who added them, or NULL if joining themselves
|
||||
* @param \IPS\Member|NULL $invitedBy The member who invited them, or NULL if joining themselves
|
||||
@@ -1171,11 +1182,28 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
$save['added_by'] = $addedBy->member_id;
|
||||
}
|
||||
|
||||
if ( $invitedBy )
|
||||
{
|
||||
$save['invited_by'] = $invitedBy->member_id;
|
||||
}
|
||||
|
||||
if( $updateJoinedDate === TRUE )
|
||||
{
|
||||
$save['joined'] = time();
|
||||
}
|
||||
|
||||
|
||||
/* Log to Member History */
|
||||
if ( \in_array( $status, array( \IPS\Member\Club::STATUS_MEMBER, \IPS\Member\Club::STATUS_BANNED ) ) )
|
||||
{
|
||||
$memberStatus = $this->memberStatus( $member, 2 );
|
||||
|
||||
/* Joining by invite */
|
||||
if ( $memberStatus['status'] == \IPS\Member\Club::STATUS_INVITED )
|
||||
{
|
||||
$addedBy = \IPS\Member::load( $memberStatus['invited_by'] );
|
||||
}
|
||||
$member->logHistory( 'core', 'club_membership', array('club_id' => $this->id, 'type' => $status ), $addedBy );
|
||||
}
|
||||
\IPS\Db::i()->update( 'core_clubs_memberships', $save, array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
|
||||
|
||||
$member->rebuildPermissionArray();
|
||||
@@ -1195,6 +1223,14 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$params = [
|
||||
'club' => $this,
|
||||
'member' => $member,
|
||||
'status' => $status
|
||||
];
|
||||
|
||||
\IPS\Api\Webhook::fire( 'club_member_added', $params );
|
||||
|
||||
/* Achievements */
|
||||
if( \in_array( $status, array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) ) )
|
||||
@@ -1236,6 +1272,9 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
public function removeMember( \IPS\Member $member )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_clubs_memberships', array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
|
||||
|
||||
\IPS\Api\Webhook::fire( 'club_member_removed', ['club' => $this, 'member' => $member] );
|
||||
|
||||
|
||||
$member->rebuildPermissionArray();
|
||||
if ( \IPS\Settings::i()->club_nodes_in_apps )
|
||||
@@ -1252,7 +1291,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
*/
|
||||
public function recountMembers()
|
||||
{
|
||||
$this->members = \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( 'club_id=? AND ( status=? OR status=? OR status=? OR status=? OR status=? )', $this->id, static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) );
|
||||
$this->members = \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( 'club_id=? AND ( status=? OR status=? OR status=? OR status=? OR status=? )', $this->id, static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) )->first();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
@@ -1331,6 +1370,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
'url' => \IPS\Http\Url::internal( $class::$urlBase . $row['node_id'], 'front', $class::$urlTemplate, array( \IPS\Http\Url\Friendly::seoTitle( $row['name'] ) ) ),
|
||||
'node_class' => $row['node_class'],
|
||||
'node_id' => $row['node_id'],
|
||||
'public' => $row['public']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1663,12 +1703,14 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
\IPS\Api\Webhook::fire( 'club_deleted', $this );
|
||||
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_club_pages', array( 'page_club=?', $this->id ) ), 'IPS\Member\Club\Page' ) as $page )
|
||||
{
|
||||
$page->delete( FALSE );
|
||||
}
|
||||
|
||||
$this->coverPhoto()->delete();
|
||||
$this->coverPhoto( FALSE )->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1768,7 +1810,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
{
|
||||
if( isset( $fieldValues['field_' . $field->id ] ) )
|
||||
{
|
||||
$fieldObject = new \IPS\core\ProfileFields\Api\Field( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'core_pfield_' . $field->id ), $fieldValues[ 'field_' . $field->id ] );
|
||||
$fieldObject = new \IPS\core\ProfileFields\Api\Field( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'core_clubfield_' . $field->id ), $fieldValues[ 'field_' . $field->id ] );
|
||||
$customFields[] = $fieldObject->apiOutput( $authorizedMember );
|
||||
}
|
||||
}
|
||||
@@ -1800,6 +1842,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
$node['url'] = (string) $node['url'];
|
||||
$node['id'] = $node['node_id'];
|
||||
$node['class'] = $node['node_class'];
|
||||
$node['public'] = $node['public'];
|
||||
|
||||
unset( $node['node_id'], $node['node_class'] );
|
||||
|
||||
@@ -1860,19 +1903,19 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
$tabs['club_members'] = array( 'href' => $this->url()->setQueryString('do', 'members'), 'title' => \IPS\Member::loggedIn()->language()->addToStack( 'club_members' ), 'isActive' => ( \IPS\Request::i()->module == 'clubs' AND \IPS\Request::i()->do == 'members' ) ? TRUE : FALSE );
|
||||
}
|
||||
|
||||
if ( $this->canRead() )
|
||||
foreach( $this->nodes() as $nodeID => $node )
|
||||
{
|
||||
foreach( $this->nodes() as $nodeID => $node )
|
||||
if ( $this->canRead() or $node['public'] )
|
||||
{
|
||||
$tabs[$nodeID] = array( 'href' => $node['url'] , 'title' => $node['name'], 'isActive' => ( isset( $container ) AND \get_class( $container ) === $node['node_class'] and $container->_id == $node['node_id'] ) ? TRUE : FALSE );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( $this->pages() AS $pageId => $page )
|
||||
foreach( $this->pages() AS $pageId => $page )
|
||||
{
|
||||
if ( $page->canView() )
|
||||
{
|
||||
if ( $page->canView() )
|
||||
{
|
||||
$tabs[$pageId] = array( 'href' => $page->url(), 'title' => $page->title, 'isActive' => ( \IPS\Request::i()->module == 'clubs' AND \IPS\Request::i()->controller == 'page' AND \IPS\Request::i()->id == $page->id ) );
|
||||
}
|
||||
$tabs[$pageId] = array( 'href' => $page->url(), 'title' => $page->title, 'isActive' => ( \IPS\Request::i()->module == 'clubs' AND \IPS\Request::i()->controller == 'page' AND \IPS\Request::i()->id == $page->id ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ class _Page extends \IPS\Patterns\ActiveRecord
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ( \in_array( 'nonmember', $this->can_view ) AND $this->club->memberStatus( $member ) === NULL )
|
||||
if ( \in_array( 'nonmember', $this->can_view ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -323,7 +323,7 @@ class _Page extends \IPS\Patterns\ActiveRecord
|
||||
if( $updateClub === TRUE )
|
||||
{
|
||||
$tabs = @json_decode( $this->club->menu_tabs, TRUE );
|
||||
if ( \count( $tabs ) )
|
||||
if ( is_countable( $tabs ) AND \count( $tabs ) )
|
||||
{
|
||||
if ( isset( $tabs['page-' . $this->id] ) )
|
||||
{
|
||||
|
||||
@@ -238,6 +238,9 @@ class _Group extends \IPS\Patterns\ActiveRecord
|
||||
unset( \IPS\Data\Store::i()->applications );
|
||||
unset( \IPS\Data\Store::i()->modules );
|
||||
unset( \IPS\Data\Store::i()->groups );
|
||||
|
||||
/* Rebuild search index */
|
||||
\IPS\Content\Search\Index::i()->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,6 +396,29 @@ class _Group extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
/* Update Core Leaders */
|
||||
\IPS\core\StaffDirectory\User::updateEmptySetting();
|
||||
|
||||
/* Deleted group, expire widget caches (online stats may try to load a group) */
|
||||
\IPS\Widget::deleteCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this group be deleted?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canDelete() : bool
|
||||
{
|
||||
$extensions = \IPS\Application::allExtensions( 'core', 'GroupForm', FALSE, 'core', 'GroupSettings', TRUE );
|
||||
|
||||
foreach ( $extensions as $class )
|
||||
{
|
||||
if( method_exists( $class, 'canDelete' ) AND !$class->canDelete( $this ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ class _GroupPromotion extends \IPS\Node\Model
|
||||
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' ) as $key => $extension )
|
||||
{
|
||||
if( method_exists( $extension, 'getSettingField' ) AND $extension->availableIn( 'group_promotions' ) )
|
||||
if( method_exists( $extension, 'getSettingField' ) AND $extension->availableIn( 'group_promotions' ) AND method_exists( $extension, 'matches' ) )
|
||||
{
|
||||
/* See if we need a new form header - one per app */
|
||||
$_key = explode( '_', $key );
|
||||
@@ -304,11 +304,17 @@ class _GroupPromotion extends \IPS\Node\Model
|
||||
static::$extensions = \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' );
|
||||
}
|
||||
|
||||
/* Did we check any matches? - It's possible that some rules are available */
|
||||
$matchMethodExists = FALSE;
|
||||
|
||||
/* Loop over the filters */
|
||||
foreach( $this->_filters as $key => $filter )
|
||||
{
|
||||
if( isset( static::$extensions[ $key ] ) )
|
||||
if( isset( static::$extensions[ $key ] ) AND static::$extensions[ $key ]->availableIn( 'group_promotions' ) AND method_exists( static::$extensions[ $key ], 'matches' ) )
|
||||
{
|
||||
/* Yes the rule had a match method */
|
||||
$matchMethodExists = TRUE;
|
||||
|
||||
/* Ask the extension if this member matches the defined rule...if not, just return FALSE now */
|
||||
if( !static::$extensions[ $key ]->matches( $member, $filter, $this ) )
|
||||
{
|
||||
@@ -317,8 +323,8 @@ class _GroupPromotion extends \IPS\Node\Model
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are still here, then the rule matched */
|
||||
return TRUE;
|
||||
/* If we are still here, then the rule matched unless we didn't find any matches methods */
|
||||
return $matchMethodExists;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+219
-38
@@ -115,7 +115,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'link_pref_unread' => 524288, // Take me to comments I haven't read
|
||||
'link_pref_last' => 1048576, // Take me to the latest comment
|
||||
'mobile_notifications' => 2097152, // Has the user has enabled push notifications on at least 1 mobile device?
|
||||
'link_pref_first' => 4194304 // Take me to the first comment
|
||||
'link_pref_first' => 4194304, // Take me to the first comment
|
||||
'datalayer_event_fired' => 8388608,
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -289,7 +290,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
$this->members_bitoptions['show_pm_popup'] = ( \IPS\Settings::i()->notification_prefs_popup ) ? TRUE : FALSE;
|
||||
$this->members_bitoptions['email_notifications_once'] = ( \IPS\Settings::i()->notification_prefs_one_per_view ) ? TRUE : FALSE;
|
||||
$this->members_bitoptions['disable_notification_sounds'] = ( \IPS\Settings::i()->notification_prefs_sound ) ? FALSE : TRUE;
|
||||
|
||||
$this->_data['auto_track'] = json_encode( array(
|
||||
'content' => \IPS\Settings::i()->auto_follow_new_content ? 1 : 0,
|
||||
@@ -342,7 +342,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
\IPS\core\AdminNotification::remove( 'core', 'NewRegValidate' );
|
||||
}
|
||||
if ( $latestReg = \IPS\Db::i()->select( 'MAX(joined)', 'core_members', array( 'email<>?', '' ) )->first() )
|
||||
if ( $latestReg = \IPS\Db::i()->select( 'joined', 'core_members', array( 'email != ?', '' ), 'joined DESC', 1 )->first() )
|
||||
{
|
||||
\IPS\core\AdminNotification::remove( 'core', 'NewRegComplete', NULL, \IPS\DateTime::ts( $latestReg ) );
|
||||
}
|
||||
@@ -413,7 +413,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
else
|
||||
{
|
||||
/* Run member sync, but not if the only change is the last_activity timestamp */
|
||||
if( \count( $this->changedCustomFields ) > 0 or \count( $changes ) > 1 OR !isset( $changes['last_activity'] ) )
|
||||
if( \count( $this->changedCustomFields ) > 0 OR ( \count( $changes ) > 0 AND !( \count( $changes ) === 1 AND isset( $changes['last_activity'] ) ) ) )
|
||||
{
|
||||
$this->memberSync( 'onProfileUpdate', array( 'changes' => array_merge( $changes, $this->changedCustomFields ) ) );
|
||||
}
|
||||
@@ -513,7 +513,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('guest');
|
||||
}
|
||||
|
||||
return $this->member_id ? $this->_data['name'] : \IPS\Member::loggedIn()->language()->addToStack( 'guest_name_shown', NULL, array( 'sprintf' => array( $this->_data['name'] ) ) );
|
||||
return $this->member_id ? $this->_data['name'] : \IPS\Member::loggedIn()->language()->addToStack( 'guest_name_shown', FALSE, array( 'sprintf' => array( $this->_data['name'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1664,12 +1664,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
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'];
|
||||
return \IPS\DateTime::getFixedTimezone( $this->_data['timezone'] );
|
||||
}
|
||||
|
||||
/* !Photos */
|
||||
@@ -1974,6 +1969,13 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If the group is forced to be anonymous */
|
||||
if( $this->group['g_hide_online_list'] == 1 )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Anonymous status is not allowed */
|
||||
if( $this->group['g_hide_online_list'] == 2 )
|
||||
{
|
||||
return FALSE;
|
||||
@@ -2193,9 +2195,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* Profile Fields
|
||||
*
|
||||
* @param int $location \IPS\core\ProfileFields\Field::PROFILE for profile, \IPS\core\ProfileFields\Field::REG for registration screen, \IPS\core\ProfileFields\Field::STAFF for ModCP/ACP, \IPS\core\ProfileFields\Field::EDIT for member editing
|
||||
* @param bool $raw Returns the raw value if true or the display value if false. Useful for comparisons for field types like Yes/NO to see if a value is set.
|
||||
* @return array
|
||||
*/
|
||||
public function profileFields( $location = 0 )
|
||||
public function profileFields( $location = 0, $raw = FALSE )
|
||||
{
|
||||
if ( !$this->member_id )
|
||||
{
|
||||
@@ -2218,7 +2221,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if( !empty( $values ) )
|
||||
{
|
||||
foreach ( \IPS\core\ProfileFields\Field::values( $values, $location ) as $group => $fields )
|
||||
foreach ( \IPS\core\ProfileFields\Field::values( $values, $location, $raw ) as $group => $fields )
|
||||
{
|
||||
$this->rawProfileFieldsData[ 'core_pfieldgroups_' . $group ] = $fields;
|
||||
}
|
||||
@@ -2784,7 +2787,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function passwordResetForced( ?\IPS\Http\Url $ref = NULL ): ?\IPS\Http\Url
|
||||
{
|
||||
if ( $this->members_bitoptions['password_reset_forced'] AND $this->members_pass_hash )
|
||||
if ( $this->members_bitoptions['password_reset_forced'] AND !$this->members_pass_hash )
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=password', 'front', 'settings_password' )->setQueryString( 'ref', base64_encode( $ref ) );
|
||||
}
|
||||
@@ -2797,6 +2800,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @note This property is public so it can be populated en-masse in areas that may benefit from doing so
|
||||
*/
|
||||
public $notificationsConfiguration = NULL;
|
||||
|
||||
static protected $extCache = [];
|
||||
|
||||
/**
|
||||
* Notifications Configuration
|
||||
@@ -2808,6 +2813,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
if( $this->notificationsConfiguration === NULL )
|
||||
{
|
||||
$this->notificationsConfiguration = array();
|
||||
$defaultNotifications = [];
|
||||
|
||||
foreach (
|
||||
\IPS\Db::i()->select(
|
||||
@@ -2821,13 +2827,52 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
) {
|
||||
if ( $row['preference'] === NULL or !$row['editable'] )
|
||||
{
|
||||
$this->notificationsConfiguration[ $row['notification_key'] ] = explode( ',', $row['default'] );
|
||||
$defaultNotifications[ $row['notification_key'] ] = explode( ',', $row['default'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->notificationsConfiguration[ $row['notification_key'] ] = array_diff( explode( ',', $row['preference'] ), explode( ',', $row['disabled'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Only cycle through if this member has any non-default notifications */
|
||||
if( \count( $this->notificationsConfiguration ) )
|
||||
{
|
||||
/* Cache extension references */
|
||||
if ( !\count( static::$extCache ) )
|
||||
{
|
||||
static::$extCache = \IPS\Application::allExtensions( 'core', 'Notifications' );
|
||||
}
|
||||
|
||||
foreach ( $defaultNotifications as $k => $v )
|
||||
{
|
||||
foreach ( static::$extCache as $extension )
|
||||
{
|
||||
if ( method_exists( $extension, 'configurationOptions' ) )
|
||||
{
|
||||
foreach ( $extension->configurationOptions( null ) as $optionKey => $option )
|
||||
{
|
||||
if ( $option['type'] == 'standard' and \in_array( $k, $option['notificationTypes'] ) and \count( $option['notificationTypes'] ) > 1 )
|
||||
{
|
||||
if ( !empty( $this->notificationsConfiguration[ $option['notificationTypes'][0] ] ) )
|
||||
{
|
||||
$defaultNotifications[ $k ] = $this->notificationsConfiguration[ $option['notificationTypes'][0] ];
|
||||
\IPS\Db::i()->insert( 'core_notification_preferences', [
|
||||
'member_id' => $this->member_id,
|
||||
'notification_key' => $k,
|
||||
'preference' => implode( ',', $this->notificationsConfiguration[ $option['notificationTypes'][0] ] )
|
||||
] );
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->notificationsConfiguration = array_merge( $this->notificationsConfiguration, $defaultNotifications );
|
||||
}
|
||||
|
||||
return $this->notificationsConfiguration;
|
||||
@@ -3027,7 +3072,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( ! \in_array( $this->member_id, static::$modBadge ) )
|
||||
if ( ! array_key_exists( $this->member_id, static::$modBadge ) )
|
||||
{
|
||||
static::$modBadge[ $this->member_id ] = false;
|
||||
|
||||
@@ -3445,6 +3490,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
* @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
|
||||
* @apiresponse bool allowAdminEmails Whether or not this member wants to receive admin emails
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL, $otherFields = NULL )
|
||||
{
|
||||
@@ -3545,6 +3591,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 $return;
|
||||
}
|
||||
@@ -3941,7 +3988,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'spam-service' );
|
||||
\IPS\Log::debug( $e, 'spam-service' );
|
||||
$spamCode = 0;
|
||||
}
|
||||
|
||||
@@ -4332,6 +4379,42 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique member hash for use in RSS keys and such.
|
||||
*
|
||||
* If the member has a current style password hash, the md5() of that value will be returned. Otherwise,
|
||||
* one will be generated, stored in a new column in the members table, and its md5() will be returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueMemberHash()
|
||||
{
|
||||
/* If this is a guest, just return a random string. */
|
||||
if ( !$this->member_id )
|
||||
{
|
||||
return \IPS\Login::generateRandomString();
|
||||
}
|
||||
|
||||
/* Return the password hash if we already have it */
|
||||
if ( !empty( $this->members_pass_hash ) )
|
||||
{
|
||||
return md5( ( $this->members_pass_hash ?: $this->email ) . $this->members_pass_salt );
|
||||
}
|
||||
|
||||
/* Do we already have one? */
|
||||
if ( !empty( $this->unique_hash ) )
|
||||
{
|
||||
return $this->unique_hash;
|
||||
}
|
||||
|
||||
/* Otherwise, generate one */
|
||||
$this->unique_hash = md5( \IPS\Login::generateRandomString(32) );
|
||||
|
||||
$this->save();
|
||||
|
||||
return $this->unique_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a database update on all members
|
||||
@@ -5135,11 +5218,11 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( $item::$firstCommentRequired and isset( $commentClass::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
$filters[ $commentClass ] = \IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_x', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$commentClass::$title}_pl_lc") ) ) );
|
||||
$filters[ $commentClass ] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_tab_x', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$commentClass::$title}_pl_lc") ) ) );
|
||||
}
|
||||
elseif ( isset( $item::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
$filters[ $item ] = \IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_x', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$item::$title}_pl_lc") ) ) );
|
||||
$filters[ $item ] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_tab_x', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$item::$title}_pl_lc") ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5566,7 +5649,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
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() );
|
||||
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 ) { }
|
||||
}
|
||||
@@ -5602,7 +5685,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
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') );
|
||||
$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 )
|
||||
@@ -5627,6 +5710,30 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return $this->rankHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of days since the last rank upgrade
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function daysSinceLastRank(): int
|
||||
{
|
||||
$history = $this->rankHistory();
|
||||
$lastRank = NULL;
|
||||
|
||||
foreach( $history['earned'] as $earned )
|
||||
{
|
||||
if ( $earned['rank']->id == $this->rank()->id )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$lastRank = $earned;
|
||||
}
|
||||
|
||||
$time = ( $lastRank and $lastRank['time'] ) ? $lastRank['time'] : $this->joined;
|
||||
return \intval( \IPS\DateTime::ts( time() )->diff( $time )->days );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief List of badge IDs that this member has earned
|
||||
*/
|
||||
@@ -5641,7 +5748,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if ( $this->_badgeIds === NULL )
|
||||
{
|
||||
$this->_badgeIds = iterator_to_array( \IPS\Db::i()->select( 'badge', 'core_member_badges', [ 'member=?', $this->member_id ] ) );
|
||||
$this->_badgeIds = iterator_to_array( \IPS\Db::i()->select( 'badge', 'core_member_badges', [ '`member`=?', $this->member_id ] ) );
|
||||
}
|
||||
return $this->_badgeIds;
|
||||
}
|
||||
@@ -5660,7 +5767,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if ( $this->_badgeCount === NULL )
|
||||
{
|
||||
$this->_badgeCount = \IPS\Db::i()->select( 'count(*)', 'core_member_badges', [ 'member=?', $this->member_id ] )->first();
|
||||
$this->_badgeCount = \IPS\Db::i()->select( 'count(*)', 'core_member_badges', [ '`member`=?', $this->member_id ] )->first();
|
||||
}
|
||||
return $this->_badgeCount;
|
||||
}
|
||||
@@ -5674,7 +5781,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
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 )
|
||||
\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'
|
||||
);
|
||||
@@ -5697,9 +5804,14 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( ! $this->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( static::$rulesExcluded === NULL )
|
||||
{
|
||||
static::$rulesExcluded = json_decode( \IPS\Settings::i()->rules_exclude_groups, TRUE );;
|
||||
static::$rulesExcluded = json_decode( \IPS\Settings::i()->rules_exclude_groups, TRUE );
|
||||
}
|
||||
|
||||
if ( ! \is_array( static::$rulesExcluded ) or ! \count( static::$rulesExcluded ) )
|
||||
@@ -5740,6 +5852,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
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) */
|
||||
@@ -5753,9 +5866,15 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
/* There are no enabled rules with this extension */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Work out what we have to dish out */
|
||||
$awards = [];
|
||||
$milestoneHitWithRules = [];
|
||||
foreach ( \IPS\core\Achievements\Rule::getStore()["{$app}_{$extension}"] as $ruleId => $ruleData )
|
||||
{
|
||||
if ( ! $ruleData['enabled'] )
|
||||
@@ -5764,9 +5883,20 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
if ( !$ruleData['filters'] or $extensionObject->filtersMatch( $this, $ruleData['filters'], $extra ) )
|
||||
{
|
||||
{
|
||||
/* Did we already award for this milestone? */
|
||||
if ( \IPS\core\Achievements\Rule::ruleHasMilestone( $ruleData['filters'] ) )
|
||||
{
|
||||
$milestoneHitWithRules[ $ruleId ] = [];
|
||||
}
|
||||
|
||||
if ( $ruleData['points_subject'] or $ruleData['badge_subject'] )
|
||||
{
|
||||
if ( array_key_exists( $ruleId, $milestoneHitWithRules ) )
|
||||
{
|
||||
$milestoneHitWithRules[ $ruleId ][ 'subject-' . $this->member_id ] = 'subject-' . $this->member_id;
|
||||
}
|
||||
|
||||
if ( !isset( $awards[ $this->member_id ] ) )
|
||||
{
|
||||
$awards[ $this->member_id ] = [ 'points' => [], 'badges' => [], 'actor' => [] ];
|
||||
@@ -5782,10 +5912,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$awards[ $this->member_id ]['badges'][ $ruleId ] = $ruleData['badge_subject'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( $ruleData['points_other'] or $ruleData['badge_other'] )
|
||||
{
|
||||
foreach ( $extensionObject->awardOther( $extra ) as $member )
|
||||
foreach ( $extensionObject->awardOther( $extra, $ruleData['filters'] ) 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 */
|
||||
@@ -5793,7 +5923,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if ( array_key_exists( $ruleId, $milestoneHitWithRules ) )
|
||||
{
|
||||
$milestoneHitWithRules[ $ruleId ][ 'other-' . $member->member_id ] = 'other-' . $member->member_id;
|
||||
}
|
||||
|
||||
/* Otherwise carry on as usual... */
|
||||
if ( !isset( $awards[ $member->member_id ] ) )
|
||||
{
|
||||
@@ -5804,7 +5939,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$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() ) )
|
||||
if ( $ruleData['badge_other'] and !\in_array( $ruleData['badge_other'], $member->badgeIds() ) )
|
||||
{
|
||||
$awards[ $member->member_id ]['actor']['other'] = 'other';
|
||||
$awards[ $member->member_id ]['badges'][ $ruleId ] = $ruleData['badge_other'];
|
||||
@@ -5813,25 +5948,61 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* If we have stuff to dish out... */
|
||||
if ( $awards )
|
||||
{
|
||||
$alreadyHitMilestone = [];
|
||||
|
||||
/* Insert these milestone maps */
|
||||
foreach( $milestoneHitWithRules as $ruleId => $members )
|
||||
{
|
||||
foreach( $members as $data )
|
||||
{
|
||||
list( $type, $memberId ) = explode( '-', $data );
|
||||
$insertId = \IPS\Db::i()->insert( 'core_achievements_log_milestones', [
|
||||
'milestone_member_id' => $memberId,
|
||||
'milestone_rule' => $ruleId,
|
||||
'milestone_log_id' => $logId,
|
||||
'milestone_type' => $type,
|
||||
], FALSE, TRUE );
|
||||
|
||||
if ( ! $insertId )
|
||||
{
|
||||
$alreadyHitMilestone[ $memberId ][] = $ruleId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
foreach( $memberAwards['points'] as $ruleId => $data )
|
||||
{
|
||||
if ( isset( $alreadyHitMilestone[ $memberId ] ) and \in_array( $ruleId, $alreadyHitMilestone[ $memberId ] ) )
|
||||
{
|
||||
unset( $memberAwards['points'][ $ruleId ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'points', $memberAwards ) and \count( $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
|
||||
if ( ! isset( $alreadyHitMilestone[ $memberId ] ) or ! \in_array( $ruleId, $alreadyHitMilestone[ $memberId ] ) )
|
||||
{
|
||||
\IPS\Member::load( $memberId )->awardBadge( \IPS\core\Achievements\Badge::load( $badgeId ), $logId, $ruleId, $memberAwards['actor'], 0, $date );
|
||||
try
|
||||
{
|
||||
\IPS\Member::load( $memberId )->awardBadge( \IPS\core\Achievements\Badge::load( $badgeId ), $logId, $ruleId, $memberAwards['actor'], 0, $date );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5886,10 +6057,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* 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 = new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_rank', $this, [ $newRank, $this ], [ $newRank->id ] );
|
||||
$notification->recipients->attach( $this );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
$this->save();
|
||||
|
||||
if ( isset( static::$_memberRecognizedPoints[ $this->member_id ] ) )
|
||||
{
|
||||
@@ -5932,10 +6105,18 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* 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 = new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_badge', $this, [ $this, $badge ], [ $badge->id ] );
|
||||
$notification->recipients->attach( $this );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'member' => $this->apiOutput(),
|
||||
'badge' => $badge->apiOutput(),
|
||||
];
|
||||
|
||||
\IPS\Api\Webhook::fire( 'member_badge_awarded', $data );
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public static $_memberRecognizedPoints = array();
|
||||
|
||||
@@ -746,4 +746,14 @@ class _ProfileStep extends \IPS\Node\Model
|
||||
|
||||
return $finalSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Does the currently logged in user have permission to copy this node?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canCopy()
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user