Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+121 -35
View File
@@ -2,9 +2,9 @@
/**
* @brief Text input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 18 Feb 2013
* @version SVN_VERSION_NUMBER
*/
@@ -39,7 +39,7 @@ class _Text extends TextArea
'unique' => TRUE, // Specifies if the values must be unique
'forceLower' => TRUE, // If TRUE, all values will be converted to lowercase
'minLength' => 5, // The minimum length of each tag (characters) - if not specified, will be unlimited
'maxLength' => 10, // The minimum length of each tag (characters) - if not specified, will be unlimited
'maxLength' => 10, // The maximum length of each tag (characters) - if not specified, will be unlimited
'prefix' => TRUE, // If TRUE, user will have option to specify one tag as a prefix
'resultItemTemplate'=> 'core.foo.bar', // Can be used to specify a custom JavaScript template to use for the result
'minAjaxLength' => 3, // Minimum length of value before sending AJAX lookup call
@@ -50,6 +50,7 @@ class _Text extends TextArea
'nullLang' => 'no_value', // If provided, an "or X" checkbox will appear with X being the value of this language key. When checked, NULL will be returned as the value.
'accountUsername' => TRUE, // If TRUE or an \IPS\Member, additional checks will be performed to ensure provided value is acceptable for use as a username. Pass an \IPS\Member object to exclude that member from the duplicate checks
'trim' => TRUE, // If TRUE (which is the default), whitespace will be stripped from the start and end of the value
'bypassProfanity' => FALSE, // If TRUE, profanity filter will be bypassed when it is not appropriate to do so (ex: on the login form). Defaults to FALSE.
);
* @endcode
*/
@@ -64,6 +65,7 @@ class _Text extends TextArea
'nullLang' => NULL,
'accountUsername' => FALSE,
'trim' => TRUE,
'bypassProfanity' => FALSE,
);
/**
@@ -92,11 +94,12 @@ class _Text extends TextArea
$args = func_get_args();
if ( isset( $args[3]['accountUsername'] ) and $args[3]['accountUsername'] !== FALSE )
{
$args[3]['minLength'] = \IPS\Settings::i()->min_user_name_length;
$args[3]['maxLength'] = \IPS\Settings::i()->max_user_name_length;
if ( \IPS\Settings::i()->username_characters )
{
$args[3]['regex'] = '/^[' . str_replace( '\-', '-', preg_quote( \IPS\Settings::i()->username_characters, '/' ) ) . ']*$/i';
$args[3]['regex'] = '/^[' . str_replace( '\-', '-', preg_quote( \IPS\Settings::i()->username_characters, '/' ) ) . ']*$/iu';
}
}
@@ -125,7 +128,7 @@ class _Text extends TextArea
{
/* 10/19/15 - adding htmlspecialchars around value if autocomplete is enabled so that html tag characters can be used (e.g. for members) */
/* This value is decoded by the JS widget before use. See https://community.invisionpower.com/4bugtrack/active-reports/cannot-pm-certain-members-r6181/ */
if( $this->options['autocomplete'] and !empty( $this->value ) )
if( $this->options['autocomplete'] and !empty( $this->value ) and is_array( $this->value ) )
{
foreach( $this->value as $key => $value )
{
@@ -168,18 +171,28 @@ class _Text extends TextArea
if ( isset( $this->options['autocomplete'] ) )
{
$return = htmlspecialchars_decode( $return, ENT_QUOTES );
if ( !is_array( $return ) )
{
$return = array_filter( explode( "\n", $return ) );
}
}
/* Remove all invisible characters */
$return = preg_replace( '/\p{C}+/u', '', $return );
if ( !is_array( $return ) )
{
$return = preg_replace( '/\p{C}+/u', '', $return );
}
else
{
foreach( $return as $k => $v )
{
$return = preg_replace( '/\p{C}+/u', '', $return );
}
}
if ( isset( $this->options['autocomplete']['prefix'] ) and $this->options['autocomplete']['prefix'] )
{
if ( !is_array( $return ) )
{
$return = array_filter( explode( ',', $return ) );
}
$firstAsPrefix = $this->name . '_first_as_prefix';
$freechoicePrefixCheckbox = $this->name . '_freechoice_prefix';
$freechoicePrefix = $this->name . '_prefix';
@@ -220,7 +233,7 @@ class _Text extends TextArea
{
if ( $this->options['autocomplete'] !== NULL and ( !isset( $this->options['autocomplete']['maxItems'] ) or $this->options['autocomplete']['maxItems'] != 1 ) and !is_array( $this->value ) and $this->value !== NULL )
{
return array_filter( array_map( 'trim', explode( ',', $this->value ) ) );
return array_filter( array_map( 'trim', explode( "\n", $this->value ) ) );
}
return $this->value;
@@ -237,19 +250,37 @@ class _Text extends TextArea
{
parent::validate();
/* skip validation if it's an username field and if the name wasn't changed */
if ( $this->options['accountUsername'] AND $this->options['accountUsername'] instanceOf \IPS\Member AND $this->options['accountUsername']->name == $this->value )
{
return TRUE;
}
if( \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front and !\IPS\Member::loggedIn()->group['g_bypass_badwords'] )
{
$looseProfanity = array();
$exactProfanity = array();
/* Set up profanity filters */
$exactProfanity = iterator_to_array( \IPS\Db::i()->select( '*', 'core_profanity_filters', array('m_exact=?', TRUE) )->setKeyField( 'type' )->setValueField( 'swop' ) );
$looseProfanity = iterator_to_array( \IPS\Db::i()->select( '*', 'core_profanity_filters', array('m_exact=?', FALSE) )->setKeyField( 'type' )->setValueField( 'swop' ) );
foreach( \IPS\core\Profanity::getProfanity() AS $profanity )
{
if ( $profanity->m_exact )
{
$exactProfanity[ $profanity->type ] = $profanity;
}
else
{
$looseProfanity[ $profanity->type ] = $profanity;
}
}
foreach( $exactProfanity as $key => $value )
{
$exactProfanity[mb_strtolower( $key )] = $value;
$exactProfanity[ mb_strtolower( $key ) ] = $value;
}
/* Construct break points */
$breakPoints = array();
$exactProfanityBreakpoints = array();
if( count( $exactProfanity ) )
{
$array = array();
@@ -259,7 +290,7 @@ class _Text extends TextArea
}
/* @note: We use \b because of http://community.invisionpower.com/resources/bugs.html/_/4-0-0/anacronym-doesnt-work-in-middle-of-a-phrase-r47612 */
$breakPoints[] = '((?=<^|\b)(?:' . implode( '|', $array ) . ')(?=\b|$))';
$exactProfanityBreakpoints[] = '((?=<^|\b)(?:' . implode( '|', $array ) . ')(?=\b|$))';
}
}
@@ -315,11 +346,11 @@ class _Text extends TextArea
if ( isset( $this->options['autocomplete']['maxItems'] ) and count( $values ) > $this->options['autocomplete']['maxItems'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_max', FALSE, array( 'pluralize' => $this->options['autocomplete']['maxItems'] ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_max', FALSE, array( 'pluralize' => array( $this->options['autocomplete']['maxItems'] ) ) ) );
}
if ( isset( $this->options['autocomplete']['minItems'] ) and ( $this->required or count( $values ) > 0 ) and count( $values ) < $this->options['autocomplete']['minItems'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_min', FALSE, array( 'pluralize' => $this->options['autocomplete']['minItems'] ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_min', FALSE, array( 'pluralize' => array( $this->options['autocomplete']['minItems'] ) ) ) );
}
if ( isset( $this->options['autocomplete']['minLength'] ) )
@@ -338,7 +369,7 @@ class _Text extends TextArea
{
if ( mb_strlen( $v ) > $this->options['autocomplete']['maxLength'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_length_max', FALSE, array( 'pluralize' => $this->options['autocomplete']['maxLength'] ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_length_max', FALSE, array( 'pluralize' => array( $this->options['autocomplete']['maxLength'] ) ) ) );
}
}
}
@@ -348,11 +379,35 @@ class _Text extends TextArea
{
if ( array_key_exists( $v, $exactProfanity ) )
{
$this->value[ $k ] = $exactProfanity[ $v ];
if ( $exactProfanity[ $v ]->action == 'swap' )
{
$this->value[ $k ] = $exactProfanity[ $v ]->swop;
}
else
{
throw new \DomainException( 'Profanity Filter' ); // @todo proper lang string
}
}
else
{
$this->value[ $k ] = str_replace( array_keys( $looseProfanity ), array_values( $looseProfanity ), $v );
$swaps = array();
foreach( $looseProfanity AS $type => $row )
{
if ( $row->action == 'swap' )
{
$swaps[ $row->type ] = $row->swop;
}
else
{
if ( mb_stristr( $value, $type ) )
{
throw new \DomainException( 'Profanity Filter' );
}
}
}
/* Still here? Normal swaps. */
$this->value[ $k ] = str_ireplace( array_keys( $swaps ), array_values( $swaps ), $this->value[ $k ] );
}
}
}
@@ -369,34 +424,42 @@ class _Text extends TextArea
}
}
if ( is_array ( $this->value ) AND isset( $this->options['autocomplete']['source'] ) AND is_array( $this->options['autocomplete']['source'] ) AND !$this->options['autocomplete']['freeChoice'] )
if ( is_array ( $this->value ) AND isset( $this->options['autocomplete']['source'] ) AND is_array( $this->options['autocomplete']['source'] ) AND ( !isset( $this->options['autocomplete']['freeChoice'] ) OR !$this->options['autocomplete']['freeChoice'] ) )
{
$this->value = array_uintersect( $this->value, $this->options['autocomplete']['source'], function( $a, $b ){
return ( mb_strtolower( $a ) === mb_strtolower( $b ) );
} );
if( isset( $this->options['autocomplete']['forceLower'] ) AND $this->options['autocomplete']['forceLower'] )
{
$this->value = array_uintersect( array_map( 'mb_strtolower', $this->value ), array_map( 'mb_strtolower', $this->options['autocomplete']['source'] ), 'strcasecmp' );
}
else
{
$this->value = array_uintersect( $this->value, $this->options['autocomplete']['source'], 'strcasecmp' );
}
}
}
/* Split on profanity */
if( is_string( $this->value ) and \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front and !\IPS\Member::loggedIn()->group['g_bypass_badwords'] )
if( $this->options['bypassProfanity'] === FALSE and is_string( $this->value ) and \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front and !\IPS\Member::loggedIn()->group['g_bypass_badwords'] )
{
$newVal = NULL;
if ( count( $breakPoints ) )
if ( count( $exactProfanityBreakpoints ) )
{
/* preg_split can return boolean false*/
$split = preg_split( '/' . implode( '|', $breakPoints ) . '/iu', $this->value, null, PREG_SPLIT_DELIM_CAPTURE );
$split = preg_split( '/' . implode( '|', $exactProfanityBreakpoints ) . '/iu', $this->value, null, PREG_SPLIT_DELIM_CAPTURE );
if( is_array( $split ) )
{
foreach ( $split as $section )
{
/* Replace loose profanity */
$section = str_ireplace( array_keys( $looseProfanity ), array_values( $looseProfanity ), $section );
if ( isset( $exactProfanity[ mb_strtolower( $section ) ] ) )
{
$newVal .= $exactProfanity[ mb_strtolower( $section ) ];
if ( $exactProfanity[ mb_strtolower( $section ) ]->action == 'swap' )
{
$newVal .= $exactProfanity[ mb_strtolower( $section ) ]->swop;
}
else
{
throw new \DomainException( 'Profanity Filter' );
}
}
else
{
@@ -405,8 +468,31 @@ class _Text extends TextArea
}
}
}
$value = $newVal ?: $this->value;
if ( count( $looseProfanity ) )
{
$swaps = array();
foreach( $looseProfanity AS $type => $row )
{
if ( $row->action == 'swap' )
{
$swaps[ $row->type ] = $row->swop;
}
else
{
if ( mb_stristr( $value, $type ) )
{
throw new \DomainException( 'Profanity Filter' );
}
}
}
$value = str_ireplace( array_keys( $swaps ), array_values( $swaps ), $value );
}
$this->value = $newVal ?: $this->value;
$this->value = $value;
}
return TRUE;