Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+15 -1
View File
@@ -43,7 +43,7 @@ class _Captcha extends FormAbstract
if ( \IPS\Settings::i()->bot_antispam_type != 'none' )
{
$class = '\IPS\Helpers\Form\Captcha\\' . ucfirst( \IPS\Settings::i()->bot_antispam_type );
$class = '\IPS\Helpers\Form\Captcha\\' . mb_ucfirst( \IPS\Settings::i()->bot_antispam_type );
if ( !class_exists( $class ) )
{
\IPS\Output::i()->error( 'unexpected_captcha', '4S262/1', 500, 'unexpected_captcha_admin' );
@@ -83,6 +83,20 @@ class _Captcha extends FormAbstract
return $this->captcha->getHtml();
}
/**
* Get HTML
*
* @return string
*/
public function rowHtml( $form=NULL )
{
if ( $this->captcha === NULL )
{
return '';
}
return ( method_exists( $this->captcha, 'rowHtml' ) and !$this->error ) ? $this->captcha->rowHtml() : parent::rowHtml( $form );
}
/**
* Get Value
*
+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* @brief Invisible reCAPTCHA
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 15 June 2017
*/
namespace IPS\Helpers\Form\Captcha;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Invisible reCAPTCHA
*/
class _Invisible
{
/**
* @brief Error
*/
protected $error;
/**
* Display
*
* @return string
*/
public function getHtml()
{
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->captchaInvisible( \IPS\Settings::i()->recaptcha2_public_key, preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short ) );
}
/**
* Display
*
* @return string
*/
public function rowHtml()
{
return $this->getHtml();
}
/**
* Verify
*
* @return bool|null TRUE/FALSE indicate if the test passed or not. NULL indicates the test failed, but the captcha system will display an error so we don't have to.
*/
public function verify()
{
try
{
$response = \IPS\Http\Url::external( 'https://www.google.com/recaptcha/api/siteverify' )->request()->post( array(
'secret' => \IPS\Settings::i()->recaptcha2_private_key,
'response' => trim( \IPS\Request::i()->__get('g-recaptcha-response') ),
'remoteip' => \IPS\Request::i()->ipAddress(),
) )->decodeJson( TRUE );
return ( ( (bool) $response['success'] ) and ( $response['hostname'] === \IPS\Http\Url::internal('')->data[ \IPS\Http\Url::COMPONENT_HOST ] ) );
}
catch( \RuntimeException $e )
{
if( $e->getMessage() == 'BAD_JSON' )
{
return FALSE;
}
else
{
throw $e;
}
}
}
}
+3 -15
View File
@@ -35,23 +35,11 @@ class _Keycaptcha
public function getHtml()
{
$explodedKey = explode( '0', \IPS\Settings::i()->keycaptcha_privatekey, 2 );
$uniq = md5( uniqid() );
$uniq = md5( mt_rand() );
$sign = md5( $uniq . \IPS\Request::i()->ipAddress . $explodedKey[0] );
$sign2 = md5( $uniq . $explodedKey[0] );
return <<<HTML
<input type='hidden' id='capcode' name='keycaptcha'>
<script type="text/javascript">
// required
var s_s_c_user_id = '{$explodedKey[1]}';
var s_s_c_session_id = '{$uniq}';
var s_s_c_captcha_field_id = 'capcode';
var s_s_c_submit_button_id ='sbutton-#-r';
var s_s_c_web_server_sign = '{$sign}';
var s_s_c_web_server_sign2 = '{$sign2}';
</script>
<div data-ipsCaptcha data-ipsCaptcha-service='keycaptcha' id='div_for_keycaptcha'></div>
HTML;
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->captchaKeycaptcha( $explodedKey[1], $uniq, $sign, $sign2 );
}
/**
-87
View File
@@ -1,87 +0,0 @@
<?php
/**
* @brief reCAPTCHA
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 18 Apr 2013
*/
namespace IPS\Helpers\Form\Captcha;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* reCAPTCHA
*/
class _Recaptcha
{
/**
* @brief Error
*/
protected $error;
/**
* Display
*
* @return string
*/
public function getHtml()
{
$publicKey = \IPS\Settings::i()->recaptcha_public_key;
$theme = \IPS\Theme::i()->settings['recaptcha_theme'];
$lang = preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short );
return <<<HTML
<div data-ipsCaptcha data-ipsCaptcha-service='recaptcha' data-ipsCaptcha-key="{$publicKey}" data-ipsCaptcha-lang="{$lang}" data-ipsCaptcha-theme="{$theme}">
<noscript>
<iframe src="//www.google.com/recaptcha/api/noscript?k={$publicKey}&hl={$lang}&error={$this->error}"
height="300" width="500"></iframe><br>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
</noscript>
</div>
HTML;
}
/**
* Verify
*
* @return bool|null TRUE/FALSE indicate if the test passed or not. NULL indicates the test failed, but the captcha system will display an error so we don't have to.
*/
public function verify()
{
try
{
$response = \IPS\Http\Url::external( 'http://www.google.com/recaptcha/api/verify' )->request()->post( array(
'privatekey' => \IPS\Settings::i()->recaptcha_private_key,
'remoteip' => \IPS\Request::i()->ipAddress(),
'challenge' => \IPS\Request::i()->recaptcha_challenge_field,
'response' => \IPS\Request::i()->recaptcha_response_field
) );
}
catch ( \IPS\Http\Request\Exception $e )
{
return FALSE;
}
$response = explode( "\n", $response );
if ( $response[0] === 'true' )
{
return true;
}
else
{
$this->error = $response[1];
return null;
}
}
}
+8 -23
View File
@@ -34,28 +34,7 @@ class _Recaptcha2
*/
public function getHtml()
{
$publicKey = \IPS\Settings::i()->recaptcha2_public_key;
$theme = \IPS\Theme::i()->settings['recaptcha2_theme'];
$lang = preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short );
return <<<HTML
<div data-ipsCaptcha data-ipsCaptcha-service='recaptcha2' data-ipsCaptcha-key="{$publicKey}" data-ipsCaptcha-lang="{$lang}" data-ipsCaptcha-theme="{$theme}">
<noscript>
<div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.google.com/recaptcha/api/fallback?k={$publicKey}" style="width: 302px; height:352px; border-style: none;">
</iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;" value="">
</textarea>
</div>
</div>
</div>
</noscript>
</div>
HTML;
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->captchaRecaptcha2( \IPS\Settings::i()->recaptcha2_public_key, preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short ), \IPS\Theme::i()->settings['recaptcha2_theme'] );
}
/**
@@ -73,7 +52,13 @@ HTML;
'remoteip' => \IPS\Request::i()->ipAddress(),
) )->decodeJson( TRUE );
return (bool) $response['success'];
return ( ( (bool) $response['success'] ) and ( $response['hostname'] === \IPS\Http\Url::internal('')->data[ \IPS\Http\Url::COMPONENT_HOST ] ) );
}
catch( \IPS\Http\Request\Exception $e )
{
\IPS\Log::log( $e, 'recaptcha2_exception' );
return FALSE;
}
catch( \RuntimeException $e )
{
+5 -3
View File
@@ -32,7 +32,8 @@ class _Codemirror extends TextArea
'placeholder' => 'e.g. ...', // A placeholder (NB: Will only work on compatible browsers)
'tags' => array(), // An array of extra insertable tags in key => value pair with key being what is inserted and value serving as a description
'mode' => 'php' // Formatting mode. Default is htmlmixed.
'height' => 300 // Height of code mirror editor
'height' => 300 // Height of code mirror editor
'preview' => 'http://...' // A URL where the value can be POSTed (as "value") and will return a preview. Defaults to NULL, which will hide the preview button.
);
* @endcode
*/
@@ -44,7 +45,8 @@ class _Codemirror extends TextArea
'tags' => array(),
'mode' => 'htmlmixed',
'nullLang' => NULL,
'height' => 300
'height' => 300,
'preview' => NULL
);
/**
@@ -80,6 +82,6 @@ class _Codemirror extends TextArea
$this->options['height'] = is_numeric( $this->options['height'] ) ? $this->options['height'] . 'px' : $this->options['height'];
}
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->codemirror( $this->name, $this->value, $this->required, $this->options['maxLength'], $this->options['disabled'], '', $this->options['placeholder'], $this->options['tags'], $this->options['mode'], $this->htmlId ? "{$this->htmlId}-input" : NULL, $this->options['height'] );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->codemirror( $this->name, $this->value, $this->required, $this->options['maxLength'], $this->options['disabled'], '', $this->options['placeholder'], $this->options['tags'], $this->options['mode'], $this->htmlId ? "{$this->htmlId}-input" : NULL, $this->options['height'], $this->options['preview'] );
}
}
+6 -6
View File
@@ -268,6 +268,12 @@ class _Date extends FormAbstract
{
throw new \InvalidArgumentException('form_required');
}
/* Check it's valid */
if ( !( $this->value instanceof \IPS\DateTime ) and $this->value !== NULL AND ( $this->options['unlimited'] === NULL OR $this->value !== $this->options['unlimited'] ) )
{
throw new \InvalidArgumentException( 'form_date_bad' );
}
parent::validate();
@@ -276,12 +282,6 @@ class _Date extends FormAbstract
{
return TRUE;
}
/* Check it's valid */
if ( !( $this->value instanceof \IPS\DateTime ) and $this->value !== NULL )
{
throw new \InvalidArgumentException( 'form_date_bad' );
}
/* Check minimum */
if ( $this->value and $this->options['min'] !== NULL and $this->options['min'] > $this->value )
+10
View File
@@ -79,6 +79,16 @@ class _DateRange extends FormAbstract
call_user_func_array( 'parent::__construct', func_get_args() );
}
/**
* Get the value to use in the label 'for' attribute
*
* @return mixed
*/
public function getLabelForAttribute()
{
return NULL;
}
/**
* Format Value
+56 -39
View File
@@ -94,7 +94,13 @@ class _Editor extends FormAbstract
{
$args = func_get_args();
$this->postKey = md5( $args[3]['autoSaveKey'] . ':' . session_id() );
if ( isset( \IPS\Request::i()->usingEditor ) and \IPS\Request::i()->isAjax() and \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' )
{
\IPS\Session::i()->setUsingEditor();
\IPS\Output::i()->json( array( true ) );
}
$this->defaultOptions['controller'] = 'app=core&module=system&controller=editor';
/* Get our extension */
@@ -245,6 +251,9 @@ class _Editor extends FormAbstract
$value = \IPS\Text\Parser::mungeResources( $value );
}
/* Fix Emoji */
\IPS\Output::i()->replaceEmojiWithImages( $value );
/* CKEditor will replace <p></p> (which doesn't display anything in a normal post) with <p><br></p> (which does)
which creates a discrepency between wheat displays in a post and what displays in the editor */
$value = preg_replace( '/<p>\s*<\/p>/', '', $value );
@@ -278,7 +287,7 @@ class _Editor extends FormAbstract
/* Display */
$template = $raw ? 'editorRaw' : 'editor';
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->$template( $this->name, $value, $this->options, $allowed, md5( $this->options['autoSaveKey'] . ':' . session_id() ), $attachmentArea, json_encode( static::getEmoticons() ), $this->options['tags'] );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->$template( $this->name, $value, $this->options, $allowed, md5( $this->options['autoSaveKey'] . ':' . session_id() ), $attachmentArea, json_encode( array() ), $this->options['tags'] );
}
/**
@@ -345,7 +354,12 @@ class _Editor extends FormAbstract
{
if ( !isset( $parser ) or !in_array( $attachment['attach_id'], $parser->mappedAttachments ) or array_key_exists( $attachment['attach_id'], $parser->existingAttachments ) )
{
if ( $attachment['attach_is_image'] )
$ext = mb_substr( $attachment['attach_file'], mb_strrpos( $attachment['attach_file'], '.' ) + 1 );
if ( in_array( mb_strtolower( $ext ), \IPS\File::$videoExtensions ) )
{
$value .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedVideo( $attachment['attach_location'], \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ) . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'], $attachment['attach_file'], \IPS\File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
}
elseif ( $attachment['attach_is_image'] )
{
$value .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedImage( $attachment['attach_location'], $attachment['attach_thumb_location'] ? $attachment['attach_thumb_location'] : $attachment['attach_location'], $attachment['attach_file'], $attachment['attach_id'] );
}
@@ -380,7 +394,7 @@ class _Editor extends FormAbstract
}
/* Remove abandoned attachments */
foreach ( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id NOT IN(?)', \IPS\Db::i()->select( 'DISTINCT attachment_id', 'core_attachments_map' ) ), array( 'attach_member_id=? AND attach_date<?', \IPS\Member::loggedIn()->member_id, time() - 86400 ) ) ) as $attachment )
foreach ( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id NOT IN(?)', \IPS\Db::i()->select( 'DISTINCT attachment_id', 'core_attachments_map', NULL, NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER ) ), array( 'attach_member_id=? AND attach_date<?', \IPS\Member::loggedIn()->member_id, time() - 86400 ) ) ) as $attachment )
{
try
{
@@ -404,6 +418,26 @@ class _Editor extends FormAbstract
/* Return */
return $value;
}
/**
* Set the value of the element
*
* @param bool $initial Whether this is the initial call or not. Do not reset default values on subsequent calls.
* @param bool $force Set the value even if one was not submitted (done on the final validation when getting values)?
* @return void
*/
public function setValue( $initial=FALSE, $force=FALSE )
{
// @todo extend this to all form elements. See commit notes.
if ( !isset( \IPS\Request::i()->csrfKey ) or !\IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
{
$name = $this->name;
unset( \IPS\Request::i()->$name );
}
return parent::setValue( $initial, $force );
}
/**
* Get parser object
@@ -475,6 +509,7 @@ class _Editor extends FormAbstract
/* Load existing attachments */
$existing = array();
$currentPostUsage = 0;
foreach ( $this->getAttachments() as $attachment )
{
try
@@ -487,6 +522,8 @@ class _Editor extends FormAbstract
$file->originalFilename = $attachment['attach_file'];
$existing[ $attachment['attach_id'] ] = $file;
$currentPostUsage += $attachment['attach_filesize'];
}
catch ( \Exception $e ) { }
}
@@ -495,8 +532,17 @@ class _Editor extends FormAbstract
$maxTotalSize = array();
if ( \IPS\Member::loggedIn()->group['g_attach_max'] > 0 and \IPS\Member::loggedIn()->member_id )
{
$globalSpaceRemaining = ( ( \IPS\Member::loggedIn()->group['g_attach_max'] * 1024 ) - \IPS\Db::i()->select( 'SUM(attach_filesize)', 'core_attachments', array( 'attach_member_id=?', \IPS\Member::loggedIn()->member_id ) )->first() ) / 1048576;
/* Only query logged in member's usage once per page load, even if there are more than one editor */
static $loggedInMembersAttachmentUsage = NULL;
if( $loggedInMembersAttachmentUsage === NULL )
{
$loggedInMembersAttachmentUsage = \IPS\Db::i()->select( 'SUM(attach_filesize)', 'core_attachments', array( 'attach_member_id=?', \IPS\Member::loggedIn()->member_id ) )->first();
}
$globalSpaceRemaining = ( ( \IPS\Member::loggedIn()->group['g_attach_max'] * 1024 ) - $loggedInMembersAttachmentUsage ) / 1048576;
$maxTotalSize[] = $globalSpaceRemaining;
if ( $globalSpaceRemaining <= 0 )
{
$this->noUploaderError = 'editor_used_global_space';
@@ -504,7 +550,7 @@ class _Editor extends FormAbstract
}
if ( \IPS\Member::loggedIn()->group['g_attach_per_post'] )
{
$perPostSpaceRemaining = \IPS\Member::loggedIn()->group['g_attach_per_post'] / 1024;
$perPostSpaceRemaining = ( ( \IPS\Member::loggedIn()->group['g_attach_per_post'] * 1024 ) - $currentPostUsage ) / 1048576;
$maxTotalSize[] = $perPostSpaceRemaining;
if ( $perPostSpaceRemaining <= 0 )
{
@@ -538,6 +584,10 @@ class _Editor extends FormAbstract
{
$options['allowedFileTypes'] = explode( ',', \IPS\Settings::i()->attach_allowed_extensions );
}
elseif ( \IPS\Settings::i()->attach_allowed_types == 'media' )
{
$options['allowedFileTypes'] = array_merge( \IPS\Image::$imageExtensions, \IPS\File::$videoExtensions );
}
if ( \IPS\Settings::i()->attachment_resample_size )
{
@@ -633,39 +683,6 @@ class _Editor extends FormAbstract
/* Return */
return $uploader;
}
/**
* Fetch the emoticons - abstracted to a static method for caching
*
* @return array
*/
public static function getEmoticons()
{
if( !isset( \IPS\Data\Store::i()->emoticons ) )
{
$emoticons = iterator_to_array( \IPS\Db::i()->select( '*', 'core_emoticons', array( 'typed<>?', '' ) )->setKeyField('typed') );
foreach( $emoticons as $key => $emoticon )
{
try
{
$emoticons[ $key ] = array(
'image' => (string) \IPS\File::get( "core_Emoticons", $emoticon['image'] )->url,
'image_2x' => ( isset( $emoticon['image_2x'] ) and $emoticon['image_2x'] ) ? (string) \IPS\File::get( "core_Emoticons", $emoticon['image_2x'] )->url . ' 2x' : FALSE,
'height' => $emoticon['height'] ?: FALSE,
'width' => $emoticon['width'] ?: FALSE,
);
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'bad_emoticon' );
}
}
\IPS\Data\Store::i()->emoticons = $emoticons;
}
return \IPS\Data\Store::i()->emoticons;
}
/**
* Attachments
+24 -29
View File
@@ -55,45 +55,40 @@ class _Email extends Text
if ( $this->options['accountEmail'] )
{
/* Check if it exists */
foreach ( \IPS\Login::handlers( TRUE ) as $k => $handler )
if ( $error = \IPS\Login::emailIsInUse( $this->value, ( $this->options['accountEmail'] instanceof \IPS\Member ) ? $this->options['accountEmail'] : NULL, \IPS\Member::loggedIn()->isAdmin() ) )
{
if ( $handler->emailIsInUse( $this->value, ( $this->options['accountEmail'] instanceof \IPS\Member ) ? $this->options['accountEmail'] : NULL ) === TRUE )
{
if ( \IPS\Member::loggedIn()->isAdmin() )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('member_email_exists_admin', FALSE, array( 'sprintf' => array( $k ) ) ) );
}
else
{
throw new \DomainException( 'member_email_exists' );
}
}
throw new \DomainException( $error );
}
/* Check it's not banned */
foreach ( \IPS\Db::i()->select( 'ban_content', 'core_banfilters', array( "ban_type=?", 'email' ) ) as $bannedEmail )
{
if ( preg_match( '/^' . str_replace( '\*', '.*', preg_quote( $bannedEmail, '/' ) ) . '$/i', $this->value ) )
{
throw new \DomainException( 'form_email_banned' );
}
}
/* Check it's an allowed domain */
if ( !\IPS\Member::loggedIn()->isAdmin() and \IPS\Settings::i()->allowed_reg_email !== '' AND $allowedEmailDomains = explode( ',', \IPS\Settings::i()->allowed_reg_email ) )
/* Check Banned and Allowed Emails only if the data are not coming from an administrator */
if ( !\IPS\Member::loggedIn()->isAdmin() )
{
$matched = FALSE;
foreach ( $allowedEmailDomains AS $domain )
/* Check it's not a banned address */
foreach ( \IPS\Db::i()->select( 'ban_content', 'core_banfilters', array( "ban_type=?", 'email' ) ) as $bannedEmail )
{
if( \mb_stripos( $this->value, "@" . $domain ) !== FALSE )
if ( preg_match( '/^' . str_replace( '\*', '.*', preg_quote( $bannedEmail, '/' ) ) . '$/i', $this->value ) )
{
$matched = TRUE;
throw new \DomainException( 'form_email_banned' );
}
}
if ( count( $allowedEmailDomains ) AND !$matched )
/* Check it's an allowed domain */
if ( \IPS\Settings::i()->allowed_reg_email !== '' AND $allowedEmailDomains = explode( ',', \IPS\Settings::i()->allowed_reg_email ) )
{
throw new \DomainException( 'form_email_banned' );
$matched = FALSE;
foreach ( $allowedEmailDomains AS $domain )
{
if( \mb_stripos( $this->value, "@" . $domain ) !== FALSE )
{
$matched = TRUE;
}
}
if ( count( $allowedEmailDomains ) AND !$matched )
{
throw new \DomainException( 'form_email_banned' );
}
}
}
}
+29 -11
View File
@@ -511,6 +511,8 @@ class _Form
$values = array();
$name = "{$this->id}_submitted";
$uploadFieldNames = array();
$uploadRetainDeleted = array();
$uploadCurrentFiles = array();
/* Did we submit the form? */
if( isset( \IPS\Request::i()->$name ) and \IPS\Request::i()->csrfKey === \IPS\Session::i()->csrfKey )
@@ -596,8 +598,8 @@ class _Form
/* If this is dependant on a toggle which isn't set, don't return a value so that it doesn't
trigger an error we cannot see */
if ( $element->htmlId and in_array( $element->htmlId, $htmlIdsToIgnore ) )
{
$values[ $_name ] = $element->defaultValue;
{
$values[ $_name ] = $stringValues ? $element::stringValue( $element->defaultValue ) : $element->defaultValue;
continue;
}
@@ -611,6 +613,21 @@ class _Form
if ( ( $element instanceof Form\Upload ) )
{
$uploadFieldNames[] = $element->name;
if ( $element->options['retainDeleted'] )
{
$uploadRetainDeleted[] = $element->name;
if ( is_array( $element->value ) )
{
foreach( $element->value AS $value )
{
$uploadCurrentFiles[] = $value->originalFilename;
}
}
else
{
$uploadCurrentFiles[] = $element->value->originalFilename;
}
}
}
/* If it has an error, set it and return */
@@ -668,7 +685,15 @@ class _Form
/* At this point we are about to return the values. Any uploaded files are now the responsibility of the controller, so release the hold on them */
foreach ( $uploadFieldNames as $name )
{
\IPS\Db::i()->delete( 'core_files_temp', array( 'upload_key=?', md5( $name . session_id() ) ) );
if ( !in_array( $name, $uploadRetainDeleted ) )
{
\IPS\Db::i()->delete( 'core_files_temp', array( 'upload_key=?', md5( $name . session_id() ) ) );
}
else
{
/* If we're retaining deleted files, then remove any files that are actually are a part of the valye */
\IPS\Db::i()->delete( 'core_files_temp', array( "upload_key=? AND " . \IPS\Db::i()->in( 'filename', $uploadCurrentFiles ), md5( $name . session_id() ) ) );
}
}
/* And return */
@@ -696,14 +721,7 @@ class _Form
if ( $values )
{
foreach ( $values as $k => $v )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $v ), array( 'conf_key=?', $k ) );
\IPS\Settings::i()->$k = $v;
}
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->changeValues( $values );
return TRUE;
}
+11 -1
View File
@@ -303,13 +303,23 @@ abstract class _FormAbstract
if ( \IPS\IN_DEV )
{
echo '<pre>';
print_r( $e );
var_dump( $e );
exit;
}
throw $e;
}
}
/**
* Get the value to use in the label 'for' attribute
*
* @return mixed
*/
public function getLabelForAttribute()
{
return $this->name;
}
/**
* Get Value
+5 -1
View File
@@ -75,6 +75,10 @@ class _Item extends FormAbstract
{
$template = array( \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' ), 'itemResult' );
}
else
{
$template = $this->options['itemTemplate'];
}
/* Are we getting some AJAX stuff? */
if ( isset( \IPS\Request::i()->_itemSelectName ) and \IPS\Request::i()->_itemSelectName === $this->name )
@@ -152,7 +156,7 @@ class _Item extends FormAbstract
{
if ( isset( $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] ) )
{
$order = $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] . ' ' . $this->options['orderResults'][1];
$order = $itemClass::$databaseTable . '.' . $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] . ' ' . $this->options['orderResults'][1];
}
}
+13 -2
View File
@@ -240,7 +240,19 @@ class _Matrix extends \IPS\Helpers\Form
}
if ( $added )
{
{
/* Select lists can have user-supplied input, so look for that too */
foreach( \IPS\Request::i() as $inputKey => $inputValue )
{
if( $pos = mb_strpos( $inputKey, '_new_' ) AND $inputKey !== '_new_' )
{
if( is_array( $inputValue ) AND isset( $inputValue[ $newId ] ) )
{
$data = array_merge( $data, $inputValue[ $newId ] );
}
}
}
$this->elements["_new_[{$i}]"] = $this->buildRow( "_new_[{$i}]", $data );
$this->addedRows[] = "_new_[{$i}]";
$i++;
@@ -314,7 +326,6 @@ class _Matrix extends \IPS\Helpers\Form
$name = "{$this->id}_submitted";
if( $force or ( isset( \IPS\Request::i()->$name ) and \IPS\Request::i()->csrfKey === \IPS\Session::i()->csrfKey ) )
{
// Do we need to unsquash any values?
// Squashed values are json_encoded by javascript to prevent us exceeding max_post_vars
$squashedField = $this->id . '_squashed';
+1 -2
View File
@@ -88,9 +88,8 @@ class _Member extends Text
$value = $value->name;
}
/* 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. */
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->text( $this->name, 'text', ( $this->options['autocomplete'] AND !is_null( $value ) ) ? htmlspecialchars( $value, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ) : $value, $this->required, $this->options['maxLength'], $this->options['size'], $this->options['disabled'], $this->options['autocomplete'], $this->options['placeholder'], NULL, $this->options['nullLang'] );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->text( $this->name, 'text', ( $this->options['autocomplete'] AND !is_null( $value ) ) ? htmlspecialchars( $value, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) : $value, $this->required, $this->options['maxLength'], $this->options['size'], $this->options['disabled'], $this->options['autocomplete'], $this->options['placeholder'], NULL, $this->options['nullLang'] );
}
/**
+42 -5
View File
@@ -118,7 +118,23 @@ class _Node extends FormAbstract
try
{
$node = $permCheck ? $nodeClass::loadAndCheckPerms( \IPS\Request::i()->_nodeId, 'view' ) : $nodeClass::load( \IPS\Request::i()->_nodeId );
$children = $node->children( $permCheck, NULL, $this->options['subnodes'], $disabled, $this->options['where'] );
/* Note - we must check 'view' permissions here, so that the list can properly populate even if we do not have the original permission
* - subsequent children may eventually have those permissions, so if the actual permCheck fails, add the node to the disabled list
* - where $permCheck is null, we need to keep it null for areas such as the AdminCP where view permissions should not be set.
*/
$children = $node->children( $permCheck === NULL ? NULL : 'view', NULL, $this->options['subnodes'], $disabled, $this->options['where'] );
if( $permCheck !== NULL )
{
foreach( $children AS $child )
{
if ( !$child->can( $permCheck ) )
{
$this->options['disabledIds'][] = $child->_id;
}
}
}
}
catch ( \Exception $e )
{
@@ -169,6 +185,10 @@ class _Node extends FormAbstract
if( isset( $nodeClass::$ownerTypes ) and $nodeClass::$ownerTypes !== NULL and $this->options['forceOwner'] !== FALSE )
{
$nodes = $nodeClass::loadByOwner( $this->options['forceOwner'] ?: \IPS\Member::loggedIn(), $this->options['where'] );
if ( $this->options['clubs'] )
{
$nodes = array_merge( $nodes, $nodeClass::clubNodes( $permCheck, NULL, $this->options['where'] ) );
}
}
else
{
@@ -218,18 +238,18 @@ class _Node extends FormAbstract
{
foreach ( $this->value as $node )
{
$title = isset( $node::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $node::$titleLangPrefix . $node->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : ( $node->_stripTagsTitle ? $node->_stripTagsTitle : $node->_title );
$title = isset( $node::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $node::$titleLangPrefix . $node->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : $node->_title;
$selected[ !( $node instanceof $nodeClass ) ? "{$node->_id}.s" : $node->_id ] = array( 'title' => $title, 'parents' => array_values( array_map( function( $val ){
return isset( $val::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $val::$titleLangPrefix . $val->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : ( $val->_stripTagsTitle ? $val->_stripTagsTitle : $val->_title );
return isset( $val::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $val::$titleLangPrefix . $val->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : $val->_title;
}, iterator_to_array( $node->parents() ) ) ) );
}
}
elseif ( $this->value instanceof \IPS\Node\Model )
{
$class = get_class( $this->value );
$title = isset( $nodeClass::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $class::$titleLangPrefix . $this->value->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : ( $this->value->_stripTagsTitle ? $this->value->_stripTagsTitle : $this->value->_title );
$title = isset( $nodeClass::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $class::$titleLangPrefix . $this->value->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : $this->value->_title;
$selected[ !( $this->value instanceof $nodeClass ) ? "{$this->value->_id}.s" : $this->value->_id ] = array( 'title' => $title, 'parents' => array_values( array_map( function( $val ){
return isset( $val::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $val::$titleLangPrefix . $val->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : ( $val->_stripTagsTitle ? $val->_stripTagsTitle : $val->_title );
return isset( $val::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $val::$titleLangPrefix . $val->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : $val->_title;
}, iterator_to_array( $this->value->parents() ) ) ) );
}
@@ -407,6 +427,23 @@ class _Node extends FormAbstract
return $this->value;
}
/**
* Validate
*
* @throws \InvalidArgumentException
* @return TRUE
*/
public function validate()
{
/* We return a NULL value instead of an empty string, so we need to check that if field is required */
if( ( $this->value === NULL OR ( is_array( $this->value ) AND empty( $this->value ) ) ) and $this->required )
{
throw new \InvalidArgumentException('form_required');
}
return parent::validate();
}
/**
* String Value
+8 -20
View File
@@ -26,7 +26,7 @@ class _Password extends Text
* @brief Default Options
* @code
$childDefaultOptions = array(
'validateFor' => \IPS\Member::loggedIn(), // If an \IPS\Member object is provided, the password will be checked if it is valid for that account. Default is NULL.
'validateFor' => \IPS\Member::loggedIn(), // If an \IPS\Member object is provided, the password will be checked if it is valid for that account. Default is NULL. Note that it is possible a user may not have any available password-based login handler, so this should only be used when *changing* a password.
'confirm' => 'password1', // If the name of another element in the form is provided, will check is the values match. Default is NULL.
'showMeter' => FALSE, // Show a "strength" meter
'minimumStrength => 3 // If a strength is provided validation will fail for any lesser value.
@@ -50,7 +50,7 @@ class _Password extends Text
{
$value = parent::getValue();
if ( $value === '********' )
if ( $value === '********' and $this->defaultValue )
{
$value = $this->defaultValue;
}
@@ -93,25 +93,13 @@ class _Password extends Text
{
$valid = FALSE;
foreach( \IPS\Login::handlers( TRUE ) as $k => $handler )
$login = new \IPS\Login();
foreach ( $login->usernamePasswordMethods() as $method )
{
if ( $handler->authTypes & \IPS\Login::AUTH_TYPE_USERNAME )
if ( $method->authenticatePasswordForMember( $this->options['validateFor'], $this->value ) )
{
try
{
$handler->authenticate( array( 'auth' => $this->options['validateFor']->name, 'password' => $this->value ) );
$valid = TRUE;
}
catch ( \Exception $e ) {}
}
if ( $handler->authTypes & \IPS\Login::AUTH_TYPE_EMAIL )
{
try
{
$handler->authenticate( array( 'auth' => $this->options['validateFor']->email, 'password' => $this->value ) );
$valid = TRUE;
}
catch ( \Exception $e ) {}
$valid = TRUE;
break;
}
}
@@ -148,7 +136,7 @@ class _Password extends Text
{
foreach( $this->value as $key => $value )
{
$this->value[ $key ] = htmlspecialchars( $value, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$this->value[ $key ] = htmlspecialchars( $value, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE );
}
}
+2 -2
View File
@@ -183,12 +183,12 @@ class _Select extends FormAbstract
{
foreach ( $v as $x => $y )
{
$options[ $k ][ $x ] = htmlspecialchars( $y, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$options[ $k ][ $x ] = htmlspecialchars( $y, ENT_DISALLOWED, 'UTF-8', FALSE );
}
}
else
{
$options[ $k ] = htmlspecialchars( $v, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$options[ $k ] = htmlspecialchars( $v, ENT_DISALLOWED, 'UTF-8', FALSE );
}
}
break;
+1 -1
View File
@@ -27,7 +27,7 @@ class _SocialGroup extends Member
* @code
$childDefaultOptions = array(
'owner' => 1, // \IPS\Member object or member ID who "owns" this group
'multiple' => 1, // Maximum number of members. NULL for any. Default is 1.
'multiple' => 1, // Maximum number of members. NULL for any. Default is NULL.
);
* @endcode
*/
+2 -2
View File
@@ -168,12 +168,12 @@ class _Stack extends FormAbstract
if ( $this->options['maxItems'] !== NULL and count( $this->value ) > $this->options['maxItems'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_max', FALSE, array( 'pluralize' => array( $this->options['maxItems'] ) ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_items_max', FALSE, array( 'pluralize' => array( $this->options['maxItems'] ) ) ) );
}
if ( $this->options['minItems'] !== NULL and count( $this->value ) < $this->options['minItems'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_min', FALSE, array( 'pluralize' => array( $this->options['minItems'] ) ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_items_min', FALSE, array( 'pluralize' => array( $this->options['minItems'] ) ) ) );
}
$classType = mb_strpos( $this->options['stackFieldType'], '\\' ) === FALSE ? ( "\\IPS\\Helpers\\Form\\" . $this->options['stackFieldType'] ) : $this->options['stackFieldType'];
+30 -29
View File
@@ -42,7 +42,8 @@ class _Text extends TextArea
'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
'disallowedCharacters' => array() // An array of disallowed characters (default < > ' ")
'disallowedCharacters' => array(), // An array of disallowed characters (default < > ' ")
'minimized' => TRUE // Whether the autocomplete shows a 'choose' link to activate. Existing values or required = true will always override this.
),
'placeholder' => 'e.g. ...', // A placeholder (NB: Will only work on compatible browsers)
'regex' => '/[A-Z]+/i', // RegEx of acceptable value
@@ -131,7 +132,7 @@ class _Text extends TextArea
{
foreach( $this->value as $key => $value )
{
$this->value[ $key ] = htmlspecialchars( $value, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$this->value[ $key ] = htmlspecialchars( $value, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE );
}
}
@@ -171,23 +172,27 @@ class _Text extends TextArea
{
$return = htmlspecialchars_decode( $return, ENT_QUOTES );
if ( !is_array( $return ) )
if ( !is_array( $return ) and ( !isset( $this->options['autocomplete']['maxItems'] ) or $this->options['autocomplete']['maxItems'] != 1 ) )
{
$return = array_filter( explode( "\n", $return ) );
$return = array_filter( array_map( 'trim', explode( "\n", $return ) ) );
}
}
/* Remove all invisible characters */
if ( !is_array( $return ) )
/* Remove all invisible characters if this is a username */
if( $this->options['accountUsername'] )
{
$return = preg_replace( '/\p{C}+/u', '', $return );
}
else
{
foreach( $return as $k => $v )
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'] )
@@ -229,7 +234,7 @@ class _Text extends TextArea
* @return mixed
*/
public function formatValue()
{
{
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( "\n", $this->value ) ) );
@@ -249,6 +254,12 @@ class _Text extends TextArea
{
parent::validate();
/* Check it isn't just invisible characters (we don't strip them, because things like zero-width-joiners when in between other characters have a special meaning */
if ( $this->required and is_string( $this->value ) and mb_strlen( preg_replace( '/\p{C}+/u', '', $this->value ) ) === 0 )
{
throw new \DomainException('form_required');
}
/* 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 )
{
@@ -304,21 +315,11 @@ class _Text extends TextArea
/* Check if it exists */
if ( !( $this->options['accountUsername'] instanceof \IPS\Member ) or mb_strtolower( $this->options['accountUsername']->name ) !== mb_strtolower( $this->value ) )
{
foreach( \IPS\Login::handlers( TRUE ) as $k => $handler )
if ( $error = \IPS\Login::usernameIsInUse( $this->value, ( $this->options['accountUsername'] instanceof \IPS\Member ) ? $this->options['accountUsername'] : NULL, \IPS\Member::loggedIn()->isAdmin() ) )
{
if( $handler->usernameIsInUse( $this->value, $this->options['accountUsername'] ) === TRUE )
{
if( \IPS\Member::loggedIn()->isAdmin() )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'member_name_exists_admin', FALSE, array('sprintf' => array($k)) ) );
}
else
{
throw new \DomainException( 'member_name_exists' );
}
}
throw new \DomainException( $error );
}
/* Check it's not banned */
foreach( \IPS\Db::i()->select( 'ban_content', 'core_banfilters', array("ban_type=?", 'name') ) as $bannedName )
{
@@ -383,7 +384,7 @@ class _Text extends TextArea
}
else
{
throw new \DomainException( 'Profanity Filter' ); // @todo proper lang string
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $v ) ) ) );
}
}
else
@@ -399,7 +400,7 @@ class _Text extends TextArea
{
if ( mb_stristr( $value, $type ) )
{
throw new \DomainException( 'Profanity Filter' );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $v ) ) ) );
}
}
}
@@ -426,11 +427,11 @@ class _Text extends TextArea
{
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' );
$this->value = array_uintersect( array_map( 'mb_strtolower', $this->value ), array_map( 'mb_strtolower', array_map( 'trim', $this->options['autocomplete']['source'] ) ), 'strcasecmp' );
}
else
{
$this->value = array_uintersect( $this->value, $this->options['autocomplete']['source'], 'strcasecmp' );
$this->value = array_uintersect( $this->value, array_map( 'trim', $this->options['autocomplete']['source'] ), 'strcasecmp' );
}
}
}
+36 -24
View File
@@ -30,6 +30,7 @@ class _Upload extends FormAbstract
'storageContainer' => NULL, // The file storage container to use.
'multiple' => TRUE, // Specifies if the field should allow multiple file uploads. Default is FALSE.
'image' => array( 'maxWidth' => 100, 'maxHeight' => 100 ), // If the upload must be an image, can pass TRUE or an array with max width and height (in which case, image will be resized appropriately). Default is NULL. Max width/height cannot be used in conjunction with temporary uploads. If it can be, but doesn't have to an image, and you still want to specify max width and height, add an "optional" property set to true
'checkImage' => TRUE, // If TRUE, will load uploaded images into the image processing engine to automatically reorient if needed
'allowedFileTypes' => array( 'pdf', 'txt' ), // Allowed file extensions. NULL allows any. Default is NULL.
'maxFileSize' => 100, // Maximum file size in megabytes. NULL is no limit. Default is NULL. Note that there *may* be server limitations regardless of this value which are calculated automatically.
'totalMaxSize' => 100, // If this is a "multiple" upload field, the maximum storage space allowed in total in megabytes.
@@ -48,6 +49,7 @@ class _Upload extends FormAbstract
protected $defaultOptions = array(
'multiple' => FALSE,
'image' => NULL,
'checkImage' => TRUE,
'allowedFileTypes' => NULL,
'maxFileSize' => NULL,
'totalMaxSize' => NULL,
@@ -212,10 +214,8 @@ class _Upload extends FormAbstract
/* If there is an error, an exception will be thrown and will be caught below like normal */
\IPS\File::validateUpload( $fileArray, $this->options['allowedFileTypes'], $this->options['maxFileSize'] );
if ( is_array( $this->options['image'] ) and $fileObj->isImage() )
if ( $fileObj->isImage() AND ( $this->options['checkImage'] OR is_array( $this->options['image'] ) ) )
{
$options = $this->options;
try
{
$image = \IPS\Image::create( $fileObj->contents() );
@@ -225,7 +225,10 @@ class _Upload extends FormAbstract
$image->setExifData( $fileObj->contents() );
}
$image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
if( is_array( $this->options['image'] ) )
{
$image->resizeToMax( $this->options['image']['maxWidth'] ?: NULL, $this->options['image']['maxHeight'] ?: NULL );
}
$fileObj->replace( (string) $image );
}
@@ -254,6 +257,7 @@ class _Upload extends FormAbstract
'id' => $insertId,
'key' => $_SERVER['HTTP_X_PLUPLOAD'],
'imagesrc' => $fileObj->isImage() ? (string) $fileObj->url : NULL,
'videosrc' => $fileObj->isVideo() ? (string) $fileObj->url : NULL,
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
) );
}
@@ -270,6 +274,7 @@ class _Upload extends FormAbstract
'id' => $insertId,
'key' => $_SERVER['HTTP_X_PLUPLOAD'],
'imagesrc' => $fileObj->isImage() ? (string) $fileObj->url : NULL,
'videosrc' => $fileObj->isVideo() ? (string) $fileObj->url : NULL,
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
) );
}
@@ -583,6 +588,16 @@ class _Upload extends FormAbstract
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('uploaderr_total_size', FALSE, array( 'sprintf' => array( $this->options['totalMaxSize'] ) ) ) );
}
}
/* Now fix the array in case we are using the defaultValue which was array( fileurl =>, default => ) */
$toReturn = array();
foreach( $return as $k => $v )
{
$toReturn[ $k ] = ( is_array( $v ) AND isset( $v['fileurl'] ) ) ? $v['fileurl'] : $v;
}
$return = $toReturn;
/* Return */
if ( !$this->options['multiple'] )
@@ -630,33 +645,30 @@ class _Upload extends FormAbstract
/* Normal - send to storage extension */
else
{
if ( is_array( $this->options['image'] ) )
$options = $this->options;
$fileObjects = \IPS\File::createFromUploads( $this->options['storageExtension'], $fieldName, $this->options['allowedFileTypes'], $this->options['maxFileSize'], $this->options['totalMaxSize'], 0, function( $contents, $filename ) use ( $options )
{
$options = $this->options;
$fileObjects = \IPS\File::createFromUploads( $this->options['storageExtension'], $fieldName, $this->options['allowedFileTypes'], $this->options['maxFileSize'], $this->options['totalMaxSize'], 0, function( $contents, $filename ) use ( $options )
{
$ext = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
$ext = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
/* Resize images */
if ( in_array( $ext, \IPS\Image::$imageExtensions ) )
/* Resize images */
if ( in_array( $ext, \IPS\Image::$imageExtensions ) AND ( is_array( $options['image'] ) OR $options['checkImage'] ) )
{
try
{
try
$image = \IPS\Image::create( $contents );
if( is_array( $options['image'] ) )
{
$image = \IPS\Image::create( $contents );
$image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
return (string) $image;
}
catch ( \Exception $e ) {}
return (string) $image;
}
return $contents;
}, $this->options['storageContainer'], $this->options['obscure'] );
}
else
{
$fileObjects = \IPS\File::createFromUploads( $this->options['storageExtension'], $fieldName, $this->options['allowedFileTypes'], $this->options['maxFileSize'], $this->options['totalMaxSize'], 0, NULL, $this->options['storageContainer'], $this->options['obscure'] );
}
catch ( \Exception $e ) {}
}
return $contents;
}, $this->options['storageContainer'], $this->options['obscure'] );
foreach ( $fileObjects as $fileObj )
{
+1 -1
View File
@@ -68,7 +68,7 @@ class _Url extends Text
}
/* And that it's an allowed protocol */
if ( !in_array( mb_strtolower( $value->data['scheme'] ), $this->options['allowedProtocols'] ) )
if ( $this->options['allowedProtocols'] and !in_array( mb_strtolower( $value->data['scheme'] ), $this->options['allowedProtocols'] ) )
{
throw new \DomainException('form_url_bad_protocol');
}