Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+184 -60
View File
@@ -11,7 +11,7 @@
namespace IPS\Helpers\Form;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -41,8 +41,10 @@ class _Upload extends FormAbstract
'callback' => function() { ... }, // A callback function to run against submitted files
'minimize' => TRUE, // Default is minimized. Pass FALSE to show the maximized field. Cannot be used in conjunction with temporary uploads
'retainDeleted' => FALSE, // By default, if you specify a default value and the user deletes the files specified, the files will be physically deleted. This option overrides this behaviour.
'template' => 'core.attachments.fileItem' // The javascript template key to use when rendering uploaded items
'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)
'template' => 'core.attachments.fileItem', // The javascript template key to use when rendering uploaded items. e.g. core.attachments.fileItem
'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
);
* @endcode
*/
@@ -63,7 +65,9 @@ class _Upload extends FormAbstract
'retainDeleted' => FALSE,
'template' => 'core.attachments.fileItem',
'default' => NULL,
'obscure' => TRUE
'obscure' => TRUE,
'supportsDelete' => TRUE,
'allowStockPhotos' => FALSE
);
/**
@@ -94,21 +98,20 @@ class _Upload extends FormAbstract
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $id=NULL )
{
/* What's PHP's upload limit */
$potentialValues = array();
if( (float) ini_get('upload_max_filesize') > 0 )
if ( $maxChunkSize = static::maxChunkSize() )
{
$potentialValues[] = \IPS\File::returnBytes( ini_get('upload_max_filesize') );
$this->maxChunkSize = $maxChunkSize / 1048576;
}
if( (float) ini_get('post_max_size') > 0 )
/* Can we really use stock photos? */
if ( ! empty( $options['allowStockPhotos'] ) )
{
$potentialValues[] = \IPS\File::returnBytes( ini_get('post_max_size') ) - 1048576;
if ( ! \IPS\Settings::i()->pixabay_enabled )
{
$options['allowStockPhotos'] = FALSE;
}
}
if( (float) ini_get('memory_limit') > 0 )
{
$potentialValues[] = \IPS\File::returnBytes( ini_get('memory_limit') );
}
$this->maxChunkSize = min( $potentialValues ) / 1048576;
/* Work out storage extension */
if ( isset( $options['postKey'] ) and $options['postKey'] and ( !isset( $options['storageExtension'] ) or !$options['storageExtension'] ) )
{
@@ -123,9 +126,31 @@ class _Upload extends FormAbstract
if ( isset( $options['storageExtension'] ) and $options['storageExtension'] )
{
$storageClass = \IPS\File::getClass( $options['storageExtension'] );
if( !$storageClass::$supportsChunking and ( !isset( $options['maxFileSize'] ) or $this->maxChunkSize < $options['maxFileSize'] ) )
/* If the storage engine has a minimum chunk size, but our server can't handle uploads that large, we can't support chunking */
$supportsChunking = $storageClass::$supportsChunking;
if ( $storageClass::$supportsChunking and isset( $storageClass::$minChunkSize ) and $storageClass::$minChunkSize > $this->maxChunkSize )
{
$options['maxFileSize'] = $this->maxChunkSize;
$supportsChunking = FALSE;
}
/* If we support chunking... */
if ( $supportsChunking )
{
/* Just need to make sure the storage engine's maximum chunk size is bigger than what our server can handle */
if ( isset( $storageClass::$maxChunkSize ) and $storageClass::$maxChunkSize < $this->maxChunkSize )
{
$this->maxChunkSize = $storageClass::$maxChunkSize;
}
}
/* If we don't... */
elseif ( !$supportsChunking )
{
/* Then we need to set the maximum file size to what our server can handle */
if ( !isset( $options['maxFileSize'] ) or $this->maxChunkSize < $options['maxFileSize'] )
{
$options['maxFileSize'] = $this->maxChunkSize;
}
}
}
@@ -185,22 +210,31 @@ class _Upload extends FormAbstract
{
try
{
if ( $storageClass::$supportsChunking and isset( \IPS\Request::i()->chunks ) and \IPS\Request::i()->chunks > 1 )
/* Chunked */
if ( $storageClass::$supportsChunking and method_exists( $storageClass, 'chunkInit' ) and isset( \IPS\Request::i()->chunks ) and \IPS\Request::i()->chunks > 1 )
{
$chunkName = 'chunk-' . md5( \IPS\Request::i()->name ) . '-' . $this->name . '-' . session_id() . '.txt';
$lastChunk = ( \IPS\Request::i()->chunk == \IPS\Request::i()->chunks - 1 );
/* We need to be able to set data into sessions */
\IPS\Request::i()->setCookie( 'noCache', 1 );
/* If this is the FIRST chunk, start the process */
if ( \IPS\Request::i()->chunk == 0 )
{
$url = \IPS\File::create( $this->options['storageExtension'], $chunkName, NULL, $this->options['storageContainer'], FALSE, $_FILES[ $this->name ]['tmp_name'], FALSE );
$ref = $storageClass->chunkInit( \IPS\Request::i()->name, $this->options['storageContainer'], $this->options['obscure'] );
}
else
{
$url = $storageClass->chunkAppend( $chunkName, $_FILES[ $this->name ]['tmp_name'], $lastChunk ? \IPS\Request::i()->name : NULL, $this->options['storageContainer'] );
$ref = $_SESSION['chunkUploads'][ md5( \IPS\Request::i()->name ) . '-' . $this->name ];
}
if ( $lastChunk )
/* Process this chunk */
$ref = $storageClass->chunkProcess( $ref, $_FILES[ $this->name ]['tmp_name'], \IPS\Request::i()->chunk );
$_SESSION['chunkUploads'][ md5( \IPS\Request::i()->name ) . '-' . $this->name ] = $ref;
/* If this is the LAST chunk, finish the process */
if ( \IPS\Request::i()->chunk == \IPS\Request::i()->chunks - 1 )
{
$url = $storageClass->chunkFinish( $ref );
$fileObj = \IPS\File::get( $this->options['storageExtension'], $url );
$fileArray = array(
@@ -210,11 +244,11 @@ class _Upload extends FormAbstract
'name' => $fileObj->originalFilename,
'tmp_name' => $_FILES[ $this->name ]['tmp_name']
);
/* 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 ( $fileObj->isImage() AND ( $this->options['checkImage'] OR is_array( $this->options['image'] ) ) )
if ( $fileObj->isImage() AND ( $this->options['checkImage'] OR \is_array( $this->options['image'] ) ) )
{
try
{
@@ -225,12 +259,16 @@ class _Upload extends FormAbstract
$image->setExifData( $fileObj->contents() );
}
if( is_array( $this->options['image'] ) )
$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 );
}
if ( $hasBeenResized or $image->hasBeenRotated )
{
$fileObj->replace( (string) $image );
}
$fileObj->replace( (string) $image );
}
catch ( \Exception $e ) { }
}
@@ -246,7 +284,8 @@ class _Upload extends FormAbstract
if ( $this->options['callback'] )
{
$r = call_user_func( $this->options['callback'], $fileObj );
$callbackFunction = $this->options['callback'];
$r = $callbackFunction( $fileObj );
if ( $r !== NULL )
{
$insertId = $r;
@@ -258,7 +297,7 @@ class _Upload extends FormAbstract
'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,
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and \is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
) );
}
else
@@ -266,6 +305,7 @@ class _Upload extends FormAbstract
\IPS\Output::i()->json( array( 'chunk' => 'OK' ) );
}
}
/* Not chunked */
else
{
foreach ( $this->processUploads() as $insertId => $fileObj )
@@ -275,7 +315,7 @@ class _Upload extends FormAbstract
'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,
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and \is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
) );
}
}
@@ -285,22 +325,45 @@ class _Upload extends FormAbstract
\IPS\Log::log( $e, 'upload_failure' );
$message = $e->getMessage();
if ( \IPS\Member::loggedIn()->isAdmin() and \IPS\Member::loggedIn()->language()->checkKeyExists("uploaderr_{$e->getMessage()}_admin") )
if( $e instanceof \IPS\File\Exception )
{
$message = \IPS\Member::loggedIn()->language()->addToStack("uploaderr_{$e->getMessage()}_admin");
$message = \IPS\Member::loggedIn()->language()->addToStack("files-{$e->getCode()}", FALSE, array( 'sprintf' => array( $e->originalFilename ?: $e->file ) ) );
}
elseif ( \IPS\Member::loggedIn()->language()->checkKeyExists("uploaderr_{$e->getMessage()}") )
$subMessage = NULL;
if( $e instanceof \IPS\File\Exception AND $extra = $e->extraErrorMessage() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack("uploaderr_{$e->getMessage()}");
if ( \IPS\Member::loggedIn()->isAdmin() and \IPS\Member::loggedIn()->language()->checkKeyExists( $extra . "_admin" ) )
{
$subMessage = \IPS\Member::loggedIn()->language()->addToStack( $extra . "_admin" );
}
else
{
$subMessage = \IPS\Member::loggedIn()->language()->addToStack( $extra );
}
}
else
{
$message = \IPS\Member::loggedIn()->language()->addToStack("uploaderr_unspecified");
if ( \IPS\Member::loggedIn()->isAdmin() and \IPS\Member::loggedIn()->language()->checkKeyExists("uploaderr_{$e->getMessage()}_admin") )
{
$subMessage = \IPS\Member::loggedIn()->language()->addToStack( "uploaderr_{$e->getMessage()}_admin" );
}
elseif ( \IPS\Member::loggedIn()->language()->checkKeyExists("uploaderr_{$e->getMessage()}") )
{
$subMessage = \IPS\Member::loggedIn()->language()->addToStack( "uploaderr_{$e->getMessage()}" );
}
else
{
$subMessage = \IPS\Member::loggedIn()->language()->addToStack("uploaderr_unspecified" );
}
}
\IPS\Output::i()->json( array(
'error' => $message,
'extra' => $e->getCode()
'extra' => $e->getCode(),
'sub' => $subMessage
) );
}
}
@@ -331,7 +394,7 @@ class _Upload extends FormAbstract
/* Put the value in an array if we need to */
if ( $this->value !== NULL and ! empty( $this->value ) )
{
if ( $this->options['multiple'] !== TRUE and !is_array( $this->value ) )
if ( $this->options['multiple'] !== TRUE and !\is_array( $this->value ) )
{
$this->value = array( $this->value );
}
@@ -340,12 +403,12 @@ class _Upload extends FormAbstract
/* Build JSON version of the existing value, which allows the widget to build the interface */
$existing = array();
if( $this->value and count( $this->value ) )
if( $this->value and \count( $this->value ) )
{
foreach( $this->value as $id => $file )
{
/* If this was attachment data, expand */
if( is_array( $file ) )
if( \is_array( $file ) )
{
$attachment = $file;
$file = $file['fileurl'];
@@ -366,6 +429,7 @@ class _Upload extends FormAbstract
'hasThumb' => false,
'originalFileName' => $file,
'size' => \IPS\Output\Plugin\Filesize::humanReadableFilesize( $file, FALSE, TRUE ),
'sizeRaw' => $file,
'custom' => NULL
);
}
@@ -380,6 +444,7 @@ class _Upload extends FormAbstract
'originalFileName' => $file->originalFilename,
'thumbnail' => ( isset( $attachment ) AND isset( $attachment['attach_is_image'] ) AND $attachment['attach_is_image'] AND $attachment['attach_thumb_location'] ) ? ( (string) \IPS\File::get( $this->options['storageExtension'], $attachment['attach_thumb_location'] )->url ) : ( $file->isImage() ? (string) $file->url : NULL ),
'size' => \IPS\Output\Plugin\Filesize::humanReadableFilesize( $file->filesize(), FALSE, TRUE ),
'sizeRaw' => $file->filesize(),
'default' => ( isset( $attachment ) AND $attachment['default'] ) ? $attachment['default'] : NULL,
);
}
@@ -389,15 +454,23 @@ class _Upload extends FormAbstract
foreach( $this->value as $id => $file )
{
$this->value[ $id ] = ( is_array( $file ) ) ? $file['fileurl'] : $file;
$this->value[ $id ] = ( \is_array( $file ) ) ? $file['fileurl'] : $file;
}
}
/* We want this to use decimals even if locale wants commas for decimal separator, i.e. for uploader */
$maxFileSize = $this->options['maxFileSize'] ? number_format( $this->options['maxFileSize'], 4, '.', '' ) : $this->options['maxFileSize'];
$maxFileSize = $this->options['maxFileSize'] ? number_format( $this->options['maxFileSize'], 2, '.', '' ) : $this->options['maxFileSize'];
/* Do we have maximum image dimensions which we can ask plupload to handle for us client-side? */
$maxImageDims = NULL;
if ( $this->options['image'] and isset( $this->options['image']['maxWidth'] ) and isset( $this->options['image']['maxHeight'] ) )
{
$maxImageDims = "{$this->options['image']['maxWidth']}x{$this->options['image']['maxHeight']}";
}
/* The html() method is called more than once, however $this->value is wiped out so if it was an attachments array all of the array properties are lost */
$this->builtHtml = call_user_func( $this->template, $this->name, $this->value, $this->options['minimize'], $maxFileSize, $this->options['maxFiles'], $this->maxChunkSize, $this->options['totalMaxSize'], $this->options['allowedFileTypes'], $uploadKey, $this->options['multiple'], $this->options['postKey'], $this->options['temporary'] or ( isset( \IPS\Request::i()->cookie['vle_editor'] ) and \IPS\Request::i()->cookie['vle_editor'] ), $this->options['template'], $existing, $this->options['default'] );
$templateFunction = $this->template;
$this->builtHtml = $templateFunction( $this->name, $this->value, $this->options['minimize'], $maxFileSize, $this->options['maxFiles'], $this->maxChunkSize, $this->options['totalMaxSize'], $this->options['allowedFileTypes'], $uploadKey, $this->options['multiple'], $this->options['postKey'], $this->options['temporary'] or ( isset( \IPS\Request::i()->cookie['vle_editor'] ) and \IPS\Request::i()->cookie['vle_editor'] ), $this->options['template'], $existing, $this->options['default'], $this->options['supportsDelete'], $maxImageDims, $this->options['allowStockPhotos'] );
return $this->builtHtml;
}
@@ -413,13 +486,13 @@ class _Upload extends FormAbstract
{
/* Get the files we had already */
$return = array();
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename', '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', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
if ( $this->options['storageExtension'] )
{
$existingName = $this->name . '_existing';
$keepName = $this->name . '_keep';
$keep = \IPS\Request::i()->$keepName;
if ( isset( \IPS\Request::i()->$existingName ) and is_array( \IPS\Request::i()->$existingName ) )
if ( isset( \IPS\Request::i()->$existingName ) and \is_array( \IPS\Request::i()->$existingName ) )
{
foreach ( \IPS\Request::i()->$existingName as $id => $tempId )
{
@@ -431,6 +504,7 @@ class _Upload extends FormAbstract
$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'];
$file->exifData = $tempFiles[ $tempId ]['exif'] ? json_decode( $tempFiles[ $tempId ]['exif'], TRUE ) : NULL;
$return[ $id ] = $file;
}
else
@@ -492,7 +566,7 @@ class _Upload extends FormAbstract
{
try
{
if ( is_array( $this->defaultValue ) )
if ( \is_array( $this->defaultValue ) )
{
foreach( $this->defaultValue as $file )
{
@@ -557,7 +631,7 @@ class _Upload extends FormAbstract
{
$message = \IPS\Member::loggedIn()->language()->get( 'pluploaderr_' . $e->getMessage() );
$message = str_replace( '{{max_file_size}}', $this->maxChunkSize, $message );
if ( is_array( $this->options['allowedFileTypes'] ) )
if ( \is_array( $this->options['allowedFileTypes'] ) )
{
$message = str_replace( '{{allowed_extensions}}', implode( ', ', $this->options['allowedFileTypes'] ), $message );
}
@@ -587,7 +661,7 @@ class _Upload extends FormAbstract
}
if ( $total > $this->options['totalMaxSize'] * 1048576 )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('uploaderr_total_size', FALSE, array( 'sprintf' => array( $this->options['totalMaxSize'] ) ) ) );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('uploaderr_total_size', FALSE, array( 'sprintf' => array( \IPS\Output\Plugin\Filesize::humanReadableFilesize( $this->options['totalMaxSize'] * 1048576 ) ) ) ) );
}
}
@@ -596,7 +670,7 @@ class _Upload extends FormAbstract
foreach( $return as $k => $v )
{
$toReturn[ $k ] = ( is_array( $v ) AND isset( $v['fileurl'] ) ) ? $v['fileurl'] : $v;
$toReturn[ $k ] = ( \is_array( $v ) AND isset( $v['fileurl'] ) ) ? $v['fileurl'] : $v;
}
$return = $toReturn;
@@ -631,7 +705,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::$imageExtensions ) )
if( \in_array( $ext, \IPS\File::$safeFileExtensions ) AND \in_array( $ext, \IPS\Image::$imageExtensions ) )
{
if( \IPS\File::checkXssInFile( file_get_contents( $file['tmp_name'] ) ) )
{
@@ -653,18 +727,22 @@ class _Upload extends FormAbstract
$ext = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
/* Resize images */
if ( in_array( $ext, \IPS\Image::$imageExtensions ) AND ( is_array( $options['image'] ) OR $options['checkImage'] ) )
if ( \in_array( $ext, \IPS\Image::$imageExtensions ) AND ( \is_array( $options['image'] ) OR $options['checkImage'] ) )
{
try
{
$image = \IPS\Image::create( $contents );
if( is_array( $options['image'] ) )
$hasBeenResized = FALSE;
if( \is_array( $options['image'] ) )
{
$image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
}
return (string) $image;
if ( $hasBeenResized or $image->hasBeenRotated )
{
return (string) $image;
}
}
catch ( \Exception $e ) {}
}
@@ -674,18 +752,27 @@ class _Upload extends FormAbstract
foreach ( $fileObjects as $fileObj )
{
$exif = NULL;
if( \IPS\Image::exifSupported() and $fileObj->isImage() and $fileObj->exifData )
{
$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']
'storage_extension' => $this->options['storageExtension'],
'exif' => $exif
) );
if ( $this->options['callback'] )
{
$r = call_user_func( $this->options['callback'], $fileObj );
$callbackFunction = $this->options['callback'];
$r = $callbackFunction( $fileObj );
if ( $r !== NULL )
{
@@ -724,11 +811,11 @@ class _Upload extends FormAbstract
*/
public static function stringValue( $value )
{
if ( is_array( $value ) )
if ( \is_array( $value ) )
{
return implode( ',', array_map( function( $v )
{
if ( is_object( $v ) )
if ( \is_object( $v ) )
{
return (string) $v->url;
}
@@ -737,4 +824,41 @@ class _Upload extends FormAbstract
return ( $value ) ? (string) $value->url : NULL;
}
/**
* Get the maximum chunk size (i.e. PHP's limit) in bytes
*
* @return int|NULL
*/
public static function maxChunkSize()
{
if ( $potentialValues = static::maxChunkSizeValues() )
{
return min( $potentialValues );
}
return NULL;
}
/**
* Get the maximum chunk size (i.e. PHP's limit) in bytes
*
* @return int|NULL
*/
public static function maxChunkSizeValues()
{
$potentialValues = array();
if( (float) ini_get('upload_max_filesize') > 0 )
{
$potentialValues['upload_max_filesize'] = \IPS\File::returnBytes( ini_get('upload_max_filesize') );
}
if( (float) ini_get('post_max_size') > 0 )
{
$potentialValues['post_max_size'] = \IPS\File::returnBytes( ini_get('post_max_size') ) - 1048576;
}
if( (float) ini_get('memory_limit') > 0 )
{
$potentialValues['memory_limit'] = \IPS\File::returnBytes( ini_get('memory_limit') );
}
return $potentialValues;
}
}