Commit 2c42b0f7 authored by Zachary Scott's avatar Zachary Scott
Browse files

Merge pull request #1 from humanmade/master

Update
parents bb2b322b d831fcc4
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -20,6 +20,13 @@ define( 'S3_UPLOADS_KEY', '' );
define( 'S3_UPLOADS_SECRET', '' );
```

The next thing that you should do is to verify your setup. You can do this using the `verify` command
like so:

```
wp s3-uploads verify
```

You'll want to create a new IAM user for the S3-Uploads plugin, so you are not using admin level access keys on your site. S3-Uploads can create the IAM user for you and asign the correct permissions.

```
@@ -81,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
```

composer.json

0 → 100644
+24 −0
Original line number Diff line number Diff line
{
	"name": "humanmade/s3-uploads",
	"description": "WordPress plugin to store uploads on S3",
	"homepage": "https://github.com/humanmade/S3-Uploads",
	"keywords": [
		"wordpress"
	],
	"license": "GPL-2.0+",
	"authors": [
		{
			"name":"Human Made Limited",
			"email":"support@humanmade.co.uk",
			"homepage":"http://hmn.md/"
		}
	],
	"support"    : {
			"issues": "https://github.com/humanmade/s3-uploads/issues",
			"source": "https://github.com/humanmade/s3-uploads"
	},
	"type": "wordpress-plugin",
	"require": {
		"composer/installers": "~1.0"
	 }
}
+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
	}
+58 −11
Original line number Diff line number Diff line
@@ -2,6 +2,53 @@

class S3_Uploads_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() {

		S3_Uploads::get_instance(); // Boot

		$upload_dir = wp_upload_dir();

		// The upload file location on the local filesystem
		$s3_path = $upload_dir['basedir'] . '/' . mt_rand() . '.jpg';

		// Attempt to copy the file to S3
		WP_CLI::print_value( 'Attempting to upload file '. $s3_path );
		
		// Copy canola from the test dir, upto S3
		$copy = copy(
			dirname( dirname(__FILE__) ) . '/tests/data/canola.jpg',
			$s3_path
		);
        
		// Check that 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 off S3
		WP_CLI::print_value( 'Attempting to delete file '. $s3_path );
		$delete = unlink( $s3_path );

		// Check that 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.' );

	}

	/**
	 * @subcommand migrate-attachments
	 * @synopsis [--delete-local]
+9 −4
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 );
@@ -89,7 +94,7 @@ class S3_Uploads {
	 */
	public function s3() {

		require_once dirname( __FILE__ ) . '/aws-sdk/aws-autoloader.php';
		require_once dirname( dirname( __FILE__ ) ) . '/lib/aws-sdk/aws-autoloader.php';
		require_once dirname( __FILE__ ). '/class-s3-uploads-stream-wrapper.php';

		if ( ! empty( $this->s3 ) )
Loading