Version 5.0.13

This commit is contained in:
Neo committed 2025-12-19 19:04:57 -08:00
1 parent b89858b2ff
commit 5efe744c5f
1407 files changed
+69690 -30177

No files matched your search

+226 -15
View File
@@ -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 ){}
}
}
}