Version 5.0.6

This commit is contained in:
Neo committed 2025-12-19 18:55:49 -08:00
1 parent a3a4a9ccbd
commit b89858b2ff
1214 files changed
+92828 -14859

No files matched your search

+256 -4
View File
@@ -44,6 +44,9 @@ use IPS\Helpers\Form\Text;
use IPS\Helpers\Form\TextArea;
use IPS\Helpers\Form\Upload;
use IPS\Helpers\Form\YesNo;
use IPS\Helpers\Menu;
use IPS\Helpers\Menu\Buttons;
use IPS\Helpers\Menu\Link;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\IPS;
@@ -52,6 +55,7 @@ use IPS\Log;
use IPS\Math\Number;
use IPS\Member;
use IPS\Member\Club\CustomField;
use IPS\Member\Club\Page as ClubPage;
use IPS\nexus\Customer;
use IPS\nexus\Invoice;
use IPS\nexus\Money;
@@ -930,6 +934,7 @@ class Club extends ActiveRecord implements Embeddable
{
return [];
}
$values = array_filter( $values, function ( $val) { return ( $val !== '' AND $val !== NULL ); } );
return $values;
}
@@ -944,9 +949,10 @@ class Club extends ActiveRecord implements Embeddable
*
* @param bool $getOverlay If FALSE, will not set the overlay, which saves queries if it will not be used (such as in clubCard)
* @param string $position Position of cover photo
* @param Model|ClubPage|null $container Node or Page currently viewing
* @return CoverPhoto
*/
public function coverPhoto( bool $getOverlay=TRUE, string $position='full' ): CoverPhoto
public function coverPhoto( bool $getOverlay=TRUE, string $position='full', Model|ClubPage|null $container=null ): CoverPhoto
{
$photo = new CoverPhoto;
@@ -958,7 +964,7 @@ class Club extends ActiveRecord implements Embeddable
}
if ( $getOverlay )
{
$photo->overlay = Theme::i()->getTemplate( 'clubs', 'core', 'front' )->coverPhotoOverlay( $this, $position );
$photo->overlay = Theme::i()->getTemplate( 'clubs', 'core', 'front' )->coverPhotoOverlay( $this, $position, $container );
}
$photo->editable = $this->isLeader();
$photo->object = $this;
@@ -1491,12 +1497,19 @@ class Club extends ActiveRecord implements Embeddable
{
$class = $row['node_class'];
$classBits = explode( '\\', $class );
if( !Application::load( $classBits[1] )->_enabled )
try
{
if( !Application::load( $classBits[1] )->_enabled )
{
continue;
}
}
catch( \OutOfRangeException $e)
{
continue;
}
/* Make sure the class exists. This can happen in the case of Pages databases/categories */
if( !class_exists( $row['node_class'] ) )
{
@@ -2097,6 +2110,207 @@ class Club extends ActiveRecord implements Embeddable
return static::$tabs;
}
/**
* Build the club menu
*
* @param Model|ClubPage|null $container
* @return Menu
*/
public function menu( Model|ClubPage|null $container = null ) : Menu
{
$menu = new Menu( 'club_manage', css: 'ipsButton ipsButton--inherit' );
if( Settings::i()->clubs_header == 'sidebar' )
{
$menu->css .= ' i-width_100p i-margin-top_2';
}
if( !$this->isLeader() and $this->owner->member_id != Member::loggedIn()->member_id and !Member::loggedIn()->modPermission( 'can_access_all_clubs' ) )
{
return $menu;
}
if( !Settings::i()->clubs_require_approval or $this->approved )
{
if ( $this->isLeader() and $nodeTypes = static::availableNodeTypes() )
{
foreach( $nodeTypes as $nodeType )
{
$menu->add( new Link( $this->url()->setQueryString( [ 'do' => 'nodeForm', 'type' => $nodeType ] ), $nodeType::clubFrontTitle(), dataAttributes: [ 'data-ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'club_create_node' ) ], opensDialog: true ) );
}
}
if( ( $this->owner and $this->owner->member_id == Member::loggedIn()->member_id ) or Member::loggedIn()->modPermission( 'can_access_all_clubs' ) or Member::loggedIn()->modPermission( 'can_manage_featured_clubs' ) )
{
$menu->addSeparator();
}
}
if( $this->isLeader() )
{
$menu->add( new Link( $this->url()->setQueryString( 'do', 'editPhoto' ), 'club_profile_photo', dataAttributes: [
'data-ipsDialog' => true,
'data-ipsDialog-modal' => true,
'data-ipsDialog-forceReload' => true,
'data-ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'club_profile_photo' ),
'data-action' => 'editPhoto'
] ) );
}
if( ( $this->owner and $this->owner->member_id == Member::loggedIn()->member_id ) or Member::loggedIn()->modPermission( 'can_access_all_clubs' ) )
{
$menu->add( new Link( $this->url()->setQueryString( 'do', 'edit' ), 'club_edit_settings', opensDialog: true ) );
}
if( Member::loggedIn()->modPermission( 'can_manage_featured_clubs' ) )
{
if( $this->featured )
{
$menu->add( new Link( $this->url()->csrf()->setQueryString( 'do', 'unfeature' ), 'club_unfeature', dataAttributes: [ 'data-confirm' => '' ] ) );
}
else
{
$menu->add( new Link( $this->url()->csrf()->setQueryString( 'do', 'feature' ), 'club_feature', dataAttributes: [ 'data-confirm' => '' ] ) );
}
}
if( $this->canInvite() )
{
$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 )
{
$menu->add( new Link( '#', 'reorder_club_menu', dataAttributes: [ 'data-action' => 'reorderClubmenu' ] ) );
}
if( ClubPage::canAdd( $this ) )
{
$menu->add( new Link( $this->url()->setQueryString( 'do', 'addPage' ), 'club_add_page', dataAttributes: [
'data-ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'add_page_to_club', true, [ 'sprintf' => $this->name ] )
], opensDialog: true ) );
}
if( $container instanceof ClubPage)
{
if( $container->canEdit() )
{
$menu->add( new Link( $container->url( 'edit' ), 'edit_page', opensDialog: true ) );
}
if( $container->canDelete() )
{
$menu->add( new Link( $container->url( 'delete' )->csrf(), 'delete_page', dataAttributes: [ 'data-confirm' => '' ] ) );
}
}
if( $container instanceof Model and $this->isLeader() )
{
$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 ) );
if( isset( $container::$contentItemClass ) and $itemClass = $container::$contentItemClass and ( $container->modPermission( 'delete', Member::loggedIn(), $itemClass ) or !!$itemClass::contentCount( $container, true, true, true, 1 ) ) )
{
$menu->add( new Link( $this->url()->setQueryString( [ 'do' => 'nodeDelete', 'type' => get_class( $container ), 'node' => $container->_id] )->csrf(), Member::loggedIn()->language()->addToStack( 'clubs_delete_this_container', true, [ 'sprintf' => Member::loggedIn()->language()->addToStack( $container::$nodeTitle . '_sg' ) ] ), dataAttributes: [ 'data-confirm' => '', 'data-confirmSubMessage' => Member::loggedIn()->language()->addToStack( 'clubs_delete_container_confirm' ) ] ) );
}
}
/* Extensions */
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
{
foreach( $ext->menu( $this, $container ) as $link )
{
$menu->add( $link );
}
}
return $menu;
}
/**
* Generate a buttons list for the club header
*
* @return Buttons
*/
public function buttons() : Buttons
{
$buttons = new Buttons();
$memberStatus = $this->memberStatus( Member::loggedIn() );
if( $this->type !== static::TYPE_PUBLIC )
{
if( $memberStatus === static::STATUS_BANNED )
{
$buttons->addButton( null, 'club_banned_title', icon: 'fa-solid fa-xmark', tooltip: 'club_banned_desc_short' );
}
elseif( in_array( $memberStatus, [ static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR, static::STATUS_WAITING_PAYMENT ] ) )
{
if( $memberStatus === static::STATUS_WAITING_PAYMENT )
{
$buttons->addButton( $this->url()->setQueryString( 'do', 'join' )->csrf(), 'club_pay_membership_fee' );
}
if( in_array( $memberStatus, [ static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ] ) )
{
$buttons->addButton( $this->url()->setQueryString( 'do', 'renew' )->csrf(), 'club_renew_membership' );
}
if( !$this->owner or $this->owner->member_id != Member::loggedIn()->member_id )
{
$attributes = [ 'data-confirm' => '' ];
if( $this->isPaid() )
{
$attributes['data-confirmSubMessage'] = Member::loggedIn()->language()->addToStack( 'club_leave_paid_warning' );
}
$buttons->addButton( $this->url()->setQueryString( 'do', 'leave' )->csrf(), 'club_leave', dataAttributes: $attributes );
}
}
elseif( $this->canJoin() )
{
/* We skip the join button for the sidebar view; it's shown somewhere else */
if( Settings::i()->clubs_header != 'sidebar' )
{
$attributes = [];
if( $this->isPaid() and $memberStatus !== static::STATUS_INVITED_BYPASSING_PAYMENT )
{
$attributes = [
'data-confirm' => '',
'data-confirmIcon' => 'info',
'data-confirmMessage' => Member::loggedIn()->language()->addToStack( 'club_membership_item' ),
'data-confirmSubmessage' => Member::loggedIn()->language()->addToStack( $this->memberFeeMessage() )
];
}
$buttons->addButton( $this->url()->csrf()->setQueryString( 'do', 'join' )->addRef( Request::i()->url() ), 'club_join', dataAttributes: $attributes );
}
}
elseif( !$this->canRead() )
{
if( $memberStatus === static::STATUS_REQUESTED )
{
$buttons->addButton( $this->url()->csrf()->setQueryString( 'do', 'cancelJoin' ), 'club_cancel_request', 'ipsButton ipsButton--text', icon: 'fa-solid fa-xmark' );
}
elseif( $memberStatus === static::STATUS_DECLINED )
{
$buttons->addButton( null, 'club_denied_title', dataAttributes: [ 'aria-disabled' => 'true' ], icon: 'fa-solid fa-xmark', tooltip: Member::loggedIn()->language()->addToStack( 'club_denied_desc_short' ) );
}
}
}
/* Extensions */
foreach( Application::allExtensions( 'core', 'Club' ) AS $ext )
{
foreach( $ext->buttons( $this ) as $button )
{
if( is_string( $button ) )
{
$buttons->addHtml( $button );
}
else
{
$buttons->add( $button );
}
}
}
return $buttons;
}
/**
* Can a member view the members page
*
@@ -2383,4 +2597,42 @@ class Club extends ActiveRecord implements Embeddable
$purchase->save();
}
/**
* Iterate through all the club nodes and set the last_activity
*
* @param Member|null $member
* @return void
*/
public function updateLastActivityAndItemCount( Member $member = null ) : void
{
$member = $member ?: Member::loggedIn();
$this->content = 0;
$this->last_activity = 0;
foreach ( $this->nodes() as $node )
{
try
{
$nodeClass = $node['node_class'];
if ( ! class_exists( $nodeClass ) )
{
continue;
}
$node = $nodeClass::load( $node['node_id'] );
if ( $lastCommentTime = $node->getLastCommentTime( $member ) and $lastCommentTime->getTimestamp() > $this->last_activity )
{
$this->last_activity = $lastCommentTime->getTimestamp();
}
$this->content += (int) $node->getContentItemCount();
}
catch ( \Exception $e ) { }
}
$this->rebuilt = time();
$this->save();
}
}
+1
View File
@@ -114,6 +114,7 @@ class Group extends ActiveRecord
'gbw_club_manage_indexing' => 1024, // Users in this group can manage indexing of clubs
'gbw_view_helpful' => 2048, // Users in this group can view who found content helpful
'gbw_change_layouts' => 4096, // Users in this group can change the layouts of key areas set by themes
'gbw_can_mark_helpful' => 8192, // Users in this group can mark content as helpful
)
)
);
+1 -15
View File
@@ -18,9 +18,7 @@ use IPS\DateTime;
use IPS\Db;
use IPS\Http\Url;
use IPS\Log;
use IPS\Member;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Theme;
use Throwable;
use function defined;
use const IPS\Helpers\Table\SEARCH_DATE_RANGE;
@@ -148,17 +146,7 @@ class History extends \IPS\Helpers\Table\Db
},
'log_member'=> function( $val, $row ) use ( $extensions )
{
try
{
return $extensions[ $row['log_app'] ]->parseLogMember( $val, $row );
}
catch( Throwable $e )
{
Log::log( $e, 'member_history' );
}
$member = Member::load( $val );
return Theme::i()->getTemplate( 'global', 'core' )->userPhoto( $member, 'tiny' ) . ' ' . $member->link();
return $extensions[ $row['log_app'] ]->parseLogMember( $val, $row );
},
'log_data' => function( $val, $row ) use ( $extensions )
{
@@ -171,8 +159,6 @@ class History extends \IPS\Helpers\Table\Db
Log::log( $e, 'member_history' );
return $val; # Return the value so the admin may have some clue as to what the log entry was for
}
return '';
}
);
}
+6 -6
View File
@@ -1121,7 +1121,7 @@ class Member extends ActiveRecord
if( $this->_data['mgroup_others'] )
{
foreach( explode( ',', $this->_data['mgroup_others'] ) as $id )
foreach( array_filter( explode( ',', $this->_data['mgroup_others'] ) ) as $id )
{
$this->_groups[] = intval( $id );
}
@@ -3514,14 +3514,14 @@ class Member extends ActiveRecord
"(assign_type=? and assign_to=?)"
];
$binds = [
Assignment::ASSIGNMENT_TEAM,
Assignment::ASSIGNMENT_MEMBER,
$this->member_id
];
if( $teams = $this->teams() )
{
$clause[] = "(assign_type=? and " . Db::i()->in( 'assign_to', array_keys( $teams ) ) . ")";
$binds[] = Assignment::ASSIGNMENT_MEMBER;
$binds[] = Assignment::ASSIGNMENT_TEAM;
}
$where = array_merge( array( implode( " OR ", $clause ) ), $binds );
@@ -4123,7 +4123,7 @@ class Member extends ActiveRecord
}
/* Otherwise return it */
return $permissions[$key] ?? NULL;
return $permissions[$key] ?? false;
}
/**
@@ -5077,9 +5077,9 @@ class Member extends ActiveRecord
/* Send referral notification */
$this->_sendReferralNotification();
/* Send an admin notification */
AdminNotification::send( 'core', 'NewRegComplete', NULL, TRUE, $this, !$initiatedByAdmin );
AdminNotification::send( 'core', 'NewRegComplete', NULL, TRUE, $this, $initiatedByAdmin );
/* Delete rows */
Db::i()->delete( 'core_validating', array( 'member_id=?', $this->member_id ) );
+22 -12
View File
@@ -28,6 +28,7 @@ use IPS\Node\Model;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Settings;
use OutOfBoundsException;
use OutOfRangeException;
use function count;
use function defined;
use function is_array;
@@ -462,8 +463,15 @@ class ProfileStep extends Model
}
list( $app, $extension ) = explode( '_', $this->_data['extension'] );
$class = "\\IPS\\{$app}\\extensions\\core\\ProfileSteps\\{$extension}";
return new $class;
try
{
$class = Application::getExtensionClass( $app, 'ProfileSteps', $extension );
return new $class;
}
catch( OutOfRangeException )
{
return null;
}
}
/**
@@ -642,18 +650,16 @@ class ProfileStep extends Model
$return[ str_replace( 'profile_', '', $key ) ] = $value;
}
try
/*try
{
$ext = static::findExtensionFromAction( $return['step_completion_act'] );
$this->extension = $ext;
$ext = explode( '_', $ext );
$class = "\\IPS\\{$ext[0]}\\extensions\\core\\ProfileSteps\\{$ext[1]}";
$extension = new $class;
$ext = explode( '_', static::findExtensionFromAction( $return['step_completion_act'] ) );
$class = Application::getExtensionClass( $ext[0], 'ProfileSteps', $ext[1] );
$return['step_extension'] = implode( "_", $ext );
}
catch( OutOfBoundsException $e )
catch( OutOfBoundsException | OutOfRangeException $e )
{
throw new InvalidArgumentException;
}
}*/
return $return;
}
@@ -746,8 +752,12 @@ class ProfileStep extends Model
if( $extensionKey = static::findExtensionFromAction( $key ) )
{
list( $app, $extension ) = explode( '_', $extensionKey );
$class = "\\IPS\\{$app}\\extensions\\core\\ProfileSteps\\{$extension}";
return new $class;
try
{
$class = Application::getExtensionClass( $app, 'ProfileSteps', $extension );
return new $class;
}
catch( OutOfRangeException ){}
}
return null;
+28 -3
View File
@@ -17,11 +17,13 @@ namespace IPS\Member;
use IPS\Application;
use IPS\Application\Module;
use IPS\Dispatcher;
use IPS\Extensions\MemberFilterAbstract;
use IPS\Helpers\CoverPhoto;
use IPS\Helpers\Menu;
use IPS\Helpers\Menu\Link;
use IPS\Http\Url;
use IPS\Member;
use IPS\Output\UI\MenuExtension;
use IPS\Request;
use IPS\Settings;
use IPS\Theme;
@@ -256,6 +258,29 @@ class UserMenu
return $menu;
}
/**
* Notification count displayed only on the mobile menu.
*
* @return int
*/
public static function mobileNotificationCount() : int
{
$notificationCount = 0;
if( Member::loggedIn()->canAccessModule( Module::get( 'core', 'modcp' ) ) and Member::loggedIn()->canAccessReportCenter() )
{
$notificationCount += Member::loggedIn()->reportCount();
}
foreach( Application::allExtensions( 'core', 'UserMenu', Member::loggedIn() ) as $ext )
{
/** @var MenuExtension $ext */
$notificationCount += $ext->mobileNotificationCount();
}
return $notificationCount;
}
/**
* @param Member $member
* @return Menu|null
@@ -268,11 +293,11 @@ class UserMenu
return null;
}
$menu = new Menu( 'profile_edit','fa-regular fa-pen-to-square');
$menu = new Menu( 'profile_edit','fa-solid fa-pen-to-square');
$menu->css = 'ipsButton ipsButton--overlay';
/* Only the logged in member should be able to modify the photos */
if( Member::loggedIn()->member_id == $member->member_id AND explode( ':', $member->group['g_photo_max_vars'] )[0] )
if( explode( ':', $member->group['g_photo_max_vars'] )[0] )
{
$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';
@@ -303,7 +328,7 @@ class UserMenu
}
}
$link = new Link( $member->url()->setQueryString('do', 'edit'), 'profile_edit', icon: 'fa-regular fa-pen-to-square');
$link = new Link( $member->url()->setQueryString('do', 'edit'), 'profile_edit', icon: 'fa-solid fa-pen-to-square');
$link->opensDialog('profile_edit');
$menu->add( $link);
if( $member->member_id === Member::loggedIn()->member_id )