Version 4.1.2
This commit is contained in:
1 parent
3ba79295ee
commit
28bd025b35
1131 files changed
+63658
-20579
No files matched your search
@@ -27,12 +27,14 @@ class _Address extends FormAbstract
|
||||
* @brief Default Options
|
||||
* @code
|
||||
$defaultOptions = array(
|
||||
'minimize' => FALSE, // Minimize the address field until the user focuses?
|
||||
'minimize' => FALSE, // Minimize the address field until the user focuses?
|
||||
'requireFullAddress'=> TRUE, // Does this have to be a full address? Default is TRUE, may set to FALSE if a more generic location is acceptable
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
'minimize' => FALSE
|
||||
'minimize' => FALSE,
|
||||
'requireFullAddress' => TRUE,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -181,7 +183,7 @@ class _Address extends FormAbstract
|
||||
}
|
||||
|
||||
/* Validate, return NULL if we have nothing */
|
||||
if ( !$fullyCompleted )
|
||||
if ( !$fullyCompleted and $this->options['requireFullAddress'] )
|
||||
{
|
||||
if ( $partiallyCompleted )
|
||||
{
|
||||
@@ -199,13 +201,25 @@ class _Address extends FormAbstract
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add in latitude and longitude if we can */
|
||||
try
|
||||
elseif( !$fullyCompleted )
|
||||
{
|
||||
$value->getLatLong();
|
||||
/* If we don't have any country, region city AND postal code, return NULL, otherwise everywhere ends up storing empty
|
||||
location arrays which makes it difficult to confirm if a location was actually set or not */
|
||||
if( !$value->country AND !$value->region AND !$value->city AND !$value->postalCode )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add in latitude and longitude if we can */
|
||||
if ( $fullyCompleted )
|
||||
{
|
||||
try
|
||||
{
|
||||
$value->getLatLong();
|
||||
}
|
||||
catch( \BadFunctionCallException $e ){}
|
||||
}
|
||||
catch( \BadFunctionCallException $e ){}
|
||||
|
||||
/* Return */
|
||||
return $value;
|
||||
|
||||
@@ -100,6 +100,7 @@ class _Captcha extends FormAbstract
|
||||
/* If we previously did an AJAX validate which is still valid, return true */
|
||||
if ( isset( $_SESSION[ 'captcha-val-' . $this->name ] ) and $_SESSION[ 'captcha-val-' . $this->name ] > ( time() - 60 ) )
|
||||
{
|
||||
unset( $_SESSION[ 'captcha-val-' . $this->name ] );
|
||||
return TRUE;
|
||||
}
|
||||
/* Otherwise, check with service */
|
||||
|
||||
@@ -90,7 +90,7 @@ class _CheckboxSet extends Select
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
$value = array_keys( parent::getValue() );
|
||||
$value = is_array( parent::getValue() ) ? array_keys( parent::getValue() ) : array();
|
||||
if ( $this->options['returnLabels'] )
|
||||
{
|
||||
if ( is_array( $value ) )
|
||||
|
||||
@@ -33,6 +33,7 @@ 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
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
@@ -44,6 +45,7 @@ class _Codemirror extends TextArea
|
||||
'tags' => array(),
|
||||
'mode' => 'htmlmixed',
|
||||
'nullLang' => NULL,
|
||||
'height' => 300
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -74,6 +76,11 @@ class _Codemirror extends TextArea
|
||||
*/
|
||||
public function html()
|
||||
{
|
||||
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 );
|
||||
if ( $this->options['height'] )
|
||||
{
|
||||
$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'] );
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ class _Date extends FormAbstract
|
||||
* @brief Default Options
|
||||
* @code
|
||||
$defaultOptions = array(
|
||||
'min' => new \IPS\DateTime('1970-01-01'), // Minimum date. NULL will mean no minimum (though note hard limits if it will be stored as a UNIX timestamp). Default is null.
|
||||
'max' => new \IPS\DateTime('2038-01-19'), // Maximum date. NULL will mean no maximum (though note hard limits if it will be stored as a UNIX timestamp). Default is null.
|
||||
'min' => new \IPS\DateTime('1970-01-01'), // Minimum date. NULL will mean 1901-12-13 20:45:52 (the minimum 32-bit timestamp). Default is null.
|
||||
'max' => new \IPS\DateTime('2038-01-19'), // Maximum date. NULL will mean 2038-01-19 03:14:07 (the maximum 32-bit timestamp). Default is null.
|
||||
'disabled' => FALSE, // Disables input. Default is FALSE.
|
||||
'time' => FALSE, // Also allow time input?
|
||||
'unlimited' => -1, // If any value other than NULL is provided, an "Unlimited" checkbox will be displayed. If checked, the value specified will be sent.
|
||||
@@ -85,9 +85,19 @@ class _Date extends FormAbstract
|
||||
$this->timeName = "{$name}_time";
|
||||
$this->unlimitedName = "{$name}_unlimited";
|
||||
}
|
||||
|
||||
|
||||
/* Call parent constructor */
|
||||
call_user_func_array( 'parent::__construct', func_get_args() );
|
||||
|
||||
/* Set min/max - hardcoded to 32bit values rather than using PHP_INT_MAX because the MySQL server may be 32-bit even if the web server is 64 */
|
||||
if ( !isset( $this->options['min'] ) or $this->options['min']->getTimestamp() < 2147483648 )
|
||||
{
|
||||
$this->options['min'] = \IPS\DateTime::ts( -2147483648 );
|
||||
}
|
||||
if ( !isset( $this->options['max'] ) or $this->options['max']->getTimestamp() > 2147483647 )
|
||||
{
|
||||
$this->options['max'] = \IPS\DateTime::ts( 2147483647 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,13 +277,13 @@ class _Date extends FormAbstract
|
||||
}
|
||||
|
||||
/* Check minimum */
|
||||
if ( $this->options['min'] !== NULL and $this->options['min'] > $this->value )
|
||||
if ( $this->value and $this->options['min'] !== NULL and $this->options['min'] > $this->value )
|
||||
{
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_min', FALSE, array( 'sprintf' => array( $this->options['min']->localeDate() ) ) ) );
|
||||
}
|
||||
|
||||
/* Check maximum */
|
||||
if ( $this->options['max'] !== NULL and $this->options['max'] < $this->value )
|
||||
if ( $this->value and $this->options['max'] !== NULL and $this->options['max'] < $this->value )
|
||||
{
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_max', FALSE, array( 'sprintf' => array( $this->options['max']->localeDate() ) ) ) );
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class _DateRange extends FormAbstract
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array() )
|
||||
{
|
||||
{
|
||||
$this->start = new \IPS\Helpers\Form\Date( "{$name}[start]", isset( $defaultValue['start'] ) ? $defaultValue['start'] : NULL, FALSE, isset( $options['start'] ) ? $options['start'] : array() );
|
||||
$this->end = new \IPS\Helpers\Form\Date( "{$name}[end]", isset( $defaultValue['end'] ) ? $defaultValue['end'] : NULL, FALSE, isset( $options['end'] ) ? $options['end'] : array() );
|
||||
|
||||
|
||||
@@ -141,7 +141,6 @@ class _Editor extends FormAbstract
|
||||
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, array( \IPS\Http\Url::internal( 'applications/core/interface/ckeditor/ckeditor/ckeditor.js', 'none', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE ) ) );
|
||||
}
|
||||
}
|
||||
\IPS\Output::i()->jsVars['strip_quotes'] = \IPS\Settings::i()->strip_quotes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +268,7 @@ class _Editor extends FormAbstract
|
||||
|
||||
/* If it was made without JS, convert linebreaks to <br>s */
|
||||
$noJsKey = $this->name . '_noscript';
|
||||
if ( isset( \IPS\Request::i()->$noJsKey ) AND !$value )
|
||||
if ( isset( \IPS\Request::i()->$noJsKey ) )
|
||||
{
|
||||
$value = nl2br( \IPS\Request::i()->$noJsKey, FALSE );
|
||||
}
|
||||
@@ -297,9 +296,9 @@ class _Editor extends FormAbstract
|
||||
if ( $this->uploader )
|
||||
{
|
||||
$inserts = array();
|
||||
foreach ( static::getAttachments( NULL, $this->postKey ) as $attachment )
|
||||
foreach ( $this->getAttachments() as $attachment )
|
||||
{
|
||||
if ( !isset( $parser ) or !in_array( $attachment['attach_id'], $parser->mappedAttachments ) )
|
||||
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'] )
|
||||
{
|
||||
@@ -309,21 +308,30 @@ class _Editor extends FormAbstract
|
||||
{
|
||||
$value .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedFile( \IPS\Settings::i()->base_url . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'], $attachment['attach_file'] );
|
||||
}
|
||||
|
||||
$inserts[] = array(
|
||||
'attachment_id' => $attachment['attach_id'],
|
||||
'location_key' => "{$this->options['app']}_{$this->options['key']}",
|
||||
'id1' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][0] ) ) ? $this->options['attachIds'][0] : NULL,
|
||||
'id2' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][1] ) ) ? $this->options['attachIds'][1] : NULL,
|
||||
'id3' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][2] ) ) ? $this->options['attachIds'][2] : NULL,
|
||||
'temp' => is_string( $this->options['attachIds'] ) ? $this->options['attachIds'] : ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] )
|
||||
);
|
||||
|
||||
if ( !isset( $parser ) or !in_array( $attachment['attach_id'], $parser->mappedAttachments ) )
|
||||
{
|
||||
$inserts[] = array(
|
||||
'attachment_id' => $attachment['attach_id'],
|
||||
'location_key' => "{$this->options['app']}_{$this->options['key']}",
|
||||
'id1' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][0] ) ) ? $this->options['attachIds'][0] : NULL,
|
||||
'id2' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][1] ) ) ? $this->options['attachIds'][1] : NULL,
|
||||
'id3' => ( is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][2] ) ) ? $this->options['attachIds'][2] : NULL,
|
||||
'temp' => is_string( $this->options['attachIds'] ) ? $this->options['attachIds'] : ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( count( $inserts ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_attachments_map', $inserts, TRUE );
|
||||
}
|
||||
|
||||
/* Clear out the post key for attachments that are claimed automatically */
|
||||
if ( $this->options['attachIds'] )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_attachments', array( 'attach_post_key' => '' ), array( 'attach_post_key=?', $this->postKey ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove abandoned attachments */
|
||||
@@ -416,26 +424,8 @@ class _Editor extends FormAbstract
|
||||
}
|
||||
|
||||
/* Load existing attachments */
|
||||
$existingAttachments = '';
|
||||
$counts = array();
|
||||
$where = array();
|
||||
if ( isset( $this->options['attachIds'] ) and is_array( $this->options['attachIds'] ) and count( $this->options['attachIds'] ) )
|
||||
{
|
||||
$where = array( array( 'location_key=?', "{$this->options['app']}_{$this->options['key']}" ) );
|
||||
$i = 1;
|
||||
foreach ( $this->options['attachIds'] as $id )
|
||||
{
|
||||
$where[] = array( "id{$i}=?", $id );
|
||||
$i++;
|
||||
}
|
||||
$_existingAttachments = iterator_to_array( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $where ) );
|
||||
if ( !empty( $_existingAttachments ) )
|
||||
{
|
||||
$existingAttachments = \IPS\Db::i()->in( 'attach_id', $_existingAttachments ) . ' OR ';
|
||||
}
|
||||
}
|
||||
$existing = array();
|
||||
foreach ( static::getAttachments( $existingAttachments, $this->postKey ) as $attachment )
|
||||
foreach ( $this->getAttachments() as $attachment )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -519,7 +509,7 @@ class _Editor extends FormAbstract
|
||||
try
|
||||
{
|
||||
$attachment = \IPS\Db::i()->select( '*', 'core_attachments', array( 'attach_id=?', \IPS\Request::i()->deleteFile ) )->first();
|
||||
\IPS\Db::i()->delete( 'core_attachments_map', array_merge( $where, array( array( 'attachment_id=?', $attachment['attach_id'] ) ) ) );
|
||||
\IPS\Db::i()->delete( 'core_attachments_map', array( array( 'location_key=?', "{$this->options['app']}_{$this->options['key']}" ), array( 'attachment_id=?', $attachment['attach_id'] ) ) );
|
||||
$otherMaps = \IPS\Db::i()->select( 'COUNT(*)', 'core_attachments_map', array( 'attachment_id=?', $attachment['attach_id'] ) )->first();
|
||||
if ( !$otherMaps )
|
||||
{
|
||||
@@ -551,13 +541,17 @@ class _Editor extends FormAbstract
|
||||
*/
|
||||
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')->setValueField('image') );
|
||||
$emoticons = iterator_to_array( \IPS\Db::i()->select( '*', 'core_emoticons', array( 'typed<>?', '' ) )->setKeyField('typed') );
|
||||
foreach( $emoticons as $key => $emoticon )
|
||||
{
|
||||
$emoticons[ $key ] = (string) \IPS\File::get( "core_Emoticons", $emoticon )->url;
|
||||
$emoticons[ $key ] = array(
|
||||
'image' => (string) \IPS\File::get( "core_Emoticons", $emoticon['image'] )->url,
|
||||
'image_2x' => $emoticon['image_2x'] ? (string) \IPS\File::get( "core_Emoticons", $emoticon['image_2x'] )->url . ' 2x' : FALSE,
|
||||
'height' => $emoticon['height'] ?: FALSE,
|
||||
'width' => $emoticon['width'] ?: FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
\IPS\Data\Store::i()->emoticons = $emoticons;
|
||||
@@ -565,27 +559,53 @@ class _Editor extends FormAbstract
|
||||
|
||||
return \IPS\Data\Store::i()->emoticons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachments
|
||||
*/
|
||||
protected $attachments;
|
||||
|
||||
/**
|
||||
* Fetch existing attachments - abstracted to a static method for caching
|
||||
* Fetch existing attachments
|
||||
*
|
||||
* @param string $existing Existing attachments limiter
|
||||
* @param string $postKey Post key
|
||||
* @return array
|
||||
*/
|
||||
public static function getAttachments( $existing, $postKey )
|
||||
protected function getAttachments()
|
||||
{
|
||||
static $attachments = array();
|
||||
|
||||
$_key = md5( $existing . $postKey );
|
||||
|
||||
if( isset( $attachments[ $_key ] ) )
|
||||
if ( $this->attachments === NULL )
|
||||
{
|
||||
return $attachments[ $_key ];
|
||||
$existingAttachments = '';
|
||||
$where = array();
|
||||
if ( isset( $this->options['attachIds'] ) and is_array( $this->options['attachIds'] ) and count( $this->options['attachIds'] ) )
|
||||
{
|
||||
$where = array( array( 'location_key=?', "{$this->options['app']}_{$this->options['key']}" ) );
|
||||
$i = 1;
|
||||
foreach ( $this->options['attachIds'] as $id )
|
||||
{
|
||||
$where[] = array( "id{$i}=?", $id );
|
||||
$i++;
|
||||
}
|
||||
$_existingAttachments = iterator_to_array( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $where ) );
|
||||
if ( !empty( $_existingAttachments ) )
|
||||
{
|
||||
$existingAttachments = \IPS\Db::i()->in( 'attach_id', $_existingAttachments ) . ' OR ';
|
||||
}
|
||||
}
|
||||
|
||||
$this->attachments = \IPS\Db::i()->select( '*', 'core_attachments', array( $existingAttachments . 'attach_post_key=?', $this->postKey ) );
|
||||
}
|
||||
|
||||
$attachments[ $_key ] = \IPS\Db::i()->select( '*', 'core_attachments', array( $existing . 'attach_post_key=?', $postKey ) );
|
||||
|
||||
return $attachments[ $_key ];
|
||||
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CKEditor Version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function ckeditorVersion()
|
||||
{
|
||||
preg_match( '/CKEditor\s+Included version: (.+?)\s/', file_get_contents( \IPS\ROOT_PATH . '/Credits.txt' ), $matches );
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
@@ -121,8 +121,7 @@ class _Form
|
||||
public function __construct( $id='form', $submitLang='save', $action=NULL, $attributes=array() )
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->action = $action ?: \IPS\Request::i()->url();
|
||||
$this->action = $this->action->stripQueryString('csrfKey');
|
||||
$this->action = $action ?: \IPS\Request::i()->url()->stripQueryString( array( 'csrfKey', 'ajaxValidate' ) );
|
||||
|
||||
$this->attributes = $attributes;
|
||||
|
||||
@@ -136,17 +135,18 @@ class _Form
|
||||
$potentialMaxUploadValues = array();
|
||||
if( (float) ini_get('upload_max_filesize') > 0 )
|
||||
{
|
||||
$potentialMaxUploadValues[] = (float) ini_get('upload_max_filesize');
|
||||
$potentialMaxUploadValues[] = \IPS\File::returnBytes( ini_get('upload_max_filesize') );
|
||||
}
|
||||
if( (float) ini_get('post_max_size') > 0 )
|
||||
{
|
||||
$potentialMaxUploadValues[] = (float) ini_get('post_max_size');
|
||||
/* We need to reduce post_max_size lower because it includes the ENTIRE post and other data, such as the number of chunks, will also be sent with the request */
|
||||
$potentialMaxUploadValues[] = \IPS\File::returnBytes( ini_get('post_max_size') ) - 1048576;
|
||||
}
|
||||
if( (float) ini_get('memory_limit') > 0 )
|
||||
{
|
||||
$potentialMaxUploadValues[] = (float) ini_get('memory_limit');
|
||||
$potentialMaxUploadValues[] = \IPS\File::returnBytes( ini_get('memory_limit') );
|
||||
}
|
||||
$this->uploadField = min( $potentialMaxUploadValues ) * 1048576;
|
||||
$this->uploadField = min( $potentialMaxUploadValues );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,6 +257,7 @@ class _Form
|
||||
public function addMatrix( $name, $matrix )
|
||||
{
|
||||
//$matrix->manageable = FALSE;
|
||||
$matrix->id = $this->id;
|
||||
$this->tabClasses[ $this->currentTab ] = 'ipsMatrix';
|
||||
$this->elements[ $this->currentTab ][ $name ] = $matrix;
|
||||
}
|
||||
|
||||
@@ -274,11 +274,19 @@ abstract class _FormAbstract
|
||||
/* Some form helpers might want to allow nested and outer suffixes */
|
||||
$html = $this->html();
|
||||
|
||||
if( array_key_exists( 'endSuffix', $this->options ) )
|
||||
if( array_key_exists( 'endSuffix', $this->options ) and ! empty( $this->options['endSuffix'] ) )
|
||||
{
|
||||
$this->suffix = $this->options['endSuffix'];
|
||||
}
|
||||
|
||||
/* Some elements support an array for suffix, such as Number which supports preUnlimited and postUnlimited. We need to wipe out
|
||||
the suffix here before calling the row() template, however, which only supports a string and throws an Array to string conversion error.
|
||||
By this point, the element template has already ran and used the suffix if designed to */
|
||||
if( is_array( $this->suffix ) )
|
||||
{
|
||||
$this->suffix = '';
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core' )->row( $label, $html, $desc, $warning, $this->appearRequired, $this->error, $this->prefix, $this->suffix, $this->htmlId ?: ( $form ? "{$form->id}_{$this->name}" : NULL ), $this, $form );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Item selector for Form Builder
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 12 Apr 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Helpers\Form;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* @todo limit by container IDs */
|
||||
|
||||
/**
|
||||
* Content Item selector
|
||||
*/
|
||||
class _Item extends FormAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Default Options
|
||||
* @code
|
||||
$defaultOptions = array(
|
||||
'class' => '\IPS\core\Foo', // The \Content\Item class
|
||||
'permissionCheck' => 'read', // If a permission key is provided, only content items that the member has that permission for will be available.
|
||||
'maxItems' => NULL, // Maximum items allowed to select, or NULL for unlimited
|
||||
'orderResults' => NULL|array // NULL or array( field, 'asc' ) where field is a mappable content item field (date, title, etc)
|
||||
'itemTemplate' => NULL // NULL or array of template group and template name array( \IPS\Theme::i()->getTemplate( 'group', 'app', 'location'), 'name' )
|
||||
'containerIds' => array // Array of container IDs to limit the search
|
||||
'minAjaxLength' => int // Number of characters to type before selector appears
|
||||
* 'where' => array // Array of additional where array( array( 'foo=?', 1 ) )
|
||||
* );
|
||||
* @encode
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
'class' => NULL,
|
||||
'permissionCheck' => 'read',
|
||||
'maxItems' => NULL,
|
||||
'orderResults' => NULL,
|
||||
'itemTemplate' => NULL,
|
||||
'containerIds' => array(),
|
||||
'minAjaxLength' => 3,
|
||||
'where' => array()
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
call_user_func_array( 'parent::__construct', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function html()
|
||||
{
|
||||
/* Display */
|
||||
$url = \IPS\Request::i()->url()->setQueryString( '_itemSelectName', $this->name );
|
||||
|
||||
$template = NULL;
|
||||
if ( $this->options['itemTemplate'] === NULL )
|
||||
{
|
||||
$template = array( \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' ), 'itemResult' );
|
||||
}
|
||||
|
||||
/* Are we getting some AJAX stuff? */
|
||||
if ( isset( \IPS\Request::i()->_itemSelectName ) and \IPS\Request::i()->_itemSelectName === $this->name )
|
||||
{
|
||||
$results = array();
|
||||
$class = $this->options['class'];
|
||||
$field = $class::$databasePrefix . $class::$databaseColumnMap['title'];
|
||||
$where = array( array( $field . " LIKE CONCAT('%', ?, '%')", \IPS\Request::i()->q ) );
|
||||
$idField = $class::$databaseColumnId;
|
||||
if ( isset( $class::$databaseColumnMap['container'] ) and is_array( $this->options['containerIds'] ) and count( $this->options['containerIds'] ) )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], $this->options['containerIds'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $this->options['where'] ) and count( $this->options['where'] ) )
|
||||
{
|
||||
$where = array_merge( $where, $this->options['where'] );
|
||||
}
|
||||
|
||||
foreach( $class::getItemsWithPermission( $where, 'LENGTH(' . $field . ') ASC', array( 0, 20 ), $this->options['permissionCheck'] ) as $item )
|
||||
{
|
||||
$results[] = array(
|
||||
'id' => $item->$idField,
|
||||
'html' => call_user_func( $template, $item )
|
||||
);
|
||||
}
|
||||
|
||||
\IPS\Output::i()->json( $results );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->item( $this->name, $this->value, $this->options['maxItems'], $this->options['minAjaxLength'], $url, $template );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
$name = $this->name . '_values';
|
||||
if ( isset( \IPS\Request::i()->$name ) )
|
||||
{
|
||||
return explode( ',', \IPS\Request::i()->$name );
|
||||
}
|
||||
else
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Value
|
||||
*
|
||||
* @return \IPS\Node\Model|array|NULL
|
||||
*/
|
||||
public function formatValue()
|
||||
{
|
||||
$itemClass = $this->options['class'];
|
||||
$order = NULL;
|
||||
$items = array();
|
||||
|
||||
if ( ! $this->value )
|
||||
{
|
||||
$this->value = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->value = ( is_string( $this->value ) ) ? explode( ',', $this->value ) : $this->value;
|
||||
}
|
||||
|
||||
if ( count( $this->value ) )
|
||||
{
|
||||
if ( is_array( $this->options['orderResults'] ) )
|
||||
{
|
||||
if ( isset( $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] ) )
|
||||
{
|
||||
$order = $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] . ' ' . $this->options['orderResults'][1];
|
||||
}
|
||||
}
|
||||
|
||||
$where = array( \IPS\Db::i()->in( $itemClass::$databaseColumnId, $this->value ) );
|
||||
foreach( $itemClass::getItemsWithPermission( array( $where ), $order, NULL, $this->options['permissionCheck'] ) as $item )
|
||||
{
|
||||
$items[ $item->_id ] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$this->value = $items;
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* String Value
|
||||
*
|
||||
* @param mixed $value The value
|
||||
* @return string
|
||||
*/
|
||||
public static function stringValue( $value )
|
||||
{
|
||||
if ( is_array( $value ) )
|
||||
{
|
||||
return implode( ',', array_keys( $value ) );
|
||||
}
|
||||
elseif ( is_object( $value ) )
|
||||
{
|
||||
return $value->_id;
|
||||
}
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,8 @@ class _Member extends Text
|
||||
'source' => 'app=core&module=system&controller=ajax&do=findMember',
|
||||
'resultItemTemplate' => 'core.autocomplete.memberItem',
|
||||
'unique' => true,
|
||||
'minAjaxLength' => 3
|
||||
'minAjaxLength' => 3,
|
||||
'disallowedCharacters' => array()
|
||||
);
|
||||
if( isset( $args[3] ) and array_key_exists( 'multiple', $args[3] ) and $args[3]['multiple'] > 0 )
|
||||
{
|
||||
@@ -87,7 +88,9 @@ class _Member extends Text
|
||||
$value = $value->name;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->text( $this->name, 'text', $value, $this->required, $this->options['maxLength'], $this->options['size'], $this->options['disabled'], $this->options['autocomplete'], $this->options['placeholder'], NULL, NULL );
|
||||
/* 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/ */
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->text( $this->name, 'text', ( $this->options['autocomplete'] ? 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, NULL );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,7 +37,7 @@ class _Node extends FormAbstract
|
||||
'subnodes' => TRUE, // Controls if subnodes should be included. Defaults to TRUE
|
||||
'togglePerm' => 'edit', // If a permission key is provided, nodes that have this permission will toggle the element IDs in toggleIds on/off
|
||||
'toggleIds' => array(), // Element IDs to toggle on/off when togglePerm is reached
|
||||
'forceOwner' => \IPS\Member::load(), // If nodes are 'owned' you can force a specific owner
|
||||
'forceOwner' => \IPS\Member::load(), // If nodes are 'owned', the owner. NULL for currently logged in member, FALSE to not limit by owner
|
||||
'where' => array(), // where clause to control which results to display
|
||||
'disabledIds' => array(), // Array of disabled IDs
|
||||
'noParentNodes' => 'Custom' // If a value is provided, subnodes of this class which have no parent node will be added into a pseudo-group with the title provided. e.g. custom packages in Nexus do not belong to any package group
|
||||
@@ -154,7 +154,7 @@ class _Node extends FormAbstract
|
||||
$nodes = array();
|
||||
$children = array();
|
||||
$noParentNodes = array();
|
||||
if( isset( $nodeClass::$ownerTypes ) and $nodeClass::$ownerTypes !== NULL )
|
||||
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'] );
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class _Number extends FormAbstract
|
||||
'step' => NULL, // Increase step for supported browsers. NULL will cause the value to be based off "decimals". Default is NULL.
|
||||
'range' => TRUE, // Use a range selection for supported browsers? Default is FALSE.
|
||||
'disabled' => FALSE, // Disables input. Default is FALSE.
|
||||
'suffixAfterNumber' => TRUE, // A suffix to go after the unlimited checkbox
|
||||
'endSuffix' => '...', // A suffix to go after the unlimited checkbox
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
@@ -184,12 +184,12 @@ class _Select extends FormAbstract
|
||||
{
|
||||
foreach ( $v as $x => $y )
|
||||
{
|
||||
$options[ $k ][ $x ] = htmlentities( $y, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
$options[ $k ][ $x ] = htmlspecialchars( $y, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$options[ $k ] = htmlentities( $v, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
$options[ $k ] = htmlspecialchars( $v, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -261,6 +261,12 @@ class _Select extends FormAbstract
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* If it is not required and there is no value (pagination from table advanced search form then pass validation otherwise it throws an error) */
|
||||
if ( $this->value === NULL and ! $this->required )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
parent::validate();
|
||||
|
||||
@@ -294,4 +300,15 @@ class _Select extends FormAbstract
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* String Value
|
||||
*
|
||||
* @param mixed $value The value
|
||||
* @return string
|
||||
*/
|
||||
public static function stringValue( $value )
|
||||
{
|
||||
return parent::stringValue( $value );
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ 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 < > ' ")
|
||||
),
|
||||
'placeholder' => 'e.g. ...', // A placeholder (NB: Will only work on compatible browsers)
|
||||
'regex' => '/[A-Z]+/i', // RegEx of acceptable value
|
||||
@@ -89,7 +90,7 @@ class _Text extends TextArea
|
||||
|
||||
/* Set username regex */
|
||||
$args = func_get_args();
|
||||
if ( isset( $args[3]['accountUsername'] ) and $args[3]['accountUsername'] === TRUE )
|
||||
if ( isset( $args[3]['accountUsername'] ) and $args[3]['accountUsername'] !== FALSE )
|
||||
{
|
||||
$args[3]['maxLength'] = \IPS\Settings::i()->max_user_name_length;
|
||||
|
||||
@@ -122,6 +123,16 @@ class _Text extends TextArea
|
||||
*/
|
||||
public function html()
|
||||
{
|
||||
/* 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 ) )
|
||||
{
|
||||
foreach( $this->value as $key => $value )
|
||||
{
|
||||
$this->value[ $key ] = htmlspecialchars( $value, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
}
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->text( $this->name, $this->formType, $this->value, $this->required, $this->options['maxLength'], $this->options['size'], $this->options['disabled'], $this->options['autocomplete'], $this->options['placeholder'], NULL, $this->options['nullLang'], $this->htmlId );
|
||||
}
|
||||
|
||||
@@ -154,6 +165,11 @@ class _Text extends TextArea
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $this->options['autocomplete'] ) )
|
||||
{
|
||||
$return = htmlspecialchars_decode( $return, ENT_QUOTES );
|
||||
}
|
||||
|
||||
/* Remove all invisible characters */
|
||||
$return = preg_replace( '/\p{C}+/u', '', $return );
|
||||
|
||||
|
||||
@@ -88,20 +88,21 @@ class _Upload extends FormAbstract
|
||||
|
||||
if( (float) ini_get('upload_max_filesize') > 0 )
|
||||
{
|
||||
$potentialValues[] = (float) ini_get('upload_max_filesize');
|
||||
$potentialValues[] = \IPS\File::returnBytes( ini_get('upload_max_filesize') );
|
||||
}
|
||||
|
||||
if( (float) ini_get('post_max_size') > 0 )
|
||||
{
|
||||
$potentialValues[] = (float) ini_get('post_max_size');
|
||||
$potentialValues[] = \IPS\File::returnBytes( ini_get('post_max_size') ) - 1048576;
|
||||
}
|
||||
|
||||
if( (float) ini_get('memory_limit') > 0 )
|
||||
{
|
||||
$potentialValues[] = (float) ini_get('memory_limit');
|
||||
$potentialValues[] = \IPS\File::returnBytes( ini_get('memory_limit') );
|
||||
}
|
||||
|
||||
$this->maxChunkSize = min( $potentialValues );
|
||||
/* We want the value in MB */
|
||||
$this->maxChunkSize = min( $potentialValues ) / 1048576;
|
||||
|
||||
/* Call parent constructor */
|
||||
call_user_func_array( 'parent::__construct', func_get_args() );
|
||||
@@ -388,7 +389,7 @@ class _Upload extends FormAbstract
|
||||
{
|
||||
/* Get the files we had already */
|
||||
$return = array();
|
||||
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id')->setValueField('contents') );
|
||||
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
|
||||
if ( $this->options['storageExtension'] )
|
||||
{
|
||||
$existingName = $this->name . '_existing';
|
||||
@@ -402,8 +403,10 @@ class _Upload extends FormAbstract
|
||||
{
|
||||
if ( $tempId and isset( $tempFiles[ $tempId ] ) )
|
||||
{
|
||||
$file = \IPS\File::get( $this->options['storageExtension'], $tempFiles[ $tempId ] );
|
||||
$file = \IPS\File::get( $this->options['storageExtension'], $tempFiles[ $tempId ]['contents'] );
|
||||
$file->tempId = $tempId;
|
||||
/* 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'];
|
||||
$return[ $id ] = $file;
|
||||
}
|
||||
else
|
||||
@@ -429,7 +432,7 @@ class _Upload extends FormAbstract
|
||||
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $this->options['storageExtension'], $tempFiles[ $tempId ] )->delete();
|
||||
\IPS\File::get( $this->options['storageExtension'], $tempFiles[ $tempId ]['contents'] )->delete();
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ class _Url extends Text
|
||||
$contentType = ( isset( $response->httpHeaders['Content-Type'] ) ) ? $response->httpHeaders['Content-Type'] : ( ( isset( $response->httpHeaders['content-type'] ) ) ? $response->httpHeaders['content-type'] : NULL );
|
||||
if( $contentType )
|
||||
{
|
||||
|
||||
foreach ( is_array( $this->options['allowedMimes'] ) ? $this->options['allowedMimes'] : array( $this->options['allowedMimes'] ) as $mime )
|
||||
{
|
||||
if ( preg_match( '/^' . str_replace( '~~', '.+', preg_quote( str_replace( '*', '~~', $mime ), '/' ) ) . '$/i', $contentType ) )
|
||||
@@ -116,7 +115,14 @@ class _Url extends Text
|
||||
}
|
||||
}
|
||||
|
||||
$this->value = \IPS\File::create( $this->options['file'], $filename, $response );
|
||||
try
|
||||
{
|
||||
$this->value = \IPS\File::create( $this->options['file'], $filename, $response );
|
||||
}
|
||||
catch( \InvalidArgumentException $e )
|
||||
{
|
||||
throw new \DomainException( 'form_url_error' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
|
||||
Reference in new issue
Block a user