Commit 9d2765d9 authored by Joe Hoyle's avatar Joe Hoyle
Browse files

Merge pull request #23 from humanmade/remove-aws-sdk-hacks

Move the aws-sdk modifications to our own class
parents a9db1096 06776958
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -88,11 +88,11 @@ define( 'S3_UPLOADS_CACHE_CONTROL', 30 * 24 * 60 * 60 );
	// will expire in 30 days time
```

You can also configure the `Expires` header using the `S3_UPLOADS_EXPIRES` constant
You can also configure the `Expires` header using the `S3_UPLOADS_HTTP_EXPIRES` constant
For instance if you wanted to set an asset to effectively not expire, you could
set the Expires header way off in the future.  For example:

```PHP
define( 'S3_UPLOADS_EXPIRES', gmdate( 'D, d M Y H:i:s', time() + (10 * 365 * 24 * 60 * 60) ) .' GMT' );
define( 'S3_UPLOADS_HTTP_EXPIRES', gmdate( 'D, d M Y H:i:s', time() + (10 * 365 * 24 * 60 * 60) ) .' GMT' );
	// will expire in 10 years time
```
+119 −7
Original line number Diff line number Diff line
@@ -7,12 +7,7 @@ class S3_Uploads_Stream_Wrapper extends Aws\S3\StreamWrapper {
	 *
	 * @param S3Client $client Client to use with the stream wrapper
	 */
	public static function register( Aws\S3\S3Client $client)
	{
		if (in_array('s3', stream_get_wrappers())) {
			stream_wrapper_unregister('s3');
		}

	public static function register( Aws\S3\S3Client $client) {
		stream_wrapper_register( 's3', __CLASS__, STREAM_IS_URL );
		static::$client = $client;
	}
@@ -47,6 +42,123 @@ class S3_Uploads_Stream_Wrapper extends Aws\S3\StreamWrapper {

	}

	/**
	 * @param string $path
	 * @param string $mode
	 * @param int    $options
	 * @param string $opened_path
	 *
	 * @return bool
	 */
	public function stream_open( $path, $mode, $options, &$opened_path ) {

		$result = parent::stream_open( $path, $mode, $options, $opened_path );

		if ( ! $result ) {
			return $result;
		}

		if ( $mode === 'r' || $mode === 'a' ) {
			return $result;
		}

		/**
		 * As we open a temp stream, we don't actually know if we have writing ability yet.
		 * This means functions like copy() will not fail correctly, as the write to s3
		 * is only attemped on stream_flush() which is too late to report to copy()
		 * et al that the write has failed.
		 *
		 * As a work around, we attempt to write an empty object.
		 */
		try {
			$p = $this->params;
			$p['Body'] = '';
			static::$client->putObject($p);
		} catch (\Exception $e) {
			return $this->triggerError($e->getMessage());
		}

		return $result;
	}

	/**
	 * Provides information for is_dir, is_file, filesize, etc. Works on buckets, keys, and prefixes
	 *
	 * This is overrided to handle some optimizations with directories, else wp_upload_dir() causes
	 * a stat() on every page load (atleast once).
	 *
	 * @param string $path
	 * @param int    $flags
	 *
	 * @return array Returns an array of stat data
	 * @link http://www.php.net/manual/en/streamwrapper.url-stat.php
	 */
	public function url_stat( $path, $flags ) {
		$extension = pathinfo($path, PATHINFO_EXTENSION);

		/**
		 * If the file is actually just a path to a directory
		 * then return it as always existing. This is to work
		 * around wp_upload_dir doing file_exists checks on
		 * the uploads directory on every page load
		 */
		if ( ! $extension ) {

			return array (
				0 => 0,
				'dev' => 0,
				1 => 0,
				'ino' => 0,
				2 => 16895,
				'mode' => 16895,
				3 => 0,
				'nlink' => 0,
				4 => 0,
				'uid' => 0,
				5 => 0,
				'gid' => 0,
				6 => -1,
				'rdev' => -1,
				7 => 0,
				'size' => 0,
				8 => 0,
				'atime' => 0,
				9 => 0,
				'mtime' => 0,
				10 => 0,
				'ctime' => 0,
				11 => -1,
				'blksize' => -1,
				12 => -1,
				'blocks' => -1,
			);
		}

		// Check if this path is in the url_stat cache
		if ( isset ( self::$nextStat[ $path ] ) ) {
			return self::$nextStat[ $path ];
		}

		$parts = $this->getParams( $path );

		// Stat a bucket or just s3://
		if ( ! $parts['Key'] && ( ! $parts['Bucket'] || self::$client->doesBucketExist( $parts['Bucket'] ) ) ) {
			return $this->formatUrlStat( $path );
		}

		// You must pass either a bucket or a bucket + key
		if ( ! $parts['Key'] ) {
			return $this->triggerError( "File or directory not found: {$path}", $flags );
		}

		try {
			// Attempt to stat and cache regular object
			return $this->formatUrlStat( self::$client->headObject( $parts )->toArray() );
		} catch ( Exception $e ) {
			return $this->triggerError( $e->getMessage(), $flags );
		}
	}

	public function stream_metadata( $path, $option, $value ) {
		// not implemented
	}
+7 −2
Original line number Diff line number Diff line
@@ -30,14 +30,19 @@ class S3_Uploads {
		$this->secret = $secret;
		$this->bucket_url = $bucket_url;
		$this->region = $region;
	}

	/**
	 * Register the stream wrapper for s3
	 */
	public function register_stream_wrapper() {
		if ( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL ) {
			require_once dirname( __FILE__ ) . '/class-s3-uploads-local-stream-wrapper.php';
			stream_wrapper_register( 's3', 'S3_Uploads_Local_Stream_Wrapper', STREAM_IS_URL );
		} else {
			$s3 = $this->s3();
			S3_Uploads_Stream_Wrapper::register( $s3 );
			stream_context_set_option( stream_context_get_default(), 's3', 'ACL', Aws\S3\Enum\CannedAcl::PUBLIC_READ );
			stream_context_set_option( stream_context_get_default(), 's3', 'ACL', 'public-read' );
		}

		stream_context_set_option( stream_context_get_default(), 's3', 'seekable', true );
+18 −84
Original line number Diff line number Diff line
@@ -182,24 +182,6 @@ class StreamWrapper
            } elseif ($mode == 'a') {
                $this->openAppendStream($params, $errors);
            } else {

                /**
                 * Modification by Joe Hoyle
                 *
                 * As we open a temp stream, we don't actually know if we have writing ability yet.
                 * This means functions like copy() will not fail correctly, as the write to s3
                 * is only attemped on stream_flush() which is too late to report to copy()
                 * et al that the write has failed.
                 *
                 * As a work around, we attempt to write an empty object.
                 */
                try {
                    $p = $params;
                    $p['Body'] = '';
                    static::$client->putObject($p);
                } catch (\Exception $e) {
                    return $this->triggerError($e->getMessage());
                }
                $this->openWriteStream($params, $errors);
            }
        }
@@ -333,90 +315,42 @@ class StreamWrapper
     */
    public function url_stat($path, $flags)
    {
        $extension = pathinfo($path, PATHINFO_EXTENSION);

        /**
         * If the file is actually just a path to a directory
         * then return it as always existing. This is to work
         * around wp_upload_dir doing file_exists checks on
         * the uploads directory on every page load
         */
        if ( ! $extension ) {

            return array (
                0 => 0,
                'dev' => 0,
                1 => 0,
                'ino' => 0,
                2 => 16895,
                'mode' => 16895,
                3 => 0,
                'nlink' => 0,
                4 => 0,
                'uid' => 0,
                5 => 0,
                'gid' => 0,
                6 => -1,
                'rdev' => -1,
                7 => 0,
                'size' => 0,
                8 => 0,
                'atime' => 0,
                9 => 0,
                'mtime' => 0,
                10 => 0,
                'ctime' => 0,
                11 => -1,
                'blksize' => -1,
                12 => -1,
                'blocks' => -1,
            );
        }

        // Check if this path is in the url_stat cache
        if (isset(self::$nextStat[$path])) {
            return self::$nextStat[$path];
        if (isset(static::$nextStat[$path])) {
            return static::$nextStat[$path];
        }

        $parts = $this->getParams($path);

        // Stat a bucket or just s3://
        if (!$parts['Key'] && (!$parts['Bucket'] || self::$client->doesBucketExist($parts['Bucket']))) {
            return $this->formatUrlStat($path);
        }

        // You must pass either a bucket or a bucket + key
        if (!$parts['Key']) {
            // Stat "directories": buckets, or "s3://"
            if (!$parts['Bucket'] || static::$client->doesBucketExist($parts['Bucket'])) {
                return $this->formatUrlStat($path);
            } else {
                return $this->triggerError("File or directory not found: {$path}", $flags);
            }
        }

        try {
            try {
                $result = static::$client->headObject($parts)->toArray();
                if (substr($parts['Key'], -1, 1) == '/' && $result['ContentLength'] == 0) {
                    // Return as if it is a bucket to account for console bucket objects (e.g., zero-byte object "foo/")
                    return $this->formatUrlStat($path);
                } else {
                    // Attempt to stat and cache regular object
                return $this->formatUrlStat(self::$client->headObject($parts)->toArray());
                    return $this->formatUrlStat($result);
                }
            } catch (NoSuchKeyException $e) {
                // Maybe this isn't an actual key, but a prefix. Do a prefix listing of objects to determine.

                /**
                 * Modification by Joe Hoyle
                 * 
                 * If there is an extension, we don't need to check if it's a dir. There is an issue with checking
                 * if it's a dir, as s3 doesn't have true directories. See https://forums.aws.amazon.com/thread.jspa?threadID=142985
                 * for a more in-depth example.
                 */
                if ( $extension ) {
                    return $this->triggerError("File or directory not found: {$path}", $flags);
                }

                $result = self::$client->listObjects(array(
                $result = static::$client->listObjects(array(
                    'Bucket'  => $parts['Bucket'],
                    'Prefix'  => $parts['Key'],
                    'Prefix'  => rtrim($parts['Key'], '/') . '/',
                    'MaxKeys' => 1
                ));
                if (!$result['Contents'] && !$result['CommonPrefixes']) {
                    return $this->triggerError("File or directory not found: {$path}", $flags);
                }

                // This is a directory prefix
                return $this->formatUrlStat($path);
            }
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ function s3_uploads_init() {
	}

	$instance = S3_Uploads::get_instance();
	$instance->register_stream_wrapper();

	add_filter( 'upload_dir', array( $instance, 'filter_upload_dir' ) );
	add_filter( 'wp_image_editors', array( $instance, 'filter_editors' ), 9 );
Loading