Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
+226
-15
@@ -33,8 +33,11 @@ use IPS\Db\Exception;
|
||||
use IPS\Db\Select;
|
||||
use IPS\Dispatcher;
|
||||
use IPS\Events\Event;
|
||||
use IPS\Extensions\ClubAbstract;
|
||||
use IPS\File;
|
||||
use IPS\GeoLocation;
|
||||
use IPS\Helpers\Badge;
|
||||
use IPS\Helpers\Badge\Icon;
|
||||
use IPS\Helpers\CoverPhoto;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Address;
|
||||
@@ -56,6 +59,7 @@ use IPS\Math\Number;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club\CustomField;
|
||||
use IPS\Member\Club\Page as ClubPage;
|
||||
use IPS\Member\Club\Template;
|
||||
use IPS\nexus\Customer;
|
||||
use IPS\nexus\Invoice;
|
||||
use IPS\nexus\Money;
|
||||
@@ -69,6 +73,7 @@ use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use IPS\Task;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use OverflowException;
|
||||
@@ -80,6 +85,7 @@ use function defined;
|
||||
use function get_class;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -92,7 +98,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class Club extends ActiveRecord implements Embeddable
|
||||
{
|
||||
use Followable;
|
||||
use Followable
|
||||
{
|
||||
Followable::unfollow as public _unfollow;
|
||||
Followable::follow as public _follow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Club: public
|
||||
@@ -178,6 +188,13 @@ class Club extends ActiveRecord implements Embeddable
|
||||
* @brief Not actually a status, but used for member history
|
||||
*/
|
||||
const STATUS_LEFT = 'left';
|
||||
|
||||
/**
|
||||
* @breif Constants for node permissions values
|
||||
*/
|
||||
const NODE_PRIVATE = 0;
|
||||
const NODE_PUBLIC = 1;
|
||||
const NODE_MODERATORS = 2;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
@@ -227,9 +244,10 @@ class Club extends ActiveRecord implements Embeddable
|
||||
* @param array $filters Custom field filters
|
||||
* @param mixed|null $extraWhere Additional WHERE clause
|
||||
* @param bool $countOnly Only return a count, instead of an iterator
|
||||
* @param array|null $location Array with lat and lon
|
||||
* @return ActiveRecordIterator|array|int
|
||||
*/
|
||||
public static function clubs( ?Member $member, int|array|null $limit, string $sortOption, bool|array|Member $mineOnly=FALSE, array $filters=array(), mixed $extraWhere=NULL, bool $countOnly=FALSE ): ActiveRecordIterator|int|array
|
||||
public static function clubs( ?Member $member, int|array|null $limit, string $sortOption, bool|array|Member $mineOnly=FALSE, array $filters=array(), mixed $extraWhere=NULL, bool $countOnly=FALSE, ?array $location=null ): ActiveRecordIterator|int|array
|
||||
{
|
||||
$where = array();
|
||||
$joins = array();
|
||||
@@ -300,6 +318,19 @@ class Club extends ActiveRecord implements Embeddable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$selectClause = '*';
|
||||
$having = NULL;
|
||||
|
||||
/* Location? */
|
||||
if ( isset( $location['lat'] ) and isset( $location['lon'] ) )
|
||||
{
|
||||
/* Make sure co-ordinates are in a valid format regardless of locale */
|
||||
$location['lat'] = number_format( $location['lat'], 6, '.', '' );
|
||||
$location['lon'] = number_format( $location['lon'], 6, '.', '' );
|
||||
|
||||
$selectClause .= ', ( 3959 * acos( cos( radians(' . $location['lat'] . ') ) * cos( radians( location_lat ) ) * cos( radians( location_long ) - radians( ' . $location['lon'] . ' ) ) + sin( radians( ' . $location['lat'] . ' ) ) * sin( radians( location_lat ) ) ) ) AS distance';
|
||||
}
|
||||
|
||||
/* Additional where clause */
|
||||
if ( $extraWhere )
|
||||
@@ -313,15 +344,27 @@ class Club extends ActiveRecord implements Embeddable
|
||||
$where[] = array( $extraWhere );
|
||||
}
|
||||
}
|
||||
|
||||
/* Extensions */
|
||||
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
|
||||
{
|
||||
/* @var ClubAbstract $ext */
|
||||
foreach( $ext->clubsWhere( $member ) as $clause )
|
||||
{
|
||||
$where[] = $clause;
|
||||
}
|
||||
}
|
||||
|
||||
/* Query */
|
||||
if( $countOnly )
|
||||
{
|
||||
$select = Db::i()->select( 'COUNT(*)', 'core_clubs', $where );
|
||||
$select = Db::i()->select( 'COUNT(*)', 'core_clubs', $where, null, null, null, $having );
|
||||
}
|
||||
else
|
||||
{
|
||||
$select = Db::i()->select( '*', 'core_clubs', $where, ( $sortOption === 'name' ? "{$sortOption} ASC" : "{$sortOption} DESC" ), $limit );
|
||||
/* If we are sorting by distance, we want null values last */
|
||||
$sortOption = ( $sortOption === 'distance' ) ? '-distance' : $sortOption;
|
||||
$select = Db::i()->select( $selectClause, 'core_clubs', $where, "{$sortOption} " . ( $sortOption === 'name' ? 'ASC' : 'DESC' ), $limit, null, $having );
|
||||
}
|
||||
|
||||
foreach ( $joins as $join )
|
||||
@@ -619,6 +662,28 @@ class Club extends ActiveRecord implements Embeddable
|
||||
$form->add( new YesNo( 'club_rules_reacknowledge', FALSE, FALSE, [], NULL, NULL, NULL, 'club_rules_reacknowledge' ) );
|
||||
}
|
||||
|
||||
$form->add( new YesNo( 'club_auto_follow', $this->auto_follow, false ) );
|
||||
if( !$new )
|
||||
{
|
||||
Member::loggedIn()->language()->words['club_auto_follow_desc'] = Member::loggedIn()->language()->get( 'club_auto_follow_desc_edit' );
|
||||
}
|
||||
|
||||
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
|
||||
{
|
||||
/* @var ClubAbstract $ext */
|
||||
foreach( $ext->formElements( $this ) as $element )
|
||||
{
|
||||
if( is_object( $element ) )
|
||||
{
|
||||
$form->add( $element );
|
||||
}
|
||||
else
|
||||
{
|
||||
$form->addHtml( $element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -737,6 +802,9 @@ class Club extends ActiveRecord implements Embeddable
|
||||
$this->show_membertab = "nonmember";
|
||||
}
|
||||
|
||||
$this->auto_follow = $values['club_auto_follow'];
|
||||
$autoFollow = ( !$new and isset( $this->changed['auto_follow'] ) and $this->changed['auto_follow'] );
|
||||
|
||||
$this->save();
|
||||
|
||||
if ( $values['show_rules'] )
|
||||
@@ -766,6 +834,12 @@ class Club extends ActiveRecord implements Embeddable
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are auto-following and this is not a new club, trigger a background task */
|
||||
if( $autoFollow )
|
||||
{
|
||||
Task::queue( 'core', 'AutoFollowClubs', [ 'club' => $this->id, 'node' => null ], 3, [ 'club', 'node' ] );
|
||||
}
|
||||
|
||||
if ( $new )
|
||||
{
|
||||
$this->addMember( Member::loggedIn(), Club::STATUS_LEADER );
|
||||
@@ -815,6 +889,23 @@ class Club extends ActiveRecord implements Embeddable
|
||||
}
|
||||
}
|
||||
|
||||
/* Extensions */
|
||||
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
|
||||
{
|
||||
/* @var ClubAbstract $ext */
|
||||
$ext->saveForm( $this, $values, $new );
|
||||
}
|
||||
|
||||
/* Default nodes */
|
||||
if( $new )
|
||||
{
|
||||
foreach( Template::roots() as $template )
|
||||
{
|
||||
$nodeClass = new ( $template->node_class );
|
||||
$nodeClass->saveClubForm( $this, $template->node_data, $template );
|
||||
}
|
||||
}
|
||||
|
||||
if( $new and Settings::i()->clubs_require_approval and !$this->approved )
|
||||
{
|
||||
/* Add it to the approval queue */
|
||||
@@ -1293,6 +1384,9 @@ class Club extends ActiveRecord implements Embeddable
|
||||
*/
|
||||
public function addMember( Member $member, string $status = 'member', bool $update=FALSE, Member $addedBy = NULL, Member $invitedBy = NULL, bool $updateJoinedDate=FALSE ) : void
|
||||
{
|
||||
/* Get the current status so that we can figure out if this is a new membership */
|
||||
$currentStatus = $this->memberStatus( $member );
|
||||
|
||||
try
|
||||
{
|
||||
Db::i()->insert( 'core_clubs_memberships', array(
|
||||
@@ -1332,6 +1426,7 @@ class Club extends ActiveRecord implements Embeddable
|
||||
Db::i()->update( 'core_clubs_memberships', $save, array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
|
||||
|
||||
$member->rebuildPermissionArray();
|
||||
$this->memberStatuses[ $member->member_id ] = $status;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1344,6 +1439,12 @@ class Club extends ActiveRecord implements Embeddable
|
||||
}
|
||||
}
|
||||
|
||||
/* Is this club an auto-follow? */
|
||||
if( $this->auto_follow and in_array( $status, [ static::STATUS_MEMBER, static::STATUS_LEADER, static::STATUS_MODERATOR ] ) and !in_array( $currentStatus, [ static::STATUS_MEMBER, static::STATUS_LEADER, static::STATUS_MODERATOR ] ) )
|
||||
{
|
||||
$this->follow( 'immediate', true, $member );
|
||||
}
|
||||
|
||||
/* Log to Member History */
|
||||
$memberStatus = $this->memberStatus( $member, 2 );
|
||||
if( in_array( $memberStatus['status'], [ Club::STATUS_INVITED, Club::STATUS_INVITED_BYPASSING_PAYMENT, Club::STATUS_BANNED, Club::STATUS_MEMBER, Club::STATUS_DECLINED, Club::STATUS_REQUESTED ] ) )
|
||||
@@ -1404,16 +1505,22 @@ class Club extends ActiveRecord implements Embeddable
|
||||
*/
|
||||
public function removeMember( Member $member ) : void
|
||||
{
|
||||
/* Get the member's current status */
|
||||
$memberStatus = $this->memberStatus( $member );
|
||||
|
||||
Db::i()->delete( 'core_clubs_memberships', array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
|
||||
|
||||
$member->rebuildPermissionArray();
|
||||
|
||||
/* Clear out the member from any local caches */
|
||||
if( isset( $this->memberStatuses[ $member->member_id ] ) )
|
||||
{
|
||||
unset( $this->memberStatuses[ $member->member_id ] );
|
||||
}
|
||||
|
||||
/* Unfollow the club */
|
||||
$this->unfollow( $member );
|
||||
|
||||
Webhook::fire( 'club_member_removed', ['club' => $this, 'member' => $member] );
|
||||
|
||||
Event::fire( 'onLeaveClub', $member, array( $this ) );
|
||||
|
||||
$member->rebuildPermissionArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2021,10 +2128,10 @@ class Club extends ActiveRecord implements Embeddable
|
||||
/**
|
||||
* Get the club navbar tabs
|
||||
*
|
||||
* @param Model|NULL $container Container
|
||||
* @param Model|ClubPage|null $container Container
|
||||
* @return array
|
||||
*/
|
||||
public function tabs( Model $container = NULL ) : array
|
||||
public function tabs( Model|ClubPage|null $container = NULL ) : array
|
||||
{
|
||||
if ( !static::$tabs )
|
||||
{
|
||||
@@ -2178,7 +2285,7 @@ class Club extends ActiveRecord implements Embeddable
|
||||
$menu->add( new Link( $this->url()->setQueryString( 'do', 'invite' ), 'club_invite_members', dataAttributes: [ 'data-ipsDialog-size' => 'narrow' ], opensDialog: true ) );
|
||||
}
|
||||
|
||||
if( $this->canManageNavigation() and $container instanceof Model and count( $this->tabs( $container ) ) > 1 )
|
||||
if( $this->canManageNavigation() and count( $this->tabs( $container ) ) > 1 )
|
||||
{
|
||||
$menu->add( new Link( '#', 'reorder_club_menu', dataAttributes: [ 'data-action' => 'reorderClubmenu' ] ) );
|
||||
}
|
||||
@@ -2202,7 +2309,7 @@ class Club extends ActiveRecord implements Embeddable
|
||||
}
|
||||
}
|
||||
|
||||
if( $container instanceof Model and $this->isLeader() )
|
||||
if( $container instanceof Model and $this->isLeader() and $container->canLeaderManage() )
|
||||
{
|
||||
$menu->addSeparator();
|
||||
$menu->add( new Link( $this->url()->setQueryString( [ 'do' => 'nodeForm', 'type' => get_class( $container ), 'node' => $container->_id ] ), Member::loggedIn()->language()->addToStack( 'clubs_edit_this_container', true, [ 'sprintf' => Member::loggedIn()->language()->addToStack( $container::$nodeTitle . '_sg' ) ] ), dataAttributes: [ 'data-ipsDialog-title' => $container->_title ], opensDialog: true ) );
|
||||
@@ -2311,6 +2418,58 @@ class Club extends ActiveRecord implements Embeddable
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return badges that should be displayed
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function badges() : array
|
||||
{
|
||||
$badges = [];
|
||||
|
||||
if( Settings::i()->clubs_require_approval and !$this->approved )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_WARNING, 'fa-eye-slash', Member::loggedIn()->language()->addToStack( 'club_unapproved' ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( $this->featured )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_POSITIVE, 'fa-star', Member::loggedIn()->language()->addToStack( 'featured' ) );
|
||||
}
|
||||
|
||||
$memberStatus = $this->memberStatus( Member::loggedIn() );
|
||||
if( in_array( $memberStatus, [ static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER ] ) )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_POSITIVE, 'fa-check', Member::loggedIn()->language()->addToStack( 'club_member' ) );
|
||||
}
|
||||
elseif( in_array( $memberStatus, [ static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ] ) )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_INTERMEDIARY, 'fa-triangle-exclamation', Member::loggedIn()->language()->addToStack( 'club_expired' ) );
|
||||
}
|
||||
elseif( in_array( $memberStatus, [ static::STATUS_INVITED, static::STATUS_INVITED_BYPASSING_PAYMENT ] ) )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_NEUTRAL, 'fa-envelope', Member::loggedIn()->language()->addToStack( 'club_invited' ) );
|
||||
}
|
||||
elseif( $memberStatus == static::STATUS_WAITING_PAYMENT )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_NEUTRAL, 'fa-check', Member::loggedIn()->language()->addToStack( 'club_awaiting_payment_title' ) );
|
||||
}
|
||||
elseif( $memberStatus == static::STATUS_REQUESTED )
|
||||
{
|
||||
$badges[] = new Icon( Badge::BADGE_INTERMEDIARY, 'fa-clock', Member::loggedIn()->language()->addToStack( 'club_requested_desc_short' ) );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
|
||||
{
|
||||
/* @var ClubAbstract $ext */
|
||||
$badges = array_merge( $badges, $ext->badges( $this ) );
|
||||
}
|
||||
|
||||
return $badges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member view the members page
|
||||
*
|
||||
@@ -2362,10 +2521,10 @@ class Club extends ActiveRecord implements Embeddable
|
||||
* Can be used by 3rd parties to add own club navigation tabs before they get sorted
|
||||
*
|
||||
* @param array $tabs Tabs
|
||||
* @param Model|null $container Container
|
||||
* @param Model|ClubPage|null $container Container
|
||||
* @return array
|
||||
*/
|
||||
protected function _tabs( array $tabs, Model $container = NULL ) : array
|
||||
protected function _tabs( array $tabs, Model|ClubPage|null $container = NULL ) : array
|
||||
{
|
||||
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
|
||||
{
|
||||
@@ -2635,4 +2794,56 @@ class Club extends ActiveRecord implements Embeddable
|
||||
$this->rebuilt = time();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow this object
|
||||
*
|
||||
* @param string $frequency ( 'none', 'immediate', 'daily', 'weekly' )
|
||||
* @param bool $public
|
||||
* @param Member|null $member
|
||||
* @return void
|
||||
*/
|
||||
public function follow( string $frequency, bool $public=true, ?Member $member=null ) : void
|
||||
{
|
||||
$member = $member ?: Member::loggedIn();
|
||||
$this->_follow( $frequency, $public, $member );
|
||||
|
||||
/* Follow all nodes */
|
||||
foreach( $this->nodes() as $node )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* @var Model $class */
|
||||
$class = $node['node_class'];
|
||||
$class::load( $node['node_id'] )->follow( $frequency, $public, $member );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow this object
|
||||
*
|
||||
* @param Member|null $member
|
||||
* @param string|null $followId
|
||||
* @return void
|
||||
*/
|
||||
public function unfollow( ?Member $member=null, ?string $followId=null ) : void
|
||||
{
|
||||
/* First run the main method so that we unfollow the club itself */
|
||||
$member = $member ?: Member::loggedIn();
|
||||
$this->_unfollow( $member, $followId );
|
||||
|
||||
/* Now unfollow any nodes in the club */
|
||||
foreach( $this->nodes() as $node )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* @var Model $class */
|
||||
$class = $node['node_class'];
|
||||
$class::load( $node['node_id'] )->unfollow( $member );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class CustomField extends SystemCustomField implements Permissions
|
||||
/**
|
||||
* Get fields
|
||||
*
|
||||
* @return array|ActiveRecordIterator
|
||||
* @return array<CustomField>|ActiveRecordIterator
|
||||
*/
|
||||
public static function fields(): ActiveRecordIterator|array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Template
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage
|
||||
* @since 7/9/2025
|
||||
*/
|
||||
|
||||
namespace IPS\Member\Club;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Editor;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Lang;
|
||||
use IPS\Member\Club;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Request;
|
||||
use IPS\Theme;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
class Template extends Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static ?string $databaseTable = 'core_clubs_templates';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Prefix
|
||||
*/
|
||||
public static string $databasePrefix = 'template_';
|
||||
|
||||
/**
|
||||
* @brief language string
|
||||
*/
|
||||
public static string $nodeTitle = 'clubs_templates';
|
||||
|
||||
/**
|
||||
* @brief [Node] Order Database Column
|
||||
*/
|
||||
public static ?string $databaseColumnOrder = 'position';
|
||||
|
||||
/**
|
||||
* @brief Show form in a modal?
|
||||
*/
|
||||
public static bool $modalForms = true;
|
||||
|
||||
/**
|
||||
* [Node] Get Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get__title(): string
|
||||
{
|
||||
return $this->name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_node_data() : array
|
||||
{
|
||||
return isset( $this->_data['node_data'] ) ? json_decode( $this->_data['node_data'], TRUE ) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $val
|
||||
* @return void
|
||||
*/
|
||||
public function set_node_data( ?array $val ) : void
|
||||
{
|
||||
$this->_data['node_data'] = is_array( $val ) ? json_encode( $val ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title for a node using the specified language object
|
||||
* This is commonly used where we cannot use the logged in member's language, such as sending emails
|
||||
*
|
||||
* @param Lang $language Language object to fetch the title with
|
||||
* @param array $options What options to use for language parsing
|
||||
* @return string
|
||||
*/
|
||||
public function getTitleForLanguage( Lang $language, array $options=array() ): string
|
||||
{
|
||||
return $this->_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
parent::form( $form );
|
||||
|
||||
$form->add( new Text( 'template_template_name', $this->name, true ) );
|
||||
|
||||
if( $this->_new )
|
||||
{
|
||||
/* Override the submit button language string */
|
||||
$form->actionButtons = [
|
||||
Theme::i()->getTemplate( 'forms', 'core', 'global' )->button( 'continue', 'submit', null, 'ipsButton ipsButton--primary', array( 'tabindex' => '2', 'accesskey' => 's' ) )
|
||||
];
|
||||
|
||||
$nodeOptions = array();
|
||||
foreach( Club::availableNodeTypes() as $nodeType )
|
||||
{
|
||||
$nodeOptions[ $nodeType ] = $nodeType::clubFrontTitle();
|
||||
}
|
||||
|
||||
$form->add( new Select( 'template_node_class', NULL, TRUE, array( 'options' => $nodeOptions ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->nodeForm( $form );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
if( $this->id )
|
||||
{
|
||||
$nodeData = [];
|
||||
foreach( $values as $k => $v )
|
||||
{
|
||||
if( $k != 'template_template_name' )
|
||||
{
|
||||
$nodeData[ $k ] = $v;
|
||||
unset( $values[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
$values['node_data'] = $nodeData;
|
||||
}
|
||||
|
||||
$values['template_name'] = $values['template_template_name'];
|
||||
unset( $values['template_template_name'] );
|
||||
|
||||
return parent::formatFormValues( $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node form
|
||||
*
|
||||
* @param Form $form
|
||||
* @return void $form Form
|
||||
*/
|
||||
public function nodeForm( Form &$form ) : void
|
||||
{
|
||||
$node = new ( $this->node_class );
|
||||
$node->clubForm( $form, new Club );
|
||||
$submitted = $form->id . "_submitted";
|
||||
|
||||
foreach( $form->elements as $tab => $elements )
|
||||
{
|
||||
foreach( $elements as $key => $element )
|
||||
{
|
||||
if( $element instanceof Editor )
|
||||
{
|
||||
unset( $form->elements[ $tab ][ $key ] );
|
||||
}
|
||||
elseif( isset( $this->node_data[ $key ] ) and !isset( Request::i()->$submitted ) )
|
||||
{
|
||||
$form->elements[ $tab ][ $key ]->value = $this->node_data[ $key ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form->add( new YesNo( 'clubtemplates_leader_edit', $this->node_data['clubtemplates_leader_edit'] ?? null, TRUE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search
|
||||
*
|
||||
* @param string $column Column to search
|
||||
* @param string $query Search query
|
||||
* @param string|null $order Column to order by
|
||||
* @param mixed $where Where clause
|
||||
* @return array
|
||||
*/
|
||||
public static function search( string $column, string $query, ?string $order=NULL, mixed $where=array() ): array
|
||||
{
|
||||
if( $column === '_title' )
|
||||
{
|
||||
$column = 'template_name';
|
||||
$order = 'template_name';
|
||||
}
|
||||
|
||||
return parent::search( $column, $query, $order, $where );
|
||||
}
|
||||
}
|
||||
+44
-25
@@ -63,10 +63,7 @@ class History extends \IPS\Helpers\Table\Db
|
||||
|
||||
foreach( $apps as $application )
|
||||
{
|
||||
$result = $application->extensions( 'core', 'MemberHistory' );
|
||||
|
||||
/* Since we're parsing, we only want to have one extension */
|
||||
static::$extensions[ $application->directory ] = array_pop( $result );
|
||||
static::$extensions[ $application->directory ] = $application->extensions( 'core', 'MemberHistory' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,27 +87,30 @@ class History extends \IPS\Helpers\Table\Db
|
||||
$options = array();
|
||||
$extensions = static::$extensions;
|
||||
|
||||
foreach( $extensions as $app => $extension )
|
||||
foreach( $extensions as $app => $appExtensions )
|
||||
{
|
||||
if( $extension === null )
|
||||
{
|
||||
unset( $extensions[ $app ] );
|
||||
continue;
|
||||
}
|
||||
foreach( $appExtensions as $index => $extension )
|
||||
{
|
||||
if( $extension === null )
|
||||
{
|
||||
unset( $extensions[ $app ][ $index ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach( $extension->getTypes() as $type )
|
||||
{
|
||||
if ( $type === 'oauth' and Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( Db::i()->findInSet( 'oauth_grant_types', array( 'authorization_code', 'implicit', 'password' ) ) ) )->first() === 1 )
|
||||
{
|
||||
foreach ( new ActiveRecordIterator( Db::i()->select( '*', 'core_oauth_clients', array( Db::i()->findInSet( 'oauth_grant_types', array( 'authorization_code', 'implicit', 'password' ) ) ) ), 'IPS\Api\OAuthClient' ) as $client )
|
||||
{
|
||||
$options[ $type ] = $client->_title;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
foreach( $extension->getTypes() as $type )
|
||||
{
|
||||
if ( $type === 'oauth' and Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( Db::i()->findInSet( 'oauth_grant_types', array( 'authorization_code', 'implicit', 'password' ) ) ) )->first() === 1 )
|
||||
{
|
||||
foreach ( new ActiveRecordIterator( Db::i()->select( '*', 'core_oauth_clients', array( Db::i()->findInSet( 'oauth_grant_types', array( 'authorization_code', 'implicit', 'password' ) ) ) ), 'IPS\Api\OAuthClient' ) as $client )
|
||||
{
|
||||
$options[ $type ] = $client->_title;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$options[ $type ] = 'log_type_title_' . $type;
|
||||
}
|
||||
$options[ $type ] = 'log_type_title_' . $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->advancedSearch = array(
|
||||
@@ -127,7 +127,13 @@ class History extends \IPS\Helpers\Table\Db
|
||||
{
|
||||
try
|
||||
{
|
||||
return $extensions[ $row['log_app'] ]->parseLogType( $val, $row );
|
||||
foreach( $extensions[ $row['log_app'] ] as $extension )
|
||||
{
|
||||
if( in_array( $row['log_type'], $extension->getTypes() ) )
|
||||
{
|
||||
return $extension->parseLogType( $val, $row );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Throwable $e )
|
||||
{
|
||||
@@ -146,13 +152,26 @@ class History extends \IPS\Helpers\Table\Db
|
||||
},
|
||||
'log_member'=> function( $val, $row ) use ( $extensions )
|
||||
{
|
||||
return $extensions[ $row['log_app'] ]->parseLogMember( $val, $row );
|
||||
foreach( $extensions[ $row['log_app'] ] as $extension )
|
||||
{
|
||||
if( in_array( $row['log_type'], $extension->getTypes() ) )
|
||||
{
|
||||
return $extension->parseLogMember( $val, $row );
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
'log_data' => function( $val, $row ) use ( $extensions )
|
||||
{
|
||||
try
|
||||
{
|
||||
return $extensions[ $row['log_app'] ]->parseLogData( $val, $row );
|
||||
foreach( $extensions[ $row['log_app'] ] as $extension )
|
||||
{
|
||||
if( in_array( $row['log_type'], $extension->getTypes() ) )
|
||||
{
|
||||
return $extension->parseLogData( $val, $row );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Throwable $e )
|
||||
{
|
||||
|
||||
+152
-28
@@ -34,7 +34,6 @@ use IPS\core\ProfileFields\Api\Field;
|
||||
use IPS\core\ProfileFields\Api\FieldGroup;
|
||||
use IPS\core\Reports\Report;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\Db\Select;
|
||||
use IPS\Events\Event;
|
||||
use IPS\Helpers\CoverPhoto;
|
||||
@@ -57,7 +56,6 @@ use IPS\Member\UserMenu;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Settings;
|
||||
use IPS\Text\Encrypt;
|
||||
use IPS\Xml\SimpleXML;
|
||||
use OutOfRangeException;
|
||||
@@ -92,6 +90,7 @@ class Member extends ActiveRecord
|
||||
{
|
||||
Followable::followersCount as public _followersCountBase;
|
||||
Followable::followers as public _followersBase;
|
||||
Followable::follow as public _followBase;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +206,7 @@ class Member extends ActiveRecord
|
||||
'new_device_email' => 16777216, // Send an email when a new device is used to log in.
|
||||
'no_solved_reenage' => 33554432, // User does not want to get topic re-engagement emails
|
||||
'datalayer_pii_optout' => 67108864, // User has opted in to having their PII collected by datalayer
|
||||
'email_messages_bounce' => 134217728, // User's email keeps returning hard bounces so they need to change it
|
||||
'email_messages_bounce' => 134217728, // @deprecated User's email keeps returning hard bounces so they need to change it
|
||||
// 268435456 - cookie_optout moved to cookie preferences
|
||||
'expert_user_disabled' => 536870912, // User does not want to be a community expert
|
||||
'expert_user_blocked' => 1073741824 // Admin has blocked expert user
|
||||
@@ -542,12 +541,6 @@ class Member extends ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/* If the email was changed, make sure that the bounce flag is unset so the member no longer sees the error message */
|
||||
if ( isset( $changes['email'] ) )
|
||||
{
|
||||
$this->members_bitoptions['email_messages_bounce'] = 0;
|
||||
}
|
||||
|
||||
parent::save();
|
||||
|
||||
if ( $new )
|
||||
@@ -564,8 +557,8 @@ class Member extends ActiveRecord
|
||||
Webhook::fire( 'member_registration_complete', $this, $this->webhookFilters() );
|
||||
}
|
||||
|
||||
/* Run member sync, but not if the only change is the last_activity timestamp */
|
||||
if( count( $this->changedCustomFields ) > 0 OR ( count( $changes ) > 0 AND !( count( $changes ) === 1 AND isset( $changes['last_activity'] ) ) ) )
|
||||
/* Run member sync, but not if the only change is the last_activity timestamp and if we're not just updating the member's language during language() init when no language is set */
|
||||
if( ( Member::loggedIn()->member_id != $this->member_id or $this->_lang !== null ) and ( count( $this->changedCustomFields ) > 0 OR ( count( $changes ) > 0 AND !( count( $changes ) === 1 AND isset( $changes['last_activity'] ) ) ) ) )
|
||||
{
|
||||
Event::fire( 'onProfileUpdate', $this, array( 'changes' => array_merge( $changes, $this->changedCustomFields ) ) );
|
||||
}
|
||||
@@ -781,7 +774,7 @@ class Member extends ActiveRecord
|
||||
/* Run sync */
|
||||
$this->logHistory( 'core', 'account', array( 'type' => 'spammer', 'set' => TRUE, 'actions' => $actions ) );
|
||||
Event::fire( 'onSetAsSpammer', $this );
|
||||
|
||||
Webhook::fire( 'member_flagged_as_spammer', $this );
|
||||
/* Notify admin */
|
||||
AdminNotification::send( 'core', 'Spammer', NULL, TRUE, $this->member_id, Member::loggedIn() );
|
||||
|
||||
@@ -865,6 +858,7 @@ class Member extends ActiveRecord
|
||||
|
||||
/* Run sync */
|
||||
Event::fire( 'onUnSetAsSpammer', $this );
|
||||
Webhook::fire( 'member_unflagged_as_spammer', $this );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,6 +902,8 @@ class Member extends ActiveRecord
|
||||
{
|
||||
Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'member_id=?', $this->member_id ) );
|
||||
}
|
||||
|
||||
Webhook::fire( 'member_ban_state_changed', [$this, $value] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1692,6 +1688,31 @@ class Member extends ActiveRecord
|
||||
return static::_followers( 'member', $this->member_id ?: 0, $privacy, $frequencyTypes, $date, $limit, $order, FALSE, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow this object
|
||||
*
|
||||
* @param string $frequency ( 'none', 'immediate', 'daily', 'weekly' )
|
||||
* @param bool $public
|
||||
* @param Member|null $member
|
||||
* @return void
|
||||
*/
|
||||
public function follow( string $frequency, bool $public=true, ?Member $member=null ) : void
|
||||
{
|
||||
$member = $member ?: Member::loggedIn();
|
||||
$this->_followBase( $frequency, $public, $member );
|
||||
|
||||
if( $public )
|
||||
{
|
||||
$this->achievementAction( 'core', 'FollowMember', [
|
||||
'giver' => $member
|
||||
] );
|
||||
|
||||
$notification = new Notification( Application::load( 'core' ), 'member_follow', $member, array( $member ) );
|
||||
$notification->recipients->attach( $this );
|
||||
$notification->send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a failed login attempt
|
||||
*
|
||||
@@ -1823,7 +1844,7 @@ class Member extends ActiveRecord
|
||||
/**
|
||||
* Get timezone
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_timezone(): ?string
|
||||
{
|
||||
@@ -2043,6 +2064,12 @@ class Member extends ActiveRecord
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Is the member in a group that is allowed to be an expert? This setting might have been changed. */
|
||||
if( Settings::i()->ips_experts_allowed_groups !== '*' and ! $this->inGroup( explode( ',', Settings::i()->ips_experts_allowed_groups ) ) )
|
||||
{
|
||||
$this->isExpert = false;
|
||||
}
|
||||
|
||||
if ( $this->isExpert === null )
|
||||
{
|
||||
try
|
||||
@@ -2343,7 +2370,7 @@ class Member extends ActiveRecord
|
||||
{
|
||||
if( $this->_url === NULL )
|
||||
{
|
||||
$this->_url = Url::internal( "app=core&module=members&controller=profile&id={$this->member_id}", 'front', 'profile', $this->members_seo_name );
|
||||
$this->_url = Url::internal( "app=core&module=members&controller=profile&id={$this->member_id}", 'front', 'profile', ( $this->members_seo_name ?? '-' ) );
|
||||
}
|
||||
|
||||
return $this->_url;
|
||||
@@ -3172,6 +3199,21 @@ class Member extends ActiveRecord
|
||||
return $this->_following[ $_key ];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the cached result used by the following method
|
||||
*
|
||||
* @param string $app Application key
|
||||
* @param string $area Area
|
||||
* @param int $id Item ID
|
||||
* @return void
|
||||
*/
|
||||
public function clearFollowingCache( string $app, string $area, int $id ): void
|
||||
{
|
||||
$key = md5( $app . $area . $id );
|
||||
unset( $this->_following[ $key ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin CP Restrictions
|
||||
*
|
||||
@@ -3778,12 +3820,18 @@ class Member extends ActiveRecord
|
||||
* @clientapiresponse datetime|null lastVisit Last distinct visit date on the site.
|
||||
* @clientapiresponse datetime|null lastPost Latest content submission date.
|
||||
* @apiresponse int profileViews Number of times member's profile has been viewed
|
||||
* @clientapiresponse bool spammer Is the member flagged as a spammer?
|
||||
* @clientapiresponse bool banned Is the member currently banned?
|
||||
* @apiresponse string birthday Member birthday in MM/DD/YYYY format (or MM/DD format if no year has been supplied).
|
||||
* @apiresponse array login_methods Array of login methods used <int, array{name: string, id: int|string}>, key is login method ID.
|
||||
* @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
|
||||
* @clientapiresponse bool allowAdminEmails Whether or not this member wants to receive admin emails
|
||||
* @apiresponse bool completed Whether or not the registration is completed
|
||||
* @clientapiresponse int totalMessages Total number of Personal Messages
|
||||
* @clientapiresponse int unreadMessages Total number of unread Personal Messages
|
||||
* @apiresponse [IPS\core\Achievements\Badge] badges All badges received by this member
|
||||
*/
|
||||
public function apiOutput( Member $authorizedMember = NULL, array $otherFields = NULL ): array
|
||||
{
|
||||
@@ -3837,7 +3885,7 @@ class Member extends ActiveRecord
|
||||
$fields[ $profileFieldGroup ] = ( new FieldGroup( $this->language()->get( 'core_pfieldgroups_' . $profileFieldGroup ), $groupValues ) )->apiOutput( $authorizedMember );
|
||||
}
|
||||
}
|
||||
catch( UnderflowException $e ) { } # Guests will not have any profile field information.
|
||||
catch( UnderflowException $e ) { } # Guests will not have any profile field information
|
||||
|
||||
$return = array();
|
||||
$return['id'] = $this->member_id;
|
||||
@@ -3881,8 +3929,29 @@ class Member extends ActiveRecord
|
||||
if( !$authorizedMember OR $authorizedMember->member_id === $this->member_id )
|
||||
{
|
||||
$return['birthday'] = $this->bday_month ? ( $this->bday_month . '/' . $this->bday_day . ( $this->bday_year ? '/' . $this->bday_year : '' ) ) : NULL;
|
||||
|
||||
/* Login Links */
|
||||
$return['login_methods'] = [];
|
||||
foreach( Db::i()->select( '*', 'core_login_links', [ 'token_member=? and token_linked=?', $this->member_id, 1 ] ) as $link )
|
||||
{
|
||||
/* Only include enabled methods */
|
||||
if( isset( Login::methods()[ $link['token_login_method'] ] ) )
|
||||
{
|
||||
$return['login_methods'][ $link['token_login_method'] ] = [
|
||||
'name' => Login::methods()[ $link['token_login_method'] ]->_title,
|
||||
'id' => $link['token_identifier']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$return['profileViews'] = $this->members_profile_views;
|
||||
|
||||
if( $authorizedMember === null or $authorizedMember->member_id == $this->member_id )
|
||||
{
|
||||
$return['spammer'] = (bool) $this->members_bitoptions['bw_is_spammer'];
|
||||
$return['banned'] = (bool) $this->isBanned();
|
||||
}
|
||||
|
||||
$return['customFields'] = $fields;
|
||||
|
||||
if ( !$authorizedMember )
|
||||
@@ -3898,9 +3967,29 @@ class Member extends ActiveRecord
|
||||
|
||||
$return['rank'] = $this->rank() ? $this->rank()->apiOutput( $authorizedMember ) : NULL;
|
||||
$return['achievements_points'] = $this->achievements_points;
|
||||
$return['allowAdminEmails'] = $this->allow_admin_mails;
|
||||
|
||||
if( $authorizedMember === null or $authorizedMember->member_id == $this->member_id )
|
||||
{
|
||||
$return['allowAdminEmails'] = $this->allow_admin_mails;
|
||||
}
|
||||
|
||||
$return['completed'] = $this->completed;
|
||||
|
||||
$counts = \IPS\Db::i()->select( 'COUNT(map_id) as total_count, COUNT(CASE WHEN map_has_unread = 1 THEN 1 END) as unread_messages', 'core_message_topic_user_map', [ 'map_user_id=?', $this->member_id ] )->first();
|
||||
|
||||
if( $authorizedMember === null or $authorizedMember->member_id == $this->member_id )
|
||||
{
|
||||
$return['totalMessages'] = $counts['total_count'];
|
||||
$return['unreadMessages'] = $counts['unread_messages'];
|
||||
}
|
||||
|
||||
/* Add member badges */
|
||||
$return['badges'] = array();
|
||||
foreach( $this->recentBadges( NULL ) as $badge )
|
||||
{
|
||||
$return['badges'][] = $badge->apiOutput();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -4695,7 +4784,7 @@ class Member extends ActiveRecord
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
/* Log the error as part of the failure audit trail */
|
||||
\IPS\Log::log( "Group promotion found a member (#{$this->member_id}: {$this->name} [Group ID {$this->member_group_id}] with an invalid member group", 'group_promotion' );
|
||||
Log::log( "Group promotion found a member (#{$this->member_id}: {$this->name} [Group ID {$this->member_group_id}] with an invalid member group", 'group_promotion' );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5223,11 +5312,27 @@ class Member extends ActiveRecord
|
||||
*/
|
||||
protected function _sendWelcomeEmail() : void
|
||||
{
|
||||
try
|
||||
if( Settings::i()->reg_welcome_email )
|
||||
{
|
||||
Email::buildFromTemplate( 'core', 'registration_complete', array( $this ), Email::TYPE_TRANSACTIONAL )->send( $this );
|
||||
if( $this->language()->checkKeyExists( 'reg_welcome_email_message' ) )
|
||||
{
|
||||
$message = $this->language()->get( 'reg_welcome_email_message' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$message = $this->language()->addToStack( 'email_reg_complete', false, [ 'sprintf' => [ Settings::i()->board_name ] ] );
|
||||
if( $this->members_pass_hash )
|
||||
{
|
||||
$message .= $this->language()->addToStack( 'email_reg_complete_pass' );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Email::buildFromTemplate( 'core', 'registration_complete', array( $this, $message ), Email::TYPE_TRANSACTIONAL )->send( $this );
|
||||
}
|
||||
catch( ErrorException $e ) { }
|
||||
}
|
||||
catch( ErrorException $e ) { }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5264,7 +5369,7 @@ class Member extends ActiveRecord
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Profile Sync
|
||||
*
|
||||
@@ -5274,7 +5379,7 @@ class Member extends ActiveRecord
|
||||
{
|
||||
$profileSync = $this->profilesync;
|
||||
|
||||
foreach (Login::methods() as $method )
|
||||
foreach ( Login::methods() as $method )
|
||||
{
|
||||
if ( $method->canProcess( $this ) )
|
||||
{
|
||||
@@ -5285,6 +5390,16 @@ class Member extends ActiveRecord
|
||||
$profileSync[ $type ] = array( 'handler' => $method->_id, 'ref' => NULL, 'error' => NULL );
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform any extra profile sync the login handler may need */
|
||||
try
|
||||
{
|
||||
if( method_exists( $method, 'extraProfileSync' ) )
|
||||
{
|
||||
$method->extraProfileSync( $this );
|
||||
}
|
||||
}
|
||||
catch( Login\Exception $e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5571,7 +5686,7 @@ class Member extends ActiveRecord
|
||||
if ( $this->member_id )
|
||||
{
|
||||
/* Set this only if this was not called by the task system, otherwise we'll set the member who triggered the task as moderator */
|
||||
if ( Dispatcher::hasInstance() and !Dispatcher::i()->inDestructor and $by === NULL )
|
||||
if ( Dispatcher::hasInstance() and isset( Dispatcher::i()->inDestructor ) and !Dispatcher::i()->inDestructor and $by === NULL )
|
||||
{
|
||||
$by = Session::i()->member; // Not \IPS\Member::loggedIn() because if this is an admin logged in as a member, we want to log that the action was done by the admin
|
||||
}
|
||||
@@ -6300,6 +6415,12 @@ class Member extends ActiveRecord
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Cloudy stuff */
|
||||
if( !Bridge::i()->checkAchievementRule( array_merge( [ 'action' => "{$app}_{$extension}" ], $ruleData ), $this ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !$ruleData['filters'] or $extensionObject->filtersMatch( $this, $ruleData['filters'], $extra ) )
|
||||
{
|
||||
/* Did we already award for this milestone? */
|
||||
@@ -6364,6 +6485,9 @@ class Member extends ActiveRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fire the post-achievement here */
|
||||
Bridge::i()->postAchievement( $ruleId, array_merge( [ 'action' => "{$app}_{$extension}" ], $ruleData ), $this, $extra );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6631,7 +6755,7 @@ class Member extends ActiveRecord
|
||||
{
|
||||
$lastLogin = Db::i()->select( 'member_timestamp', 'core_members_logins', [ 'member_id=?', $this->member_id ], 'member_timestamp DESC', 1 )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
/* This is the first login, or they haven't logged in since upgrading from < 106100 */
|
||||
try
|
||||
@@ -6642,7 +6766,7 @@ class Member extends ActiveRecord
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
/* This could be a new install, or IN_DEV where upgrade history isn't available */
|
||||
return false;
|
||||
@@ -6658,7 +6782,7 @@ class Member extends ActiveRecord
|
||||
}
|
||||
|
||||
/* If this login is within the period, don't send an email */
|
||||
if( $lastLoginObj > DateTime::create()->sub( new \DateInterval( static::LOGIN_INACTIVITY_NOTIFICATION ) ) )
|
||||
if( $lastLoginObj > DateTime::create()->sub( new DateInterval( static::LOGIN_INACTIVITY_NOTIFICATION ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -6669,7 +6793,7 @@ class Member extends ActiveRecord
|
||||
{
|
||||
$location = GeoLocation::getRequesterLocation();
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
$location = NULL;
|
||||
}
|
||||
|
||||
+22
-2
@@ -95,8 +95,8 @@ class Team extends ActiveRecord
|
||||
return iterator_to_array(
|
||||
new ActiveRecordIterator(
|
||||
Db::i()->select( '*', 'core_members', Db::i()->in( 'member_id', $this->members ), 'name' ),
|
||||
Member::class
|
||||
)
|
||||
Member::class,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,4 +168,24 @@ class Team extends ActiveRecord
|
||||
|
||||
return static::$_teams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output for API
|
||||
*
|
||||
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @return array
|
||||
* @apiresponse string name Title
|
||||
* @apiresponse \IPS\Member members The team members
|
||||
*/
|
||||
public function apiOutput( Member $authorizedMember = NULL ): array
|
||||
{
|
||||
$response = [];
|
||||
$response['id'] = $this->id;
|
||||
$response['name'] = $this->name;
|
||||
foreach( $this->members() as $member )
|
||||
{
|
||||
$response['members'][$member->member_id] = $member->apiOutput( $authorizedMember );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use IPS\Helpers\Menu\Link;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output\UI\MenuExtension;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
@@ -61,10 +62,6 @@ class UserMenu
|
||||
}
|
||||
|
||||
$settingsLink = new Link( Url::internal( "app=core&module=system&controller=settings", "front", "settings" ), 'menu_settings', icon: 'fa-solid fa-gear', identifier: 'settings' );
|
||||
if( $member->members_bitoptions['email_messages_bounce'] )
|
||||
{
|
||||
$settingsLink->notificationIcon = 'fa-solid fa-triangle-exclamation';
|
||||
}
|
||||
$menu->add( $settingsLink );
|
||||
|
||||
foreach( Application::allExtensions( 'core', 'UserMenu', $member ) as $ext )
|
||||
@@ -168,10 +165,6 @@ class UserMenu
|
||||
$menu->add( new Link( $member->url(), 'menu_profile', '', icon:'fa-solid fa-user', identifier: 'profile' ) );
|
||||
$menu->add( new Link( $member->url()->setQueryString( 'do', 'edit' ), 'profile_edit', '', icon: 'fa-solid fa-pen-to-square', identifier: 'editProfile' ) );
|
||||
$settingsLink = new Link( Url::internal( "app=core&module=system&controller=settings", "front", "settings" ), "menu_settings", "", icon: 'fa-solid fa-gear', identifier: "settings" );
|
||||
if( $member->members_bitoptions['email_messages_bounce'] )
|
||||
{
|
||||
$settingsLink->notificationIcon = 'fa-solid fa-triangle-exclamation';
|
||||
}
|
||||
$menu->add( $settingsLink );
|
||||
|
||||
foreach( Application::allExtensions( 'core', 'UserMenu', $member ) as $ext )
|
||||
@@ -297,7 +290,7 @@ class UserMenu
|
||||
$menu->css = 'ipsButton ipsButton--overlay';
|
||||
|
||||
/* Only the logged in member should be able to modify the photos */
|
||||
if( explode( ':', $member->group['g_photo_max_vars'] )[0] )
|
||||
if( explode( ':', $member->group['g_photo_max_vars'] )[0] or ( Bridge::i()->featureIsEnabled( 'profile_gallery' ) and Settings::i()->cloud_profile_gallery ) )
|
||||
{
|
||||
$link = new Link( Url::internal( "app=core&module=members&controller=profile&do=editPhoto&id={$member->member_id}", 'front','edit_profile_photo', $member->members_seo_name ), 'profile_edit_photo_tab',icon: 'fa-regular fa-circle-user' );
|
||||
$link->dataAttributes['data-action'] = 'editPhoto';
|
||||
|
||||
Reference in new issue
Block a user