Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

@@ -10,6 +10,8 @@
namespace IPS\core\modules\admin\members;
use IPS\Text\Encrypt;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -123,6 +125,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'core_members.email<>?', '' ) ), $forceIndex );
$table->langPrefix = 'members_';
$table->keyField = 'member_id';
/* Columns we need */
$table->include = array( 'photo', 'name', 'email', 'joined', 'group_name' );
@@ -196,7 +199,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$options = array( '' => "");
}
if( \count( $field->options['options'] ) )
if( \is_array( $field->options['options'] ) AND \count( $field->options['options'] ) )
{
foreach ( $field->options['options'] as $option )
{
@@ -2530,7 +2533,9 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
{
\IPS\Output::i()->error( 'node_error', '2C114/9', 404, '' );
}
$member->failed_logins = array();
/* Remove login failures */
\IPS\Db::i()->delete( 'core_login_failures', [ 'login_member_id=?', $member->member_id ] );
$member->failed_login_count = 0;
$member->failed_mfa_attempts = 0;
$mfaDetails = $member->mfa_details;
if ( isset( $mfaDetails['_lockouttime'] ) )
@@ -2682,25 +2687,31 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
{
if ( !$row['user_verified'] )
{
$plainTextKey = '';
if( !empty( $row['security_key'] ) )
{
$plainTextKey = Encrypt::fromTag( $row['security_key'] )->decrypt();
}
/* Lost Pass */
if ( $row['lost_pass'] )
{
\IPS\Email::buildFromTemplate( 'core', 'lost_password_init', array( $member, $row['vid'] ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
\IPS\Email::buildFromTemplate( 'core', 'lost_password_init', array( $member, $row['vid'], $plainTextKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
}
/* New Reg */
elseif ( $row['new_reg'] )
{
\IPS\Email::buildFromTemplate( 'core', 'registration_validate', array( $member, $row['vid'] ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
\IPS\Email::buildFromTemplate( 'core', 'registration_validate', array( $member, $row['vid'], $plainTextKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
}
/* Email Change */
elseif ( $row['email_chg'] )
{
\IPS\Email::buildFromTemplate( 'core', 'email_change', array( $member, $row['vid'] ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
\IPS\Email::buildFromTemplate( 'core', 'email_change', array( $member, $row['vid'], $plainTextKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
}
/* Forgot security answers */
elseif ( $row['forgot_security'] )
{
\IPS\Email::buildFromTemplate( 'core', 'forgotSecurityAnswers', array( $member, $row['vid'] ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
\IPS\Email::buildFromTemplate( 'core', 'mfaRecovery', array( $member, $row['vid'], $plainTextKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
}
}
}
@@ -3028,7 +3039,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Session::i()->log( 'acplog__deleted_guest_content', array( $values['guest_name_to_delete'] => FALSE ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=moderation&controller=spam&searchResult=guest_captcha" ), 'deleted' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=moderation&controller=spam" ), 'deleted' );
}
\IPS\Output::i()->output = $form;
@@ -3573,7 +3584,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
break;
}
$matrix->rows[] = array( 'import_column' => $header, 'import_as' => $value );
$matrix->rows[] = array( 'import_column' => htmlspecialchars( $header, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ), 'import_as' => $value );
}
else
{
@@ -3634,7 +3645,11 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
{
fseek( $fh, $mrData['currentPosition'] );
}
$count = 0;
begin:
$line = fgetcsv( $fh );
$count++;
/* Are we done/ */
if ( !$line )
@@ -3883,6 +3898,12 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Email::buildFromTemplate( 'core', 'admin_reg', array( $member, $member->members_bitoptions['password_reset_forced'], md5( \IPS\SUITE_UNIQUE_KEY . $member->email . $member->real_name ) ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
}
/* If we haven't hit our limit, go again. */
if ( $count <= 100 )
{
goto begin;
}
/* Continue */
$mrData['currentPosition'] = ftell( $fh );
fclose( $fh );
@@ -4128,9 +4149,9 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$mrData['count'] = $query->first();
}
/* Compile query */
$query = \IPS\Db::i()->select( implode( ',', $select ), 'core_members', $where, 'core_members.member_id', array( $mrData['offset'], $doPerLoop ) );
/* Compile member_id query. Just getting the member_ids first is more efficient on larger tables */
$query = \IPS\Db::i()->select( 'core_members.member_id', 'core_members', $where, 'core_members.member_id', array( $mrData['offset'], $doPerLoop ) );
/* Run callbacks */
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' ) as $key => $extension )
{
@@ -4145,36 +4166,73 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
}
/* Finished? */
if ( !$query->count() )
/* Get the IDs */
$memberIds = iterator_to_array( $query );
/* Nothing to do? */
if ( ! count( $memberIds ) )
{
if( \count( $mrData['removedData'] ) AND ( !isset( $wizardData['includeInsecure'] ) OR !$wizardData['includeInsecure'] ) )
{
$_SESSION['removedData'] = $mrData['removedData'];
}
/* Finish here as we got everything */
\IPS\Output::i()->redirect( $baseUrl->setQueryString( array( 'buildDone' => 1 ) ) );
}
/* Now prepare the actual query to get all the data */
$where[] = [ \IPS\Db::i()->in( 'core_members.member_id', $memberIds ) ];
$dataQuery = \IPS\Db::i()->select( implode( ',', $select ), 'core_members', $where, 'core_members.member_id' );
/* Run callbacks */
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' ) as $key => $extension )
{
if( method_exists( $extension, 'queryCallback' ) )
{
/* Grab our fields and add to the form */
if( !empty( $wizardData['filters'][ $key ] ) )
{
$data = $wizardData['filters'][ $key ];
$extension->queryCallback( $data, $dataQuery );
}
}
}
/* Open file */
$fh = fopen( $wizardData['file'], 'a' );
/* Run */
foreach ( $query as $member )
$done = 0;
foreach ( $dataQuery as $member )
{
$dataToWrite = $this->_getDataToWriteCsv( $member, $wizardData, $mrData );
$done++; /* This is here because the query fetched 2500 but fewer than 2500 may be processed throwing the last bit out */
if( $dataToWrite === FALSE )
{
continue;
}
/* Write */
fputcsv( $fh, $dataToWrite );
}
/* Close and loop */
fclose( $fh );
if ( $done < $doPerLoop )
{
if( \count( $mrData['removedData'] ) AND ( !isset( $wizardData['includeInsecure'] ) OR !$wizardData['includeInsecure'] ) )
{
$_SESSION['removedData'] = $mrData['removedData'];
}
/* Just finish here as we got everything */
\IPS\Output::i()->redirect( $baseUrl->setQueryString( array( 'buildDone' => 1 ) ) );
}
$mrData['offset'] += $doPerLoop;
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('export_members_processing'), ( $mrData['offset'] > $mrData['count'] ) ? 100 : ( 100 / $mrData['count'] ) * $mrData['offset'] );
},
@@ -4289,7 +4347,14 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
if ( mb_substr( $column, 0, 7 ) == 'pfield_' )
{
$fieldId = mb_substr( $column, 7 );
$valueToWrite = $member[ 'field_' . $fieldId ] ? \IPS\core\ProfileFields\Field::load( $fieldId )->displayValue( $member[ 'field_' . $fieldId ], FALSE, 0, NULL, TRUE, ',' ) : '';
/* If this is a Checkbox or YesNo field, then we can't use the displayValue() as it will just come out as a hashed string, and the CSV file can be large, so just replace these at generation time rather than parsing the entire file. */
$field = \IPS\core\ProfileFields\Field::load( $fieldId );
$valueToWrite = match( $field->type ) {
'YesNo', 'Checkbox' => (bool) $member[ 'field_' . $fieldId ] ? \IPS\Member::loggedIn()->language()->get('yes') : \IPS\Member::loggedIn()->language()->get('no'),
default => $member[ 'field_' . $fieldId ] ? \IPS\core\ProfileFields\Field::load( $fieldId )->displayValue( $member[ 'field_' . $fieldId ], FALSE, 0, NULL, TRUE, ',' ) : ''
};
}
else
{