Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
+234
-209
File diff suppressed because it is too large.
Load diff
@@ -1,311 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief File Handler: Backblaze
|
||||
* @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 Oct 2024
|
||||
*/
|
||||
|
||||
namespace IPS\File;
|
||||
|
||||
/* Set use statements to make Uncle Matt happy */
|
||||
use IPS\DateTime AS DT;
|
||||
use IPS\File\Amazon;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Http\Request\Exception as RequestException;
|
||||
use IPS\Member;
|
||||
use DateInterval;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function debug_backtrace;
|
||||
use function defined;
|
||||
use function header;
|
||||
use function in_array;
|
||||
use function mb_strstr;
|
||||
use function trim;
|
||||
|
||||
use const ARRAY_FILTER_USE_KEY;
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
class _Backblaze extends Amazon
|
||||
{
|
||||
/**
|
||||
* Settings
|
||||
*
|
||||
* @param array $configuration Configuration if editing a setting, or array() if creating a setting.
|
||||
* @return array
|
||||
*/
|
||||
public static function settings( $configuration=array() )
|
||||
{
|
||||
$default = ( isset( $configuration['custom_url'] ) and ! empty( $configuration['custom_url'] ) ) ? TRUE : FALSE;
|
||||
$return = parent::settings( $configuration );
|
||||
unset( $return['toggle'], $return['custom_url'] );
|
||||
|
||||
$return['endpoint'] = array( 'type' => 'Text', 'default' => 's3.backblazeb2.com' );
|
||||
$return['toggle'] = array( 'type' => 'YesNo', 'default' => $default, 'options' => array(
|
||||
'togglesOn' => array( 'Backblaze_custom_url' )
|
||||
) );
|
||||
$return['custom_url'] = array( 'type' => 'Text', 'default' => '' );
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name
|
||||
*
|
||||
* @param array $settings Configuration settings
|
||||
* @return string
|
||||
*/
|
||||
public static function displayName( $settings )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'filehandler_display_name', FALSE, array( 'sprintf' => array( Member::loggedIn()->language()->addToStack('filehandler__Backblaze'), $settings['bucket'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign and make request
|
||||
*
|
||||
* @param string $uri The URI (relative to the bucket)
|
||||
* @param string $verb The HTTP verb to use
|
||||
* @param array $configuration The configuration for this instance
|
||||
* @param int $configurationId The configuration ID
|
||||
* @param string|null $content The content to send
|
||||
* @param string|null $storageExtension Storage extension
|
||||
* @param bool $skipExtraChecks Skips the endpoint check (to prevent infinite looping)
|
||||
* @param bool $isPrivate This can be set to true to access/store private files (i.e. that are not publicly readable)
|
||||
* @param array $queryString Array of key => value pairs
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
*/
|
||||
protected static function makeRequest( $uri, $verb, $configuration, $configurationId, $content=NULL, $storageExtension=NULL, $skipExtraChecks=FALSE, $isPrivate=false, $queryString=array() )
|
||||
{
|
||||
/* Amazon requires filename characters to be properly encoded - let's urlencode the filename here */
|
||||
$uriPieces = explode( '/', $uri );
|
||||
$filename = array_pop( $uriPieces );
|
||||
$uri = ltrim( implode( '/', $uriPieces ) . '/' . rawurlencode( $filename ), '/' );
|
||||
|
||||
/* Build a request */
|
||||
$url = \IPS\Http\Url::external( static::buildBaseUrl( $configuration ) . $uri );
|
||||
if ( $queryString )
|
||||
{
|
||||
$url = $url->setQueryString( $queryString );
|
||||
}
|
||||
$request = $url->request( \IPS\VERY_LONG_REQUEST_TIMEOUT, NULL, FALSE ); # Amazon will send a 301 header code, but no Location header, if we need to try another endpoint
|
||||
|
||||
/* When using virtual hosted–style buckets with SSL, the SSL wild card certificate only matches buckets that do not contain periods. To work around this, use HTTP or write your own certificate verification logic. @link http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html */
|
||||
if ( \IPS\Request::i()->isSecure() and mb_strstr( $configuration['bucket'], '.' ) )
|
||||
{
|
||||
$request->sslCheck( FALSE );
|
||||
}
|
||||
|
||||
/* Set headers */
|
||||
$headers = array(
|
||||
'Content-Type' => \IPS\File::getMimeType( $uri ),
|
||||
'Content-MD5' => base64_encode( md5( $content, TRUE ) ),
|
||||
);
|
||||
|
||||
/* If uploading a file, need to specify length and cache control */
|
||||
if( mb_strtoupper( $verb ) === 'PUT' )
|
||||
{
|
||||
$headers['Content-Length'] = \strlen( $content );
|
||||
|
||||
$cacheSeconds = 3600 * 24 * 365;
|
||||
|
||||
/* Custom Cache-Control */
|
||||
if( $storageExtension !== NULL AND mb_strpos( $storageExtension, '_' ) !== FALSE )
|
||||
{
|
||||
$bits = explode( '_', $storageExtension );
|
||||
$class = '\IPS\\' . $bits[0] . '\extensions\core\FileStorage\\' . $bits[1];
|
||||
|
||||
if ( isset( $class::$cacheControlTtl ) and $class::$cacheControlTtl )
|
||||
{
|
||||
$cacheSeconds = $class::$cacheControlTtl;
|
||||
}
|
||||
}
|
||||
|
||||
$headers['Cache-Control'] = 'public, max-age=' . $cacheSeconds;
|
||||
}
|
||||
|
||||
/* We need to strip query string parameters for the signature, but not always (e.g. a subresource such as ?acl needs to be included and multi-
|
||||
object delete requests must include the query string params). Let the callee decide to do this or not. */
|
||||
if( isset( $configuration['_strip_querystring'] ) AND $configuration['_strip_querystring'] === TRUE )
|
||||
{
|
||||
$uri = preg_replace( "/^(.*?)\?.*$/", "$1", $uri );
|
||||
}
|
||||
|
||||
/* Sign the request */
|
||||
$authorization = static::signature( $configuration, $verb, $uri, $headers, $queryString, $content );
|
||||
$headers['Authorization'] = $authorization;
|
||||
unset( $headers['Host'] );
|
||||
$request->setHeaders( $headers );
|
||||
|
||||
/* Make the request */
|
||||
$verb = mb_strtolower( $verb );
|
||||
$response = $request->$verb( $content );
|
||||
|
||||
/* If we are skipping extra checks, return response now */
|
||||
if( $skipExtraChecks )
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Change endpoint if necessary */
|
||||
if ( $response->httpResponseCode == 301 )
|
||||
{
|
||||
$xml = $response->decodeXml();
|
||||
if ( isset( $xml->Endpoint ) )
|
||||
{
|
||||
/* We have an endpoint, but if we called s3.amazonaws.com then it might be wrong. Try to detect the correct one. */
|
||||
$configuration['endpoint'] = 's3-us-west-1.amazonaws.com';
|
||||
|
||||
$endpointResponse = static::makeRequest( $uri, $verb, $configuration, $configurationId, $content, NULL, TRUE );
|
||||
$update = FALSE;
|
||||
|
||||
/* If the response code is 200, we got lucky and that's our endpoint */
|
||||
if( $endpointResponse->httpResponseCode == 200 )
|
||||
{
|
||||
$update = TRUE;
|
||||
}
|
||||
/* If it's a 301 response, we should be able to pull out the correct endpoint now */
|
||||
elseif( $endpointResponse->httpResponseCode == 301 )
|
||||
{
|
||||
$xml = $endpointResponse->decodeXml();
|
||||
if ( isset( $xml->Endpoint ) )
|
||||
{
|
||||
/* Strip out the bucket from the endpoint */
|
||||
$configuration['endpoint'] = preg_replace( '/^' . preg_quote( $configuration['bucket'], '/' ) . '\./', '', (string) $xml->Endpoint );
|
||||
$update = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we need to update, do it now and return the result */
|
||||
if( $update === TRUE )
|
||||
{
|
||||
static::$updatedEndpoint = $configuration['endpoint'];
|
||||
|
||||
if ( $configurationId )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_file_storage', array( 'configuration' => json_encode( $configuration ) ), array( "id=?", $configurationId ) );
|
||||
unset( \IPS\Data\Store::i()->storageConfigurations );
|
||||
}
|
||||
}
|
||||
|
||||
return static::makeRequest( $uri, $verb, $configuration, $configurationId, $content );
|
||||
}
|
||||
}
|
||||
|
||||
/* Change region if necessary */
|
||||
if ( $response->httpResponseCode == 400 )
|
||||
{
|
||||
try
|
||||
{
|
||||
$xml = $response->decodeXml();
|
||||
if ( isset( $xml->Region ) )
|
||||
{
|
||||
$configuration['region'] = (string) $xml->Region;
|
||||
if ( $configurationId )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_file_storage', array( 'configuration' => json_encode( $configuration ) ), array( 'id=?', $configurationId ) );
|
||||
unset( \IPS\Data\Store::i()->storageConfigurations );
|
||||
}
|
||||
return static::makeRequest( $uri, $verb, $configuration, $configurationId, $content );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a temporary download URL the user can be redirected to
|
||||
*
|
||||
* @param $validForSeconds int The number of seconds the link should be valid for
|
||||
* @return Url
|
||||
*/
|
||||
public function generateTemporaryDownloadUrl( $validForSeconds = 1200 )
|
||||
{
|
||||
$expires = ( new DT )->add( new DateInterval( "PT{$validForSeconds}S" ) );
|
||||
|
||||
/* Authorize */
|
||||
$authorize = Url::external( "https://api.backblazeb2.com/b2api/v3/b2_authorize_account" )
|
||||
->request()
|
||||
->login( $this->configuration['access_key'], $this->configuration['secret_key'] )
|
||||
->get();
|
||||
$response = $authorize->decodeJson();
|
||||
if ( $authorize->httpResponseCode !== '200' )
|
||||
{
|
||||
throw new Exception( $response['code'] );
|
||||
}
|
||||
|
||||
$download = URL::external( "{$response['apiInfo']['storageApi']['apiUrl']}/b2api/v3/get_download_authorization" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => $response['authorizationToken']
|
||||
) )
|
||||
->post( json_encode( array(
|
||||
'bucketId' => $response['apiInfo']['storageApi']['bucketId'],
|
||||
'fileNamePrefix' => "{$this->container}/",
|
||||
'validDurationInSeconds' => $expires->getTimestamp(),
|
||||
'b2ContentDisposition' => 'attachment; filename="' . $this->originalFilename . '"',
|
||||
'b2Expires' => $expires->rfc1123(),
|
||||
'b2ContentType' => static::getMimeType( $this->originalFilename ),
|
||||
|
||||
) ) )
|
||||
->decodeJson();
|
||||
|
||||
$query = http_build_query( array(
|
||||
'Authorization' => $download['authorizationToken'],
|
||||
'b2ContentDisposition' => 'attachment; filename="' . $this->originalFilename . '"',
|
||||
'b2Expires' => $expires->rfc1123(),
|
||||
'b2ContentType' => static::getMimeType( $this->originalFilename ),
|
||||
) );
|
||||
|
||||
return Url::external( "{$response['apiInfo']['storageApi']['downloadUrl']}/file/{$this->configuration['bucket']}/{$this->container}/{$this->filename}?{$query}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up the base Amazon URL
|
||||
* @param array $configuration Configuration data
|
||||
* @return string
|
||||
*/
|
||||
public static function buildBaseUrl( $configuration )
|
||||
{
|
||||
if ( mb_strstr( $configuration['bucket'], '.' ) )
|
||||
{
|
||||
return (
|
||||
\IPS\Request::i()->isSecure() ? "https" : "http" ) . "://"
|
||||
. ( isset( $configuration['endpoint'] ) ? $configuration['endpoint'] : "s3.backblazeb2.com" )
|
||||
. "/{$configuration['bucket']}"
|
||||
. static::bucketPath( $configuration )
|
||||
. '/';
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
\IPS\Request::i()->isSecure() ? "https" : "http" ) . "://{$configuration['bucket']}."
|
||||
. ( isset( $configuration['endpoint'] ) ? $configuration['endpoint'] : "s3.backblazeb2.com" )
|
||||
. static::bucketPath( $configuration )
|
||||
. '/';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow gzip versions for Backblaze
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function needsGzipVersion()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+47
-37
@@ -11,16 +11,26 @@
|
||||
namespace IPS\File;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\File;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use LogicException;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Handler: Database
|
||||
*/
|
||||
class _Database extends \IPS\File
|
||||
class Database extends File
|
||||
{
|
||||
/* !ACP Configuration */
|
||||
|
||||
@@ -30,7 +40,7 @@ class _Database extends \IPS\File
|
||||
* @param array $configuration Configuration if editing a setting, or array() if creating a setting.
|
||||
* @return array
|
||||
*/
|
||||
public static function settings( $configuration=array() )
|
||||
public static function settings( array $configuration=array() ) : array
|
||||
{
|
||||
return array();
|
||||
}
|
||||
@@ -40,9 +50,9 @@ class _Database extends \IPS\File
|
||||
*
|
||||
* @param array $values The submitted values
|
||||
* @return void
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public static function testSettings( &$values )
|
||||
public static function testSettings( array &$values ) : void
|
||||
{
|
||||
|
||||
}
|
||||
@@ -53,9 +63,9 @@ class _Database extends \IPS\File
|
||||
* @param array $settings Configuration settings
|
||||
* @return string
|
||||
*/
|
||||
public static function displayName( $settings )
|
||||
public static function displayName( array $settings ) : string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('filehandler__Database');
|
||||
return Member::loggedIn()->language()->addToStack('filehandler__Database');
|
||||
}
|
||||
|
||||
/* !File Handling */
|
||||
@@ -63,14 +73,14 @@ class _Database extends \IPS\File
|
||||
/**
|
||||
* Is this URL valid for this engine?
|
||||
*
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param Url $url URL
|
||||
* @param array $configuration Specific configuration for this method
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidUrl( $url, $configuration )
|
||||
public static function isValidUrl( Url $url, array $configuration ) : bool
|
||||
{
|
||||
$check = \IPS\Http\Url::internal( "applications/core/interface/file/index.php", 'none', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE );
|
||||
if ( mb_substr( $url, 0, mb_strlen( $check ) ) === $check )
|
||||
$check = Url::internal( "applications/core/interface/file/index.php", 'none', NULL, array(), Url::PROTOCOL_RELATIVE );
|
||||
if ( mb_substr( (string)$url, 0, mb_strlen( $check ) ) === $check )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -84,7 +94,7 @@ class _Database extends \IPS\File
|
||||
* @param array $configuration Storage configuration
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $configuration )
|
||||
public function __construct( array $configuration )
|
||||
{
|
||||
$this->container = 'monthly_' . date( 'Y' ) . '_' . date( 'm' );
|
||||
parent::__construct( $configuration );
|
||||
@@ -95,9 +105,9 @@ class _Database extends \IPS\File
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl()
|
||||
public function baseUrl() : string
|
||||
{
|
||||
return (string) \IPS\Http\Url::internal( "applications/core/interface/file/index.php?file=", 'none', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE );
|
||||
return (string) Url::internal( "applications/core/interface/file/index.php?file=", 'none', NULL, array(), Url::PROTOCOL_RELATIVE );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,9 +116,9 @@ class _Database extends \IPS\File
|
||||
* @param string $filename The filename
|
||||
* @return string
|
||||
*/
|
||||
public function encodeFileUrl( $filename )
|
||||
public function encodeFileUrl( string $filename ) : string
|
||||
{
|
||||
return \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_QUERY_VALUE, $filename );
|
||||
return Url::encodeComponent( Url::COMPONENT_QUERY_VALUE, $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +126,7 @@ class _Database extends \IPS\File
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load()
|
||||
public function load() : void
|
||||
{
|
||||
parent::load();
|
||||
|
||||
@@ -130,15 +140,15 @@ class _Database extends \IPS\File
|
||||
{
|
||||
try
|
||||
{
|
||||
$record = \IPS\Db::i()->select( '*', 'core_files', array( 'id=? AND salt=?', $this->url->queryString['id'], $this->url->queryString['salt'] ) )->first();
|
||||
$record = Db::i()->select( '*', 'core_files', array( 'id=? AND salt=?', $this->url->queryString['id'], $this->url->queryString['salt'] ) )->first();
|
||||
$this->contents = $record['contents'];
|
||||
$this->filename = $record['filename'];
|
||||
$this->originalFilename = $this->unObscureFilename( $this->filename );
|
||||
$this->container = $record['container'];
|
||||
}
|
||||
catch( \UnderflowException $ex )
|
||||
catch( UnderflowException $ex )
|
||||
{
|
||||
throw new \IPS\File\Exception( $this->container . '/' . $this->filename, \IPS\File\Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
throw new Exception( $this->container . '/' . $this->filename, Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,18 +159,18 @@ class _Database extends \IPS\File
|
||||
* @param bool $refresh If TRUE, will fetch again
|
||||
* @return string
|
||||
*/
|
||||
public function contents( $refresh=FALSE )
|
||||
public function contents( bool $refresh=FALSE ) : string
|
||||
{
|
||||
if ( $this->contents === NULL or $refresh === TRUE )
|
||||
{
|
||||
try
|
||||
{
|
||||
$record = \IPS\Db::i()->select( '*', 'core_files', array( 'filename=? AND container=?', $this->filename, $this->container ) )->first();
|
||||
$record = Db::i()->select( '*', 'core_files', array( 'filename=? AND container=?', $this->filename, $this->container ) )->first();
|
||||
$this->contents = $record['contents'];
|
||||
}
|
||||
catch( \UnderflowException $ex )
|
||||
catch( UnderflowException $ex )
|
||||
{
|
||||
throw new \IPS\File\Exception( $this->container . '/' . $this->filename, \IPS\File\Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
throw new Exception( $this->container . '/' . $this->filename, Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
return $this->contents;
|
||||
@@ -172,10 +182,10 @@ class _Database extends \IPS\File
|
||||
* @param string $contents New contents
|
||||
* @return void
|
||||
*/
|
||||
public function replace( $contents )
|
||||
public function replace( string $contents ) : void
|
||||
{
|
||||
/* Ensure any existing files with this name are removed otherwise the wrong file may be selected from the database as it will store multiple copies when replace() is used */
|
||||
\IPS\Db::i()->delete( 'core_files', array( 'filename=? and container=?', $this->filename, $this->container ) );
|
||||
Db::i()->delete( 'core_files', array( 'filename=? and container=?', $this->filename, $this->container ) );
|
||||
|
||||
parent::replace( $contents );
|
||||
}
|
||||
@@ -185,18 +195,18 @@ class _Database extends \IPS\File
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
public function save() : void
|
||||
{
|
||||
$salt = md5( mt_rand() );
|
||||
|
||||
$id = \IPS\Db::i()->insert( 'core_files', array(
|
||||
$id = Db::i()->insert( 'core_files', array(
|
||||
'filename' => $this->filename,
|
||||
'salt' => $salt,
|
||||
'contents' => $this->contents(),
|
||||
'container' => $this->container
|
||||
) );
|
||||
|
||||
$this->url = \IPS\Http\Url::internal( "applications/core/interface/file/index.php?file={$this->container}/{$this->filename}", 'none', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE );
|
||||
$this->url = Url::internal( "applications/core/interface/file/index.php?file={$this->container}/{$this->filename}", 'none', NULL, array(), Url::PROTOCOL_RELATIVE );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,15 +214,15 @@ class _Database extends \IPS\File
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
public function delete() : void
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_files', array( 'filename=? AND container=?', $this->filename, $this->container ) );
|
||||
Db::i()->delete( 'core_files', array( 'filename=? AND container=?', $this->filename, $this->container ) );
|
||||
|
||||
/* Log deletion request */
|
||||
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
|
||||
$debug = array_map( function( $row ) {
|
||||
return array_filter( $row, function( $key ) {
|
||||
return \in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
return in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
}, ARRAY_FILTER_USE_KEY );
|
||||
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
|
||||
$this->log( "file_deletion", 'delete', $debug, 'log' );
|
||||
@@ -224,9 +234,9 @@ class _Database extends \IPS\File
|
||||
* @param string $container Key
|
||||
* @return void
|
||||
*/
|
||||
public function deleteContainer( $container )
|
||||
public function deleteContainer( string $container ) : void
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_files', array( 'container=?', $container ) );
|
||||
Db::i()->delete( 'core_files', array( 'container=?', $container ) );
|
||||
|
||||
/* Log deletion request */
|
||||
$realContainer = $this->container;
|
||||
@@ -242,7 +252,7 @@ class _Database extends \IPS\File
|
||||
* @param array $engines All file storage engine extension objects
|
||||
* @return array
|
||||
*/
|
||||
public function removeOrphanedFiles( $fileIndex, $engines )
|
||||
public function removeOrphanedFiles( int $fileIndex, array $engines ) : array
|
||||
{
|
||||
/* Start off our results array */
|
||||
$results = array(
|
||||
@@ -254,7 +264,7 @@ class _Database extends \IPS\File
|
||||
$checked = 0;
|
||||
|
||||
/* Loop over files */
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_files', array(), 'id ASC', array( $fileIndex, 100 ) ) as $file )
|
||||
foreach( Db::i()->select( '*', 'core_files', array(), 'id ASC', array( $fileIndex, 100 ) ) as $file )
|
||||
{
|
||||
$checked++;
|
||||
|
||||
|
||||
+16
-11
@@ -11,16 +11,21 @@
|
||||
namespace IPS\File;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Exception Class
|
||||
*/
|
||||
class _Exception extends \RuntimeException
|
||||
class Exception extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @brief Cannot open the file
|
||||
@@ -60,22 +65,22 @@ class _Exception extends \RuntimeException
|
||||
/**
|
||||
* @brief File path
|
||||
*/
|
||||
public $filepath = NULL;
|
||||
public ?string $filepath = NULL;
|
||||
|
||||
/**
|
||||
* @brief Original filename (used for friendlier errors)
|
||||
*/
|
||||
public $originalFilename = NULL;
|
||||
public ?string $originalFilename = NULL;
|
||||
|
||||
/**
|
||||
* @brief Additional error information
|
||||
*/
|
||||
public $errorMessage = NULL;
|
||||
public ?string $errorMessage = NULL;
|
||||
|
||||
/**
|
||||
* @brief Additional log information
|
||||
*/
|
||||
public $extraLog = NULL;
|
||||
public ?string $extraLog = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -87,7 +92,7 @@ class _Exception extends \RuntimeException
|
||||
* @param string|null $extraLog Error information to log
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $file, $error, $originalFilename = NULL, $errorMessage = NULL, $extraLog = NULL )
|
||||
public function __construct( string $file, int $error, ?string $originalFilename = NULL, ?string $errorMessage = NULL, ?string $extraLog = NULL )
|
||||
{
|
||||
/* Store the file */
|
||||
$this->filepath = $file;
|
||||
@@ -97,7 +102,7 @@ class _Exception extends \RuntimeException
|
||||
$this->errorMessage = $errorMessage;
|
||||
$this->extraLog = $extraLog;
|
||||
|
||||
$message = array_flip( ( new \ReflectionClass( __CLASS__ ) )->getConstants() )[ $error ];
|
||||
$message = array_flip( ( new ReflectionClass( __CLASS__ ) )->getConstants() )[ $error ];
|
||||
|
||||
parent::__construct( $message, $error );
|
||||
}
|
||||
@@ -107,7 +112,7 @@ class _Exception extends \RuntimeException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extraErrorMessage()
|
||||
public function extraErrorMessage() : string
|
||||
{
|
||||
return (string) $this->errorMessage;
|
||||
}
|
||||
@@ -117,7 +122,7 @@ class _Exception extends \RuntimeException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extraLogData()
|
||||
public function extraLogData() : string
|
||||
{
|
||||
return (string) $this->extraLog;
|
||||
}
|
||||
|
||||
+315
-284
File diff suppressed because it is too large.
Load diff
+106
-91
@@ -11,16 +11,41 @@
|
||||
namespace IPS\File;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DirectoryIterator;
|
||||
use DomainException;
|
||||
use FilesystemIterator;
|
||||
use InvalidArgumentException;
|
||||
use IPS\File;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Member;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use LogicException;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RuntimeException;
|
||||
use function copy;
|
||||
use function defined;
|
||||
use function file_put_contents;
|
||||
use function function_exists;
|
||||
use function fwrite;
|
||||
use function in_array;
|
||||
use function rename;
|
||||
use const IPS\IPS_FILE_PERMISSION;
|
||||
use const IPS\IPS_FOLDER_PERMISSION;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Handler: File System
|
||||
*/
|
||||
class _FileSystem extends \IPS\File
|
||||
class FileSystem extends File
|
||||
{
|
||||
/* !ACP Configuration */
|
||||
|
||||
@@ -30,7 +55,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param array $configuration Configuration if editing a setting, or array() if creating a setting.
|
||||
* @return array
|
||||
*/
|
||||
public static function settings( $configuration=array() )
|
||||
public static function settings( array $configuration=array() ) : array
|
||||
{
|
||||
$default = ( isset( $configuration['custom_url'] ) and ! empty( $configuration['custom_url'] ) ) ? TRUE : FALSE;
|
||||
|
||||
@@ -48,9 +73,9 @@ class _FileSystem extends \IPS\File
|
||||
*
|
||||
* @param array $values The submitted values
|
||||
* @return void
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public static function testSettings( &$values )
|
||||
public static function testSettings( array &$values ) : void
|
||||
{
|
||||
$values['dir'] = rtrim( $values['dir'], '\\/' );
|
||||
$testDir = str_replace( '{root}', \IPS\ROOT_PATH, $values['dir'] );
|
||||
@@ -63,15 +88,15 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
if ( !$testDir )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'dir_not_provided', FALSE ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'dir_not_provided', FALSE ) );
|
||||
}
|
||||
if ( !is_dir( $testDir ) )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'dir_does_not_exist', FALSE, array( 'sprintf' => array( $testDir ) ) ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'dir_does_not_exist', FALSE, array( 'sprintf' => array( $testDir ) ) ) );
|
||||
}
|
||||
if ( !is_writable( $testDir ) )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'dir_is_not_writable', FALSE, array( 'sprintf' => array( $testDir ) ) ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'dir_is_not_writable', FALSE, array( 'sprintf' => array( $testDir ) ) ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $values['custom_url'] ) )
|
||||
@@ -90,7 +115,7 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
if ( filter_var( $test, FILTER_VALIDATE_URL ) === false )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'url_is_not_real', FALSE, array( 'sprintf' => array( $values['custom_url'] ) ) ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'url_is_not_real', FALSE, array( 'sprintf' => array( $values['custom_url'] ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +127,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param array $oldConfiguration Existing Storage Configuration
|
||||
* @return boolean
|
||||
*/
|
||||
public static function moveCheck( $configuration, $oldConfiguration )
|
||||
public static function moveCheck( array $configuration, array $oldConfiguration ) : bool
|
||||
{
|
||||
if ( str_replace( '{root}', \IPS\ROOT_PATH, preg_replace( '#/{1,}/#', '/', rtrim( $configuration['dir'], '/' ) ) ) !== str_replace( '{root}', \IPS\ROOT_PATH, preg_replace( '#/{1,}/#', '/', rtrim( $oldConfiguration['dir'], '/' ) ) ) )
|
||||
{
|
||||
@@ -118,9 +143,9 @@ class _FileSystem extends \IPS\File
|
||||
* @param array $settings Configuration settings
|
||||
* @return string
|
||||
*/
|
||||
public static function displayName( $settings )
|
||||
public static function displayName( array $settings ) : string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'filehandler_display_name', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('filehandler__FileSystem'), str_replace( '{root}', \IPS\ROOT_PATH, $settings['dir'] ) ) ) );
|
||||
return Member::loggedIn()->language()->addToStack( 'filehandler_display_name', FALSE, array( 'sprintf' => array( Member::loggedIn()->language()->addToStack('filehandler__FileSystem'), str_replace( '{root}', \IPS\ROOT_PATH, $settings['dir'] ) ) ) );
|
||||
}
|
||||
|
||||
/* !File Handling */
|
||||
@@ -128,7 +153,7 @@ class _FileSystem extends \IPS\File
|
||||
/**
|
||||
* @brief Does this storage method support chunked uploads?
|
||||
*/
|
||||
public static $supportsChunking = TRUE;
|
||||
public static bool $supportsChunking = TRUE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -136,7 +161,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param array $configuration Storage configuration
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $configuration )
|
||||
public function __construct( array $configuration )
|
||||
{
|
||||
$this->container = 'monthly_' . date( 'Y' ) . '_' . date( 'm' );
|
||||
$configuration['dir'] = str_replace( '{root}', \IPS\ROOT_PATH, $configuration['dir'] );
|
||||
@@ -147,7 +172,7 @@ class _FileSystem extends \IPS\File
|
||||
/**
|
||||
* @brief Store the path to the file so we can just move it later
|
||||
*/
|
||||
protected $temporaryFilePath = NULL;
|
||||
protected ?string $temporaryFilePath = NULL;
|
||||
|
||||
/**
|
||||
* Set the file
|
||||
@@ -155,7 +180,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param string $filepath The path to the file on disk
|
||||
* @return void
|
||||
*/
|
||||
public function setFile( $filepath )
|
||||
public function setFile( string $filepath ) : void
|
||||
{
|
||||
$this->temporaryFilePath = $filepath;
|
||||
}
|
||||
@@ -165,10 +190,10 @@ class _FileSystem extends \IPS\File
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl()
|
||||
public function baseUrl() : string
|
||||
{
|
||||
$url = ( empty( $this->configuration['custom_url'] ) ) ? rtrim( \IPS\Settings::i()->base_url, '/' ) . '/' . ltrim( $this->configuration['url'], '/' ) : $this->configuration['custom_url'];
|
||||
if ( \IPS\Request::i()->isSecure() )
|
||||
$url = ( empty( $this->configuration['custom_url'] ) ) ? rtrim( Settings::i()->base_url, '/' ) . '/' . ltrim( $this->configuration['url'], '/' ) : $this->configuration['custom_url'];
|
||||
if ( Request::i()->isSecure() )
|
||||
{
|
||||
$url = str_replace( 'http://', 'https://', $url );
|
||||
}
|
||||
@@ -180,9 +205,9 @@ class _FileSystem extends \IPS\File
|
||||
*
|
||||
* @param int $storageConfiguration New storage configuration ID
|
||||
* @param int $flags Bitwise Flags
|
||||
* @return \IPS\File
|
||||
* @return File
|
||||
*/
|
||||
public function move( $storageConfiguration, $flags=0 )
|
||||
public function move( int $storageConfiguration, int $flags=0 ) : File
|
||||
{
|
||||
if ( $this->configurationId === $storageConfiguration and isset( $this->configuration['old_url'] ) )
|
||||
{
|
||||
@@ -198,7 +223,7 @@ class _FileSystem extends \IPS\File
|
||||
if ( mb_substr( $this->url, 0, mb_strlen( $this->configuration['url'] ) ) != $this->configuration['url'] )
|
||||
{
|
||||
/* No? Something has gone wrong */
|
||||
throw new \RuntimeException('url_update_incorrect_url');
|
||||
throw new RuntimeException('url_update_incorrect_url');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -228,7 +253,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param int|null $throttle Throttle speed
|
||||
* @return void
|
||||
*/
|
||||
public function printFile( $start=NULL, $length=NULL, $throttle=NULL )
|
||||
public function printFile( ?int $start=NULL, ?int $length=NULL, ?int $throttle=NULL ) : void
|
||||
{
|
||||
$file = $this->configuration['dir'] . '/' . $this->container . '/' . $this->filename;
|
||||
|
||||
@@ -240,9 +265,9 @@ class _FileSystem extends \IPS\File
|
||||
*
|
||||
* @param bool $refresh If TRUE, will fetch again
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function contents( $refresh=FALSE )
|
||||
public function contents( bool $refresh=FALSE ) : string
|
||||
{
|
||||
if ( $this->contents === NULL or $refresh === TRUE )
|
||||
{
|
||||
@@ -252,12 +277,12 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
if ( $this->contents === FALSE )
|
||||
{
|
||||
throw new \IPS\File\Exception( $this->container . '/' . $this->filename, \IPS\File\Exception::CANNOT_OPEN, $this->originalFilename );
|
||||
throw new Exception( $this->container . '/' . $this->filename, Exception::CANNOT_OPEN, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\File\Exception( $this->container . '/' . $this->filename, \IPS\File\Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
throw new Exception( $this->container . '/' . $this->filename, Exception::DOES_NOT_EXIST, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,22 +293,22 @@ class _FileSystem extends \IPS\File
|
||||
* If the file is an image, get the dimensions
|
||||
*
|
||||
* @return array
|
||||
* @throws \DomainException
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws DomainException
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getImageDimensions()
|
||||
public function getImageDimensions() : array
|
||||
{
|
||||
if( !$this->isImage() )
|
||||
{
|
||||
throw new \DomainException;
|
||||
throw new DomainException;
|
||||
}
|
||||
|
||||
$file = $this->configuration['dir'] . '/' . $this->container . '/' . $this->filename;
|
||||
|
||||
if ( ! file_exists( $file ) )
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
throw new RuntimeException;
|
||||
}
|
||||
|
||||
if( ( $image = getimagesize( $file ) ) === FALSE )
|
||||
@@ -298,9 +323,9 @@ class _FileSystem extends \IPS\File
|
||||
* Save File
|
||||
*
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save()
|
||||
public function save() : void
|
||||
{
|
||||
/* Make the folder */
|
||||
$folder = $this->configuration['dir'] . '/' . $this->getFolder();
|
||||
@@ -310,64 +335,60 @@ class _FileSystem extends \IPS\File
|
||||
{
|
||||
if( static::$copyFiles === TRUE )
|
||||
{
|
||||
if( !@\copy( $this->temporaryFilePath, "{$folder}/{$this->filename}" ) )
|
||||
if( !@copy( $this->temporaryFilePath, "{$folder}/{$this->filename}" ) )
|
||||
{
|
||||
\IPS\Log::log( "Could not copy file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new \IPS\File\Exception( "{$folder}/{$this->filename}", \IPS\File\Exception::CANNOT_COPY, $this->originalFilename );
|
||||
Log::log( "Could not copy file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new Exception( "{$folder}/{$this->filename}", Exception::CANNOT_COPY, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( !@\rename( $this->temporaryFilePath, "{$folder}/{$this->filename}" ) )
|
||||
if( !@rename( $this->temporaryFilePath, "{$folder}/{$this->filename}" ) )
|
||||
{
|
||||
\IPS\Log::log( "Could not move file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new \IPS\File\Exception( "{$folder}/{$this->filename}", \IPS\File\Exception::CANNOT_MOVE, $this->originalFilename );
|
||||
Log::log( "Could not move file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new Exception( "{$folder}/{$this->filename}", Exception::CANNOT_MOVE, $this->originalFilename );
|
||||
}
|
||||
}
|
||||
|
||||
@chmod( "{$folder}/{$this->filename}", \IPS\IPS_FILE_PERMISSION );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $contents = $this->contents() )
|
||||
{
|
||||
if ( !@\file_put_contents( "{$folder}/{$this->filename}", $contents ) )
|
||||
if ( !@file_put_contents( "{$folder}/{$this->filename}", $contents ) )
|
||||
{
|
||||
\IPS\Log::log( "Could not write file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new \IPS\File\Exception( "{$folder}/{$this->filename}", \IPS\File\Exception::CANNOT_WRITE, $this->originalFilename );
|
||||
Log::log( "Could not write file {$folder}/{$this->filename}" , 'FileSystem' );
|
||||
throw new Exception( "{$folder}/{$this->filename}", Exception::CANNOT_WRITE, $this->originalFilename );
|
||||
}
|
||||
|
||||
@chmod( "{$folder}/{$this->filename}", \IPS\IPS_FILE_PERMISSION );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = touch( "{$folder}/{$this->filename}" );
|
||||
@chmod( "{$folder}/{$this->filename}", \IPS\IPS_FILE_PERMISSION );
|
||||
}
|
||||
}
|
||||
|
||||
@chmod( "{$folder}/{$this->filename}", IPS_FILE_PERMISSION );
|
||||
|
||||
/* Clear zend opcache if enabled */
|
||||
if ( \function_exists( 'opcache_invalidate' ) )
|
||||
if ( function_exists( 'opcache_invalidate' ) )
|
||||
{
|
||||
@opcache_invalidate( "{$folder}/{$this->filename}" );
|
||||
}
|
||||
|
||||
/* Set the URL */
|
||||
$this->url = \IPS\Http\Url::createFromString( $this->fullyQualifiedUrl( "{$this->container}/{$this->filename}" ), FALSE );
|
||||
$this->url = Url::createFromString( $this->fullyQualifiedUrl( "{$this->container}/{$this->filename}" ), FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete()
|
||||
public function delete() : void
|
||||
{
|
||||
/* Log deletion request */
|
||||
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
|
||||
$debug = array_map( function( $row ) {
|
||||
return array_filter( $row, function( $key ) {
|
||||
return \in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
return in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
}, ARRAY_FILTER_USE_KEY );
|
||||
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
|
||||
$this->log( "file_deletion", 'delete', $debug, 'log' );
|
||||
@@ -377,16 +398,10 @@ class _FileSystem extends \IPS\File
|
||||
$result = @unlink( "{$this->configuration['dir']}/{$this->container}/{$this->filename}" );
|
||||
|
||||
/* Clear zend opcache if enabled */
|
||||
if ( \function_exists( 'opcache_invalidate' ) )
|
||||
if ( function_exists( 'opcache_invalidate' ) )
|
||||
{
|
||||
@opcache_invalidate( "{$this->configuration['dir']}/{$this->container}/{$this->filename}" );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +412,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param bool $skipClear Skip clearing the opcache, used for recursive calls since the parent call will do it
|
||||
* @return void
|
||||
*/
|
||||
public function deleteContainer( $container, $skipClear = FALSE )
|
||||
public function deleteContainer( string $container, bool $skipClear = FALSE ) : void
|
||||
{
|
||||
$dir = $this->configuration['dir'] . '/' . $container;
|
||||
|
||||
@@ -406,7 +421,7 @@ class _FileSystem extends \IPS\File
|
||||
$files = array();
|
||||
$dirs = array();
|
||||
|
||||
foreach ( new \DirectoryIterator( $dir ) as $f )
|
||||
foreach ( new DirectoryIterator( $dir ) as $f )
|
||||
{
|
||||
if ( !$f->isDot() )
|
||||
{
|
||||
@@ -433,7 +448,7 @@ class _FileSystem extends \IPS\File
|
||||
unlink( $file );
|
||||
|
||||
/* Clear zend opcache if enabled */
|
||||
if( $skipClear === FALSE and \function_exists( 'opcache_invalidate' ) )
|
||||
if( $skipClear === FALSE and function_exists( 'opcache_invalidate' ) )
|
||||
{
|
||||
@opcache_invalidate( $file );
|
||||
}
|
||||
@@ -456,10 +471,10 @@ class _FileSystem extends \IPS\File
|
||||
* @param string $filename The desired filename
|
||||
* @param string|null $container Key to identify container for storage
|
||||
* @param bool $obscure Controls if an md5 hash should be added to the filename
|
||||
* @return mixed A reference to be passed to chunkProcess() and chunkFinish()
|
||||
* @throws \RuntimeException
|
||||
* @return array A reference to be passed to chunkProcess() and chunkFinish()
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function chunkInit( $filename, $container = '', $obscure = TRUE )
|
||||
public function chunkInit( string $filename, ?string $container = '', bool $obscure = TRUE ) : array
|
||||
{
|
||||
/* If we don't have a chunk folder, create one */
|
||||
$tempFolder = $this->getFolder( 'chunks' );
|
||||
@@ -477,32 +492,32 @@ class _FileSystem extends \IPS\File
|
||||
/**
|
||||
* Append more contents in a chunked upload
|
||||
*
|
||||
* @param mixed $ref The reference for this upload as returned by chunkInit()
|
||||
* @param array $ref The reference for this upload as returned by chunkInit()
|
||||
* @param string $temporaryFileOrContents The contents to write, or the path to the temporary file on disk with the contents
|
||||
* @param int $chunkNumber Which chunk this is (0 for the first chunk, 1 for the second, etc)
|
||||
* @param bool $isContents If TRUE, $temporaryFileOrContents is treated as raw contents. If FALSE, is path to file
|
||||
* @return mixed Updated reference fore future chunkProcess() calls and chunkFinish()
|
||||
* @throws \RuntimeException
|
||||
* @return array Updated reference fore future chunkProcess() calls and chunkFinish()
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function chunkProcess( $ref, $temporaryFileOrContents, $chunkNumber, $isContents = FALSE )
|
||||
public function chunkProcess( array $ref, string $temporaryFileOrContents, int $chunkNumber, bool $isContents = FALSE ) : array
|
||||
{
|
||||
$file = fopen( $ref['temp'], 'ab' );
|
||||
if ( $isContents )
|
||||
{
|
||||
\fwrite( $file, $temporaryFileOrContents );
|
||||
fwrite( $file, $temporaryFileOrContents );
|
||||
}
|
||||
else
|
||||
{
|
||||
$_chunk = fopen( $temporaryFileOrContents, 'rb' );
|
||||
while ( $buffer = fread( $_chunk, 4096 ) )
|
||||
{
|
||||
\fwrite( $file, $buffer );
|
||||
fwrite( $file, $buffer );
|
||||
}
|
||||
@unlink( $temporaryFileOrContents );
|
||||
}
|
||||
fclose( $file );
|
||||
|
||||
if ( \function_exists( 'opcache_invalidate' ) )
|
||||
if ( function_exists( 'opcache_invalidate' ) )
|
||||
{
|
||||
@opcache_invalidate( $ref['temp'] );
|
||||
}
|
||||
@@ -515,14 +530,14 @@ class _FileSystem extends \IPS\File
|
||||
*
|
||||
* @param array $ref The reference for this upload as returned by chunkInit()
|
||||
* @param string $storageConfiguration Storage configuration name
|
||||
* @return \IPS\File The file object just created
|
||||
* @throws \RuntimeException
|
||||
* @return File The file object just created
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function chunkFinish( array $ref, string $storageConfiguration ): \IPS\File
|
||||
public function chunkFinish( array $ref, string $storageConfiguration ): File
|
||||
{
|
||||
rename( $ref['temp'], "{$this->configuration['dir']}/{$ref['final']}" );
|
||||
|
||||
$fileObj = \IPS\File::get( $storageConfiguration,$ref['final'] );
|
||||
$fileObj = File::get( $storageConfiguration,$ref['final'] );
|
||||
|
||||
/* This isn't preserved for chunk uploads, so we need to set it so apps can get the real value */
|
||||
if( !empty( $ref['original'] ) )
|
||||
@@ -541,17 +556,17 @@ class _FileSystem extends \IPS\File
|
||||
* @param string|null $folderName Folder name - if NULL, a monthly name will be used
|
||||
* @return string
|
||||
*/
|
||||
protected function getFolder( $folderName=NULL )
|
||||
protected function getFolder( ?string $folderName=NULL ) : string
|
||||
{
|
||||
$folderName = $folderName ?: $this->container;
|
||||
$folder = $this->configuration['dir'] . '/' . $folderName;
|
||||
if( !is_dir( $folder ) )
|
||||
{
|
||||
if( @mkdir( $folder, \IPS\IPS_FOLDER_PERMISSION, TRUE ) === FALSE or @chmod( $folder, \IPS\IPS_FOLDER_PERMISSION ) === FALSE )
|
||||
if( @mkdir( $folder, IPS_FOLDER_PERMISSION, TRUE ) === FALSE or @chmod( $folder, IPS_FOLDER_PERMISSION ) === FALSE )
|
||||
{
|
||||
throw new \IPS\File\Exception( $folder, \IPS\File\Exception::CANNOT_MAKE_DIR );
|
||||
throw new Exception( $folder, Exception::CANNOT_MAKE_DIR );
|
||||
}
|
||||
@\file_put_contents( $folder . '/index.html', '' );
|
||||
@file_put_contents( $folder . '/index.html', '' );
|
||||
}
|
||||
|
||||
return $folderName;
|
||||
@@ -564,7 +579,7 @@ class _FileSystem extends \IPS\File
|
||||
* @param array $engines All file storage engine extension objects
|
||||
* @return array
|
||||
*/
|
||||
public function removeOrphanedFiles( $fileIndex, $engines )
|
||||
public function removeOrphanedFiles( int $fileIndex, array $engines ) : array
|
||||
{
|
||||
/* Start off our results array */
|
||||
$results = array(
|
||||
@@ -581,12 +596,12 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
if ( is_dir( $dir ) )
|
||||
{
|
||||
$iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS ) );
|
||||
$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS ) );
|
||||
|
||||
foreach ( $iterator as $f )
|
||||
{
|
||||
/* We aren't checking directories */
|
||||
if( $f->isDir() OR $f->getFilename() == 'index.html' OR mb_substr( $f->getFilename(), 0, 1 ) === '.' OR mb_substr( $iterator->getSubPathname(), 0, 5 ) === 'logs/' )
|
||||
if( $f->isDir() OR $f->getFilename() == 'index.html' OR mb_substr( $f->getFilename(), 0, 1 ) === '.' OR mb_substr( $f->getSubPathname(), 0, 5 ) === 'logs/' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -612,19 +627,19 @@ class _FileSystem extends \IPS\File
|
||||
/* If this file is valid for the engine, skip to the next file */
|
||||
try
|
||||
{
|
||||
if( $engine->isValidFile( $iterator->getSubPathname() ) )
|
||||
if( $engine->isValidFile( $engine->getSubPathname() ) )
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
catch( \InvalidArgumentException $e )
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are still here, the file was not valid */
|
||||
$this->logOrphanedFile( $iterator->getSubPathname() );
|
||||
$this->logOrphanedFile( $f->getSubPathname() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,9 +657,9 @@ class _FileSystem extends \IPS\File
|
||||
/**
|
||||
* Get filesize (in bytes)
|
||||
*
|
||||
* @return string|bool
|
||||
* @return int|bool
|
||||
*/
|
||||
public function filesize()
|
||||
public function filesize() : int|bool
|
||||
{
|
||||
if( $this->_cachedFilesize !== NULL )
|
||||
{
|
||||
|
||||
+76
-56
@@ -11,16 +11,36 @@
|
||||
namespace IPS\File;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use BadFunctionCallException;
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use IPS\File;
|
||||
use IPS\Ftp as FtpClass;
|
||||
use IPS\Ftp\Exception;
|
||||
use IPS\Ftp\RecursiveDirectoryFtpIterator;
|
||||
use IPS\Ftp\Sftp as SftpClass;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use LogicException;
|
||||
use RecursiveIteratorIterator;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function file_put_contents;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use const IPS\TEMP_DIRECTORY;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Handler: FTP
|
||||
*/
|
||||
class _Ftp extends \IPS\File
|
||||
class Ftp extends File
|
||||
{
|
||||
/* !ACP Configuration */
|
||||
|
||||
@@ -30,7 +50,7 @@ class _Ftp extends \IPS\File
|
||||
* @param array $configuration Configuration if editing a setting, or array() if creating a setting.
|
||||
* @return array
|
||||
*/
|
||||
public static function settings( $configuration=array() )
|
||||
public static function settings( array $configuration=array() ) : array
|
||||
{
|
||||
return array(
|
||||
'ftp_details' => array( 'type' => 'Ftp', 'options' => array( 'validate' => FALSE ) ),
|
||||
@@ -43,47 +63,47 @@ class _Ftp extends \IPS\File
|
||||
*
|
||||
* @param array $values The submitted values
|
||||
* @return void
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public static function testSettings( &$values )
|
||||
public static function testSettings( array &$values ) : void
|
||||
{
|
||||
if ( !\function_exists( 'ftp_connect' ) )
|
||||
if ( !function_exists( 'ftp_connect' ) )
|
||||
{
|
||||
throw new \BadFunctionCallException( 'ftp_err_no_ext' );
|
||||
throw new BadFunctionCallException( 'ftp_err_no_ext' );
|
||||
}
|
||||
elseif ( $values['ftp_details']['protocol'] === 'ssl_ftp' and !\function_exists( 'ftp_ssl_connect' ) )
|
||||
elseif ( $values['ftp_details']['protocol'] === 'ssl_ftp' and !function_exists( 'ftp_ssl_connect' ) )
|
||||
{
|
||||
throw new \BadFunctionCallException( 'ftp_err_no_ssl' );
|
||||
throw new BadFunctionCallException( 'ftp_err_no_ssl' );
|
||||
}
|
||||
elseif ( $values['ftp_details']['protocol'] === 'sftp' and !\function_exists( 'ssh2_connect' ) )
|
||||
elseif ( $values['ftp_details']['protocol'] === 'sftp' and !function_exists( 'ssh2_connect' ) )
|
||||
{
|
||||
throw new \BadFunctionCallException( 'ftp_err_no_sftp' );
|
||||
throw new BadFunctionCallException( 'ftp_err_no_sftp' );
|
||||
}
|
||||
|
||||
$values['url'] = rtrim( $values['url'], '/' );
|
||||
|
||||
if ( filter_var( $values['url'], FILTER_VALIDATE_URL ) === false )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'url_is_not_real', FALSE, array( 'sprintf' => array( $values['url'] ) ) ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'url_is_not_real', FALSE, array( 'sprintf' => array( $values['url'] ) ) ) );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ( $values['ftp_details']['protocol'] == 'sftp' )
|
||||
{
|
||||
$ftp = new \IPS\Ftp\Sftp( $values['ftp_details']['server'], $values['ftp_details']['un'], $values['ftp_details']['pw'], $values['ftp_details']['port'] );
|
||||
$ftp = new SftpClass( $values['ftp_details']['server'], $values['ftp_details']['un'], $values['ftp_details']['pw'], $values['ftp_details']['port'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$ftp = new \IPS\Ftp( $values['ftp_details']['server'], $values['ftp_details']['un'], $values['ftp_details']['pw'], $values['ftp_details']['port'], ( $values['ftp_details']['protocol'] == 'ssl_ftp' ) );
|
||||
$ftp = new FtpClass( $values['ftp_details']['server'], $values['ftp_details']['un'], $values['ftp_details']['pw'], $values['ftp_details']['port'], ( $values['ftp_details']['protocol'] == 'ssl_ftp' ) );
|
||||
}
|
||||
$ftp->chdir( $values['ftp_details']['path'] );
|
||||
|
||||
$filename = md5( mt_rand() ) . '.ips.txt';
|
||||
$tmpFileName = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
|
||||
\file_put_contents( $tmpFileName, 'OK' );
|
||||
$tmpFileName = tempnam( TEMP_DIRECTORY, 'IPS' );
|
||||
file_put_contents( $tmpFileName, 'OK' );
|
||||
$ftp->upload( $filename, $tmpFileName );
|
||||
$result = \IPS\Http\Url::external( $values['url'] . '/' . $filename)->request()->get();
|
||||
$result = Url::external( $values['url'] . '/' . $filename)->request()->get();
|
||||
$ftp->delete( $filename );
|
||||
unlink( $tmpFileName );
|
||||
|
||||
@@ -91,17 +111,17 @@ class _Ftp extends \IPS\File
|
||||
{
|
||||
if ( $result->httpResponseCode == 200 )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'file_storage_test_ftp_unexpected_response', FALSE, array( 'sprintf' => array( $values['ftp_details']['server'] ) ) ) );
|
||||
throw new InvalidArgumentException( Member::loggedIn()->language()->addToStack( 'file_storage_test_ftp_unexpected_response', FALSE, array( 'sprintf' => array( $values['ftp_details']['server'] ) ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'file_storage_test_error_ftp', FALSE, array( 'sprintf' => array( $values['ftp_details']['server'], $result->httpResponseCode ) ) ) );
|
||||
throw new InvalidArgumentException( Member::loggedIn()->language()->addToStack( 'file_storage_test_error_ftp', FALSE, array( 'sprintf' => array( $values['ftp_details']['server'], $result->httpResponseCode ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Ftp\Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
throw new \DomainException( 'ftp_err-' . $e->getMessage() );
|
||||
throw new DomainException( 'ftp_err-' . $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +131,9 @@ class _Ftp extends \IPS\File
|
||||
* @param array $settings Configuration settings
|
||||
* @return string
|
||||
*/
|
||||
public static function displayName( $settings )
|
||||
public static function displayName( array $settings ) : string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'filehandler_display_name', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('filehandler__Ftp'), ( !empty( $settings['ftp_details']['path'] ) ) ? $settings['ftp_details']['server'] . " (" . $settings['ftp_details']['path'] . ")" : $settings['ftp_details']['server'] ) ) );
|
||||
return Member::loggedIn()->language()->addToStack( 'filehandler_display_name', FALSE, array( 'sprintf' => array( Member::loggedIn()->language()->addToStack('filehandler__Ftp'), ( !empty( $settings['ftp_details']['path'] ) ) ? $settings['ftp_details']['server'] . " (" . $settings['ftp_details']['path'] . ")" : $settings['ftp_details']['server'] ) ) );
|
||||
}
|
||||
|
||||
/* !File Handling */
|
||||
@@ -124,7 +144,7 @@ class _Ftp extends \IPS\File
|
||||
* @param array $configuration Storage configuration
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $configuration )
|
||||
public function __construct( array $configuration )
|
||||
{
|
||||
$this->container = 'monthly_' . date( 'm' ) . '_' . date( 'Y' );
|
||||
parent::__construct( $configuration );
|
||||
@@ -135,7 +155,7 @@ class _Ftp extends \IPS\File
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl()
|
||||
public function baseUrl() : string
|
||||
{
|
||||
return rtrim( $this->configuration['url'], '/' );
|
||||
}
|
||||
@@ -144,26 +164,26 @@ class _Ftp extends \IPS\File
|
||||
* Save File
|
||||
*
|
||||
* @return void
|
||||
* @throws \IPS\Ftp\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function save()
|
||||
public function save() : void
|
||||
{
|
||||
/* Get contents */
|
||||
$contents = $this->contents();
|
||||
|
||||
/* Create a temporary file */
|
||||
$tmpFileName = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
|
||||
\file_put_contents( $tmpFileName, $contents );
|
||||
$tmpFileName = tempnam( TEMP_DIRECTORY, 'IPS' );
|
||||
file_put_contents( $tmpFileName, $contents );
|
||||
|
||||
/* Move into the correct folder */
|
||||
if( !\in_array( $this->container, $this->ftp()->ls() ) )
|
||||
if( !in_array( $this->container, $this->ftp()->ls() ) )
|
||||
{
|
||||
/* Make dir */
|
||||
$this->ftp()->mkdir( $this->container );
|
||||
|
||||
/* Stick an index.html file in */
|
||||
$tmpIndexFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
|
||||
\file_put_contents( $tmpIndexFile, '' );
|
||||
$tmpIndexFile = tempnam( TEMP_DIRECTORY, 'IPS' );
|
||||
file_put_contents( $tmpIndexFile, '' );
|
||||
$this->ftp()->upload( 'index.html', $tmpIndexFile );
|
||||
|
||||
unlink( $tmpIndexFile );
|
||||
@@ -180,16 +200,16 @@ class _Ftp extends \IPS\File
|
||||
unlink( $tmpFileName );
|
||||
|
||||
/* Set the URL */
|
||||
$this->url = \IPS\Http\Url::createFromString( $this->fullyQualifiedUrl( "{$this->container}/{$this->filename}" ), FALSE );
|
||||
$this->url = Url::createFromString( $this->fullyQualifiedUrl( "{$this->container}/{$this->filename}" ), FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* @return void
|
||||
* @throws \IPS\Ftp\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete()
|
||||
public function delete() : void
|
||||
{
|
||||
/* Only issue delete command if the file (and container) exist...otherwise a delete command would fail but our intention is complete */
|
||||
if( $this->checkExists( $this->container . '/' . $this->filename ) )
|
||||
@@ -201,7 +221,7 @@ class _Ftp extends \IPS\File
|
||||
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
|
||||
$debug = array_map( function( $row ) {
|
||||
return array_filter( $row, function( $key ) {
|
||||
return \in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
return in_array( $key, array( 'class', 'function', 'line' ) );
|
||||
}, ARRAY_FILTER_USE_KEY );
|
||||
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
|
||||
$this->log( "file_deletion", 'delete', $debug, 'log' );
|
||||
@@ -212,14 +232,14 @@ class _Ftp extends \IPS\File
|
||||
*
|
||||
* @param string $container Key
|
||||
* @return void
|
||||
* @throws \IPS\Ftp\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteContainer( $container )
|
||||
public function deleteContainer( string $container ) : void
|
||||
{
|
||||
/* Actually remove, checking if it exists before attempting */
|
||||
if( $this->checkExists( $container ) )
|
||||
{
|
||||
$this->ftp()->rmdir( $container, TRUE );
|
||||
$this->ftp()->rmdir($container, TRUE);
|
||||
}
|
||||
|
||||
/* Log deletion request */
|
||||
@@ -235,15 +255,15 @@ class _Ftp extends \IPS\File
|
||||
* @param string $path Path to check
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkExists( $path )
|
||||
protected function checkExists( string $path ) : bool
|
||||
{
|
||||
$pathBits = explode( '/', $path );
|
||||
$reconstructed = array();
|
||||
|
||||
foreach( $pathBits as $piece )
|
||||
{
|
||||
$pathToCheck = \count( $reconstructed ) ? implode( '/', $reconstructed ) : '.';
|
||||
if( $this->ftp()->ls( $pathToCheck ) === FALSE OR !\in_array( $piece, $this->ftp()->ls( $pathToCheck ) ) )
|
||||
$pathToCheck = count( $reconstructed ) ? implode( '/', $reconstructed ) : '.';
|
||||
if( $this->ftp()->ls( $pathToCheck ) === FALSE OR !in_array( $piece, $this->ftp()->ls( $pathToCheck ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -259,15 +279,15 @@ class _Ftp extends \IPS\File
|
||||
/**
|
||||
* @brief FTP Connection
|
||||
*/
|
||||
protected static $ftp = array();
|
||||
protected static array $ftp = array();
|
||||
|
||||
/**
|
||||
* Get FTP Connection
|
||||
*
|
||||
* @return \IPS\Ftp
|
||||
* @throws \IPS\Ftp\Exception
|
||||
* @return FtpClass
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function ftp()
|
||||
protected function ftp() : FtpClass
|
||||
{
|
||||
$key = md5( json_encode( $this->configuration ) );
|
||||
|
||||
@@ -275,11 +295,11 @@ class _Ftp extends \IPS\File
|
||||
{
|
||||
if ( $this->configuration['ftp_details']['protocol'] == 'sftp' )
|
||||
{
|
||||
static::$ftp[ $key ] = new \IPS\Ftp\Sftp( $this->configuration['ftp_details']['server'], $this->configuration['ftp_details']['un'], $this->configuration['ftp_details']['pw'], $this->configuration['ftp_details']['port'] );
|
||||
static::$ftp[ $key ] = new SftpClass( $this->configuration['ftp_details']['server'], $this->configuration['ftp_details']['un'], $this->configuration['ftp_details']['pw'], $this->configuration['ftp_details']['port'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
static::$ftp[ $key ] = new \IPS\Ftp( $this->configuration['ftp_details']['server'], $this->configuration['ftp_details']['un'], $this->configuration['ftp_details']['pw'], $this->configuration['ftp_details']['port'], ( $this->configuration['ftp_details']['protocol'] == 'ssl_ftp' ) );
|
||||
static::$ftp[ $key ] = new FtpClass( $this->configuration['ftp_details']['server'], $this->configuration['ftp_details']['un'], $this->configuration['ftp_details']['pw'], $this->configuration['ftp_details']['port'], ( $this->configuration['ftp_details']['protocol'] == 'ssl_ftp' ) );
|
||||
}
|
||||
|
||||
static::$ftp[ $key ]->chdir( $this->configuration['ftp_details']['path'] );
|
||||
@@ -294,7 +314,7 @@ class _Ftp extends \IPS\File
|
||||
* @param array $engines All file storage engine extension objects
|
||||
* @return array
|
||||
*/
|
||||
public function removeOrphanedFiles( $fileIndex, $engines )
|
||||
public function removeOrphanedFiles( int $fileIndex, array $engines ) : array
|
||||
{
|
||||
/* Start off our results array */
|
||||
$results = array(
|
||||
@@ -306,7 +326,7 @@ class _Ftp extends \IPS\File
|
||||
$checked = 0;
|
||||
$skipped = 0;
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator( new \IPS\Ftp\RecursiveDirectoryFtpIterator( $this->ftp(), $this->configuration['ftp_details']['path'] ) );
|
||||
$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryFtpIterator( $this->ftp(), $this->configuration['ftp_details']['path'] ) );
|
||||
|
||||
while( $iterator->valid() )
|
||||
{
|
||||
@@ -366,9 +386,9 @@ class _Ftp extends \IPS\File
|
||||
/**
|
||||
* Get filesize (in bytes)
|
||||
*
|
||||
* @return string|bool
|
||||
* @return int|bool
|
||||
*/
|
||||
public function filesize()
|
||||
public function filesize() : int|bool
|
||||
{
|
||||
if( $this->_cachedFilesize !== null )
|
||||
{
|
||||
@@ -390,7 +410,7 @@ class _Ftp extends \IPS\File
|
||||
|
||||
$this->_cachedFilesize = $size ?: parent::filesize();
|
||||
}
|
||||
catch( \IPS\Ftp\Exception $e )
|
||||
catch( Exception $e )
|
||||
{
|
||||
$this->_cachedFilesize = FALSE;
|
||||
}
|
||||
@@ -406,10 +426,10 @@ class _Ftp extends \IPS\File
|
||||
* @param int|null $throttle Throttle speed
|
||||
* @return void
|
||||
*/
|
||||
public function printFile( $start=NULL, $length=NULL, $throttle=NULL )
|
||||
public function printFile( ?int $start=NULL, ?int $length=NULL, ?int $throttle=NULL ) : void
|
||||
{
|
||||
/* Download the file locally */
|
||||
$file = $this->ftp()->download( $this->configuration['ftp_details']['path'] . ( $this->container ? '/' . $this->container : '' ) . '/' . $this->filename, NULL, TRUE );
|
||||
$file = $this->ftp()->download($this->configuration['ftp_details']['path'] . ($this->container ? '/' . $this->container : '') . '/' . $this->filename, NULL, TRUE);
|
||||
|
||||
/* Send the file */
|
||||
$this->sendFile( $file, $start, $length, $throttle );
|
||||
|
||||
+29
-19
@@ -11,41 +11,51 @@
|
||||
namespace IPS\File;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use Countable;
|
||||
use Exception;
|
||||
use IPS\File;
|
||||
use IteratorIterator;
|
||||
use Traversable;
|
||||
use function defined;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File IteratorIterator
|
||||
*/
|
||||
class _Iterator extends \IteratorIterator implements \Countable
|
||||
class Iterator extends IteratorIterator implements Countable
|
||||
{
|
||||
/**
|
||||
* @brief Stroage Extension
|
||||
*/
|
||||
protected $storageExtension;
|
||||
protected string|null $storageExtension = null;
|
||||
|
||||
/**
|
||||
* @brief URL Field
|
||||
*/
|
||||
protected $urlField;
|
||||
protected $urlField = null;
|
||||
|
||||
/**
|
||||
* @brief URLs Only
|
||||
*/
|
||||
protected $fileUrlsOnly;
|
||||
protected bool $fileUrlsOnly = false;
|
||||
|
||||
/**
|
||||
* @brief Used to restore 'real' names when filenames cleaned (eg adh1029_file.php back to fi^le.php)
|
||||
*/
|
||||
protected $replaceNameField;
|
||||
protected ?string $replaceNameField = null;
|
||||
|
||||
/**
|
||||
* @brief Used to pre-cache the filesize value if available
|
||||
*/
|
||||
protected $fileSizeField;
|
||||
protected ?string $fileSizeField = null;
|
||||
|
||||
|
||||
/**
|
||||
@@ -53,28 +63,28 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
*
|
||||
* @param Traversable $iterator The iterator
|
||||
* @param string $storageExtension The storage extension
|
||||
* @param string|NULL $urlField If passed a string, will look for an element with that key in the data returned from the iterator
|
||||
* @param string|callable|NULL $urlField If passed a string, will look for an element with that key in the data returned from the iterator
|
||||
* @param bool $fileUrlsOnly Only return the file URL instead of the file object
|
||||
* @param string|NULL $replaceNameField If passed a string, it will replace the originalFilename with the data in the array. Used to restore 'real' names when filenames cleaned (eg adh1029_file.php back to fi^le.php)
|
||||
* @param string $fileSizeField Field to use to pre-cache the filesize
|
||||
* @param string|null $fileSizeField Field to use to pre-cache the filesize
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \Traversable $iterator, $storageExtension, $urlField=NULL, $fileUrlsOnly=FALSE, $replaceNameField=NULL, $fileSizeField=NULL )
|
||||
public function __construct( Traversable $iterator, string $storageExtension, string|callable|null $urlField=NULL, bool $fileUrlsOnly=FALSE, ?string $replaceNameField=NULL, ?string $fileSizeField=NULL )
|
||||
{
|
||||
$this->storageExtension = $storageExtension;
|
||||
$this->urlField = $urlField;
|
||||
$this->fileUrlsOnly = $fileUrlsOnly;
|
||||
$this->replaceNameField = $replaceNameField;
|
||||
$this->fileSizeField = $fileSizeField;
|
||||
return parent::__construct( $iterator );
|
||||
parent::__construct( $iterator );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current
|
||||
*
|
||||
* @return \IPS\File
|
||||
* @return File|string
|
||||
*/
|
||||
public function current()
|
||||
public function current() : File|string
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -83,7 +93,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
|
||||
if ( $this->urlField )
|
||||
{
|
||||
if ( !\is_string( $this->urlField ) and \is_callable( $this->urlField ) )
|
||||
if ( !is_string( $this->urlField ) and is_callable( $this->urlField ) )
|
||||
{
|
||||
$urlFieldCallback = $this->urlField;
|
||||
$urlField = $urlFieldCallback( $data );
|
||||
@@ -95,7 +105,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
}
|
||||
|
||||
$fileSize = ( $this->fileSizeField AND isset( $data[ $this->fileSizeField ] ) ) ? $data[ $this->fileSizeField ] : ( $this->fileSizeField === FALSE ? FALSE : NULL );
|
||||
$obj = \IPS\File::get( $this->storageExtension, $urlField ? $data[ $urlField ] : $data, $fileSize );
|
||||
$obj = File::get( $this->storageExtension, $urlField ? $data[ $urlField ] : $data, $fileSize );
|
||||
|
||||
if ( $this->replaceNameField and ! empty( $data[ $this->replaceNameField ] ) )
|
||||
{
|
||||
@@ -104,7 +114,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
|
||||
return ( $this->fileUrlsOnly ) ? (string) $obj->url : $obj;
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
$this->next();
|
||||
return $this->current();
|
||||
@@ -116,7 +126,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function data()
|
||||
public function data() : mixed
|
||||
{
|
||||
return parent::current();
|
||||
}
|
||||
@@ -126,7 +136,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
public function count(): int
|
||||
{
|
||||
return $this->getInnerIterator()->count();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user