Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -41,14 +41,22 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Some advanced search links may bring us here */
\IPS\Output::i()->bypassCsrfKeyCheck = true;
$forceIndex = 'joined';
/* Yeah, this is the level of hackery we've gotten to. Millions of rows in core_members is hard work */
if ( isset( \IPS\Request::i()->filter ) and \IPS\Request::i()->filter == 'members_filter_validating' )
{
$forceIndex = NULL;
}
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'email<>?', '' ) ), 'joined' );
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'email<>?', '' ) ), $forceIndex );
$table->langPrefix = 'members_';
/* Columns we need */
$table->include = array( 'photo', 'name', 'email', 'joined', 'group_name' );
if ( \IPS\core\Achievements\Rank::getStore() )
if ( \IPS\Settings::i()->achievements_enabled AND \IPS\core\Achievements\Rank::getStore() )
{
$table->include[] = 'achievements_points';
$table->widths['achievements_points'] = '17';
@@ -153,20 +161,26 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
array(
'select' => 'm.row_id',
'from' => array( 'core_admin_permission_rows', 'm' ),
'where' => "m.row_id=core_members.member_id AND m.row_id_type='member'" ),
array(
'select' => 'g.row_id',
'from' => array( 'core_admin_permission_rows', 'g' ),
'where' => array( 'g.row_id', \IPS\Db::i()->select( 'row_id', array( 'core_admin_permission_rows', 'sub' ), array( "((sub.row_id=core_members.member_group_id OR FIND_IN_SET( sub.row_id, core_members.mgroup_others ) ) AND sub.row_id_type='group') AND g.row_id_type='group'" ), NULL, array( 0, 1 ) ) ) )
'where' => "( m.row_id_type='group' and ( m.row_id=core_members.member_group_id OR FIND_IN_SET( m.row_id, core_members.mgroup_others ) ) or ( m.row_id=core_members.member_id AND m.row_id_type='member' ))",
'type' => 'STRAIGHT_JOIN' )
);
/* Makes query less efficient */
if ( $table->filter !== 'members_filter_administrators' )
{
unset( $table->joins[2] );
unset( $table->joins[3] );
}
if ( $table->filter !== 'members_filter_validating' )
{
unset( $table->joins[0] );
}
if ( ! isset( \IPS\Request::i()->advanced_search_submitted ) )
{
unset( $table->joins[1] );
}
/* Ranks */
$rankOptions = array( '' => 'any' );
foreach( \IPS\core\Achievements\Rank::getStore() as $rank )
@@ -256,7 +270,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
'members_filter_banned' => 'temp_ban<>0',
'members_filter_spam' => \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer' ),
'members_filter_validating' => '( v.lost_pass=0 AND v.forgot_security=0 AND v.vid IS NOT NULL )',
'members_filter_administrators' => '( m.row_id IS NOT NULL OR g.row_id IS NOT NULL )'
'members_filter_administrators' => '(1=1)' // We use a straight join when this filter is activated, so there's no additional where needed
);
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_members', array( "email <> '' and name=''" ) )->first() )
@@ -272,9 +286,25 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Custom parsers */
$table->parsers = array(
'email' => function( $val, $row )
'email' => function( $val, &$row )
{
if ( $row['vid'] )
if ( ! array_key_exists( 'vid', $row ) )
{
/* Grab the data if need be */
$member = \IPS\Member::constructFromData( $row );
if ( $member->members_bitoptions['validating'] )
{
try
{
$validating = \IPS\Db::i()->select( '*', 'core_validating', [ 'member_id=?', $member->member_id ] )->first();
$row = array_merge( $validating, $row );
}
catch( \UnderflowException $e ) { }
}
}
if ( ! empty( $row['vid'] ) )
{
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'admin' )->memberEmailCell( \IPS\Theme::i()->getTemplate( 'members', 'core', 'admin' )->memberValidatingCell( $val, \IPS\Member::constructFromData( $row )->validatingDescription( $row ) ) );
}
@@ -384,7 +414,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['approve'] = array(
'icon' => 'check-circle',
'title' => 'approve',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=approve&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=approve&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'id' => "{$member->member_id}-approve",
'data' => array(
'bubble' => '',
@@ -393,7 +423,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['ban'] = array(
'icon' => 'times',
'title' => 'ban',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=ban&id=' . $member->member_id . '&permban=1' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=ban&id=' . $member->member_id . '&permban=1' . $this->getSafeUrlFromFilters() )->csrf(),
'id' => "{$member->member_id}-ban",
'data' => array(
'bubble' => '',
@@ -405,7 +435,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['resend_email'] = array(
'icon' => 'envelope-o',
'title' => 'resend_validation_email',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=resendEmail&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=resendEmail&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'data' => array( 'doajax' => '' ),
'id' => "{$member->member_id}-resend",
);
@@ -423,7 +453,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['unlock'] = array(
'icon' => 'unlock',
'title' => 'unlock',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=unlock&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=unlock&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'data' => array( 'bubble' => '' )
);
}
@@ -431,7 +461,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['view'] = array(
'icon' => 'search',
'title' => 'view',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' ) . $member->member_id,
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' . $member->member_id . $this->getSafeUrlFromFilters() ),
);
if ( !\IPS\Request::i()->_groupFilter )
@@ -443,7 +473,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['flag'] = array(
'icon' => 'flag',
'title' => 'spam_flag',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=1' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=1' . $this->getSafeUrlFromFilters() )->csrf(),
'hidden' => $member->members_bitoptions['bw_is_spammer'],
'id' => "{$member->member_id}-flag",
'data' => array(
@@ -453,7 +483,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['unflag'] = array(
'icon' => 'flag ipsType_spammer',
'title' => 'spam_unflag',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=0' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=0' . $this->getSafeUrlFromFilters() )->csrf(),
'hidden' => !$member->members_bitoptions['bw_is_spammer'],
'id' => "{$member->member_id}-unflag",
'data' => array(
@@ -468,7 +498,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['delete'] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=delete&id=' ) . $member->member_id,
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=delete&id=' . $member->member_id . $this->getSafeUrlFromFilters() ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'delete_member', FALSE, array( 'sprintf' => $member->name ) ) ),
);
}
@@ -569,6 +599,25 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->output .= (string) $table;
}
protected function toggleDataLayerPii()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'member_edit' );
$member = \IPS\Member::load( \IPS\Request::i()->id );
if ( $member->isAdmin() )
{
\IPS\Dispatcher::i()->checkAcpPermission( 'member_edit_admin' );
}
if ( $member->member_id )
{
$member->datalayer_collect_pii = (bool) !$member->datalayer_collect_pii;
$member->save();
}
$_SESSION['member_datalayer_changed'] = 1;
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' . \IPS\Request::i()->id ?? 0 ) );
}
/**
* Prune members
*
@@ -1180,6 +1229,12 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$history = \IPS\Theme::i()->getTemplate('memberprofile')->history( $member, $history, $historyFilters );
}
if ( isset( $_SESSION['member_datalayer_changed'] ) )
{
\IPS\Output::i()->inlineMessage = 'Saved';
unset( $_SESSION['member_datalayer_changed'] );
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'members/view.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_members.js', 'core' ) );
@@ -1343,7 +1398,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$form = new \IPS\Helpers\Form;
// This looks weird, but we only want this showing here, and not anywhere else 'password' is used as a form key.
\IPS\Member::loggedIn()->language()->words['password_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member', NULL, array( 'sprintf' => array( $member->acpUrl()->csrf()->setQueryString( 'do', 'forcePassReset' ), \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_confirmmsg' ) ) ) );
\IPS\Member::loggedIn()->language()->words['password_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member', FALSE, array( 'sprintf' => array( $member->acpUrl()->csrf()->setQueryString( 'do', 'forcePassReset' ), \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_confirmmsg' ) ) ) );
$form->add( new \IPS\Helpers\Form\Password( 'password', '', TRUE, array( 'protect' => TRUE, 'confirm' => 'password_confirm', 'showMeter' => \IPS\Settings::i()->password_strength_meter, 'minimumStrength' => 1, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
$form->add( new \IPS\Helpers\Form\Password( 'password_confirm', '', TRUE, array( 'protect' => TRUE, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
@@ -1470,7 +1525,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* If we have external login handlers, we need to show an interstitual page indicating that with caveats. */
if ( \count( $externals ) )
{
$msg = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_external', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $externals ) ) ) );
$msg = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_external', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $externals ) ) ) );
$form = new \IPS\Helpers\Form( 'form', 'reset_password' );
$form->hiddenValues['id'] = $member->member_id;
$form->addMessage( $msg, 'ipsMessage ipsMessage_warning' );
@@ -2940,6 +2995,26 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->output = $form;
}
/**
* Return a partial query string based on any filters or sort options
*
* @return string
*/
protected function getSafeUrlFromFilters(): string
{
$extraUrlParams = [];
foreach( [ 'filter', 'sortby', 'sortdirection' ] as $param )
{
if ( isset( \IPS\Request::i()->$param ) )
{
$extraUrlParams[ $param ] = \IPS\Request::i()->$param;
}
}
return \count( $extraUrlParams ) ? '&' . http_build_query( $extraUrlParams ) : '';
}
/**
* Delete Member
*
@@ -2990,7 +3065,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Handle Submissions */
if ( $values = $form->values() )
{
\IPS\Session::i()->log( 'acplog__members_deleted', array( $member->name => FALSE ) );
\IPS\Session::i()->log( 'acplog__members_deleted_id', array( $member->name => FALSE, $member->member_id => FALSE ) );
switch( $values['member_deletion_content'] )
{
@@ -3007,7 +3082,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
break;
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=members&controller=members"), 'deleted' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=members&controller=members" . $this->getSafeUrlFromFilters() ), 'deleted' );
}
/* Display Form */
@@ -3841,8 +3916,8 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->parseFileObjectUrls( $csv );
$csv = \IPS\Output::i()->replaceEmojiWithImages( $csv );
/* Excel requires this for non-ASCII characters to show properly */
$csv = \chr(255) . \chr(254) . mb_convert_encoding( $csv, 'UTF-16LE', 'UTF-8' );
/* Excel requires a BOM this for non-ASCII characters to show properly */
$csv = \chr(0xEF) . \chr(0xBB) . \chr(0xBF) . $csv;
\IPS\Session::i()->log( 'acplog__exported_member_list' );
\IPS\Output::i()->sendOutput( $csv, 200, 'text/csv', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', \IPS\Member::loggedIn()->language()->get('member_pl') . '.csv' ) ), FALSE, FALSE, FALSE, FALSE );
@@ -3886,6 +3961,8 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
'last_post' => 'last_post',
'birthday' => 'import_birthday',
'allow_admin_mails' => 'import_allow_admin_mails',
'achievements_points' => 'achievement_points',
'member_rank' => "members_member_rank_id",
);
if ( \IPS\Settings::i()->signatures_enabled )
{
@@ -4072,7 +4149,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Write */
fputcsv( $fh, $dataToWrite, "\t" );
fputcsv( $fh, $dataToWrite );
}
/* Close and loop */
@@ -4176,6 +4253,16 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$valueToWrite = '';
}
break;
case 'member_rank':
if( $rank = \IPS\core\Achievements\Rank::fromPoints( $member['achievements_points'] ) )
{
$valueToWrite = \IPS\Member::loggedIn()->language()->get( 'core_member_rank_' . $rank->_id );
}
else
{
$valueToWrite = '';
}
break;
default:
if ( mb_substr( $column, 0, 7 ) == 'pfield_' )
@@ -4263,7 +4350,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$wizardData['file'] = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
$fh = fopen( $wizardData['file'], 'w' );
fputcsv( $fh, $this->_getExportCsvHeaders( $wizardData ), "\t" );
fputcsv( $fh, $this->_getExportCsvHeaders( $wizardData ) );
fclose( $fh );
return $wizardData;
@@ -4559,7 +4646,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
try
{
$recognize = \IPS\core\Achievements\Recognize::load( $row['recognize'] );
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_badge_from_recognize', NULL, [ 'sprintf' => [ $recognize->content()->url(), $recognize->content()->indefiniteArticle() ] ] );
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_badge_from_recognize', FALSE, [ 'sprintf' => [ $recognize->content()->url(), $recognize->content()->indefiniteArticle() ] ] );
}
catch( \Exception $e )
{
@@ -4628,7 +4715,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
{
return $node->manually_awarded;
},
'disabledIds' => iterator_to_array( \IPS\Db::i()->select( 'id', 'core_member_badges', [ 'member=?', $member->member_id ] ) ),
'disabledIds' => iterator_to_array( \IPS\Db::i()->select( 'badge', 'core_member_badges', [ '`member`=?', $member->member_id ] ) ),
'url' => $member->acpUrl()->setQueryString('do', 'addBadge')
] ) );