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

@@ -0,0 +1,39 @@
<?php
/**
* @brief Invisible captcha Interface
* @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 10 Mai 2022
*/
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;
}
/**
* captcha Interface
*/
interface CaptchaInterface
{
/**
* Display
*
* @return string
*/
public function 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();
}
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* @brief hCaptcha
* @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 10 Mai 2022
*/
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;
}
/**
* hCaptcha
*/
class _Hcaptcha implements CaptchaInterface
{
/**
* Does this CAPTCHA service support being added in a modal?
*/
public static $supportsModal = FALSE;
/**
* @brief Error
*/
protected $error;
/**
* Display
*
* @return string
*/
public function getHtml()
{
\IPS\Output::i()->jsFilesAsync[] = "https://js.hcaptcha.com/1/api.js?hl=" . \IPS\Member::loggedIn()->language()->short ;
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->hCaptcha( \IPS\Settings::i()->hcaptcha_sitekey, preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short ) );
}
/**
* Display
*
* @return string
*/
public function rowHtml()
{
\IPS\Output::i()->jsFilesAsync[] = "https://js.hcaptcha.com/1/api.js?hl=" . \IPS\Member::loggedIn()->language()->short ;
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->hCaptcha( \IPS\Settings::i()->hcaptcha_sitekey, preg_replace( '/^(.+?)\..*$/', '$1', \IPS\Member::loggedIn()->language()->short ) );
}
/**
* 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://hcaptcha.com/siteverify' )->request()->post( array(
'secret' => \IPS\Settings::i()->hcaptcha_secret,
'response' => trim( \IPS\Request::i()->__get('h-captcha-response') ),
'remoteip' => \IPS\Request::i()->ipAddress(),
) )->decodeJson( TRUE );
$hostname = \IPS\Http\Url::internal('')->data[ \IPS\Http\Url::COMPONENT_HOST ];
return ( ( (bool) $response['success'] ) and ( $response['hostname'] === mb_substr( $hostname, \mb_strlen( $hostname ) - mb_strlen( $response['hostname'] ) ) ) );
}
catch( \RuntimeException $e )
{
if( $e->getMessage() == 'BAD_JSON' )
{
return FALSE;
}
else
{
throw $e;
}
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* Invisible reCAPTCHA
*/
class _Invisible
class _Invisible implements CaptchaInterface
{
/**
* Does this CAPTCHA service support being added in a modal?
+1 -1
View File
@@ -20,7 +20,7 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* keyCAPTCHA
*/
class _Keycaptcha
class _Keycaptcha implements CaptchaInterface
{
/**
* Does this CAPTCHA service support being added in a modal?
+1 -1
View File
@@ -20,7 +20,7 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* reCAPTCHA
*/
class _Recaptcha2
class _Recaptcha2 implements CaptchaInterface
{
/**
* Does this CAPTCHA service support being added in a modal?
+3 -3
View File
@@ -38,9 +38,9 @@ class _CheckboxSet extends Select
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $id=NULL )
{
$options['multiple'] = TRUE;
$options['showAllNone'] = $options['showAllNone'] ?? TRUE;
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
if ( !isset( $this->options['descriptions'] ) )
{
$this->options['descriptions'] = array();
@@ -93,7 +93,7 @@ class _CheckboxSet extends Select
$value = array( $value );
}
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->checkboxset( $this->name, $value, $this->required, $this->parseOptions(), $this->options['multiple'], $this->options['class'], $this->options['disabled'], $this->options['toggles'], NULL, $this->options['unlimited'], $this->options['unlimitedLang'], $this->options['unlimitedToggles'], $this->options['unlimitedToggleOn'], $descriptions, $this->options['impliedUnlimited'] );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->checkboxset( $this->name, $value, $this->required, $this->parseOptions(), $this->options['multiple'], $this->options['class'], $this->options['disabled'], $this->options['toggles'], NULL, $this->options['unlimited'], $this->options['unlimitedLang'], $this->options['unlimitedToggles'], $this->options['unlimitedToggleOn'], $descriptions, $this->options['impliedUnlimited'], $this->options['showAllNone'] );
}
/**
+1 -1
View File
@@ -179,7 +179,7 @@ class _Date extends FormAbstract
{
if ( \is_int( $v ) or ( $v and \is_string( $v ) and preg_match( '/^[0-9]+$/', $v ) ) )
{
return \IPS\DateTime::ts( $v, $timezone );
return \IPS\DateTime::ts( $v );
}
else if( \is_array( $v ) )
{
+101 -11
View File
@@ -66,7 +66,8 @@ class _Editor extends FormAbstract
'maxLength' => 16777215, // The default for MEDIUMTEXT */
'editorId' => NULL,
'allowAttachments' => TRUE,
'ipsPlugins' => "ipsautolink,ipsautosave,ipsctrlenter,ipscode,ipscontextmenu,ipsemoticon,ipsimage,ipslink,ipsmentions,ipspage,ipspaste,ipspreview,ipsquote,ipsspoiler,ipsautogrow,ipssource,removeformat"
'ipsPlugins' => "ipsautolink,ipsautosave,ipsctrlenter,ipscode,ipscontextmenu,ipsemoticon,ipsimage,ipslink,ipsmentions,ipspage,ipspaste,ipsquote,ipsspoiler,ipsautogrow,ipssource,removeformat",
'profanityBlock' => TRUE,
);
/**
@@ -103,7 +104,34 @@ class _Editor extends FormAbstract
if ( \IPS\Settings::i()->giphy_enabled )
{
$this->defaultOptions['ipsPlugins'] .= ',ipsgiphy';
$this->defaultOptions['ipsPlugins'] .= ',ipsgiphy';
}
if( \IPS\Dispatcher::hasInstance() and ( \IPS\Dispatcher::i()->controllerLocation == 'front' OR ( \IPS\Dispatcher::i()->controllerLocation == 'admin' AND \IPS\Dispatcher::i()->module->key === 'editor' AND \IPS\Dispatcher::i()->controller == 'toolbar' )))
{
$this->defaultOptions['ipsPlugins'] .= ',ipspreview';
}
if ( isset( $options['allButtons'] ) AND $options['allButtons'] )
{
$showStockReplies = TRUE;
}
else
{
$showStockReplies = FALSE;
foreach ( \IPS\core\StoredReplies::getStore() as $reply )
{
$reply = \IPS\core\StoredReplies::constructFromData( $reply );
if ( $reply->enabled and $reply->can( 'view' ) )
{
$showStockReplies = TRUE;
}
}
}
if ( $showStockReplies )
{
$this->defaultOptions['ipsPlugins'] .= ',ipsstockreplies';
}
if ( isset( \IPS\Request::i()->usingEditor ) and \IPS\Request::i()->isAjax() and \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' )
@@ -309,6 +337,23 @@ class _Editor extends FormAbstract
$attachmentArea = \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->editorAttachmentsPlaceholder( $this->name, $this->postKey, $this->noUploaderError, $this->canUseMediaExtension() );
}
if ( \IPS\Member::loggedIn()->group['g_bypass_badwords'] )
{
$this->options['profanityBlock'] = FALSE;
}
if ( $this->options['profanityBlock'] )
{
$this->options['profanityBlock'] = [];
foreach( \IPS\core\Profanity::getProfanity() AS $profanity )
{
if ( $profanity->action == 'block' )
{
$this->options['profanityBlock'][] = [ 'word' => $profanity->type, 'type' => $profanity->m_exact ? 'exact' : 'loose' ];
}
}
}
/* 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( array() ), $this->options['tags'] );
@@ -339,7 +384,12 @@ class _Editor extends FormAbstract
return $value;
}
/**
* @brief Save on queries and fetch the alt label would just the once
*/
protected $_altLabelWord = NULL;
/**
* Get Value
*
@@ -381,9 +431,22 @@ class _Editor extends FormAbstract
if ( !isset( $parser ) or !\in_array( $attachment['attach_id'], $parser->mappedAttachments ) or array_key_exists( $attachment['attach_id'], $parser->existingAttachments ) )
{
$ext = mb_substr( $attachment['attach_file'], mb_strrpos( $attachment['attach_file'], '.' ) + 1 );
if ( \in_array( mb_strtolower( $ext ), \IPS\File::$videoExtensions ) )
if ( \in_array( mb_strtolower( $ext ), \IPS\File::$videoExtensions ) or \in_array( mb_strtolower( $ext ), \IPS\File::$audioExtensions ) )
{
$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'] );
$url = \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ) . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'];
if ( $attachment['attach_security_key'] )
{
$url .= "&key={$attachment['attach_security_key']}";
}
if ( \in_array( mb_strtolower( $ext ), \IPS\File::$videoExtensions ) )
{
$value .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedVideo( $attachment['attach_location'], $url, $attachment['attach_file'], \IPS\File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
}
elseif ( \in_array( mb_strtolower( $ext ), \IPS\File::$audioExtensions ) )
{
$value .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedAudio( $attachment['attach_location'], $url, $attachment['attach_file'], \IPS\File::getMimeType( $attachment['attach_file'] ), $attachment['attach_id'] );
}
}
elseif ( $attachment['attach_is_image'] )
{
@@ -397,8 +460,21 @@ class _Editor extends FormAbstract
$ratio = round( ( $attachment['attach_img_height'] / $attachment['attach_img_width'] ) * 100, 2 );
$width = $attachment['attach_img_width'];
}
$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'], $width, $ratio );
$altText = NULL;
if ( ! empty( $attachment['attach_labels'] ) )
{
if ( $this->_altLabelWord === NULL )
{
/* This is stored with the post, so it cannot be the user's language */
$this->_altLabelWord = \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'alt_label_could_be' );
}
$altText = $this->_altLabelWord . ' ' . implode( ', ', \IPS\Text\Parser::getAttachmentLabels( $attachment ) );
}
$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'], $width, $ratio, $altText );
}
else
{
@@ -409,7 +485,7 @@ class _Editor extends FormAbstract
}
$fileAttachments[] = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedFile( $url, $attachment['attach_file'], FALSE, $attachment['attach_ext'], $attachment['attach_id'], $attachment['attach_security_key'] );
}
if ( !isset( $parser ) or !\in_array( $attachment['attach_id'], $parser->mappedAttachments ) )
{
$inserts[] = array(
@@ -435,7 +511,7 @@ class _Editor extends FormAbstract
{
\IPS\Db::i()->insert( 'core_attachments_map', $inserts, TRUE );
}
/* Clear out the post key for attachments that are claimed automatically */
if ( $this->options['attachIds'] )
{
@@ -457,7 +533,7 @@ class _Editor extends FormAbstract
}
catch ( \Exception $e ) { }
}
/* Throw any errors */
if ( $error )
{
@@ -638,10 +714,24 @@ class _Editor extends FormAbstract
'totalMaxSize' => $maxTotalSize,
'maxFileSize' => \IPS\Member::loggedIn()->group['g_attach_per_post'] ? ( \IPS\Member::loggedIn()->group['g_attach_per_post'] * 1024 ) / 1048576 : NULL,
'allowStockPhotos' => $allowStockPhotos,
'canBeModerated' => ($this->extension AND method_exists( $this->extension, 'canBeModerated' ) and $this->extension->canBeModerated( \IPS\Member::loggedIn(), $this ) ),
'callback' => function( $file ) use ( $postKey )
{
try
{
$fileInfo = \IPS\Db::i()->select( 'requires_moderation, labels', 'core_files_temp', array( 'contents=?', (string) $file ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
$requiresModeration = (bool) $fileInfo['requires_moderation'];
$labels = $fileInfo['labels'];
}
catch ( \UnderflowException $e )
{
$requiresModeration = FALSE;
$labels = NULL;
}
\IPS\Db::i()->delete( 'core_files_temp', array( 'contents=?', (string) $file ) );
$attachment = $file->makeAttachment( $postKey, \IPS\Member::loggedIn() );
$attachment = $file->makeAttachment( $postKey, \IPS\Member::loggedIn(), $requiresModeration, $labels );
return $attachment['attach_id'];
}
);
+9 -3
View File
@@ -130,6 +130,11 @@ class _Matrix extends \IPS\Helpers\Form
* @brief Form Id, Set it Matrix is part of a form
*/
public $formId = NULL;
/**
* @brief Determines whether the Row Titles are processed as raw html instead of a language string. Not needed if $langPrefix is set
*/
public $styledRowTitle = FALSE;
/**
* Get HTML
@@ -140,7 +145,7 @@ class _Matrix extends \IPS\Helpers\Form
{
try
{
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->matrix( $this->id, array_keys( $this->columns ), $this->elements(), $this->action, $this->hiddenValues, $this->actionButtons, $this->langPrefix, $this->calculateWidths(), $this->manageable, $this->checkAlls, $this->checkAllRows, $this->classes, $this->showTooltips, $this->squashFields, $this->sortable );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->matrix( $this->id, array_keys( $this->columns ), $this->elements(), $this->action, $this->hiddenValues, $this->actionButtons, $this->langPrefix, $this->calculateWidths(), $this->manageable, $this->checkAlls, $this->checkAllRows, $this->classes, $this->showTooltips, $this->squashFields, $this->sortable, $this->styledRowTitle );
}
catch ( \Exception $e )
{
@@ -159,7 +164,7 @@ class _Matrix extends \IPS\Helpers\Form
*/
public function nested()
{
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->matrixNested( $this->id, array_keys( $this->columns ), $this->elements(), $this->action, $this->hiddenValues, $this->actionButtons, $this->langPrefix, $this->calculateWidths(), $this->manageable, $this->checkAlls, $this->checkAllRows, $this->classes, $this->showTooltips, $this->squashFields, $this->sortable );
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->matrixNested( $this->id, array_keys( $this->columns ), $this->elements(), $this->action, $this->hiddenValues, $this->actionButtons, $this->langPrefix, $this->calculateWidths(), $this->manageable, $this->checkAlls, $this->checkAllRows, $this->classes, $this->showTooltips, $this->squashFields, $this->sortable, $this->styledRowTitle );
}
/**
@@ -402,7 +407,8 @@ class _Matrix extends \IPS\Helpers\Form
if ( isset( $element->options['unlimited'] ) )
{
$unlimitedKey = "{$rowId}[{$columnName}_unlimited]";
if ( $value = \IPS\Request::i()->valueFromArray( $unlimitedKey ) )
$value = \IPS\Request::i()->valueFromArray( $unlimitedKey );
if ( $value !== NULL )
{
$element->value = $value;
}
+2 -1
View File
@@ -297,7 +297,8 @@ 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->_title;
$title = isset( $node::$titleLangPrefix ) ? \IPS\Member::loggedIn()->language()->addToStack( $node::$titleLangPrefix . $node->_id, FALSE, array( 'json' => TRUE, 'escape' => TRUE, 'striptags'=> TRUE ) ) : str_replace( "&#039;", "&apos;", htmlspecialchars( $node->_title, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', false ) );
$selected[ !( $node instanceof $nodeClass ) ? "{$node->_id}.s" : $node->_id ] = array( 'title' => $title, 'parents' => array_values( array_map( function( $val ){
if( isset( $val::$titleLangPrefix ) )
{
+13 -13
View File
@@ -184,15 +184,9 @@ class _Number extends FormAbstract
do the thousand seperator in case what we received was the former alredy, which would turn
for example "10,00" into "1000" */
$value = str_replace( trim( \IPS\Member::loggedIn()->language()->locale['decimal_point'] ), '.', $value );
/* If it's not numeric, throw an exception */
if ( ( !\is_numeric( $value ) and $value !== '' and $this->required === FALSE ) or ( !\is_numeric( $value ) and $this->required === TRUE ) )
{
throw new \InvalidArgumentException( 'form_number_bad' );
}
/* Convert to int/float */
if ( $this->options['decimals'] )
if ( $this->options['decimals'] and $value != '' )
{
$value = \floatval( $value );
if ( \is_int( $this->options['decimals'] ) )
@@ -202,7 +196,7 @@ class _Number extends FormAbstract
}
else
{
$value = \intval( $value );
$value = ( $value != '' ) ? \intval( $value ) : '';
}
/* Return */
@@ -216,16 +210,22 @@ class _Number extends FormAbstract
* @return TRUE
*/
public function validate()
{
{
parent::validate();
if ( $this->options['unlimited'] === NULL or $this->value !== $this->options['unlimited'] )
{
if ( $this->options['min'] !== NULL and $this->value < $this->options['min'] )
{
/* If it's not numeric, throw an exception */
if ( ( !\is_numeric( $this->value ) and $this->value !== '' and $this->required === FALSE ) or ( !\is_numeric( $this->value ) and $this->required === TRUE ) )
{
throw new \InvalidArgumentException( 'form_number_bad' );
}
if ( $this->options['min'] !== NULL and (int) $this->value < $this->options['min'] )
{
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_number_min', FALSE, array( 'sprintf' => array( $this->options['min'] ) ) ) );
}
if ( $this->options['max'] !== NULL and $this->value > $this->options['max'] )
if ( $this->options['max'] !== NULL and (int) $this->value > $this->options['max'] )
{
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_number_max', FALSE, array( 'sprintf' => array( $this->options['max'] ) ) ) );
}
-1
View File
@@ -59,7 +59,6 @@ class _Poll extends FormAbstract
foreach ( $choice['choice'] as $i => $question )
{
$thisChoice['title'] = $question;
$thisChoice['count'] = isset( $choice['votes'][ $i ] ) ? $choice['votes'][ $i ] : 0;
$thisQuestion['choices'][ $i ] = $thisChoice;
}
-4
View File
@@ -333,10 +333,6 @@ class _Tel extends Text
array (
0 => '33',
),
'FX' =>
array (
0 => '33',
),
'GA' =>
array (
0 => '241',
+6 -1
View File
@@ -311,6 +311,11 @@ class _Text extends TextArea
/* Set up profanity filters */
foreach( \IPS\core\Profanity::getProfanity() AS $profanity )
{
if ( $profanity->action == 'block' )
{
continue;
}
if ( $profanity->m_exact )
{
$exactProfanity[ $profanity->type ] = $profanity;
@@ -441,7 +446,7 @@ class _Text extends TextArea
}
else
{
if ( mb_stristr( $value, $type ) )
if ( mb_stristr( $this->value[ $k ], $type ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $v ) ) ) );
}
+9 -1
View File
@@ -78,7 +78,15 @@ class _Translatable extends FormAbstract
{
if ( \is_array( $this->options['sprintf'] ) )
{
$values[ $k ] = vsprintf( $v, $this->options['sprintf'] );
try
{
$values[ $k ] = vsprintf( $v, $this->options[ 'sprintf' ] );
}
catch ( \ValueError $e )
{
/* Note in pre-PHP8, vsprintf() returns FALSE */
$values[ $k ] = FALSE;
}
}
else
{
+61 -34
View File
@@ -45,6 +45,7 @@ class _Upload extends FormAbstract
'obscure' => TRUE, // Controls if an md5 hash should be added to the filename. *Must* be TRUE unless the uploaded files are public to all users (like emoticons),
'supportsDelete' => TRUE // Should the uploaded file be direct deletable from the upload field
'allowStockPhotos' => FALSE // If Pixabay enhancement is enabled, this will allow users to select stock art from Pixabay to upload
'canBeModerated' => TRUE // Set to TRUE if this uploader is capable of holding the submitted content for moderation
);
* @endcode
*/
@@ -67,7 +68,8 @@ class _Upload extends FormAbstract
'default' => NULL,
'obscure' => TRUE,
'supportsDelete' => TRUE,
'allowStockPhotos' => FALSE
'allowStockPhotos' => FALSE,
'canBeModerated' => FALSE
);
/**
@@ -167,7 +169,7 @@ class _Upload extends FormAbstract
/* If this has to be an image, set the allowed file types */
if ( isset( $options['image'] ) and !isset( $options['image']['optional'] ) and !isset( $options['allowedFileTypes'] ) )
{
$options['allowedFileTypes'] = \IPS\Image::supportedExtensions();
$options['allowedFileTypes'] = \IPS\Image::$imageExtensions;
}
/* Call parent constructor */
@@ -256,12 +258,16 @@ class _Upload extends FormAbstract
$image->setExifData( $fileObj->contents() );
}
$hasBeenResized = FALSE;
if( \is_array( $this->options['image'] ) )
{
$image->resizeToMax( $this->options['image']['maxWidth'] ?: NULL, $this->options['image']['maxHeight'] ?: NULL );
$hasBeenResized = $image->resizeToMax( $this->options['image']['maxWidth'] ?: NULL, $this->options['image']['maxHeight'] ?: NULL );
}
$fileObj->replace( (string) $image );
if ( $hasBeenResized or $image->hasBeenRotated )
{
$fileObj->replace( (string) $image );
}
}
catch ( \Exception $e ) { }
}
@@ -441,7 +447,6 @@ class _Upload extends FormAbstract
'size' => \IPS\Output\Plugin\Filesize::humanReadableFilesize( $file->filesize(), FALSE, TRUE ),
'sizeRaw' => $file->filesize(),
'default' => ( isset( $attachment ) AND $attachment['default'] ) ? $attachment['default'] : NULL,
'securityKey' => ( isset( $attachment ) ) ? $attachment['attach_security_key'] : $file->securityKey
);
}
catch( \UnderflowException $e ){}
@@ -482,7 +487,7 @@ class _Upload extends FormAbstract
{
/* Get the files we had already */
$return = array();
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename,exif', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename,exif,requires_moderation', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
if ( $this->options['storageExtension'] )
{
$existingName = $this->name . '_existing';
@@ -501,6 +506,7 @@ class _Upload extends FormAbstract
/* Reset the original filename to the real file name as $this->originalFilename is generated from the now AWS-safe $this->filename */
$file->originalFilename = $tempFiles[ $tempId ]['filename'];
$file->exifData = $tempFiles[ $tempId ]['exif'] ? json_decode( $tempFiles[ $tempId ]['exif'], TRUE ) : NULL;
$file->requiresModeration = (bool) $tempFiles[ $tempId ]['requires_moderation'];
$return[ $id ] = $file;
}
else
@@ -701,7 +707,7 @@ class _Upload extends FormAbstract
$ext = mb_strtolower( mb_substr( $file['name'], ( mb_strrpos( $file['name'], '.' ) + 1 ) ) );
/* Don't allow "XSS" in images */
if( \in_array( $ext, \IPS\File::$safeFileExtensions ) AND \in_array( $ext, \IPS\Image::supportedExtensions() ) )
if( \in_array( $ext, \IPS\File::$safeFileExtensions ) AND \in_array( $ext, \IPS\Image::$imageExtensions ) )
{
if( \IPS\File::checkXssInFile( file_get_contents( $file['tmp_name'] ) ) )
{
@@ -718,36 +724,42 @@ class _Upload extends FormAbstract
else
{
$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 )
$canBeModerated = $options['canBeModerated'];
$fileObjects = \IPS\File::createFromUploads( $this->options['storageExtension'], $fieldName, $this->options['allowedFileTypes'], $this->options['maxFileSize'], $this->options['totalMaxSize'], 0, function( $contents, $filename, $i ) use ( $options, &$requiresModeration, $canBeModerated )
{
$ext = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
/* Resize images */
if ( \in_array( $ext, \IPS\Image::supportedExtensions() ) AND ( \is_array( $options['image'] ) OR $options['checkImage'] ) )
/* Do image-specific stuff */
if ( \in_array( $ext, \IPS\Image::$imageExtensions ) )
{
try
/* Resize */
$image = NULL;
$hasBeenResized = FALSE;
if ( \is_array( $options['image'] ) OR $options['checkImage'] )
{
$image = \IPS\Image::create( $contents );
if( $image::exifSupported() )
try
{
$image->setExifData( $contents );
$image = \IPS\Image::create( $contents );
if( \is_array( $options['image'] ) )
{
$hasBeenResized = $image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
}
if ( $hasBeenResized or $image->hasBeenRotated )
{
$contents = (string) $image;
}
}
if( \is_array( $options['image'] ) )
{
$image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
}
return (string) $image;
catch ( \Exception $e ) {}
}
catch ( \Exception $e ) {}
}
return $contents;
}, $this->options['storageContainer'], $this->options['obscure'] );
foreach ( $fileObjects as $fileObj )
foreach ( $fileObjects as $i => $fileObj )
{
$exif = NULL;
@@ -756,15 +768,17 @@ class _Upload extends FormAbstract
$exif = json_encode( $fileObj->exifData );
}
$insertId = \IPS\Db::i()->insert( 'core_files_temp', array(
'upload_key' => md5( $this->name . session_id() ),
'filename' => $fileObj->originalFilename,
'mime' => \IPS\File::getMimeType( $fileObj->originalFilename ),
'contents' => (string) $fileObj,
'time' => time(),
'storage_extension' => $this->options['storageExtension'],
'exif' => $exif
) );
$data = $this->populateTempData( array(
'upload_key' => md5( $this->name . session_id() ),
'filename' => $fileObj->originalFilename,
'mime' => \IPS\File::getMimeType( $fileObj->originalFilename ),
'contents' => (string) $fileObj,
'time' => time(),
'storage_extension' => $this->options['storageExtension'],
'exif' => $exif
), $fileObj, $options );
$insertId = \IPS\Db::i()->insert( 'core_files_temp', $data );
if ( $this->options['callback'] )
{
@@ -799,6 +813,19 @@ class _Upload extends FormAbstract
throw new \InvalidArgumentException('form_required');
}
}
/**
* Finish populating the data before its inserted
*
* @param array $data Array of data to change/add to
* @param \IPS\File $fileObj File Object data
* @param array $options Field options
* @return array
*/
protected function populateTempData( array $data, \IPS\File $fileObj, array $options ): array
{
return $data;
}
/**
* String Value