forked from LiveCarta/LiveCartaWP
Changed source root directory
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Imagick;
|
||||
use WP_Error;
|
||||
use WP_Image_Editor_Imagick;
|
||||
|
||||
class Image_Editor_Imagick extends WP_Image_Editor_Imagick {
|
||||
|
||||
/**
|
||||
* @var ?Imagick
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @var ?array{width: int, height: int}
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected $remote_filename = null;
|
||||
|
||||
/**
|
||||
* Hold on to a reference of all temp local files.
|
||||
*
|
||||
* These are cleaned up on __destruct.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $temp_files_to_cleanup = [];
|
||||
|
||||
/**
|
||||
* Loads image from $this->file into new Imagick Object.
|
||||
*
|
||||
* @return true|WP_Error True if loaded; WP_Error on failure.
|
||||
*/
|
||||
public function load() {
|
||||
if ( $this->image instanceof Imagick ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->file !== null && $this->file !== '' && ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) {
|
||||
return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( $this->file === null || $this->file === '' || strpos( $this->file, $upload_dir['basedir'] ) !== 0 ) {
|
||||
return parent::load();
|
||||
}
|
||||
|
||||
$temp_filename = tempnam( get_temp_dir(), 's3-uploads' );
|
||||
$this->temp_files_to_cleanup[] = $temp_filename;
|
||||
|
||||
copy( $this->file, $temp_filename );
|
||||
$this->remote_filename = $this->file;
|
||||
$this->file = $temp_filename;
|
||||
|
||||
$result = parent::load();
|
||||
|
||||
$this->file = $this->remote_filename;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagick by default can't handle s3:// paths
|
||||
* for saving images. We have instead save it to a file file,
|
||||
* then copy it to the s3:// path as a workaround.
|
||||
*
|
||||
* @param Imagick $image
|
||||
* @param ?string $filename
|
||||
* @param ?string $mime_type
|
||||
* @return WP_Error|array{path: string, file: string, width: int, height: int, mime-type: string}
|
||||
*/
|
||||
protected function _save( $image, $filename = null, $mime_type = null ) {
|
||||
list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
|
||||
|
||||
if ( ! $filename ) {
|
||||
$filename = $this->generate_filename( null, null, $extension );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $filename, $upload_dir['basedir'] ) === 0 ) {
|
||||
$temp_filename = tempnam( get_temp_dir(), 's3-uploads' );
|
||||
} else {
|
||||
$temp_filename = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var WP_Error|array{path: string, file: string, width: int, height: int, mime-type: string}
|
||||
*/
|
||||
$parent_call = parent::_save( $image, $temp_filename !== false ? $temp_filename : $filename, $mime_type );
|
||||
|
||||
if ( is_wp_error( $parent_call ) ) {
|
||||
if ( $temp_filename !== false ) {
|
||||
unlink( $temp_filename );
|
||||
}
|
||||
|
||||
return $parent_call;
|
||||
} else {
|
||||
$save = $parent_call;
|
||||
}
|
||||
|
||||
$copy_result = copy( $save['path'], $filename );
|
||||
|
||||
unlink( $save['path'] );
|
||||
if ( $temp_filename !== false ) {
|
||||
unlink( $temp_filename );
|
||||
}
|
||||
|
||||
if ( ! $copy_result ) {
|
||||
return new WP_Error( 'unable-to-copy-to-s3', 'Unable to copy the temp image to S3' );
|
||||
}
|
||||
|
||||
$response = [
|
||||
'path' => $filename,
|
||||
'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
|
||||
'width' => $this->size['width'] ?? 0,
|
||||
'height' => $this->size['height'] ?? 0,
|
||||
'mime-type' => $mime_type,
|
||||
];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
array_map( 'unlink', $this->temp_files_to_cleanup );
|
||||
parent::__destruct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase
|
||||
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
|
||||
// phpcs:disable WordPress.NamingConventions.ValidHookName.NotLowercase
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.NotSnakeCase
|
||||
// phpcs:disable WordPress.WP.AlternativeFunctions
|
||||
|
||||
/**
|
||||
* Local stream wrapper that writes files to the upload dir
|
||||
*
|
||||
* This is for the most part taken from Drupal, with some modifications.
|
||||
*/
|
||||
class Local_Stream_Wrapper {
|
||||
/**
|
||||
* Stream context resource.
|
||||
*
|
||||
* @var ?resource
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* A generic resource handle.
|
||||
*
|
||||
* @var ?resource
|
||||
*/
|
||||
public $handle = null;
|
||||
|
||||
/**
|
||||
* Instance URI (stream).
|
||||
*
|
||||
* A stream is referenced as "scheme://target".
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* Gets the path that the wrapper is responsible for.
|
||||
*
|
||||
* @return string String specifying the path.
|
||||
*/
|
||||
static function getDirectoryPath() : string {
|
||||
$upload_dir = Plugin::get_instance()->get_original_upload_dir();
|
||||
return $upload_dir['basedir'] . '/s3';
|
||||
}
|
||||
|
||||
function setUri( string $uri ) : void {
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
function getUri() : string {
|
||||
return $this->uri ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local writable target of the resource within the stream.
|
||||
*
|
||||
* This function should be used in place of calls to realpath() or similar
|
||||
* functions when attempting to determine the location of a file. While
|
||||
* functions like realpath() may return the location of a read-only file, this
|
||||
* method may return a URI or path suitable for writing that is completely
|
||||
* separate from the URI used for reading.
|
||||
*
|
||||
* @param string $uri
|
||||
* Optional URI.
|
||||
*
|
||||
* @return string
|
||||
* Returns a string representing a location suitable for writing of a file,
|
||||
* or FALSE if unable to write to the file such as with read-only streams.
|
||||
*/
|
||||
protected function getTarget( $uri = null ) : string {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri ?? '';
|
||||
}
|
||||
|
||||
$parts = explode( '://', $uri, 2 );
|
||||
$target = $parts[1] ?? '';
|
||||
|
||||
// Remove erroneous leading or trailing, forward-slashes and backslashes.
|
||||
return trim( $target, '\/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mime type for URI
|
||||
*
|
||||
* @param string $uri
|
||||
* @param array{extensions?: string[], mimetypes: array<string,string>} $mapping
|
||||
* @return string
|
||||
*/
|
||||
static function getMimeType( string $uri, ?array $mapping = null ) : string {
|
||||
|
||||
$extension = '';
|
||||
$file_parts = explode( '.', basename( $uri ) );
|
||||
|
||||
// Remove the first part: a full filename should not match an extension.
|
||||
array_shift( $file_parts );
|
||||
|
||||
// Iterate over the file parts, trying to find a match.
|
||||
// For my.awesome.image.jpeg, we try:
|
||||
// - jpeg
|
||||
// - image.jpeg, and
|
||||
// - awesome.image.jpeg
|
||||
while ( $additional_part = array_pop( $file_parts ) ) {
|
||||
$extension = strtolower( $additional_part . ( $extension ? '.' . $extension : '' ) );
|
||||
if ( isset( $mapping['extensions'][ $extension ] ) ) {
|
||||
return $mapping['mimetypes'][ $mapping['extensions'][ $extension ] ];
|
||||
}
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function chmod( int $mode ) : bool {
|
||||
$output = @chmod( $this->getLocalPath(), $mode ); // // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
// We are modifying the underlying file here, so we have to clear the stat
|
||||
// cache so that PHP understands that URI has changed too.
|
||||
clearstatcache( true, $this->getLocalPath() );
|
||||
return $output;
|
||||
}
|
||||
|
||||
function realpath() : string {
|
||||
return $this->getLocalPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical absolute path of the URI, if possible.
|
||||
*
|
||||
* @param string $uri
|
||||
* (optional) The stream wrapper URI to be converted to a canonical
|
||||
* absolute path. This may point to a directory or another type of file.
|
||||
*
|
||||
* @return string
|
||||
* If $uri is not set, returns the canonical absolute path of the URI
|
||||
* previously. If $uri is set and valid for this class, returns its canonical absolute
|
||||
* path, as determined by the realpath() function. If $uri is set but not
|
||||
* valid, returns empty string.
|
||||
*/
|
||||
protected function getLocalPath( $uri = null ) : string {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri;
|
||||
}
|
||||
$path = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
$realpath = str_replace( '/', DIRECTORY_SEPARATOR, $path ); // ensure check against realpath passes on Windows machines
|
||||
|
||||
$directory = realpath( $this->getDirectoryPath() );
|
||||
|
||||
if ( $directory === false || strpos( $realpath, $directory ) !== 0 ) {
|
||||
return '';
|
||||
}
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fopen(), file_get_contents(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the file to open.
|
||||
* @param string $mode
|
||||
* The file mode ("r", "wb" etc.).
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
|
||||
* @param string $opened_path
|
||||
* A string containing the path actually opened.
|
||||
* @param-out string $opened_path
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if file was opened successfully.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-open.php
|
||||
*/
|
||||
public function stream_open( $uri, $mode, $options, &$opened_path ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
$this->handle = ( $options & STREAM_REPORT_ERRORS ) ? fopen( $path, $mode ) : @fopen( $path, $mode );
|
||||
|
||||
if ( (bool) $this->handle && $options & STREAM_USE_PATH ) {
|
||||
$opened_path = $path;
|
||||
}
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for chmod(), chown(), etc.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE on success or FALSE on failure.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-metadata.php
|
||||
*/
|
||||
public function stream_metadata() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for flock().
|
||||
*
|
||||
* @param int $operation
|
||||
* One of the following:
|
||||
* - LOCK_SH to acquire a shared lock (reader).
|
||||
* - LOCK_EX to acquire an exclusive lock (writer).
|
||||
* - LOCK_UN to release a lock (shared or exclusive).
|
||||
* - LOCK_NB if you don't want flock() to block while locking (not
|
||||
* supported on Windows).
|
||||
*
|
||||
* @return bool
|
||||
* Always returns TRUE at the present time.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-lock.php
|
||||
*/
|
||||
public function stream_lock( $operation ) {
|
||||
if ( in_array( $operation, [ LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB ] ) && $this->handle ) {
|
||||
return flock( $this->handle, $operation );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fread(), file_get_contents() etc.
|
||||
*
|
||||
* @param int $count
|
||||
* Maximum number of bytes to be read.
|
||||
*
|
||||
* @return string|bool
|
||||
* The string that was read, or FALSE in case of an error.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-read.php
|
||||
*/
|
||||
public function stream_read( $count ) {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return fread( $this->handle, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fwrite(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $data
|
||||
* The string to be written.
|
||||
*
|
||||
* @return int
|
||||
* The number of bytes written.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-write.php
|
||||
*/
|
||||
public function stream_write( $data ) {
|
||||
if ( ! $this->handle ) {
|
||||
return 0;
|
||||
}
|
||||
return fwrite( $this->handle, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for feof().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if end-of-file has been reached.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-eof.php
|
||||
*/
|
||||
public function stream_eof() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return feof( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fseek().
|
||||
*
|
||||
* @param int $offset
|
||||
* The byte offset to got to.
|
||||
* @param int $whence
|
||||
* SEEK_SET, SEEK_CUR, or SEEK_END.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-seek.php
|
||||
*/
|
||||
public function stream_seek( $offset, $whence ) {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
// fseek returns 0 on success and -1 on a failure.
|
||||
// stream_seek 1 on success and 0 on a failure.
|
||||
return ! fseek( $this->handle, $offset, $whence );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fflush().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if data was successfully stored (or there was no data to store).
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-flush.php
|
||||
*/
|
||||
public function stream_flush() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
$result = fflush( $this->handle );
|
||||
|
||||
$params = [
|
||||
'Bucket' => S3_UPLOADS_BUCKET,
|
||||
'Key' => trim( str_replace( S3_UPLOADS_BUCKET, '', $this->getTarget() ), '/' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* Action when a new object has been uploaded to s3.
|
||||
*
|
||||
* @param array $params S3Client::putObject parameters.
|
||||
*/
|
||||
do_action( 's3_uploads_putObject', $params );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for ftell().
|
||||
*
|
||||
* @return false|int
|
||||
* The current offset in bytes from the beginning of file.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-tell.php
|
||||
*/
|
||||
public function stream_tell() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return ftell( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fstat().
|
||||
*
|
||||
* @return array|false
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-stat.php
|
||||
*/
|
||||
public function stream_stat() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return fstat( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fclose().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if stream was successfully closed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-close.php
|
||||
*/
|
||||
public function stream_close() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
$resource = $this->handle;
|
||||
return fclose( $resource );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying stream resource for stream_select().
|
||||
*
|
||||
* @param int $cast_as
|
||||
* Can be STREAM_CAST_FOR_SELECT or STREAM_CAST_AS_STREAM.
|
||||
*
|
||||
* @return resource|false
|
||||
* The underlying stream resource or FALSE if stream_select() is not
|
||||
* supported.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-cast.php
|
||||
*/
|
||||
public function stream_cast( $cast_as ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for unlink().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the resource to delete.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if resource was successfully deleted.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.unlink.php
|
||||
*/
|
||||
public function unlink( $uri ) {
|
||||
$this->uri = $uri;
|
||||
return unlink( $this->getLocalPath() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rename().
|
||||
*
|
||||
* @param string $from_uri,
|
||||
* The URI to the file to rename.
|
||||
* @param string $to_uri
|
||||
* The new URI for file.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if file was successfully renamed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rename.php
|
||||
*/
|
||||
public function rename( $from_uri, $to_uri ) {
|
||||
return rename( $this->getLocalPath( $from_uri ), $this->getLocalPath( $to_uri ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for mkdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to create.
|
||||
* @param int $mode
|
||||
* Permission flags - see mkdir().
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully created.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.mkdir.php
|
||||
*/
|
||||
public function mkdir( $uri, $mode, $options ) {
|
||||
$this->uri = $uri;
|
||||
$recursive = (bool) ( $options & STREAM_MKDIR_RECURSIVE );
|
||||
if ( $recursive ) {
|
||||
// $this->getLocalPath() fails if $uri has multiple levels of directories
|
||||
// that do not yet exist.
|
||||
$localpath = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
} else {
|
||||
$localpath = $this->getLocalPath( $uri );
|
||||
}
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return mkdir( $localpath, $mode, $recursive );
|
||||
} else {
|
||||
return @mkdir( $localpath, $mode, $recursive );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rmdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to delete.
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully removed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rmdir.php
|
||||
*/
|
||||
public function rmdir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return rmdir( $this->getLocalPath() );
|
||||
} else {
|
||||
return @rmdir( $this->getLocalPath() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for stat().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to get information about.
|
||||
* @param int $flags
|
||||
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
|
||||
*
|
||||
* @return array
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.url-stat.php
|
||||
*/
|
||||
public function url_stat( $uri, $flags ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
// Suppress warnings if requested or if the file or directory does not
|
||||
// exist. This is consistent with PHP's plain filesystem stream wrapper.
|
||||
if ( $flags & STREAM_URL_STAT_QUIET || ! file_exists( $path ) ) {
|
||||
return @stat( $path );
|
||||
} else {
|
||||
return stat( $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for opendir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to open.
|
||||
* @param int $options
|
||||
* Unknown (parameter is not documented in PHP Manual).
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-opendir.php
|
||||
*/
|
||||
public function dir_opendir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
$this->handle = opendir( $this->getLocalPath() );
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for readdir().
|
||||
*
|
||||
* @return string
|
||||
* The next filename, or FALSE if there are no more files in the directory.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-readdir.php
|
||||
*/
|
||||
public function dir_readdir() {
|
||||
if ( ! $this->handle ) {
|
||||
return '';
|
||||
}
|
||||
return readdir( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rewinddir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-rewinddir.php
|
||||
*/
|
||||
public function dir_rewinddir() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
rewinddir( $this->handle );
|
||||
// We do not really have a way to signal a failure as rewinddir() does not
|
||||
// have a return value and there is no way to read a directory handler
|
||||
// without advancing to the next file.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for closedir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-closedir.php
|
||||
*/
|
||||
public function dir_closedir() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
closedir( $this->handle );
|
||||
// We do not really have a way to signal a failure as closedir() does not
|
||||
// have a return value.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
691
html/wp-content/plugins/s3-uploads/inc/class-plugin.php
Normal file
691
html/wp-content/plugins/s3-uploads/inc/class-plugin.php
Normal file
@@ -0,0 +1,691 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Aws;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* @psalm-consistent-constructor
|
||||
*/
|
||||
class Plugin {
|
||||
|
||||
/**
|
||||
* The S3 bucket with path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $bucket;
|
||||
|
||||
/**
|
||||
* The URL that resolves to the S3 bucket.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $bucket_url;
|
||||
|
||||
/**
|
||||
* AWS IAM access key used for S3 Access.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* AWS IAM access key secret used for S3 Access.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $secret;
|
||||
|
||||
/**
|
||||
* Original wp_upload_dir() before being replaced by S3 Uploads.
|
||||
*
|
||||
* @var ?array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public $original_upload_dir;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
private $region = null;
|
||||
|
||||
/**
|
||||
* @var ?Aws\S3\S3Client
|
||||
*/
|
||||
private $s3 = null;
|
||||
|
||||
/**
|
||||
* @var ?static
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new static(
|
||||
S3_UPLOADS_BUCKET,
|
||||
defined( 'S3_UPLOADS_KEY' ) ? S3_UPLOADS_KEY : null,
|
||||
defined( 'S3_UPLOADS_SECRET' ) ? S3_UPLOADS_SECRET : null,
|
||||
defined( 'S3_UPLOADS_BUCKET_URL' ) ? S3_UPLOADS_BUCKET_URL : null,
|
||||
S3_UPLOADS_REGION
|
||||
);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $bucket
|
||||
* @param ?string $key
|
||||
* @param ?string $secret
|
||||
* @param ?string $bucket_url
|
||||
* @param ?string $region
|
||||
*/
|
||||
public function __construct( $bucket, $key, $secret, $bucket_url = null, $region = null ) {
|
||||
$this->bucket = $bucket;
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
$this->bucket_url = $bucket_url;
|
||||
$this->region = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the hooks, urls filtering etc for S3 Uploads
|
||||
*/
|
||||
public function setup() : void {
|
||||
$this->register_stream_wrapper();
|
||||
|
||||
add_filter( 'upload_dir', [ $this, 'filter_upload_dir' ] );
|
||||
add_filter( 'wp_image_editors', [ $this, 'filter_editors' ], 9 );
|
||||
add_action( 'delete_attachment', [ $this, 'delete_attachment_files' ] );
|
||||
add_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10, 2 );
|
||||
add_filter( 'wp_resource_hints', [ $this, 'wp_filter_resource_hints' ], 10, 2 );
|
||||
|
||||
add_filter( 'wp_handle_sideload_prefilter', [ $this, 'filter_sideload_move_temp_file_to_s3' ] );
|
||||
add_filter( 'wp_generate_attachment_metadata', [ $this, 'set_filesize_in_attachment_meta' ], 10, 2 );
|
||||
|
||||
add_filter( 'wp_get_attachment_url', [ $this, 'add_s3_signed_params_to_attachment_url' ], 10, 2 );
|
||||
add_filter( 'wp_get_attachment_image_src', [ $this, 'add_s3_signed_params_to_attachment_image_src' ], 10, 2 );
|
||||
add_filter( 'wp_calculate_image_srcset', [ $this, 'add_s3_signed_params_to_attachment_image_srcset' ], 10, 5 );
|
||||
|
||||
add_filter( 'wp_generate_attachment_metadata', [ $this, 'set_attachment_private_on_generate_attachment_metadata' ], 10, 2 );
|
||||
|
||||
add_filter( 'pre_wp_unique_filename_file_list', [ $this, 'get_files_for_unique_filename_file_list' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the hooks, url filtering etc for S3 Uploads
|
||||
*/
|
||||
public function tear_down() : void {
|
||||
|
||||
stream_wrapper_unregister( 's3' );
|
||||
remove_filter( 'upload_dir', [ $this, 'filter_upload_dir' ] );
|
||||
remove_filter( 'wp_image_editors', [ $this, 'filter_editors' ], 9 );
|
||||
remove_filter( 'wp_handle_sideload_prefilter', [ $this, 'filter_sideload_move_temp_file_to_s3' ] );
|
||||
remove_filter( 'wp_generate_attachment_metadata', [ $this, 'set_filesize_in_attachment_meta' ] );
|
||||
|
||||
remove_filter( 'wp_get_attachment_url', [ $this, 'add_s3_signed_params_to_attachment_url' ] );
|
||||
remove_filter( 'wp_get_attachment_image_src', [ $this, 'add_s3_signed_params_to_attachment_image_src' ] );
|
||||
remove_filter( 'wp_calculate_image_srcset', [ $this, 'add_s3_signed_params_to_attachment_image_srcset' ] );
|
||||
|
||||
remove_filter( 'wp_generate_attachment_metadata', [ $this, 'set_attachment_private_on_generate_attachment_metadata' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the stream wrapper for s3
|
||||
*/
|
||||
public function register_stream_wrapper() : void {
|
||||
if ( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL ) {
|
||||
stream_wrapper_register( 's3', 'S3_Uploads\Local_Stream_Wrapper', STREAM_IS_URL );
|
||||
} else {
|
||||
Stream_Wrapper::register( $this );
|
||||
$acl = defined( 'S3_UPLOADS_OBJECT_ACL' ) ? S3_UPLOADS_OBJECT_ACL : 'public-read';
|
||||
stream_context_set_option( stream_context_get_default(), 's3', 'ACL', $acl );
|
||||
}
|
||||
|
||||
stream_context_set_option( stream_context_get_default(), 's3', 'seekable', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the s3:// path for the bucket.
|
||||
*/
|
||||
public function get_s3_path() : string {
|
||||
return 's3://' . $this->bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the default wp_upload_dir.
|
||||
*
|
||||
* @param array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false} $dirs
|
||||
* @return array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public function filter_upload_dir( array $dirs ) : array {
|
||||
|
||||
$this->original_upload_dir = $dirs;
|
||||
$s3_path = $this->get_s3_path();
|
||||
|
||||
$dirs['path'] = str_replace( WP_CONTENT_DIR, $s3_path, $dirs['path'] );
|
||||
$dirs['basedir'] = str_replace( WP_CONTENT_DIR, $s3_path, $dirs['basedir'] );
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL' ) || ! S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL ) {
|
||||
|
||||
if ( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL ) {
|
||||
$dirs['url'] = str_replace( $s3_path, $dirs['baseurl'] . '/s3/' . $this->bucket, $dirs['path'] );
|
||||
$dirs['baseurl'] = str_replace( $s3_path, $dirs['baseurl'] . '/s3/' . $this->bucket, $dirs['basedir'] );
|
||||
|
||||
} else {
|
||||
$dirs['url'] = str_replace( $s3_path, $this->get_s3_url(), $dirs['path'] );
|
||||
$dirs['baseurl'] = str_replace( $s3_path, $this->get_s3_url(), $dirs['basedir'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $dirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all attachment files from S3 when an attachment is deleted.
|
||||
*
|
||||
* WordPress Core's handling of deleting files for attachments via
|
||||
* wp_delete_attachment_files is not compatible with remote streams, as
|
||||
* it makes many assumptions about local file paths. The hooks also do
|
||||
* not exist to be able to modify their behavior. As such, we just clean
|
||||
* up the s3 files when an attachment is removed, and leave WordPress to try
|
||||
* a failed attempt at mangling the s3:// urls.
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function delete_attachment_files( int $post_id ) : void {
|
||||
$meta = wp_get_attachment_metadata( $post_id );
|
||||
$file = get_attached_file( $post_id );
|
||||
if ( $file === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $meta['sizes'] ) ) {
|
||||
foreach ( $meta['sizes'] as $sizeinfo ) {
|
||||
$intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
|
||||
wp_delete_file( $intermediate_file );
|
||||
}
|
||||
}
|
||||
|
||||
wp_delete_file( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the S3 URL base for uploads.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_url() : string {
|
||||
if ( $this->bucket_url !== null ) {
|
||||
return $this->bucket_url;
|
||||
}
|
||||
|
||||
$bucket = strtok( $this->bucket, '/' );
|
||||
$path = substr( $this->bucket, strlen( $bucket ) );
|
||||
|
||||
return apply_filters( 's3_uploads_bucket_url', 'https://' . $bucket . '.s3.amazonaws.com' . $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the S3 bucket name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_bucket() : string {
|
||||
return strtok( $this->bucket, '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region of the S3 bucket.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_bucket_region() : ?string {
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original upload directory before it was replaced by S3 uploads.
|
||||
*
|
||||
* @return array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public function get_original_upload_dir() : array {
|
||||
|
||||
if ( empty( $this->original_upload_dir ) ) {
|
||||
wp_upload_dir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
$upload_dir = $this->original_upload_dir;
|
||||
return $upload_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a file url in the uploads directory to the params needed for S3.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array{bucket: string, key: string, query: string|null}|null
|
||||
*/
|
||||
public function get_s3_location_for_url( string $url ) : ?array {
|
||||
$s3_url = 'https://' . $this->get_s3_bucket() . '.s3.amazonaws.com/';
|
||||
if ( strpos( $url, $s3_url ) === 0 ) {
|
||||
$parsed = wp_parse_url( $url );
|
||||
return [
|
||||
'bucket' => $this->get_s3_bucket(),
|
||||
'key' => isset( $parsed['path'] ) ? ltrim( $parsed['path'], '/' ) : '',
|
||||
'query' => $parsed['query'] ?? null,
|
||||
];
|
||||
}
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $url, $upload_dir['baseurl'] ) === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $url );
|
||||
$parsed = wp_parse_url( $path );
|
||||
if ( ! isset( $parsed['host'] ) || ! isset( $parsed['path'] ) ) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'bucket' => $parsed['host'],
|
||||
'key' => ltrim( $parsed['path'], '/' ),
|
||||
'query' => $parsed['query'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a file path in the uploads directory to the params needed for S3.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array{key: string, bucket: string}
|
||||
*/
|
||||
public function get_s3_location_for_path( string $path ) : ?array {
|
||||
$parsed = wp_parse_url( $path );
|
||||
if ( ! isset( $parsed['path'] ) || ! isset( $parsed['host'] ) || ! isset( $parsed['scheme'] ) || $parsed['scheme'] !== 's3' ) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'bucket' => $parsed['host'],
|
||||
'key' => ltrim( $parsed['path'], '/' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Aws\S3\S3Client
|
||||
*/
|
||||
public function s3() : Aws\S3\S3Client {
|
||||
|
||||
if ( ! empty( $this->s3 ) ) {
|
||||
return $this->s3;
|
||||
}
|
||||
|
||||
$this->s3 = $this->get_aws_sdk()->createS3();
|
||||
return $this->s3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AWS Sdk.
|
||||
*
|
||||
* @return Aws\Sdk
|
||||
*/
|
||||
public function get_aws_sdk() : Aws\Sdk {
|
||||
/** @var null|Aws\Sdk */
|
||||
$sdk = apply_filters( 's3_uploads_aws_sdk', null, $this );
|
||||
if ( $sdk ) {
|
||||
return $sdk;
|
||||
}
|
||||
|
||||
$params = [ 'version' => 'latest' ];
|
||||
|
||||
if ( $this->key !== null && $this->secret !== null ) {
|
||||
$params['credentials']['key'] = $this->key;
|
||||
$params['credentials']['secret'] = $this->secret;
|
||||
}
|
||||
|
||||
if ( $this->region !== null ) {
|
||||
$params['signature'] = 'v4';
|
||||
$params['region'] = $this->region;
|
||||
}
|
||||
|
||||
if ( defined( 'WP_PROXY_HOST' ) && defined( 'WP_PROXY_PORT' ) ) {
|
||||
$proxy_auth = '';
|
||||
$proxy_address = WP_PROXY_HOST . ':' . WP_PROXY_PORT;
|
||||
|
||||
if ( defined( 'WP_PROXY_USERNAME' ) && defined( 'WP_PROXY_PASSWORD' ) ) {
|
||||
$proxy_auth = WP_PROXY_USERNAME . ':' . WP_PROXY_PASSWORD . '@';
|
||||
}
|
||||
|
||||
$params['request.options']['proxy'] = $proxy_auth . $proxy_address;
|
||||
}
|
||||
|
||||
$params = apply_filters( 's3_uploads_s3_client_params', $params );
|
||||
|
||||
$sdk = new Aws\Sdk( $params );
|
||||
return $sdk;
|
||||
}
|
||||
|
||||
public function filter_editors( array $editors ) : array {
|
||||
$position = array_search( 'WP_Image_Editor_Imagick', $editors );
|
||||
if ( $position !== false ) {
|
||||
unset( $editors[ $position ] );
|
||||
}
|
||||
|
||||
array_unshift( $editors, __NAMESPACE__ . '\\Image_Editor_Imagick' );
|
||||
|
||||
return $editors;
|
||||
}
|
||||
/**
|
||||
* Copy the file from /tmp to an s3 dir so handle_sideload doesn't fail due to
|
||||
* trying to do a rename() on the file cross streams. This is somewhat of a hack
|
||||
* to work around the core issue https://core.trac.wordpress.org/ticket/29257
|
||||
*
|
||||
* @param array{tmp_name: string, name: string, type: string, size: int, error: int} $file File array
|
||||
* @return array{tmp_name: string, name: string, type: string, size: int, error: int}
|
||||
*/
|
||||
public function filter_sideload_move_temp_file_to_s3( array $file ) {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$new_path = $upload_dir['basedir'] . '/tmp/' . basename( $file['tmp_name'] );
|
||||
|
||||
copy( $file['tmp_name'], $new_path );
|
||||
unlink( $file['tmp_name'] );
|
||||
$file['tmp_name'] = $new_path;
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the attachment filesize in the attachment meta array.
|
||||
*
|
||||
* Getting the filesize of an image in S3 involves a remote HEAD request,
|
||||
* which is a bit slower than a local filesystem operation would be. As a
|
||||
* result, operations like `wp_prepare_attachments_for_js' take substantially
|
||||
* longer to complete against s3 uploads than if they were performed with a
|
||||
* local filesystem.i
|
||||
*
|
||||
* Saving the filesize in the attachment metadata when the image is
|
||||
* uploaded allows core to skip this stat when retrieving and formatting it.
|
||||
*
|
||||
* @param array<string, mixed> $metadata Attachment metadata.
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @return array<string, mixed> Attachment metadata array, with "filesize" value added.
|
||||
*/
|
||||
function set_filesize_in_attachment_meta( array $metadata, int $attachment_id ) : array {
|
||||
$file = get_attached_file( $attachment_id );
|
||||
if ( $file === false ) {
|
||||
return $metadata;
|
||||
}
|
||||
if ( ! isset( $metadata['filesize'] ) && file_exists( $file ) ) {
|
||||
$metadata['filesize'] = filesize( $file );
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters wp_read_image_metadata. exif_read_data() doesn't work on
|
||||
* file streams so we need to make a temporary local copy to extract
|
||||
* exif data from.
|
||||
*
|
||||
* @param array<string, mixed> $meta
|
||||
* @param string $file
|
||||
* @return array<string, mixed>|false
|
||||
*/
|
||||
public function wp_filter_read_image_metadata( array $meta, string $file ) {
|
||||
remove_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10 );
|
||||
$temp_file = $this->copy_image_from_s3( $file );
|
||||
$meta = wp_read_image_metadata( $temp_file );
|
||||
add_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10, 2 );
|
||||
unlink( $temp_file );
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the DNS address for the S3 Bucket to list for DNS prefetch.
|
||||
*
|
||||
* @param array $hints
|
||||
* @param string $relation_type
|
||||
* @return array
|
||||
*/
|
||||
function wp_filter_resource_hints( array $hints, string $relation_type ) : array {
|
||||
if (
|
||||
( defined( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL' ) && S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL ) ||
|
||||
( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL )
|
||||
) {
|
||||
return $hints;
|
||||
}
|
||||
|
||||
if ( 'dns-prefetch' === $relation_type ) {
|
||||
$hints[] = $this->get_s3_url();
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local copy of the file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
public function copy_image_from_s3( string $file ) : string {
|
||||
if ( ! function_exists( 'wp_tempnam' ) ) {
|
||||
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
||||
}
|
||||
$temp_filename = wp_tempnam( $file );
|
||||
copy( $file, $temp_filename );
|
||||
return $temp_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the attachment is private.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_private_attachment( int $attachment_id ) : bool {
|
||||
/**
|
||||
* Filters whether an attachment should be private.
|
||||
*
|
||||
* @param bool Whether the attachment is private.
|
||||
* @param int The attachment ID.
|
||||
*/
|
||||
$private = apply_filters( 's3_uploads_is_attachment_private', false, $attachment_id );
|
||||
return $private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ACL (Access Control List) for an attachments files.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @param 'public-read'|'private' $acl public-read|private
|
||||
* @return WP_Error|null
|
||||
*/
|
||||
public function set_attachment_files_acl( int $attachment_id, string $acl ) : ?WP_Error {
|
||||
$files = static::get_attachment_files( $attachment_id );
|
||||
$locations = array_map( [ $this, 'get_s3_location_for_path' ], $files );
|
||||
// Remove any null items in the array from get_s3_location_for_path().
|
||||
$locations = array_filter( $locations );
|
||||
$s3 = $this->s3();
|
||||
$commands = [];
|
||||
foreach ( $locations as $location ) {
|
||||
$commands[] = $s3->getCommand( 'putObjectAcl', [
|
||||
'Bucket' => $location['bucket'],
|
||||
'Key' => $location['key'],
|
||||
'ACL' => $acl,
|
||||
] );
|
||||
}
|
||||
|
||||
try {
|
||||
Aws\CommandPool::batch( $s3, $commands );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( $e->getCode(), $e->getMessage() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after ACL of files of an attachment is set.
|
||||
*
|
||||
* @param int $attachment_id Attachment whose ACL has been changed.
|
||||
* @param string $acl The new ACL that's been set.
|
||||
* @psalm-suppress TooManyArguments -- Currently do_action doesn't detect variable number of arguments.
|
||||
*/
|
||||
do_action( 's3_uploads_set_attachment_files_acl', $attachment_id, $acl );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the files stored for a given attachment.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @return list<string> Array of all full paths to the attachment's files.
|
||||
*/
|
||||
public static function get_attachment_files( int $attachment_id ) : array {
|
||||
/** @var string */
|
||||
$main_file = get_attached_file( $attachment_id );
|
||||
$main_file_directory = dirname( $main_file );
|
||||
$files = [ $main_file ];
|
||||
|
||||
$meta = wp_get_attachment_metadata( $attachment_id );
|
||||
if ( isset( $meta['sizes'] ) ) {
|
||||
foreach ( $meta['sizes'] as $size => $sizeinfo ) {
|
||||
$files[] = $main_file_directory . '/' . $sizeinfo['file'];
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string|false */
|
||||
$original_image = get_post_meta( $attachment_id, 'original_image', true );
|
||||
if ( $original_image !== false && $original_image !== '' ) {
|
||||
$files[] = $main_file_directory . '/' . $original_image;
|
||||
}
|
||||
|
||||
/** @var array<string,array{file: string}> */
|
||||
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
|
||||
if ( $backup_sizes ) {
|
||||
foreach ( $backup_sizes as $size => $sizeinfo ) {
|
||||
// Backup sizes only store the backup filename, which is relative to the
|
||||
// main attached file, unlike the metadata sizes array.
|
||||
$files[] = $main_file_directory . '/' . $sizeinfo['file'];
|
||||
}
|
||||
}
|
||||
|
||||
$files = apply_filters( 's3_uploads_get_attachment_files', $files, $attachment_id );
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params onto an image for for a given attachment.
|
||||
*
|
||||
* This function determines whether the attachment needs a signed URL, so is safe to
|
||||
* pass any URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @param integer $post_id
|
||||
* @return string
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_url( string $url, int $post_id ) : string {
|
||||
if ( ! $this->is_private_attachment( $post_id ) ) {
|
||||
return $url;
|
||||
}
|
||||
$path = $this->get_s3_location_for_url( $url );
|
||||
if ( ! $path ) {
|
||||
return $url;
|
||||
}
|
||||
$cmd = $this->s3()->getCommand(
|
||||
'GetObject',
|
||||
[
|
||||
'Bucket' => $path['bucket'],
|
||||
'Key' => $path['key'],
|
||||
]
|
||||
);
|
||||
|
||||
$presigned_url_expires = apply_filters( 's3_uploads_private_attachment_url_expiry', '+6 hours', $post_id );
|
||||
$query = $this->s3()->createPresignedRequest( $cmd, $presigned_url_expires )->getUri()->getQuery();
|
||||
|
||||
// The URL could have query params on it already (such as being an already signed URL),
|
||||
// but query params will mean the S3 signed URL will become corrupt. So, we have to
|
||||
// remove all query params.
|
||||
$url = strtok( $url, '?' ) . '?' . $query;
|
||||
$url = apply_filters( 's3_uploads_presigned_url', $url, $post_id );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params to an image src array.
|
||||
*
|
||||
* @param array{0: string, 1: int, 2: int}|false $image
|
||||
* @param integer|"" $post_id The post id, due to WordPress hook, this can be "", so can't just hint as int.
|
||||
* @return array{0: string, 1: int, 2: int}|false
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_image_src( $image, $post_id ) {
|
||||
if ( $image === false || $post_id === '' || $post_id === 0 ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
$image[0] = $this->add_s3_signed_params_to_attachment_url( $image[0], $post_id );
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params to the image srcset (response image) sizes.
|
||||
*
|
||||
* @param array{url: string, descriptor: string, value: int}[] $sources
|
||||
* @param array $sizes
|
||||
* @param string $src
|
||||
* @param array $meta
|
||||
* @param integer $post_id
|
||||
* @return array{url: string, descriptor: string, value: int}[]
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_image_srcset( array $sources, array $sizes, string $src, array $meta, int $post_id ) : array {
|
||||
foreach ( $sources as &$source ) {
|
||||
$source['url'] = $this->add_s3_signed_params_to_attachment_url( $source['url'], $post_id );
|
||||
}
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever attachment metadata is generated, set the attachment files to private if it's a private attachment.
|
||||
*
|
||||
* @param array $metadata The attachment metadata.
|
||||
* @param int $attachment_id The attachment ID
|
||||
* @return array
|
||||
*/
|
||||
public function set_attachment_private_on_generate_attachment_metadata( array $metadata, int $attachment_id ) : array {
|
||||
if ( $this->is_private_attachment( $attachment_id ) ) {
|
||||
$this->set_attachment_files_acl( $attachment_id, 'private' );
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the files used for wp_unique_filename() comparisons
|
||||
*
|
||||
* @param array|null $files
|
||||
* @param string $dir
|
||||
* @return array
|
||||
*/
|
||||
public function get_files_for_unique_filename_file_list( ?array $files, string $dir, string $filename ) : array {
|
||||
$name = pathinfo( $filename, PATHINFO_FILENAME );
|
||||
// The s3:// streamwrapper support listing by partial prefixes with wildcards.
|
||||
// For example, scandir( s3://bucket/2019/06/my-image* )
|
||||
$scandir = scandir( trailingslashit( $dir ) . $name . '*' );
|
||||
if ( $scandir === false ) {
|
||||
$scandir = []; // Set as empty array for return
|
||||
}
|
||||
return $scandir;
|
||||
}
|
||||
}
|
||||
1229
html/wp-content/plugins/s3-uploads/inc/class-stream-wrapper.php
Normal file
1229
html/wp-content/plugins/s3-uploads/inc/class-stream-wrapper.php
Normal file
File diff suppressed because it is too large
Load Diff
360
html/wp-content/plugins/s3-uploads/inc/class-wp-cli-command.php
Normal file
360
html/wp-content/plugins/s3-uploads/inc/class-wp-cli-command.php
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Aws\Command;
|
||||
use Aws\S3\Transfer;
|
||||
use Exception;
|
||||
use WP_CLI;
|
||||
|
||||
class WP_CLI_Command extends \WP_CLI_Command {
|
||||
|
||||
/**
|
||||
* Verifies the API keys entered will work for writing and deleting from S3.
|
||||
*
|
||||
* @subcommand verify
|
||||
*/
|
||||
public function verify_api_keys() : void {
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get S3 Upload instance.
|
||||
Plugin::get_instance();
|
||||
|
||||
// Create a path in the base directory, with a random file name to avoid potentially overwriting existing data.
|
||||
$upload_dir = wp_upload_dir();
|
||||
$s3_path = $upload_dir['basedir'] . '/' . wp_rand() . '.txt';
|
||||
|
||||
// Attempt to copy the local Canola test file to the generated path on S3.
|
||||
WP_CLI::print_value( 'Attempting to upload file ' . $s3_path );
|
||||
|
||||
$copy = copy(
|
||||
dirname( dirname( __FILE__ ) ) . '/verify.txt',
|
||||
$s3_path
|
||||
);
|
||||
|
||||
// Check that the copy worked.
|
||||
if ( ! $copy ) {
|
||||
WP_CLI::error( 'Failed to copy / write to S3 - check your policy?' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File uploaded to S3 successfully.' );
|
||||
|
||||
// Delete the file off S3.
|
||||
WP_CLI::print_value( 'Attempting to delete file. ' . $s3_path );
|
||||
$delete = unlink( $s3_path );
|
||||
|
||||
// Check that the delete worked.
|
||||
if ( ! $delete ) {
|
||||
WP_CLI::error( 'Failed to delete ' . $s3_path );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File deleted from S3 successfully.' );
|
||||
|
||||
WP_CLI::success( 'Looks like your configuration is correct.' );
|
||||
}
|
||||
|
||||
private function get_iam_policy() : string {
|
||||
|
||||
$bucket = strtok( S3_UPLOADS_BUCKET, '/' );
|
||||
|
||||
$path = null;
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$path = str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET );
|
||||
}
|
||||
|
||||
return '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Stmt1392016154000",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:AbortMultipartUpload",
|
||||
"s3:DeleteObject",
|
||||
"s3:GetBucketAcl",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:GetBucketPolicy",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:ListBucket",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:ListMultipartUploadParts",
|
||||
"s3:PutObject",
|
||||
"s3:PutObjectAcl"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::' . S3_UPLOADS_BUCKET . '/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sid": "AllowRootAndHomeListingOfBucket",
|
||||
"Action": ["s3:ListBucket"],
|
||||
"Effect": "Allow",
|
||||
"Resource": ["arn:aws:s3:::' . $bucket . '"],
|
||||
"Condition":{"StringLike":{"s3:prefix":["' . ( $path !== null ? $path . '/' : '' ) . '*"]}}
|
||||
}
|
||||
]
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AWS IAM Policy that S3 Uploads requires
|
||||
*
|
||||
* It's typically not a good idea to use access keys that have full access to your S3 account,
|
||||
* as if the keys are compromised through the WordPress site somehow, you don't
|
||||
* want to give full control via those keys.
|
||||
*
|
||||
* @subcommand generate-iam-policy
|
||||
*/
|
||||
public function generate_iam_policy() : void {
|
||||
|
||||
WP_Cli::print_value( $this->get_iam_policy() );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* List files in the S3 bucket
|
||||
*
|
||||
* @synopsis [<path>]
|
||||
*
|
||||
* @param array{0: string} $args
|
||||
*/
|
||||
public function ls( array $args ) : void {
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= trailingslashit( ltrim( $args[0], '/' ) );
|
||||
}
|
||||
|
||||
try {
|
||||
$objects = $s3->getIterator(
|
||||
'ListObjectsV2', [
|
||||
'Bucket' => strtok( S3_UPLOADS_BUCKET, '/' ),
|
||||
'Prefix' => $prefix,
|
||||
]
|
||||
);
|
||||
/** @var array{Key: string} $object */
|
||||
foreach ( $objects as $object ) {
|
||||
WP_CLI::line( str_replace( $prefix, '', $object['Key'] ) );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy files to / from the uploads directory. Use s3://bucket/location for S3
|
||||
*
|
||||
* @synopsis <from> <to>
|
||||
*
|
||||
* @param array{0: string, 1: string} $args
|
||||
*/
|
||||
public function cp( array $args ) : void {
|
||||
|
||||
$from = $args[0];
|
||||
$to = $args[1];
|
||||
|
||||
if ( is_dir( $from ) ) {
|
||||
$this->recurse_copy( $from, $to );
|
||||
} else {
|
||||
copy( $from, $to );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Completed copy from %s to %s', $from, $to ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a directory to S3
|
||||
*
|
||||
* @subcommand upload-directory
|
||||
* @synopsis <from> [<to>] [--concurrency=<concurrency>] [--verbose]
|
||||
*
|
||||
* @param array{0: string, 1: string} $args
|
||||
* @param array{concurrency?: int, verbose?: bool} $args_assoc
|
||||
*/
|
||||
public function upload_directory( array $args, array $args_assoc ) : void {
|
||||
|
||||
$from = $args[0];
|
||||
$to = '';
|
||||
if ( isset( $args[1] ) ) {
|
||||
$to = $args[1];
|
||||
}
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
$args_assoc = wp_parse_args(
|
||||
$args_assoc, [
|
||||
'concurrency' => 5,
|
||||
'verbose' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $args_assoc['concurrency'],
|
||||
'debug' => (bool) $args_assoc['verbose'],
|
||||
'before' => function ( Command $command ) : void {
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CreateMultipartUpload' ], true ) ) {
|
||||
$acl = defined( 'S3_UPLOADS_OBJECT_ACL' ) ? S3_UPLOADS_OBJECT_ACL : 'public-read';
|
||||
$command['ACL'] = $acl;
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, 's3://' . S3_UPLOADS_BUCKET . '/' . $to, $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files from S3
|
||||
*
|
||||
* @synopsis <path> [--regex=<regex>]
|
||||
*
|
||||
* @param array{0: string} $args
|
||||
* @param array{regex?: string} $args_assoc
|
||||
*/
|
||||
public function rm( array $args, array $args_assoc ) : void {
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
$regex = isset( $args_assoc['regex'] ) ? $args_assoc['regex'] : '';
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= ltrim( $args[0], '/' );
|
||||
|
||||
if ( strpos( $args[0], '.' ) === false ) {
|
||||
$prefix = trailingslashit( $prefix );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$s3->deleteMatchingObjects(
|
||||
strtok( S3_UPLOADS_BUCKET, '/' ),
|
||||
$prefix,
|
||||
$regex,
|
||||
[
|
||||
'before_delete',
|
||||
function() {
|
||||
WP_CLI::line( 'Deleting file' );
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Successfully deleted %s', $prefix ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the auto-rewriting of media links to S3
|
||||
*/
|
||||
public function enable() : void {
|
||||
update_option( 's3_uploads_enabled', 'enabled' );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting enabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the auto-rewriting of media links to S3
|
||||
*/
|
||||
public function disable() : void {
|
||||
delete_option( 's3_uploads_enabled' );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting disabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files for a given attachment.
|
||||
*
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @subcommand get-attachment-files
|
||||
* @synopsis <attachment-id>
|
||||
*
|
||||
* @param array{0: int} $args
|
||||
*/
|
||||
public function get_attachment_files( array $args ) : void {
|
||||
WP_CLI::print_value( Plugin::get_attachment_files( $args[0] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ACL of all files for an attachment.
|
||||
*
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @subcommand set-attachment-acl
|
||||
* @synopsis <attachment-id> <acl>
|
||||
*
|
||||
* @param array{0: int, 1: 'public-read'|'private'} $args
|
||||
*/
|
||||
public function set_attachment_acl( array $args ) : void {
|
||||
$result = Plugin::get_instance()->set_attachment_files_acl( $args[0], $args[1] );
|
||||
WP_CLI::print_value( $result );
|
||||
}
|
||||
|
||||
private function recurse_copy( string $src, string $dst ) : void {
|
||||
$dir = opendir( $src );
|
||||
@mkdir( $dst );
|
||||
while ( false !== ( $file = readdir( $dir ) ) ) {
|
||||
if ( ( '.' !== $file ) && ( '..' !== $file ) ) {
|
||||
if ( is_dir( $src . '/' . $file ) ) {
|
||||
$this->recurse_copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
} else {
|
||||
WP_CLI::line( sprintf( 'Copying from %s to %s', $src . '/' . $file, $dst . '/' . $file ) );
|
||||
copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir( $dir );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the required constants for the S3 connections are set.
|
||||
*
|
||||
* @return bool true if all constants are set, else false.
|
||||
*/
|
||||
private function verify_s3_access_constants() {
|
||||
$required_constants = [
|
||||
'S3_UPLOADS_BUCKET',
|
||||
];
|
||||
|
||||
// Credentials do not need to be set when using AWS Instance Profiles.
|
||||
if ( ! defined( 'S3_UPLOADS_USE_INSTANCE_PROFILE' ) || ! S3_UPLOADS_USE_INSTANCE_PROFILE ) {
|
||||
array_push( $required_constants, 'S3_UPLOADS_KEY', 'S3_UPLOADS_SECRET' );
|
||||
}
|
||||
|
||||
$all_set = true;
|
||||
foreach ( $required_constants as $constant ) {
|
||||
if ( ! defined( $constant ) ) {
|
||||
WP_CLI::error( sprintf( 'The required constant %s is not defined.', $constant ), false );
|
||||
$all_set = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $all_set;
|
||||
}
|
||||
}
|
||||
192
html/wp-content/plugins/s3-uploads/inc/namespace.php
Normal file
192
html/wp-content/plugins/s3-uploads/inc/namespace.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
function init() : void {
|
||||
// Ensure the AWS SDK can be loaded.
|
||||
if ( ! class_exists( '\\Aws\\S3\\S3Client' ) ) {
|
||||
trigger_error( 'S3 Uploads requires the AWS SDK. Ensure Composer dependencies have been loaded.', E_USER_WARNING );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! check_requirements() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_BUCKET' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( ! defined( 'S3_UPLOADS_KEY' ) || ! defined( 'S3_UPLOADS_SECRET' ) ) && ! defined( 'S3_UPLOADS_USE_INSTANCE_PROFILE' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_REGION' ) ) {
|
||||
wp_die( 'S3_UPLOADS_REGION constant is required. Please define it in your wp-config.php' );
|
||||
}
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
\WP_CLI::add_command( 's3-uploads', 'S3_Uploads\\WP_CLI_Command' );
|
||||
}
|
||||
|
||||
$instance = Plugin::get_instance();
|
||||
$instance->setup();
|
||||
|
||||
// Add filters to "wrap" the wp_privacy_personal_data_export_file function call as we need to
|
||||
// switch out the personal_data directory to a local temp folder, and then upload after it's
|
||||
// complete, as Core tries to write directly to the ZipArchive which won't work with the
|
||||
// S3 streamWrapper.
|
||||
add_action( 'wp_privacy_personal_data_export_file', __NAMESPACE__ . '\\before_export_personal_data', 9, 0 );
|
||||
add_action( 'wp_privacy_personal_data_export_file', __NAMESPACE__ . '\\after_export_personal_data', 11, 0 );
|
||||
add_action( 'wp_privacy_personal_data_export_file_created', __NAMESPACE__ . '\\move_temp_personal_data_to_s3', 1000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the environment meets the plugin's requirements, like the minimum PHP version.
|
||||
*
|
||||
* @return bool True if the requirements are met, else false.
|
||||
*/
|
||||
function check_requirements() : bool {
|
||||
global $wp_version;
|
||||
|
||||
if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\outdated_php_version_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( version_compare( $wp_version, '5.3.0', '<' ) ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\outdated_wp_version_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ini_get( 'allow_url_fopen' ) === false || ini_get( 'allow_url_fopen' ) === '' ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\url_fopen_disabled_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the PHP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function outdated_php_version_notice() : void {
|
||||
printf(
|
||||
'<div class="error"><p>The S3 Uploads plugin requires PHP version 7.4 or higher. Your server is running PHP version %s.</p></div>',
|
||||
PHP_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the PHP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function url_fopen_disabled_notice() : void {
|
||||
printf( '<div class="error"><p>The S3 Uploads plugin requires PHP option allow_url_fopen to be enabled. <a href="%s" target="_blank" rel="noopener noreferrer">Learn more</a>.</p></div>',
|
||||
'https://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the WP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function outdated_wp_version_notice() : void {
|
||||
global $wp_version;
|
||||
|
||||
printf(
|
||||
'<div class="error"><p>The S3 Uploads plugin requires WordPress version 5.3 or higher. Your server is running WordPress version %s.</p></div>',
|
||||
esc_html( $wp_version )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL rewriting is enabled.
|
||||
*
|
||||
* Define S3_UPLOADS_AUTOENABLE to false in your wp-config to disable, or use the
|
||||
* s3_uploads_enabled option.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function enabled() : bool {
|
||||
// Make sure the plugin is enabled when autoenable is on
|
||||
$constant_autoenable_off = ( defined( 'S3_UPLOADS_AUTOENABLE' ) && false === S3_UPLOADS_AUTOENABLE );
|
||||
|
||||
if ( $constant_autoenable_off && 'enabled' !== get_option( 's3_uploads_enabled' ) ) { // If the plugin is not enabled, skip
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the filters for wp_privacy_exports_dir to use a temp folder location.
|
||||
*/
|
||||
function before_export_personal_data() : void {
|
||||
add_filter( 'wp_privacy_exports_dir', __NAMESPACE__ . '\\set_wp_privacy_exports_dir' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the filters for wp_privacy_exports_dir as we only want it added in some cases.
|
||||
*/
|
||||
function after_export_personal_data() : void {
|
||||
remove_filter( 'wp_privacy_exports_dir', __NAMESPACE__ . '\\set_wp_privacy_exports_dir' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the wp_privacy_exports_dir location
|
||||
*
|
||||
* We don't want to use the default uploads folder location, as with S3 Uploads this is
|
||||
* going to the a s3:// custom URL handler, which is going to fail with the use of ZipArchive.
|
||||
* Instead we set to to WP's get_temp_dir and move the fail in the wp_privacy_personal_data_export_file_created
|
||||
* hook.
|
||||
*
|
||||
* @param string $dir
|
||||
* @return string
|
||||
*/
|
||||
function set_wp_privacy_exports_dir( string $dir ) {
|
||||
if ( strpos( $dir, 's3://' ) !== 0 ) {
|
||||
return $dir;
|
||||
}
|
||||
$dir = get_temp_dir() . 'wp_privacy_exports_dir/';
|
||||
if ( ! is_dir( $dir ) ) {
|
||||
mkdir( $dir );
|
||||
file_put_contents( $dir . 'index.html', '' ); // @codingStandardsIgnoreLine FS write is ok.
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the tmp personal data file to the true uploads location
|
||||
*
|
||||
* Once a personal data file has been written, move it from the overridden "temp"
|
||||
* location to the S3 location where it should have been stored all along, and where
|
||||
* the "natural" Core URL is going to be pointing to.
|
||||
*/
|
||||
function move_temp_personal_data_to_s3( string $archive_pathname ) : void {
|
||||
if ( strpos( $archive_pathname, get_temp_dir() ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
$upload_dir = wp_upload_dir();
|
||||
$exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'wp-personal-data-exports/';
|
||||
$destination = $exports_dir . pathinfo( $archive_pathname, PATHINFO_FILENAME ) . '.' . pathinfo( $archive_pathname, PATHINFO_EXTENSION );
|
||||
copy( $archive_pathname, $destination );
|
||||
unlink( $archive_pathname );
|
||||
}
|
||||
Reference in New Issue
Block a user