diff --git a/README.md b/README.md new file mode 100644 index 00000000..2c46c71f --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# WP Offload S3 # +**Contributors:** bradt +**Donate link:** https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC +**Tags:** uploads, amazon, s3, mirror, admin, media, cdn, cloudfront +**Requires at least:** 3.5 +**Tested up to:** 4.2.2 +**Stable tag:** 0.9 +**License:** GPLv3 + +Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. + +## Description ## + +This plugin automatically copies images, videos, documents, and any other media added through WordPress' media uploader to [Amazon Simple Storage Service](http://aws.amazon.com/s3/) (S3). It then automatically replaces the URL to each media file with their respective S3 URL or, if you have configured [Amazon CloudFront](http://aws.amazon.com/cloudfront/), the respective CloudFront URL. Image thumbnails are also copied to S3 and delivered through S3/CloudFront. + +Uploading files *directly* to your S3 account is not currently supported by this plugin. They are uploaded to your server first, then copied to S3. There is an option to automatically remove the files from your server once they are copied to S3 however. + +If you're adding this plugin to a site that's been around for a while, your existing media files will not be copied or served from S3. Only newly uploaded files will be copied and served from S3. + +**Pro Version** + +We’re working on a pro version that will include the following features: + +* Copy existing Media Library to S3 +* Serve theme JS & CSS from S3/CloudFront +* WooCommerce & EDD integration +* Awesome email support + +[Sign up for news about the pro version](https://confirmsubscription.com/h/t/295CA85AEB94E879) + +[Request features, report bugs, and submit pull requests on Github](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/issues) + +*This plugin has been completely rewritten, but was originally a fork of +[Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/) +which is a fork of [Amazon S3 for WordPress](http://wordpress.org/extend/plugins/tantan-s3/), also known as tantan-s3.* + +## Installation ## + +1. Install the required [Amazon Web Services plugin](http://wordpress.org/extend/plugins/amazon-web-services/) using WordPress' built-in installer +2. Follow the instructions to setup your AWS access keys +3. Install this plugin using WordPress' built-in installer +4. Access the *S3 and CloudFront* option under *AWS* and configure + +## Screenshots ## + +### 1. Choosing/creating a bucket ### +![Choosing/creating a bucket](https://raw.githubusercontent.com/deliciousbrains/wp-wp-offload-s3/assets/screenshot-1.png) + +### 2. Settings screen ### +![Settings screen](https://raw.githubusercontent.com/deliciousbrains/wp-wp-offload-s3/assets/screenshot-2.png) + + +## Upgrade Notice ## + +### 0.6 ### +This version requires PHP 5.3.3+ and the Amazon Web Services plugin + +### 0.6.1 ### +This version requires PHP 5.3.3+ and the Amazon Web Services plugin + +### 0.6.2 ### +This version requires PHP 5.3.3+ and the Amazon Web Services plugin + +## Changelog ## + +### 0.9 - 2015-07-08 ### +* New: Plugin rebranded to WP Offload S3 +* New: Support tab added to _Offload S3_ screen containing diagnostic information +* New: Compatibility with the [Media Replace](https://wordpress.org/plugins/enable-media-replace/) plugin +* New: Select bucket region when creating a new bucket +* New: Toggle switches redesigned +* Improvement: Compatibility with release candidate of Pro plugin +* Improvement: Example IAM policy more secure +* Improvement: Set default bucket region using the `AS3CF_REGION` constant +* Improvement: Added `as3cf_object_meta` filter for developers +* Improvement: Bucket selection moved to modal window +* Improvement: Don't allow bucket names to contain invalid characters on creation +* Improvement: More verbose error messages on bucket selection +* Improvement: Settings link added to plugin row on _Plugins_ screen +* Improvement: Object versioning enabled by default +* Improvement: Uninstall routines added +* Improvement: JavaScript coding standards +* Improvement: Cache result when checking S3 bucket permissions +* Bug fix: Bucket region errors result in blank WP Offload S3 screen +* Bug fix: Editing an image when _Remove Files From Server_ option is enabled results in error +* Bug fix: Metadata upgrade procedure triggered on new installs +* Bug fix: File URLs when uploaded to a subdirectory result in incorrect S3 URLs +* Bug fix: Errors logged when trying to delete non-existent HiDPI images +* Bug fix: SignatureDoesNotMatch errors on regions with v4 authentication +* Bug fix: Customizer background image not editable +* Bug fix: Error when creating buckets with US Standard region +* Bug fix: Notices appearing incorrectly on some admin screens +* Bug fix: Subsite upload paths repeated on multisite installs +* Bug fix: Handle multisite installs where `BLOG_ID_CURRENT_SITE` is not 1 + +### 0.8.2 - 2015-01-31 ### +* New: Input bucket in settings to avoid listing all buckets +* New: Specify bucket with 'AS3CF_BUCKET' constant +* Improvement: Compatibility with beta release of Pro plugin +* Bug Fix: Incorrect file prefix in S3 permission check + +### 0.8.1 - 2015-01-19 ### +* Bug Fix: Permission problems on installs running on EC2s +* Bug Fix: Blank settings page due to WP_Error on S3 permission check +* Bug Fix: Warning: strtolower() expects parameter 1 to be string, object given +* Bug Fix: Region post meta update running on subsites of Multisite installs + +### 0.8 - 2015-01-10 ### +* New: Redesigned settings UI +* Improvement: SSL setting can be fully controlled, HTTPS for urls always, based on request or never +* Improvement: Download files from S3 that are not found on server when running Regenerate Thumbnails plugin +* Improvement: When calling `get_attached_file()` and file is missing from server, return S3 URL +* Improvement: Code cleanup to WordPress coding standards +* Bug Fix: Files for all subsites going into the same S3 folder on multisite installs setup prior to WP 3.5 +* Bug Fix: 'attempting to access local file system' error for some installs + +### 0.7.2 - 2014-12-11 ### +* Bug: Some buckets in the EU region causing permission and HTTP errors +* Bug: Undefined variable: message in view/error.php also causing white screens + +### 0.7.1 - 2014-12-05 ### +* Bug: Read-only error on settings page sometimes false positive + +### 0.7 - 2014-12-04 ### +* New: Proper S3 region subdomain in URLs for buckets not in the US Standard region (e.g. https://s3-us-west-2.amazonaws.com/...) +* New: Update all existing attachment meta with bucket region (automatically runs in the background) +* New: Get secure URL for different image sizes (iamzozo) +* New: S3 bucket can be set with constant in wp-config.php (dberube) +* New: Filter for allowing/disallowing file types: `as3cf_allowed_mime_types` +* New: Filter to cancel upload to S3 for any reason: `as3cf_pre_update_attachment_metadata` +* New: Sidebar with email opt-in +* Improvement: Show warning when S3 policy is read-only +* Improvement: Tooltip added to clarify option +* Improvement: Move object versioning option to make it clear it does not require CloudFront +* Improvement: By default only allow file types in `get_allowed_mime_types()` to be uploaded to S3 +* Improvement: Compatibility with WPML Media plugin +* Bug Fix: Edited images not removed on S3 when restoring image and IMAGE_EDIT_OVERWRITE true +* Bug Fix: File names with certain characters broken not working +* Bug Fix: Edited image uploaded to incorrect month folder +* Bug Fix: When creating a new bucket the bucket select box appears empty on success +* Bug Fix: SSL not working in regions other than US Standard +* Bug Fix: 'Error uploading' and 'Error removing local file' messages when editing an image +* Bug Fix: Upload and delete failing when bucket is non-US-region and bucket name contains dot +* Bug Fix: S3 file overwritten when file with same name uploaded and local file removed (dataferret) +* Bug Fix: Manually resized images not uploaded (gmauricio) + +### 0.6.1 - 2013-09-21 ### +* WP.org download of Amazon Web Services plugin is giving a 404 Not Found, so directing people to download from Github instead + +### 0.6 - 2013-09-20 ### +* Complete rewrite +* Now requires PHP 5.3.3+ +* Now requires the [Amazon Web Services plugin](http://wordpress.org/extend/plugins/amazon-web-services/) which contains the latest PHP libraries from Amazon +* Now works with multisite +* New Option: Custom S3 object path +* New Option: Always serve files over https (SSL) +* New Option: Enable object versioning by appending a timestamp to the S3 file path +* New Option: Remove uploaded file from local filesystem once it has been copied to S3 +* New Option: Copy any HiDPI (@2x) images to S3 (works with WP Retina 2x plugin) + +### 0.5 - 2013-01-29 ### +* Forked [Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/) +* Cleaned up the UI to fit with today's WP UI +* Fixed issues causing error messages when WP_DEBUG is on +* [Delete files on S3 when deleting WP attachment](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/e777cd49a4b6999f999bd969241fb24cbbcece60) +* [Added filter to the get_attachment_url function](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/bbe1aed5c2ae900e9ba1b16ba6806c28ab8e2f1c) +* [Added function to get a temporary, secure download URL for private files](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/11f46ec2714d34907009e37ad3b97f4421aefed3) diff --git a/assets/.jshintrc b/assets/.jshintrc deleted file mode 100644 index 35162bb7..00000000 --- a/assets/.jshintrc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "boss": true, - "browser": true, - "curly": true, - "devel": true, - "eqeqeq": true, - "eqnull": true, - "es3": false, - "immed": true, - "jquery": true, - "latedef": true, - "maxerr": 10000, - "newcap": true, - "noarg": true, - "sub": true, - "undef": true, - "globals": { - "ajaxurl": true, - "as3cf_i18n": true - } -} \ No newline at end of file diff --git a/assets/Gruntfile.js b/assets/Gruntfile.js deleted file mode 100644 index 7013d6cc..00000000 --- a/assets/Gruntfile.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = function(grunt) { - - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - jshint: { - all: [ - 'js/*.js', - '!js/*.min.js' - ], - options: { - jshintrc: '.jshintrc', - force: true - } - }, - uglify: { - build: { - files: { - 'js/script.min.js': 'js/script.js' - } - } - }, - compass: { - dist: { - options: { - } - } - }, - watch: { - js: { - files: ['js/*.js', '!js/*.min.js'], - tasks: ['uglify'] - }, - sass: { - files: ['sass/*'], - tasks: ['compass'] - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-compass'); - - grunt.registerTask('default', ['jshint','uglify','compass']); - -}; diff --git a/assets/config.rb b/assets/config.rb deleted file mode 100644 index f286c0f4..00000000 --- a/assets/config.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Require any additional compass plugins here. - -# Set this to the root of your project when deployed: -http_path = "/" -css_dir = "css" -sass_dir = "sass" -images_dir = "img" -javascripts_dir = "js" - -# You can select your preferred output style here (can be overridden via the command line): -# output_style = :expanded or :nested or :compact or :compressed -output_style = :compressed - -# To enable relative paths to assets via compass helper functions. Uncomment: -# relative_assets = true - -# To disable debugging comments that display the original location of your selectors. Uncomment: -line_comments = false - - -# If you prefer the indented syntax, you might want to regenerate this -# project again passing --syntax sass, or you can uncomment this: -# preferred_syntax = :sass -# and then run: -# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass diff --git a/assets/css/modal.css b/assets/css/modal.css new file mode 100644 index 00000000..e5df3aa4 --- /dev/null +++ b/assets/css/modal.css @@ -0,0 +1 @@ +#as3cf-overlay{display:none;position:fixed;top:0;right:0;bottom:0;left:0;background-color:rgba(0,0,0,0.5);overflow:hidden;overflow-y:auto;z-index:999999}#as3cf-modal{display:none;position:relative;width:600px;margin:100px auto;padding:30px;background-color:#eee;box-shadow:0 0 10px rgba(0,0,0,0.5);font-size:14px;overflow:hidden;z-index:100000}#as3cf-modal .close-as3cf-modal{color:#999;cursor:pointer;font-family:"Times New Roman", serif;font-size:26px;font-weight:200;position:absolute;right:18px;top:18px}#as3cf-modal .close-as3cf-modal:hover{color:#666}#as3cf-modal h3{margin:0 0 20px;font-weight:normal;line-height:1}#as3cf-modal .error,#as3cf-modal .notice,#as3cf-modal .updated{margin:0 0 20px}#as3cf-modal .actions{margin:20px -30px -30px;padding:20px 30px;border-top:none;background-color:#e3e3e3;overflow:hidden}#as3cf-modal .actions .right{margin-left:15px}#as3cf-modal .actions .right:last-of-type{margin-left:0}#as3cf-modal .actions button{min-width:90px}body.as3cf-modal-open{overflow:hidden;padding-right:15px} diff --git a/assets/css/styles.css b/assets/css/styles.css index 0892879b..172164ec 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -1 +1 @@ -.aws-main .error pre{background:#eaeaea;background:rgba(0,0,0,0.07);display:block;padding:10px 15px}.aws-main .error pre code{padding:0;background:none}.aws-main .as3cf-notice,.aws-main .error{max-width:935px}.aws-main .updated{display:none}.aws-main .updated.as3cf-notice,.aws-main .updated.show{display:block}.as3cf-settings{position:relative;width:650px;min-height:800px}.as3cf-settings .as3cf-main-settings{display:none}.as3cf-settings .as3cf-main-settings p{font-size:13px}.as3cf-settings .as3cf-main-settings p a{color:#444}.as3cf-settings.as3cf-has-bucket .as3cf-bucket-select{display:none}.as3cf-settings.as3cf-has-bucket .as3cf-main-settings{display:block}.as3cf-settings .object-prefix-desc em{white-space:nowrap}.as3cf-settings .as3cf-url-preview-wrap{background:#ffffff;text-align:center;padding:20px 20px 10px;max-width:610px;width:100%}.as3cf-settings .as3cf-url-preview-wrap .as3cf-url-preview{margin-top:5px;overflow-x:scroll;padding-bottom:15px}.as3cf-settings .as3cf-url-preview-wrap span{color:#aaa;text-transform:uppercase;font-weight:bold}.as3cf-settings .as3cf-ssl p.info{margin-top:10px;padding:0}.as3cf-settings .as3cf-radio-group label{display:block;margin-bottom:10px}.as3cf-settings .as3cf-radio-group label.disabled,.as3cf-settings .as3cf-radio-group label.disabled p{color:#bbbbbb;cursor:default}.as3cf-settings .as3cf-radio-group p{padding-left:25px;color:#6b6b6b;margin:0;font-size:12px}.as3cf-settings .as3cf-radio-group p.as3cf-setting{margin-top:5px}.as3cf-settings .as3cf-switch{position:relative}.as3cf-settings .as3cf-switch.disabled span{cursor:default;margin:1px 0 0 0}.as3cf-settings .as3cf-switch.disabled span.checked{background:#F1F1F1;border:1px solid #dddddd;color:#a3a3a3}.as3cf-settings .as3cf-switch span{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;float:left;display:inline-block;height:100%;font-size:12px;line-height:20px;border-radius:2px;-webkit-border-radius:2px;font-weight:bold;padding:4px 8px;background:#F1F1F1;border:1px solid #dddddd;color:#CCCCCC;margin:1px -1px 0;z-index:1}.as3cf-settings .as3cf-switch span.checked{background:#ffffff;border-color:#E4E4E4;color:#373737;padding:5px 10px;margin:0;z-index:2}.as3cf-settings .as3cf-switch input[type="checkbox"]{position:absolute !important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.as3cf-settings .as3cf-setting.hide{display:none}.as3cf-settings .as3cf-bucket-actions{position:absolute;right:0;top:2px}.as3cf-settings .as3cf-bucket-actions .as3cf-cancel-bucket-select-wrap{display:none}.as3cf-settings .as3cf-bucket-actions .as3cf-cancel-bucket-select-wrap:after{content:" | "}.as3cf-settings .as3cf-bucket-select.manual .as3cf-manual-bucket-toggle,.as3cf-settings .as3cf-bucket-select.manual .as3cf-refresh-buckets,.as3cf-settings .as3cf-bucket-select.manual .as3cf-bucket-list-wrapper{display:none}.as3cf-settings .as3cf-bucket-select.manual .as3cf-bucket-list-toggle,.as3cf-settings .as3cf-bucket-select.manual .as3cf-manual-save-bucket-wrapper{display:block}.as3cf-settings .as3cf-bucket-select.manual .as3cf-cancel-bucket-select-wrap:after{content:""}.as3cf-settings .as3cf-bucket-select .as3cf-manual-bucket-toggle,.as3cf-settings .as3cf-bucket-select .as3cf-bucket-list-wrapper{display:block}.as3cf-settings .as3cf-bucket-select .as3cf-refresh-buckets{display:inline}.as3cf-settings .as3cf-bucket-select .as3cf-bucket-list-toggle,.as3cf-settings .as3cf-bucket-select .as3cf-manual-save-bucket-wrapper{display:none}.as3cf-settings .as3cf-bucket-list-wrapper{background:#fff;padding:15px 20px;margin-bottom:2em}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list{max-height:300px;overflow:auto;margin:0}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list li{font-size:14px;padding:10px 0}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a{color:#444;text-decoration:none}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a:hover,.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a.selected{color:#0074A2}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a.selected{font-weight:bold}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a .bucket{float:left;clear:both}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a .bucket .dashicons{margin-right:5px}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list a .spinner{float:left;background-size:15px 15px}.as3cf-settings .as3cf-bucket-list-wrapper .as3cf-bucket-list.saving{opacity:0.5}.as3cf-settings input[type="text"].as3cf-bucket-name{width:80%}.as3cf-settings select.bucket{margin-bottom:5px;width:380px}.as3cf-settings .form-table td{padding-left:0;padding-right:0}.as3cf-settings .form-table td:first-child{vertical-align:top;min-width:120px}.as3cf-settings .form-table h3{font-weight:normal;text-transform:uppercase;margin:0}.as3cf-settings .form-table h4{margin:0}.as3cf-settings .form-table .as3cf-border-bottom td{border-bottom:1px solid #ddd}.as3cf-settings .as3cf-active-bucket{font-weight:bold;margin-right:10px}.as3cf-settings .tooltip{position:relative;z-index:2;cursor:pointer}.as3cf-settings .tooltip:before,.as3cf-settings .tooltip:after{visibility:hidden;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);opacity:0;pointer-events:none}.as3cf-settings .tooltip:before{position:absolute;bottom:150%;left:50%;margin-bottom:5px;margin-left:-250px;padding:10px;width:500px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background-color:#000;background-color:rgba(51,51,51,0.9);color:#fff;content:attr(data-tooltip);text-align:center;font-size:14px;line-height:1.3}.as3cf-settings .tooltip:after{position:absolute;bottom:150%;left:50%;margin-left:-5px;width:0;border-top:5px solid #000;border-top:5px solid rgba(51,51,51,0.9);border-right:5px solid transparent;border-left:5px solid transparent;content:" ";font-size:0;line-height:0}.as3cf-settings .tooltip:hover:before,.as3cf-settings .tooltip:hover:after{visibility:visible;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);opacity:1}.as3cf-banner img{display:block}.as3cf-sidebar{position:absolute;top:17px;right:-312px;width:292px}.as3cf-sidebar .block{padding:20px;border:1px solid #ccc}.as3cf-sidebar .subscribe{border-top:none}.as3cf-sidebar .subscribe h2{padding:0;margin:0;margin-bottom:0.5em;color:#666;font-size:20px;line-height:1.2em;float:none}.as3cf-sidebar .subscribe h3{font-size:16px;margin:0}.as3cf-sidebar .subscribe p{margin:0}.as3cf-sidebar .subscribe .intro{margin-bottom:1em;line-height:1.4}.as3cf-sidebar .subscribe ul{margin-left:20px;list-style-type:disc}.as3cf-sidebar .subscribe li{line-height:1.4}.as3cf-sidebar .subscribe .links{margin-bottom:2em}.as3cf-sidebar .subscribe .links a{text-decoration:none}.as3cf-sidebar .subscribe .promise{color:#999;font-size:12px;line-height:1.4em}.as3cf-sidebar .subscribe .field{margin-bottom:0.5em}.as3cf-sidebar .subscribe .field p{margin-bottom:0.3em}.as3cf-sidebar .subscribe .field.submit-button{margin-bottom:1em}.as3cf-sidebar .credits{border-top:0}.as3cf-sidebar .credits h4{font-size:16px;margin-top:0;margin-bottom:10px}.as3cf-sidebar .credits ul{margin:0}.as3cf-sidebar .credits li{overflow:hidden}.as3cf-sidebar .credits li:last-child{margin-bottom:0}.as3cf-sidebar .credits img{float:left;margin-right:10px}.as3cf-sidebar .credits span{float:left;display:block;line-height:32px}.as3cf-sidebar .credits a{display:block;text-decoration:none;color:#444;font-size:16px;text-align:center}.as3cf-sidebar .credits a:hover{color:#888}@media (min--moz-device-pixel-ratio: 1.3), (-o-min-device-pixel-ratio: 2.6 / 2), (-webkit-min-device-pixel-ratio: 1.3), (min-device-pixel-ratio: 1.3), (min-resolution: 1.3dppx){.as3cf-sidebar .as3cf-banner{background-image:url(../img/snail@2x.jpg);background-size:292px 165px;width:292px;height:165px;display:block}.as3cf-sidebar .as3cf-banner img{display:none}}@media screen and (max-width: 1052px){.as3cf-sidebar{position:relative;top:auto;right:auto;margin-top:50px}} +.aws-main.wrap>h2{float:left}.aws-main.wrap .as3cf-notice,.aws-main.wrap .as3cf-updated,.aws-main.wrap .as3cf-error{margin-bottom:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.aws-main.wrap .as3cf-error.fatal{clear:both;float:left}.aws-main.wrap h2.nav-tab-wrapper{float:none;margin-bottom:0;width:650px;margin-top:10px;padding-left:5px;padding-right:0}.aws-main.wrap h2.nav-tab-wrapper a.nav-tab-active{color:#464646;cursor:default}.aws-main.wrap h2.nav-tab-wrapper a:focus{-webkit-box-shadow:none;box-shadow:none}.aws-main.wrap .error pre{background:#eaeaea;background:rgba(0,0,0,0.07);display:block;padding:10px 15px}.aws-main.wrap .error pre code{padding:0;background:none}.aws-main.wrap[data-tab="support"] .as3cf-notice,.aws-main.wrap[data-tab="support"] .error,.aws-main.wrap[data-tab="support"] .updated,.aws-main.wrap[data-tab="support"] .updated.show{display:none}.aws-main.wrap[data-tab="support"] .fatal .error,.aws-main.wrap[data-tab="support"] .dbrains-api-down{display:block}.aws-main.wrap .as3cf-notice,.aws-main.wrap .error,.aws-main.wrap .updated{max-width:650px;margin-top:15px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.aws-main.wrap .as3cf-updated{display:none}.aws-main.wrap .as3cf-updated.as3cf-notice,.aws-main.wrap .as3cf-updated.show{display:block}.as3cf-tab .as3cf-main-settings{display:none}.as3cf-tab .as3cf-bucket-container{display:block}.as3cf-tab.as3cf-has-bucket .as3cf-main-settings{display:block}.as3cf-tab.as3cf-has-bucket .as3cf-bucket-container{display:none}.as3cf-tab{display:none;position:relative;width:650px}.as3cf-tab .as3cf-main-settings p{font-size:13px}.as3cf-tab .as3cf-main-settings p a{color:#444}.as3cf-tab .object-prefix-desc em{white-space:nowrap}.as3cf-tab .as3cf-url-preview-wrap{background:#fff;text-align:center;padding:20px 0 0;max-width:650px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.as3cf-tab .as3cf-url-preview-wrap .as3cf-url-preview{margin-top:10px;padding:0 20px 10px;overflow-x:scroll}.as3cf-tab .as3cf-url-preview-wrap span{color:#aaa;text-transform:uppercase;font-weight:bold}.as3cf-tab .as3cf-ssl p.info{margin-top:10px;padding:0}.as3cf-tab .as3cf-radio-group label{display:block;margin-bottom:10px}.as3cf-tab .as3cf-radio-group label.disabled,.as3cf-tab .as3cf-radio-group label.disabled p{color:#bbbbbb;cursor:default}.as3cf-tab .as3cf-radio-group p{padding-left:25px;color:#6b6b6b;margin:0;font-size:12px}.as3cf-tab .as3cf-radio-group p.as3cf-setting{margin-top:5px}.as3cf-tab .as3cf-switch{position:relative;display:inline-block;padding:2px;overflow:hidden;border-radius:2px;-webkit-border-radius:2px;background-color:#d4d3d3;cursor:pointer}.as3cf-tab .as3cf-switch.on{background-color:#ade7b5}.as3cf-tab .as3cf-switch span{visibility:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;display:inline-block;height:100%;font-size:12px;line-height:20px;border-radius:2px;-webkit-border-radius:2px;font-weight:bold;padding:4px 8px;background:#fff;color:#8d8d8d;z-index:1}.as3cf-tab .as3cf-switch span.on{color:#82d78b}.as3cf-tab .as3cf-switch span.checked{visibility:visible}.as3cf-tab .as3cf-switch.disabled{cursor:default;background:#e6e6e6}.as3cf-tab .as3cf-switch.disabled span{background:#f1f1f1;color:#d6d6d6}.as3cf-tab .as3cf-switch input[type="checkbox"]{position:absolute !important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.as3cf-tab .as3cf-setting.hide{display:none}.as3cf-tab h3{font-weight:normal;text-transform:uppercase;margin:15px 0}.as3cf-tab .form-table{margin:0}.as3cf-tab .form-table tr.as3cf-border-bottom td{border-bottom:1px solid #ddd;padding:20px 0px}.as3cf-tab .form-table tr.as3cf-setting-title td{padding-bottom:0}.as3cf-tab .form-table tr.as3cf-setting-title:first-child td{padding-top:20px}.as3cf-tab .form-table tr td{padding:15px 0}.as3cf-tab .form-table tr td:first-child{vertical-align:top;min-width:120px}.as3cf-tab .form-table h3{padding:0;margin:0}.as3cf-tab .form-table h4{margin:0}.as3cf-tab .as3cf-active-bucket{font-weight:bold;margin-right:10px}.as3cf-tab .tooltip{position:relative;z-index:2;cursor:pointer}.as3cf-tab .tooltip:before,.as3cf-tab .tooltip:after{visibility:hidden;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);opacity:0;pointer-events:none}.as3cf-tab .tooltip:before{position:absolute;bottom:150%;left:50%;margin-bottom:5px;margin-left:-250px;padding:10px;width:500px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background-color:#000;background-color:rgba(51,51,51,0.9);color:#fff;content:attr(data-tooltip);text-align:center;font-size:14px;line-height:1.3}.as3cf-tab .tooltip:after{position:absolute;bottom:150%;left:50%;margin-left:-5px;width:0;border-top:5px solid #000;border-top:5px solid rgba(51,51,51,0.9);border-right:5px solid transparent;border-left:5px solid transparent;content:" ";font-size:0;line-height:0}.as3cf-tab .tooltip:hover:before,.as3cf-tab .tooltip:hover:after{visibility:visible;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);opacity:1}#tab-media{display:block}#tab-media .as3cf-main-settings{display:none}#tab-media .as3cf-bucket-container{display:block}#tab-media.as3cf-has-bucket .as3cf-main-settings{display:block}#tab-media.as3cf-has-bucket .as3cf-bucket-container{display:none}.as3cf-bucket-container h3{line-height:1.3;text-transform:none}.as3cf-bucket-container a:focus{-webkit-box-shadow:none;box-shadow:none;outline:none}.as3cf-bucket-container input[type=text]{box-sizing:border-box;width:100%}.as3cf-bucket-container select{box-sizing:border-box;width:50%}.as3cf-bucket-container .form-table td{padding:5px 0}.as3cf-bucket-container .form-table td:first-child{width:100px;line-height:30px;vertical-align:top}.as3cf-bucket-container .as3cf-invalid-bucket-name{font-size:12px;color:#a00}.as3cf-bucket-container .bucket-actions{margin:15px 0;border-top:1px solid #ccc;padding-top:15px;overflow:hidden}.as3cf-bucket-container .bucket-actions button,.as3cf-bucket-container .bucket-actions .right{float:right;margin-right:0}.as3cf-bucket-container .bucket-actions span{display:inline-block;margin-right:20px;line-height:28px}.as3cf-bucket-container .bucket-actions .bucket-action-cancel{color:#a00;text-decoration:none}.as3cf-bucket-container .bucket-actions .bucket-action-cancel:hover{color:red}.as3cf-bucket-container .as3cf-bucket-list{padding:15px;max-height:200px;overflow-x:hidden;overflow-y:auto;background-color:#fff;font-size:14px}.as3cf-bucket-container .as3cf-bucket-list li:last-of-type{margin-bottom:0}.as3cf-bucket-container .as3cf-bucket-list a{color:#444;text-decoration:none}.as3cf-bucket-container .as3cf-bucket-list a:hover{color:#0074A2}.as3cf-bucket-container .as3cf-bucket-list a.selected{font-weight:bold;color:#0074A2}.as3cf-bucket-container .as3cf-bucket-list a .dashicons{margin-right:5px}.as3cf-bucket-container .as3cf-bucket-select,.as3cf-bucket-container .as3cf-bucket-create{display:none}.as3cf-bucket-container .bucket-actions.select{display:none}.as3cf-tab{display:none}#tab-media{display:block}#tab-support .as3cf-sidebar{top:11px}#tab-support .support-section{border-bottom:1px solid #ccc;padding-bottom:20px;margin-bottom:20px}#tab-support .debug textarea{width:100%;min-height:200px;font-family:Consolas, Monaco, monospace;margin-bottom:5px}.as3cf-sidebar{position:absolute;top:25px;left:670px;width:292px}.as3cf-sidebar .block{padding:20px;border:1px solid #ccc}.as3cf-sidebar .subscribe{border-top:none}.as3cf-sidebar .subscribe h2{padding:0;margin:0;margin-bottom:0.5em;color:#666;font-size:20px;line-height:1.2em;float:none}.as3cf-sidebar .subscribe h3{font-size:16px;margin:0}.as3cf-sidebar .subscribe p{margin:0}.as3cf-sidebar .subscribe .intro{margin-bottom:1em;line-height:1.4}.as3cf-sidebar .subscribe ul{margin-left:20px;list-style-type:disc}.as3cf-sidebar .subscribe li{line-height:1.4}.as3cf-sidebar .subscribe .links{margin-bottom:2em}.as3cf-sidebar .subscribe .links a{text-decoration:none}.as3cf-sidebar .subscribe .promise{color:#999;font-size:12px;line-height:1.4em}.as3cf-sidebar .subscribe .field{margin-bottom:0.5em}.as3cf-sidebar .subscribe .field p{margin-bottom:0.3em}.as3cf-sidebar .subscribe .field.submit-button{margin-bottom:1em}.as3cf-sidebar .credits{border-top:0}.as3cf-sidebar .credits h4{font-size:16px;margin-top:0;margin-bottom:10px}.as3cf-sidebar .credits ul{margin:0}.as3cf-sidebar .credits li{overflow:hidden}.as3cf-sidebar .credits li:last-child{margin-bottom:0}.as3cf-sidebar .credits img{float:left;margin-right:10px}.as3cf-sidebar .credits span{float:left;display:block;line-height:32px}.as3cf-sidebar .credits a{display:block;text-decoration:none;color:#444;font-size:16px;text-align:center}.as3cf-sidebar .credits a:hover{color:#888}@media (min--moz-device-pixel-ratio: 1.3), (-o-min-device-pixel-ratio: 2.6 / 2), (-webkit-min-device-pixel-ratio: 1.3), (min-device-pixel-ratio: 1.3), (min-resolution: 1.3dppx){.as3cf-sidebar .as3cf-banner{background-image:url(../img/snail@2x.jpg);background-size:292px 165px;width:292px;height:165px;display:block}.as3cf-sidebar .as3cf-banner img{display:none}}@media screen and (max-width: 1052px){.as3cf-sidebar{position:relative;top:auto;right:auto;margin-top:50px}}.as3cf-banner img{display:block}.aws-compatibility-notice.error{clear:both;margin:5px 20px 5px 0} diff --git a/assets/js/modal.js b/assets/js/modal.js new file mode 100644 index 00000000..fbb800bf --- /dev/null +++ b/assets/js/modal.js @@ -0,0 +1,199 @@ +var as3cfModal = (function ( $ ) { + + var modal = { + prefix: 'as3cf' + }; + + var modals = {}; + + /** + * Target to key + * + * @param string target + * + * @return string + */ + function targetToKey( target ) { + return target.replace( /[^a-z]/g, '' ); + } + + /** + * Open modal + * + * @param string target + * @param function callback + */ + modal.open = function ( target, callback ) { + var key = targetToKey( target ); + + // Overlay + $( 'body' ).append( '
' ); + var $overlay = $( '#as3cf-overlay' ); + + // Modal container + $overlay.append( '
×
' ); + var $modal = $( '#as3cf-modal' ); + + if ( undefined === modals[ key ] ) { + var content = $( target ); + modals[ key ] = content.clone( true ).css( 'display', 'block' ); + content.remove(); + } + $modal.data( 'as3cf-modal-target', target ).append( modals[ key ] ); + + if ( 'function' === typeof callback ) { + callback( target ); + } + + // Handle modals taller than window height, + // overflow & padding-right remove duplicate scrollbars. + $( 'body' ).addClass( 'as3cf-modal-open' ); + + $overlay.fadeIn( 150 ); + $modal.fadeIn( 150 ); + + $( 'body' ).trigger( 'as3cf-modal-open', [ target ] ); + }; + + /** + * Close modal + * + * @param function callback + */ + modal.close = function ( callback ) { + var target = $( '#as3cf-modal' ).data( 'as3cf-modal-target' ); + + $( '#as3cf-overlay' ).fadeOut( 150, function () { + if ( 'function' === typeof callback ) { + callback( target ); + } + + $( 'body' ).removeClass( 'as3cf-modal-open' ); + + $( this ).remove(); + } ); + + $( 'body' ).trigger( 'as3cf-modal-close', [ target ] ); + }; + + // Setup click handlers + $( document ).ready( function () { + + $( 'body' ).on( 'click', '[data-as3cf-modal]', function ( e ) { + e.preventDefault(); + modal.open( $( this ).data( 'as3cf-modal' ) + '.' + modal.prefix ); + } ); + + $( 'body' ).on( 'click', '#as3cf-overlay, .close-as3cf-modal', function ( e ) { + e.preventDefault(); + + // Don't allow children to bubble up click event + if ( e.target !== this ) { + return false; + } + + modal.close(); + } ); + + } ); + + return modal; + +})( jQuery ); + +var as3cfFindAndReplaceModal = (function ( $, as3cfModal ) { + + var modal = { + selector: '.as3cf-find-replace-container', + isBulk: false, + link: null, + payload: {} + }; + + /** + * Open modal + * + * @param string link + * @param mixed payload + */ + modal.open = function ( link, payload ) { + if ( typeof link !== 'undefined' ) { + modal.link = link; + } + if ( typeof payload !== 'undefined' ) { + modal.payload = payload; + } + + as3cfModal.open( modal.selector ); + + $( modal.selector ).find( '.single-file' ).show(); + $( modal.selector ).find( '.multiple-files' ).hide(); + if ( modal.isBulk ) { + $( modal.selector ).find( '.single-file' ).hide(); + $( modal.selector ).find( '.multiple-files' ).show(); + } + }; + + /** + * Close modal + */ + modal.close = function () { + as3cfModal.close( modal.selector ); + }; + + /** + * Set the isBulk flag + */ + modal.setBulk = function ( isBulk ) { + modal.isBulk = isBulk; + }; + + /** + * Create the loading state + */ + modal.startLoading = function () { + $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', true ).siblings( '.spinner' ).css( 'visibility', 'visible' ).show(); + }; + + /** + * Remove the loading state + */ + modal.stopLoading = function () { + $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', false ).siblings( '.spinner' ).css( 'visibility', 'hidden' ).hide(); + }; + + // Setup click handlers + $( document ).ready( function () { + + $( 'body' ).on( 'click', modal.selector + ' [data-find-replace]', function ( e ) { + var findAndReplace = $( this ).data( 'find-replace' ); + + if ( !modal.link ) { + // If there is no link set then this must be an AJAX + // request so trigger an event instead + $( modal.selector ).trigger( 'as3cf-find-and-replace', [ findAndReplace, modal.payload ] ); + return; + } + + if ( findAndReplace ) { + modal.link += '&find_and_replace=1'; + } + + modal.startLoading(); + + window.location = modal.link; + } ); + + $( 'body' ).on( 'as3cf-modal-close', function ( e ) { + modal.isBulk = false; + modal.link = null; + modal.payload = {}; + } ); + + } ); + + return modal; + +})( jQuery, as3cfModal ); + + diff --git a/assets/js/modal.min.js b/assets/js/modal.min.js new file mode 100644 index 00000000..301292c1 --- /dev/null +++ b/assets/js/modal.min.js @@ -0,0 +1 @@ +var as3cfModal=function(a){function b(a){return a.replace(/[^a-z]/g,"")}var c={prefix:"as3cf"},d={};return c.open=function(c,e){var f=b(c);a("body").append('
');var g=a("#as3cf-overlay");g.append('
×
');var h=a("#as3cf-modal");if(void 0===d[f]){var i=a(c);d[f]=i.clone(!0).css("display","block"),i.remove()}h.data("as3cf-modal-target",c).append(d[f]),"function"==typeof e&&e(c),a("body").addClass("as3cf-modal-open"),g.fadeIn(150),h.fadeIn(150),a("body").trigger("as3cf-modal-open",[c])},c.close=function(b){var c=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){"function"==typeof b&&b(c),a("body").removeClass("as3cf-modal-open"),a(this).remove()}),a("body").trigger("as3cf-modal-close",[c])},a(document).ready(function(){a("body").on("click","[data-as3cf-modal]",function(b){b.preventDefault(),c.open(a(this).data("as3cf-modal")+"."+c.prefix)}),a("body").on("click","#as3cf-overlay, .close-as3cf-modal",function(a){return a.preventDefault(),a.target!==this?!1:void c.close()})}),c}(jQuery),as3cfFindAndReplaceModal=function(a,b){var c={selector:".as3cf-find-replace-container",isBulk:!1,link:null,payload:{}};return c.open=function(d,e){"undefined"!=typeof d&&(c.link=d),"undefined"!=typeof e&&(c.payload=e),b.open(c.selector),a(c.selector).find(".single-file").show(),a(c.selector).find(".multiple-files").hide(),c.isBulk&&(a(c.selector).find(".single-file").hide(),a(c.selector).find(".multiple-files").show())},c.close=function(){b.close(c.selector)},c.setBulk=function(a){c.isBulk=a},c.startLoading=function(){a(c.selector+" [data-find-replace]").prop("disabled",!0).siblings(".spinner").css("visibility","visible").show()},c.stopLoading=function(){a(c.selector+" [data-find-replace]").prop("disabled",!1).siblings(".spinner").css("visibility","hidden").hide()},a(document).ready(function(){a("body").on("click",c.selector+" [data-find-replace]",function(){var b=a(this).data("find-replace");return c.link?(b&&(c.link+="&find_and_replace=1"),c.startLoading(),void(window.location=c.link)):void a(c.selector).trigger("as3cf-find-and-replace",[b,c.payload])}),a("body").on("as3cf-modal-close",function(){c.isBulk=!1,c.link=null,c.payload={}})}),c}(jQuery,as3cfModal); \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index 67a2de8c..b677bff1 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -1,311 +1,560 @@ -(function($) { - var saved_settings; +(function( $, as3cfModal ) { + + var savedSettings = {}; + var bucketNamePattern = /[^a-z0-9.-]/; + + var $tabs = $( '.as3cf-tab' ); + var $activeTab; + + /** + * Return the serialized string of the settings form + * excluding the bucket and region inputs as they get saved via AJAX + * + * @param string tab + * + * @returns {string} + */ + function serializedForm( tab ) { + return $( '#' + tab + ' .as3cf-main-settings form' ).find( 'input:not(.no-compare)' ).serialize(); + } + + /** + * Set checkbox + * + * @param string checkbox_wrap + */ + function setCheckbox( checkbox_wrap ) { + var $switch = $( '#' + checkbox_wrap ); + var $checkbox = $switch.find( 'input[type=checkbox]' ); + + $switch.toggleClass( 'on' ).find( 'span' ).toggleClass( 'checked' ); + var switchOn = $switch.find( 'span.on' ).hasClass( 'checked' ); + $checkbox.attr( 'checked', switchOn ).trigger( 'change' ); + } + + as3cf.tabs = { + /** + * Toggle settings tab + * + * @param string hash + * @param bool persist_updated_notice + */ + toggle: function( hash, persist_updated_notice ) { + $tabs.hide(); + $activeTab = $( '#tab-' + hash ); + $activeTab.show(); + $( '.nav-tab' ).removeClass( 'nav-tab-active' ); + $( 'a.nav-tab[data-tab="' + hash + '"]' ).addClass( 'nav-tab-active' ); + $( '.aws-main' ).attr( 'data-tab', hash ); + if ( $activeTab.attr( 'data-prefix' ) ) { + as3cfModal.prefix = $activeTab.attr( 'data-prefix' ); + } + if ( !persist_updated_notice ) { + $( '.as3cf-updated' ).removeClass( 'show' ); + } + } + }; + + /** + * Load bucket list + * + * @param bool forceUpdate + */ + function loadBucketList( forceUpdate ) { + if ( 'undefined' === typeof forceUpdate ) { + forceUpdate = false; + } - $( document ).ready( function() { + var $bucketList = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-bucket-list' ); + var selectedBucket = $( '#' + as3cfModal.prefix + '-bucket' ).val(); - $( '.as3cf-settings' ).each( function() { - var $container = $( this ); - var $bucketList = $( '.as3cf-bucket-list' ); - var $createBucketForm = $container.find( '.as3cf-create-bucket-form' ); - var $manualBucketForm = $container.find( '.as3cf-manual-save-bucket-form' ); + if ( false === forceUpdate && $bucketList.find( 'li' ).length > 1 ) { + $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); + $( '.as3cf-bucket-list a[data-bucket="' + selectedBucket + '"]' ).addClass( 'selected' ); - if ( $createBucketForm.length ) { - var $createBucketButton = $createBucketForm.find( 'button' ); - var origButtonText = $createBucketButton.text(); + scrollToSelectedBucket(); + return; + } - $createBucketForm.on( 'submit', function( e ) { - e.preventDefault(); + $bucketList.html( '
  • ' + $bucketList.attr( 'data-working' ) + '
  • ' ); + + var data = { + action: as3cfModal.prefix + '-get-buckets', + _nonce: window[ as3cfModal.prefix.replace( /-/g, '_' ) ].nonces.get_buckets + }; + + $.ajax( { + url: ajaxurl, + type: 'POST', + dataType: 'JSON', + data: data, + error: function( jqXHR, textStatus, errorThrown ) { + $bucketList.html( '' ); + showBucketError( as3cf.strings.get_buckets_error, errorThrown, 'as3cf-bucket-select' ); + }, + success: function( data, textStatus, jqXHR ) { + $bucketList.html( '' ); + + if ( typeof data[ 'success' ] !== 'undefined' ) { $( '.as3cf-bucket-error' ).hide(); - $bucketList.addClass( 'saving' ); - $createBucketButton.text( $createBucketButton.attr( 'data-working' ) ); - $createBucketButton.prop( 'disabled', true ); - var bucketName = $createBucketForm.find( 'input[name="bucket_name"]' ).val(); - - var data = { - action : 'as3cf-create-bucket', - bucket_name: bucketName, - _nonce : as3cf_i18n.create_bucket_nonce - }; - - $.ajax( { - url : ajaxurl, - type : 'POST', - dataType: 'JSON', - data : data, - error : function( jqXHR, textStatus, errorThrown ) { - $createBucketButton.text( origButtonText ); - show_bucket_error( as3cf_i18n.create_bucket_error, errorThrown ); - }, - success : function( data, textStatus, jqXHR ) { - $createBucketButton.text( origButtonText ); - $createBucketButton.prop( 'disabled', false ); - if ( typeof data[ 'success' ] !== 'undefined' ) { - bucket_select( bucketName, data[ 'region' ], data[ 'can_write' ] ); - // tidy up create bucket form - $createBucketForm.find( 'input[name="bucket_name"]' ).val( '' ); - if ( $( '.as3cf-bucket-list-wrapper' ).is( ':visible' ) ) { - loadBuckets(); - } - $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); - $bucketList.removeClass( 'saving' ); - $manualBucketForm.find( 'input[name="bucket_name"]' ).val( bucketName ); - } else { - show_bucket_error( as3cf_i18n.create_bucket_error, data[ 'error' ] ); - } - } - } ); - } ); - } - if ( $manualBucketForm.length ) { - var $manualBucketButton = $manualBucketForm.find( 'button' ); - var origManualButtonText = $manualBucketButton.text(); - - $manualBucketForm.on( 'submit', function( e ) { - e.preventDefault(); - var bucketName = $manualBucketForm.find( 'input[name="bucket_name"]' ).val(); - if ( bucketName === $( '.as3cf-active-bucket' ).text() ) { - $( '.as3cf-bucket-error' ).hide(); - $( '.as3cf-settings' ).addClass( 'as3cf-has-bucket' ); - return; - } - $( '.as3cf-bucket-error' ).hide(); - $manualBucketButton.text( $manualBucketButton.attr( 'data-working' ) ); - $manualBucketButton.prop( 'disabled', true ); - - var data = { - action : 'as3cf-manual-save-bucket', - bucket_name: bucketName, - _nonce : as3cf_i18n.manual_bucket_nonce - }; - - $.ajax( { - url : ajaxurl, - type : 'POST', - dataType: 'JSON', - data : data, - error : function( jqXHR, textStatus, errorThrown ) { - $manualBucketButton.text( origManualButtonText ); - show_bucket_error( as3cf_i18n.save_bucket_error, errorThrown ); - }, - success : function( data, textStatus, jqXHR ) { - $manualBucketButton.text( origManualButtonText ); - $manualBucketButton.prop( 'disabled', false ); - if ( typeof data[ 'success' ] !== 'undefined' ) { - bucket_select( bucketName, data[ 'region' ], data[ 'can_write' ] ); - $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); - } else { - show_bucket_error( as3cf_i18n.save_bucket_error, data[ 'error' ] ); - } - } + $( data[ 'buckets' ] ).each( function( idx, bucket ) { + var bucketClass = bucket.Name === selectedBucket ? 'selected' : ''; + $bucketList.append( '
  • ' + bucket.Name + '
  • ' ); } ); - } ); - } - - var $changeBucket = $container.find( '.as3cf-change-bucket' ); - if ( $changeBucket.length ) { - $changeBucket.on( 'click', function( e ) { - e.preventDefault(); - $( '.updated' ).not( '.as3cf-notice' ).hide(); - $( '.as3cf-can-write-error' ).hide(); - $( '.as3cf-settings' ).removeClass( 'as3cf-has-bucket' ); - if ( $( '.as3cf-bucket-list-wrapper' ).is( ':visible' ) ) { - loadBuckets(); - if ( $( '.as3cf-active-bucket' ).html ) { - $( '.as3cf-cancel-bucket-select-wrap' ).show(); - } - } - } ); - } - var $refreshBuckets = $container.find( '.as3cf-refresh-buckets' ); - if ( $refreshBuckets.length ) { - $refreshBuckets.on( 'click', function( e ) { - e.preventDefault(); - loadBuckets(); - } ); + scrollToSelectedBucket(); + } else { + showBucketError( as3cf.strings.get_buckets_error, data[ 'error' ], 'as3cf-bucket-select' ); + } } + } ); + } + + /** + * Scroll to selected bucket + */ + function scrollToSelectedBucket() { + if ( !$( '.as3cf-bucket-list a.selected' ).length ) { + return; + } - var $cancelChangeBucket = $container.find( '.as3cf-cancel-bucket-select' ); - if ( $cancelChangeBucket.length ) { - $cancelChangeBucket.on( 'click', function( e ) { - e.preventDefault(); - $( '.as3cf-bucket-error' ).hide(); - $( '.as3cf-settings' ).addClass( 'as3cf-has-bucket' ); - } ); - } + var offset = $( 'ul.as3cf-bucket-list li' ).first().position().top + 150; + $( '.as3cf-bucket-list' ).animate( { + scrollTop: $( 'ul.as3cf-bucket-list li a.selected' ).position().top - offset } ); + } + + /** + * Reset bucket modal + */ + function resetBucketModal() { + var $bucketContainer = $( '.as3cf-bucket-container.' + as3cfModal.prefix ); + + if ( false === $activeTab.hasClass( 'as3cf-has-bucket' ) || 'manual' === $( '#' + as3cfModal.prefix + '-bucket-select' ).val() ) { + $bucketContainer.find( '.as3cf-bucket-manual' ).show().siblings().hide(); + $bucketContainer.find( '.bucket-actions.manual' ).show().siblings( '.bucket-actions' ).hide(); + } else { + $bucketContainer.find( '.as3cf-bucket-select' ).show().siblings().hide(); + $bucketContainer.find( '.bucket-actions.select' ).show().siblings( '.bucket-actions' ).hide(); + + loadBucketList(); + } - var $bucketList = $( '.as3cf-bucket-list' ); + $bucketContainer.find( '.as3cf-bucket-error' ).hide(); - function loadBuckets() { - $( '.as3cf-bucket-error' ).hide(); - $bucketList.html( '
  • ' + $bucketList.attr( 'data-working' ) + '
  • ' ); - - var data = { - action: 'as3cf-get-buckets', - _nonce: as3cf_i18n.get_buckets_nonce - }; - - $.ajax( { - url : ajaxurl, - type : 'POST', - dataType: 'JSON', - data : data, - error : function( jqXHR, textStatus, errorThrown ) { - $bucketList.html( '' ); - show_bucket_error( as3cf_i18n.get_buckets_error, errorThrown ); - }, - success : function( data, textStatus, jqXHR ) { - $bucketList.html( '' ); - if ( typeof data[ 'success' ] !== 'undefined' ) { - $( data[ 'buckets' ] ).each( function( idx, bucket ) { - var bucket_class = ( - bucket.Name === data[ 'selected' ] - ) ? 'selected' : ''; - $bucketList.append( '
  • ' + bucket.Name + '
  • ' ); - } ); - scroll_to_selected_bucket(); - } else { - show_bucket_error( as3cf_i18n.get_buckets_error, data[ 'error' ] ); - } - } - } ); - } + // Reset manual select value + var bucket = $( '#' + as3cfModal.prefix + '-bucket' ).val(); + $bucketContainer.find( '.as3cf-bucket-manual .as3cf-bucket-name' ).val( bucket ); + } - $bucketList.on( 'click', 'a', function( e ) { - e.preventDefault(); + /** + * Save manual bucket + */ + function saveManualBucket() { + var $manualBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-manual-save-bucket-form' ); + var $manualBucketInput = $manualBucketForm.find( '.as3cf-bucket-name' ); + var $manualBucketButton = $manualBucketForm.find( 'button[type=submit]' ); + var bucketName = $manualBucketInput.val(); + var originalBucketText = $manualBucketButton.first().text(); - if ( $( this ).hasClass( 'selected' ) ) { - $( '.as3cf-settings' ).addClass( 'as3cf-has-bucket' ); - return; + if ( bucketName === $( '#' + as3cfModal.prefix + '-active-bucket' ).text() ) { + $( '.as3cf-bucket-error' ).hide(); + $activeTab.addClass( 'as3cf-has-bucket' ); + as3cfModal.close(); + return; + } + $( '.as3cf-bucket-error' ).hide(); + $manualBucketButton.text( $manualBucketButton.attr( 'data-working' ) ); + $manualBucketButton.prop( 'disabled', true ); + + var data = { + action: as3cfModal.prefix + '-manual-save-bucket', + bucket_name: bucketName, + _nonce: window[ as3cfModal.prefix.replace( /-/g, '_' ) ].nonces.manual_bucket + }; + + $.ajax( { + url: ajaxurl, + type: 'POST', + dataType: 'JSON', + data: data, + error: function( jqXHR, textStatus, errorThrown ) { + $manualBucketButton.text( originalBucketText ); + showBucketError( as3cf.strings.save_bucket_error, errorThrown, 'as3cf-bucket-manual' ); + }, + success: function( data, textStatus, jqXHR ) { + $manualBucketButton.text( originalBucketText ); + $manualBucketButton.prop( 'disabled', false ); + if ( typeof data[ 'success' ] !== 'undefined' ) { + bucketSelect( bucketName, data[ 'region' ], data[ 'can_write' ] ); + $( '#' + as3cfModal.prefix + '-bucket-select' ).val( 'manual' ); + $( '.as3cf-bucket-list a' ).removeClass( 'selected' ).filter( '[data-bucket=' + bucketName + ']' ).addClass( 'selected' ); + } else { + showBucketError( as3cf.strings.save_bucket_error, data[ 'error' ], 'as3cf-bucket-manual' ); + } } + } ); + } + + /** + * Save select bucket + * + * @param object $link + */ + function saveSelectBucket( $link ) { + var $bucketList = $( '.as3cf-bucket-list' ); - var bucket = this; - var previous_bucket = $( '.as3cf-bucket-list a.selected' ).attr( 'data-bucket' ); + if ( $link.hasClass( 'selected' ) ) { + $activeTab.addClass( 'as3cf-has-bucket' ); + as3cfModal.close(); + return; + } - $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); - $( bucket ).addClass( 'selected' ); - - $bucketList.addClass( 'saving' ); - $( bucket ).find( '.spinner' ).show(); - var bucketName = $( bucket ).attr( 'data-bucket' ); - - var data = { - action : 'as3cf-save-bucket', - bucket_name: bucketName, - _nonce : as3cf_i18n.save_bucket_nonce - }; - - $.ajax( { - url : ajaxurl, - type : 'POST', - dataType: 'JSON', - data : data, - error : function( jqXHR, textStatus, errorThrown ) { - $bucketList.removeClass( 'saving' ); - show_bucket_error( as3cf_i18n.save_bucket_error, errorThrown ); + var previousBucket = $( '.as3cf-bucket-list a.selected' ).attr( 'data-bucket' ); + + $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); + $link.addClass( 'selected' ); + + $bucketList.addClass( 'saving' ); + $link.find( '.spinner' ).show().css( 'visibility', 'visible' ); + var bucketName = $link.attr( 'data-bucket' ); + + var data = { + action: as3cfModal.prefix + '-save-bucket', + bucket_name: bucketName, + _nonce: window[ as3cfModal.prefix.replace( /-/g, '_' ) ].nonces.save_bucket + }; + + $.ajax( { + url: ajaxurl, + type: 'POST', + dataType: 'JSON', + data: data, + error: function( jqXHR, textStatus, errorThrown ) { + $bucketList.removeClass( 'saving' ); + showBucketError( as3cf.strings.save_bucket_error, errorThrown, 'as3cf-bucket-select' ); + $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); + $( '.as3cf-bucket-list a[data-bucket="' + previousBucket + '"]' ).addClass( 'selected' ); + }, + success: function( data, textStatus, jqXHR ) { + $link.find( '.spinner' ).hide().css( 'visibility', 'hidden' ); + $bucketList.removeClass( 'saving' ); + if ( typeof data[ 'success' ] !== 'undefined' ) { + bucketSelect( bucketName, data[ 'region' ], data[ 'can_write' ] ); + $( '#' + as3cfModal.prefix + '-bucket-select' ).val( '' ); + } else { + showBucketError( as3cf.strings.save_bucket_error, data[ 'error' ], 'as3cf-bucket-select' ); $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); - $( '.as3cf-bucket-list a[data-bucket="' + previous_bucket + '"]' ).addClass( 'selected' ); - }, - success : function( data, textStatus, jqXHR ) { - $( bucket ).find( '.spinner' ).hide(); - $bucketList.removeClass( 'saving' ); - if ( typeof data[ 'success' ] !== 'undefined' ) { - bucket_select( bucketName, data[ 'region' ], data[ 'can_write' ] ); - } else { - show_bucket_error( as3cf_i18n.save_bucket_error, data[ 'error' ] ); - $( '.as3cf-bucket-list a' ).removeClass( 'selected' ); - $( '.as3cf-bucket-list a[data-bucket="' + previous_bucket + '"]' ).addClass( 'selected' ); - } + $( '.as3cf-bucket-list a[data-bucket="' + previousBucket + '"]' ).addClass( 'selected' ); } - } ); + } } ); + } + + /** + * Disable bucket buttons + */ + function disableBucketButtons() { + if ( 0 === $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-create-bucket-form' ).length ) { + return; + } - function scroll_to_selected_bucket() { - if ( ! $( '.as3cf-bucket-list a.selected' ).length ) { - return; - } + var $createBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-create-bucket-form' ); + var $manualBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-manual-save-bucket-form' ); - var offset = $( 'ul.as3cf-bucket-list li' ).first().position().top + 150; + if ( $createBucketForm.find( '.as3cf-bucket-name' ).val().length < 3 ) { + $createBucketForm.find( 'button[type=submit]' ).attr( 'disabled', true ); + } + else { + $createBucketForm.find( 'button[type=submit]' ).attr( 'disabled', false ); + } - $( 'ul.as3cf-bucket-list' ).animate( { - scrollTop: $( 'ul.as3cf-bucket-list li a.selected' ).position().top - offset - } ); + if ( $manualBucketForm.find( '.as3cf-bucket-name' ).val().length < 3 ) { + $manualBucketForm.find( 'button[type=submit]' ).attr( 'disabled', true ); + } + else { + $manualBucketForm.find( 'button[type=submit]' ).attr( 'disabled', false ); + } + } + + /** + * Show bucket error + * + * @param title + * @param error + * @param context + */ + function showBucketError( title, error, context ) { + var $activeView = $( '.as3cf-bucket-container' ).children( ':visible' ); + var $bucketError = $activeView.find( '.as3cf-bucket-error' ); + + context = ( 'undefined' === typeof context ) ? null : context; + + if ( context && ! $activeView.hasClass( context ) ) { + return; } - function show_bucket_error( title, error ) { - $( '.as3cf-bucket-error span.title' ).html( title ); - $( '.as3cf-bucket-error span.message' ).html( error ); - $( '.as3cf-bucket-error' ).show(); + $bucketError.find( 'span.title' ).html( title ); + $bucketError.find( 'span.message' ).html( error ); + $bucketError.show(); + } + + /** + * Select bucket + * + * @param string title + * @param string error + * @param bool canWrite + */ + function bucketSelect( bucket, region, canWrite ) { + var $manualBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-manual-save-bucket-form' ); + var $activeBucket = $( '#' + as3cfModal.prefix + '-active-bucket' ); + + if ( 'as3cf' === as3cfModal.prefix && '' === $activeBucket.text() ) { + // first time bucket select - enable main options by default + setCheckbox( 'copy-to-s3-wrap' ); + setCheckbox( 'serve-from-s3-wrap' ); } - function bucket_select( bucket, region, can_write ) { - if ( '' === $( '.as3cf-active-bucket' ).text() ) { - // first time bucket select - enable main options by default - set_checkbox( 'copy-to-s3-wrap' ); - set_checkbox( 'serve-from-s3-wrap' ); - } - $( '.as3cf-active-bucket' ).text( bucket ); - $( 'form.as3cf-manual-save-bucket-form .as3cf-bucket-name' ).val( bucket ); - $( '#as3cf-bucket' ).val( bucket ); - $( '#as3cf-region' ).val( region ); - $( '.updated' ).not( '.as3cf-notice' ).show(); - // check permission on bucket - if ( can_write === false ) { - $( '.as3cf-can-write-error' ).show(); + $activeBucket.text( bucket ); + $manualBucketForm.find( '.as3cf-bucket-name' ).val( bucket ); + $( '#' + as3cfModal.prefix + '-bucket' ).val( bucket ); + $( '#' + as3cfModal.prefix + '-region' ).val( region ); + $( '.updated' ).not( '.as3cf-notice' ).show(); + + $activeTab.addClass( 'as3cf-has-bucket' ); + // check permission on bucket + $activeTab.find( '.as3cf-can-write-error' ).toggle( !canWrite ); + $activeTab.find( '.as3cf-bucket-error' ).hide(); + + if ( 'as3cf' === as3cfModal.prefix ) { + generateUrlPreview(); + } + + as3cfModal.close(); + } + + /** + * Generate URL preview + */ + function generateUrlPreview() { + $( '.as3cf-url-preview' ).html( 'Generating...' ); + + var data = { + _nonce: as3cf.nonces.get_url_preview + }; + + $.each( $( '#tab-media .as3cf-main-settings form' ).serializeArray(), function( i, o ) { + var n = o.name, + v = o.value; + n = n.replace( '[]', '' ); + data[ n ] = data[ n ] === undefined ? v : $.isArray( data[ n ] ) ? data[ n ].concat( v ) : [ data[ n ], v ]; + } ); + + // overwrite the save action stored in the form + data[ 'action' ] = 'as3cf-get-url-preview'; + + $.ajax( { + url: ajaxurl, + type: 'POST', + dataType: 'JSON', + data: data, + error: function( jqXHR, textStatus, errorThrown ) { + alert( as3cf.strings.get_url_preview_error + errorThrown ); + }, + success: function( data, textStatus, jqXHR ) { + if ( typeof data[ 'success' ] !== 'undefined' ) { + $( '.as3cf-url-preview' ).html( data[ 'url' ] ); + } else { + alert( as3cf.strings.get_url_preview_error + data[ 'error' ] ); + } } - $( '.as3cf-settings' ).addClass( 'as3cf-has-bucket' ); - generate_url_preview(); + } ); + } + + /** + * Check for a valid bucket name + * + * Bucket names must be at least 3 and no more than 63 characters long. + * They can contain lowercase letters, numbers, periods and hyphens. + * + * @param string bucketName + * + * @return bool + */ + function isValidBucketName( bucketName ) { + if ( bucketName.length < 3 || bucketName.length > 63 ) { + return false; + } + if ( true === bucketNamePattern.test( bucketName ) ) { + return false; } - $( '.as3cf-switch' ).on( 'click', 'span', function( e ) { - if ( ! $( this ).parent().hasClass( 'disabled' ) ) { - var parent_id = $( this ).parent().attr( 'id' ); - set_checkbox( parent_id ); + return true; + } + + /** + * Update invalid bucket name notice + * + * @param string bucketName + */ + function updateBucketNameNotice( bucketName ) { + var message = null; + + if ( true === bucketNamePattern.test( bucketName ) ) { + message = as3cf.strings.create_bucket_invalid_chars; + } else if ( bucketName.length < 3 ) { + message = as3cf.strings.create_bucket_name_short; + } else if ( bucketName.length > 63 ) { + message = as3cf.strings.create_bucket_name_long; + } + + if ( message && bucketName.length > 0 ) { + $( '.as3cf-invalid-bucket-name' ).html( message ); + } else { + $( '.as3cf-invalid-bucket-name' ).html( '' ); + } + } + + /** + * Save create bucket + */ + function saveCreateBucket() { + var $createBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-create-bucket-form' ); + var $createBucketInput = $createBucketForm.find( '.as3cf-bucket-name' ); + var $createBucketSelect = $createBucketForm.find( '.bucket-create-region' ); + var $createBucketButton = $createBucketForm.find( 'button[type=submit]' ); + + var bucketName = $createBucketInput.val(); + var origButtonText = $createBucketButton.text(); + + $( '.as3cf-bucket-error' ).hide(); + $createBucketButton.text( $createBucketButton.attr( 'data-working' ) ); + $createBucketButton.prop( 'disabled', true ); + + var data = { + action: as3cfModal.prefix + '-create-bucket', + bucket_name: bucketName, + _nonce: window[ as3cfModal.prefix.replace( /-/g, '_' ) ].nonces.create_bucket + }; + + if ( $createBucketSelect.val() ) { + data[ 'region' ] = $createBucketSelect.val(); + } + + $.ajax( { + url: ajaxurl, + type: 'POST', + dataType: 'JSON', + data: data, + error: function( jqXHR, textStatus, errorThrown ) { + $createBucketButton.text( origButtonText ); + showBucketError( as3cf.strings.create_bucket_error, errorThrown, 'as3cf-bucket-create' ); + }, + success: function( data, textStatus, jqXHR ) { + $createBucketButton.text( origButtonText ); + $createBucketButton.prop( 'disabled', false ); + if ( typeof data[ 'success' ] !== 'undefined' ) { + bucketSelect( bucketName, data[ 'region' ], data[ 'can_write' ] ); + // tidy up create bucket form + $( '.as3cf-bucket-select-region' ).hide(); + $( '.as3cf-bucket-select-region' ).removeAttr( 'selected' ); + $createBucketInput.val( '' ); + $createBucketButton.attr( 'disabled', true ); + } else { + showBucketError( as3cf.strings.create_bucket_error, data[ 'error' ], 'as3cf-bucket-create' ); + } } } ); + } + + $( document ).ready( function() { - function set_checkbox( checkbox_wrap ) { - $( '#' + checkbox_wrap + ' span' ).toggleClass( 'checked' ); - var switch_on = $( '#' + checkbox_wrap + ' span.on' ).hasClass( 'checked' ); - var checkbox_name = $( '#' + checkbox_wrap ).data( 'checkbox' ); - var $checkbox = $( 'input#' + checkbox_name ); - $checkbox.attr( "checked", switch_on ); - $checkbox.trigger( "change" ); + // Tabs + // -------------------- + + // Move any compatibility errors below the nav tabs + var $navTabs = $( '.wrap.aws-main .nav-tab-wrapper' ); + $( '.aws-compatibility-notice, div.updated, div.error, div.notice' ).not( '.below-h2, .inline' ).insertAfter( $navTabs ); + + // check for hash in url and switch tabs accordingly + if ( window.location.hash ) { + var hash = window.location.hash.substring( 1 ); + as3cf.tabs.toggle( hash, true ); + } else { + // default settings tab + var defaultTab = 'media'; + $activeTab = $( '#tab-' + defaultTab ); + $( '.aws-main' ).attr( 'data-tab', defaultTab ); } - if ( $( '.as3cf-settings' ).length && ! $( '.as3cf-settings' ).hasClass( 'as3cf-has-bucket' ) ) { - if ( $( '.as3cf-bucket-list-wrapper' ).is( ':visible' ) ) { - loadBuckets(); + $( '.aws-main' ).on( 'click', '.nav-tab', function( e ) { + e.preventDefault(); + if ( $( this ).hasClass( 'nav-tab-active' ) ) { + return; } + var nextTab = $( this ).attr( 'data-tab' ); + as3cf.tabs.toggle( nextTab ); + if ( 'media' === nextTab ) { + // As it's the default remove the hash + window.location.hash = ''; + if ( typeof window.history.replaceState === 'function' && '#' === window.location.href.slice( -1 ) ) { + // Strip the # if still on the end of the URL + history.replaceState( {}, '', window.location.href.slice( 0, -1 ) ); + } + } else { + window.location.hash = nextTab; + } + } ); + + // Settings + // -------------------- + + // save the original state of the forms for comparison later + if ( $tabs.length ) { + $tabs.each( function( i, tab ) { + savedSettings[ tab.id ] = serializedForm( tab.id ); + } ); } - $( '.as3cf-settings' ).on( 'change', '.sub-toggle', function( e ) { - var setting = $( this ).attr( 'id' ); - $( '.as3cf-setting.' + setting ).toggleClass( 'hide' ); + // prompt user with dialog if leaving the settings page with unsaved changes + $( window ).on( 'beforeunload.as3cf-settings', function() { + if ( $.isEmptyObject( savedSettings ) ) { + return; + } + + var tab = $activeTab.attr( 'id' ); + + if ( serializedForm( tab ) !== savedSettings[ tab ] ) { + return as3cf.strings.save_alert; + } } ); - $( '.as3cf-settings' ).on( 'click', '.as3cf-manual-bucket-toggle', function( e ) { - e.preventDefault(); - $( '.as3cf-bucket-select' ).addClass( 'manual' ); + // let the save settings submit happen as normal + $( document ).on( 'submit', '.as3cf-main-settings form', function( e ) { + // disable unload warning + $( window ).off( 'beforeunload.as3cf-settings' ); } ); - $( '.as3cf-settings' ).on( 'click', '.as3cf-bucket-list-toggle', function( e ) { - e.preventDefault(); - loadBuckets(); - $( '.as3cf-bucket-select' ).removeClass( 'manual' ); - $( '.as3cf-cancel-bucket-select-wrap' ).show(); + $( '.as3cf-switch' ).on( 'click', function( e ) { + if ( !$( this ).hasClass( 'disabled' ) ) { + setCheckbox( $( this ).attr( 'id' ) ); + } + } ); + + $tabs.on( 'change', '.sub-toggle', function( e ) { + var setting = $( this ).attr( 'id' ); + $( '.as3cf-setting.' + setting ).toggleClass( 'hide' ); } ); $( '.as3cf-domain' ).on( 'change', 'input[type="radio"]', function( e ) { - var domain = $( 'input:radio[name="domain"]:checked' ).val(); - if ( 'cloudfront' === domain && $( '.as3cf-setting.cloudfront' ).hasClass( 'hide' ) ) { - $( '.as3cf-setting.cloudfront' ).removeClass( 'hide' ); - } else { - $( '.as3cf-setting.cloudfront' ).addClass( 'hide' ); - } + var $selected = $( this ).closest( 'input:radio[name="domain"]:checked' ); + var domain = $selected.val(); + var $cloudfront = $( this ).parents( '.as3cf-domain' ).find( '.as3cf-setting.cloudfront' ); + var cloudfrontSelected = ( 'cloudfront' === domain ); + $cloudfront.toggleClass( 'hide', !cloudfrontSelected ); } ); $( '.as3cf-ssl' ).on( 'change', 'input[type="radio"]', function( e ) { @@ -324,72 +573,98 @@ } ); $( '.url-preview' ).on( 'change', 'input', function( e ) { - generate_url_preview(); + generateUrlPreview(); } ); - function generate_url_preview() { - $( '.as3cf-url-preview' ).html( 'Generating...' ); + // Bucket select + // -------------------- - var data = { - _nonce: as3cf_i18n.get_url_preview_nonce - }; + // Move bucket errors + $( '#tab-media > .as3cf-bucket-error' ).detach().insertAfter( '.as3cf-bucket-container h3' ); - $.each( $( ".as3cf-main-settings form" ).serializeArray(), function( i, o ) { - var n = o.name, - v = o.value; - n = n.replace( '[]', '' ); - data[ n ] = data[ n ] === undefined ? v - : $.isArray( data[ n ] ) ? data[ n ].concat( v ) - : [ data[ n ], v ]; - } ); + // Action click handlers + $( 'body' ).on( 'click', '.bucket-action-manual', function( e ) { + e.preventDefault(); + $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-bucket-manual' ).show().siblings().hide(); + } ); + $( 'body' ).on( 'click', '.bucket-action-browse', function( e ) { + e.preventDefault(); + $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-bucket-select' ).show().siblings().hide(); - // overwrite the save action stored in the form - data[ 'action' ] = 'as3cf-get-url-preview'; - - $.ajax( { - url : ajaxurl, - type : 'POST', - dataType: 'JSON', - data : data, - error : function( jqXHR, textStatus, errorThrown ) { - alert( as3cf_i18n.get_url_preview_error + errorThrown ); - }, - success : function( data, textStatus, jqXHR ) { - if ( typeof data[ 'success' ] !== 'undefined' ) { - $( '.as3cf-url-preview' ).html( data[ 'url' ] ); - } else { - alert( as3cf_i18n.get_url_preview_error + data[ 'error' ] ); - } - } - } ); - } + loadBucketList(); + } ); + $( 'body' ).on( 'click', '.bucket-action-create', function( e ) { + e.preventDefault(); - /** - * Return the serialized string of the settings form - * excluding the bucket and region inputs as they get saved via AJAX - * - * @returns {string} - */ - function serialized_form() { - return $( '.as3cf-main-settings form' ).find( 'input:not(.no-compare)' ).serialize(); - } + // Reset create bucket modal + $( '.as3cf-bucket-name' ).val( '' ); + $( '.as3cf-invalid-bucket-name' ).html( '' ); - // save the original state of the form for comparison later - saved_settings = serialized_form(); + $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-bucket-create' ).show().siblings().hide(); + } ); + $( 'body' ).on( 'click', '.bucket-action-cancel', function( e ) { + e.preventDefault(); + resetBucketModal(); + } ); + $( 'body' ).on( 'click', '.bucket-action-save', function( e ) { + e.preventDefault(); + saveManualBucket(); + } ); + $( 'body' ).on( 'click', '.as3cf-create-bucket-form button[type="submit"]', function( e ) { + e.preventDefault(); + saveCreateBucket(); + } ); - // let the save settings submit happen as normal - $( document ).on( 'submit', '.as3cf-main-settings form', function( event ) { - // disable unload warning - $( window ).off( 'beforeunload.as3cf-settings' ); + // Bucket list refresh handler + $( 'body' ).on( 'click', '.bucket-action-refresh', function( e ) { + e.preventDefault(); + loadBucketList( true ); } ); - // prompt user with dialog if leaving the settings page with unsaved changes - $( window ).on( 'beforeunload.as3cf-settings', function() { - if ( serialized_form() !== saved_settings ) { - return as3cf_i18n.save_alert; + // Bucket list click handler + $( 'body' ).on( 'click', '.as3cf-bucket-list a', function( e ) { + e.preventDefault(); + saveSelectBucket( $( this ) ); + } ); + + // Modal open + $( 'body' ).on( 'as3cf-modal-open', function( e, target ) { + if ( '.as3cf-bucket-container.' + as3cfModal.prefix === target ) { + // Reset modal + resetBucketModal(); + // Change manual title text + var title = $( '.as3cf-bucket-manual h3' ).data( 'modal-title' ); + $( '.as3cf-bucket-manual h3' ).text( title ); + // Hide buttons + disableBucketButtons(); + } + } ); + disableBucketButtons(); + + // Validate bucket name on create + $( 'body' ).on( 'input keyup', '.as3cf-create-bucket-form .as3cf-bucket-name', function( e ) { + var bucketName = $( this ).val(); + var $createBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-create-bucket-form' ); + + if ( isValidBucketName( bucketName ) ) { + $createBucketForm.find( 'button[type=submit]' ).removeAttr( 'disabled' ); + } else { + $createBucketForm.find( 'button[type=submit]' ).attr( 'disabled', true ); + } + updateBucketNameNotice( bucketName ); + } ); + + // Check bucket name length on manual + $( 'body' ).on( 'input keyup', '.as3cf-manual-save-bucket-form .as3cf-bucket-name', function( e ) { + var $manualBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-manual-save-bucket-form' ); + + if ( $manualBucketForm.find( '.as3cf-bucket-name' ).val().length < 3 ) { + $manualBucketForm.find( 'button[type=submit]' ).attr( 'disabled', true ); + } else { + $manualBucketForm.find( 'button[type=submit]' ).removeAttr( 'disabled' ); } } ); } ); -})(jQuery); \ No newline at end of file +})( jQuery, as3cfModal ); diff --git a/assets/js/script.min.js b/assets/js/script.min.js index c1f19b52..02bd8af3 100644 --- a/assets/js/script.min.js +++ b/assets/js/script.min.js @@ -1 +1 @@ -!function(a){var b;a(document).ready(function(){function c(){a(".as3cf-bucket-error").hide(),j.html('
  • '+j.attr("data-working")+"
  • ");var b={action:"as3cf-get-buckets",_nonce:as3cf_i18n.get_buckets_nonce};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:b,error:function(a,b,c){j.html(""),e(as3cf_i18n.get_buckets_error,c)},success:function(b){j.html(""),"undefined"!=typeof b.success?(a(b.buckets).each(function(a,c){var d=c.Name===b.selected?"selected":"";j.append('
  • '+c.Name+'
  • ')}),d()):e(as3cf_i18n.get_buckets_error,b.error)}})}function d(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a("ul.as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}}function e(b,c){a(".as3cf-bucket-error span.title").html(b),a(".as3cf-bucket-error span.message").html(c),a(".as3cf-bucket-error").show()}function f(b,c,d){""===a(".as3cf-active-bucket").text()&&(g("copy-to-s3-wrap"),g("serve-from-s3-wrap")),a(".as3cf-active-bucket").text(b),a("form.as3cf-manual-save-bucket-form .as3cf-bucket-name").val(b),a("#as3cf-bucket").val(b),a("#as3cf-region").val(c),a(".updated").not(".as3cf-notice").show(),d===!1&&a(".as3cf-can-write-error").show(),a(".as3cf-settings").addClass("as3cf-has-bucket"),h()}function g(b){a("#"+b+" span").toggleClass("checked");var c=a("#"+b+" span.on").hasClass("checked"),d=a("#"+b).data("checkbox"),e=a("input#"+d);e.attr("checked",c),e.trigger("change")}function h(){a(".as3cf-url-preview").html("Generating...");var b={_nonce:as3cf_i18n.get_url_preview_nonce};a.each(a(".as3cf-main-settings form").serializeArray(),function(c,d){var e=d.name,f=d.value;e=e.replace("[]",""),b[e]=void 0===b[e]?f:a.isArray(b[e])?b[e].concat(f):[b[e],f]}),b.action="as3cf-get-url-preview",a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:b,error:function(a,b,c){alert(as3cf_i18n.get_url_preview_error+c)},success:function(b){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf_i18n.get_url_preview_error+b.error)}})}function i(){return a(".as3cf-main-settings form").find("input:not(.no-compare)").serialize()}a(".as3cf-settings").each(function(){var b=a(this),d=a(".as3cf-bucket-list"),g=b.find(".as3cf-create-bucket-form"),h=b.find(".as3cf-manual-save-bucket-form");if(g.length){var i=g.find("button"),j=i.text();g.on("submit",function(b){b.preventDefault(),a(".as3cf-bucket-error").hide(),d.addClass("saving"),i.text(i.attr("data-working")),i.prop("disabled",!0);var k=g.find('input[name="bucket_name"]').val(),l={action:"as3cf-create-bucket",bucket_name:k,_nonce:as3cf_i18n.create_bucket_nonce};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:l,error:function(a,b,c){i.text(j),e(as3cf_i18n.create_bucket_error,c)},success:function(b){i.text(j),i.prop("disabled",!1),"undefined"!=typeof b.success?(f(k,b.region,b.can_write),g.find('input[name="bucket_name"]').val(""),a(".as3cf-bucket-list-wrapper").is(":visible")&&c(),a(".as3cf-bucket-list a").removeClass("selected"),d.removeClass("saving"),h.find('input[name="bucket_name"]').val(k)):e(as3cf_i18n.create_bucket_error,b.error)}})})}if(h.length){var k=h.find("button"),l=k.text();h.on("submit",function(b){b.preventDefault();var c=h.find('input[name="bucket_name"]').val();if(c===a(".as3cf-active-bucket").text())return a(".as3cf-bucket-error").hide(),void a(".as3cf-settings").addClass("as3cf-has-bucket");a(".as3cf-bucket-error").hide(),k.text(k.attr("data-working")),k.prop("disabled",!0);var d={action:"as3cf-manual-save-bucket",bucket_name:c,_nonce:as3cf_i18n.manual_bucket_nonce};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:d,error:function(a,b,c){k.text(l),e(as3cf_i18n.save_bucket_error,c)},success:function(b){k.text(l),k.prop("disabled",!1),"undefined"!=typeof b.success?(f(c,b.region,b.can_write),a(".as3cf-bucket-list a").removeClass("selected")):e(as3cf_i18n.save_bucket_error,b.error)}})})}var m=b.find(".as3cf-change-bucket");m.length&&m.on("click",function(b){b.preventDefault(),a(".updated").not(".as3cf-notice").hide(),a(".as3cf-can-write-error").hide(),a(".as3cf-settings").removeClass("as3cf-has-bucket"),a(".as3cf-bucket-list-wrapper").is(":visible")&&(c(),a(".as3cf-active-bucket").html&&a(".as3cf-cancel-bucket-select-wrap").show())});var n=b.find(".as3cf-refresh-buckets");n.length&&n.on("click",function(a){a.preventDefault(),c()});var o=b.find(".as3cf-cancel-bucket-select");o.length&&o.on("click",function(b){b.preventDefault(),a(".as3cf-bucket-error").hide(),a(".as3cf-settings").addClass("as3cf-has-bucket")})});var j=a(".as3cf-bucket-list");j.on("click","a",function(b){if(b.preventDefault(),a(this).hasClass("selected"))return void a(".as3cf-settings").addClass("as3cf-has-bucket");var c=this,d=a(".as3cf-bucket-list a.selected").attr("data-bucket");a(".as3cf-bucket-list a").removeClass("selected"),a(c).addClass("selected"),j.addClass("saving"),a(c).find(".spinner").show();var g=a(c).attr("data-bucket"),h={action:"as3cf-save-bucket",bucket_name:g,_nonce:as3cf_i18n.save_bucket_nonce};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(b,c,f){j.removeClass("saving"),e(as3cf_i18n.save_bucket_error,f),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+d+'"]').addClass("selected")},success:function(b){a(c).find(".spinner").hide(),j.removeClass("saving"),"undefined"!=typeof b.success?f(g,b.region,b.can_write):(e(as3cf_i18n.save_bucket_error,b.error),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+d+'"]').addClass("selected"))}})}),a(".as3cf-switch").on("click","span",function(){if(!a(this).parent().hasClass("disabled")){var b=a(this).parent().attr("id");g(b)}}),a(".as3cf-settings").length&&!a(".as3cf-settings").hasClass("as3cf-has-bucket")&&a(".as3cf-bucket-list-wrapper").is(":visible")&&c(),a(".as3cf-settings").on("change",".sub-toggle",function(){var b=a(this).attr("id");a(".as3cf-setting."+b).toggleClass("hide")}),a(".as3cf-settings").on("click",".as3cf-manual-bucket-toggle",function(b){b.preventDefault(),a(".as3cf-bucket-select").addClass("manual")}),a(".as3cf-settings").on("click",".as3cf-bucket-list-toggle",function(b){b.preventDefault(),c(),a(".as3cf-bucket-select").removeClass("manual"),a(".as3cf-cancel-bucket-select-wrap").show()}),a(".as3cf-domain").on("change",'input[type="radio"]',function(){var b=a('input:radio[name="domain"]:checked').val();"cloudfront"===b&&a(".as3cf-setting.cloudfront").hasClass("hide")?a(".as3cf-setting.cloudfront").removeClass("hide"):a(".as3cf-setting.cloudfront").addClass("hide")}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(){var b=a('input:radio[name="ssl"]:checked').val();if("https"===b){var c=a('input:radio[name="domain"]:checked').val();"subdomain"===c&&a('input[name="domain"][value="path"]').attr("checked",!0),a(".subdomain-wrap input").attr("disabled",!0),a(".subdomain-wrap").addClass("disabled")}else a(".subdomain-wrap input").removeAttr("disabled"),a(".subdomain-wrap").removeClass("disabled")}),a(".url-preview").on("change","input",function(){h()}),b=i(),a(document).on("submit",".as3cf-main-settings form",function(){a(window).off("beforeunload.as3cf-settings")}),a(window).on("beforeunload.as3cf-settings",function(){return i()!==b?as3cf_i18n.save_alert:void 0})})}(jQuery); \ No newline at end of file +!function(a,b){function c(b){return a("#"+b+" .as3cf-main-settings form").find("input:not(.no-compare)").serialize()}function d(b){var c=a("#"+b),d=c.find("input[type=checkbox]");c.toggleClass("on").find("span").toggleClass("checked");var e=c.find("span.on").hasClass("checked");d.attr("checked",e).trigger("change")}function e(c){"undefined"==typeof c&&(c=!1);var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-list"),e=a("#"+b.prefix+"-bucket").val();if(!1===c&&d.find("li").length>1)return a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"),void f();d.html('
  • '+d.attr("data-working")+"
  • ");var g={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:g,error:function(a,b,c){d.html(""),k(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b){d.html(""),"undefined"!=typeof b.success?(a(".as3cf-bucket-error").hide(),a(b.buckets).each(function(a,b){var c=b.Name===e?"selected":"";d.append('
  • '+b.Name+'
  • ')}),f()):k(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})}function f(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a(".as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}}function g(){var c=a(".as3cf-bucket-container."+b.prefix);!1===q.hasClass("as3cf-has-bucket")||"manual"===a("#"+b.prefix+"-bucket-select").val()?(c.find(".as3cf-bucket-manual").show().siblings().hide(),c.find(".bucket-actions.manual").show().siblings(".bucket-actions").hide()):(c.find(".as3cf-bucket-select").show().siblings().hide(),c.find(".bucket-actions.select").show().siblings(".bucket-actions").hide(),e()),c.find(".as3cf-bucket-error").hide();var d=a("#"+b.prefix+"-bucket").val();c.find(".as3cf-bucket-manual .as3cf-bucket-name").val(d)}function h(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find("button[type=submit]"),f=d.val(),g=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),q.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var h={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(a,b,c){e.text(g),k(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c){e.text(g),e.prop("disabled",!1),"undefined"!=typeof c.success?(l(f,c.region,c.can_write),a("#"+b.prefix+"-bucket-select").val("manual"),a(".as3cf-bucket-list a").removeClass("selected").filter("[data-bucket="+f+"]").addClass("selected")):k(as3cf.strings.save_bucket_error,c.error,"as3cf-bucket-manual")}})}function i(c){var d=a(".as3cf-bucket-list");if(c.hasClass("selected"))return q.addClass("as3cf-has-bucket"),void b.close();var e=a(".as3cf-bucket-list a.selected").attr("data-bucket");a(".as3cf-bucket-list a").removeClass("selected"),c.addClass("selected"),d.addClass("saving"),c.find(".spinner").show().css("visibility","visible");var f=c.attr("data-bucket"),g={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:g,error:function(b,c,f){d.removeClass("saving"),k(as3cf.strings.save_bucket_error,f,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected")},success:function(g){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(l(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(k(as3cf.strings.save_bucket_error,g.error,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"))}})}function j(){if(0!==a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form").length){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<3?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").attr("disabled",!1),d.find(".as3cf-bucket-name").val().length<3?d.find("button[type=submit]").attr("disabled",!0):d.find("button[type=submit]").attr("disabled",!1)}}function k(b,c,d){var e=a(".as3cf-bucket-container").children(":visible"),f=e.find(".as3cf-bucket-error");d="undefined"==typeof d?null:d,(!d||e.hasClass(d))&&(f.find("span.title").html(b),f.find("span.message").html(c),f.show())}function l(c,e,f){var g=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),h=a("#"+b.prefix+"-active-bucket");"as3cf"===b.prefix&&""===h.text()&&(d("copy-to-s3-wrap"),d("serve-from-s3-wrap")),h.text(c),g.find(".as3cf-bucket-name").val(c),a("#"+b.prefix+"-bucket").val(c),a("#"+b.prefix+"-region").val(e),a(".updated").not(".as3cf-notice").show(),q.addClass("as3cf-has-bucket"),q.find(".as3cf-can-write-error").toggle(!f),q.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&m(),b.close()}function m(){a(".as3cf-url-preview").html("Generating...");var b={_nonce:as3cf.nonces.get_url_preview};a.each(a("#tab-media .as3cf-main-settings form").serializeArray(),function(c,d){var e=d.name,f=d.value;e=e.replace("[]",""),b[e]=void 0===b[e]?f:a.isArray(b[e])?b[e].concat(f):[b[e],f]}),b.action="as3cf-get-url-preview",a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:b,error:function(a,b,c){alert(as3cf.strings.get_url_preview_error+c)},success:function(b){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf.strings.get_url_preview_error+b.error)}})}function n(a){return a.length<3||a.length>63?!1:!0===s.test(a)?!1:!0}function o(b){var c=null;!0===s.test(b)?c=as3cf.strings.create_bucket_invalid_chars:b.length<3?c=as3cf.strings.create_bucket_name_short:b.length>63&&(c=as3cf.strings.create_bucket_name_long),a(".as3cf-invalid-bucket-name").html(c&&b.length>0?c:"")}function p(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find(".bucket-create-region"),f=c.find("button[type=submit]"),g=d.val(),h=f.text();a(".as3cf-bucket-error").hide(),f.text(f.attr("data-working")),f.prop("disabled",!0);var i={action:b.prefix+"-create-bucket",bucket_name:g,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.create_bucket};e.val()&&(i.region=e.val()),a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){f.text(h),k(as3cf.strings.create_bucket_error,c,"as3cf-bucket-create")},success:function(b){f.text(h),f.prop("disabled",!1),"undefined"!=typeof b.success?(l(g,b.region,b.can_write),a(".as3cf-bucket-select-region").hide(),a(".as3cf-bucket-select-region").removeAttr("selected"),d.val(""),f.attr("disabled",!0)):k(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})}var q,r={},s=/[^a-z0-9.-]/,t=a(".as3cf-tab");as3cf.tabs={toggle:function(c,d){t.hide(),q=a("#tab-"+c),q.show(),a(".nav-tab").removeClass("nav-tab-active"),a('a.nav-tab[data-tab="'+c+'"]').addClass("nav-tab-active"),a(".aws-main").attr("data-tab",c),q.attr("data-prefix")&&(b.prefix=q.attr("data-prefix")),d||a(".as3cf-updated").removeClass("show")}},a(document).ready(function(){var f=a(".wrap.aws-main .nav-tab-wrapper");if(a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(f),window.location.hash){var k=window.location.hash.substring(1);as3cf.tabs.toggle(k,!0)}else{var l="media";q=a("#tab-"+l),a(".aws-main").attr("data-tab",l)}a(".aws-main").on("click",".nav-tab",function(b){if(b.preventDefault(),!a(this).hasClass("nav-tab-active")){var c=a(this).attr("data-tab");as3cf.tabs.toggle(c),"media"===c?(window.location.hash="","function"==typeof window.history.replaceState&&"#"===window.location.href.slice(-1)&&history.replaceState({},"",window.location.href.slice(0,-1))):window.location.hash=c}}),t.length&&t.each(function(a,b){r[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(r)){var b=q.attr("id");return c(b)!==r[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(){a(this).hasClass("disabled")||d(a(this).attr("id"))}),t.on("change",".sub-toggle",function(){var b=a(this).attr("id");a(".as3cf-setting."+b).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(){var b=a(this).closest('input:radio[name="domain"]:checked'),c=b.val(),d=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),e="cloudfront"===c;d.toggleClass("hide",!e)}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(){var b=a('input:radio[name="ssl"]:checked').val();if("https"===b){var c=a('input:radio[name="domain"]:checked').val();"subdomain"===c&&a('input[name="domain"][value="path"]').attr("checked",!0),a(".subdomain-wrap input").attr("disabled",!0),a(".subdomain-wrap").addClass("disabled")}else a(".subdomain-wrap input").removeAttr("disabled"),a(".subdomain-wrap").removeClass("disabled")}),a(".url-preview").on("change","input",function(){m()}),a("#tab-media > .as3cf-bucket-error").detach().insertAfter(".as3cf-bucket-container h3"),a("body").on("click",".bucket-action-manual",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-manual").show().siblings().hide()}),a("body").on("click",".bucket-action-browse",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-select").show().siblings().hide(),e()}),a("body").on("click",".bucket-action-create",function(c){c.preventDefault(),a(".as3cf-bucket-name").val(""),a(".as3cf-invalid-bucket-name").html(""),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-create").show().siblings().hide()}),a("body").on("click",".bucket-action-cancel",function(a){a.preventDefault(),g()}),a("body").on("click",".bucket-action-save",function(a){a.preventDefault(),h()}),a("body").on("click",'.as3cf-create-bucket-form button[type="submit"]',function(a){a.preventDefault(),p()}),a("body").on("click",".bucket-action-refresh",function(a){a.preventDefault(),e(!0)}),a("body").on("click",".as3cf-bucket-list a",function(b){b.preventDefault(),i(a(this))}),a("body").on("as3cf-modal-open",function(c,d){if(".as3cf-bucket-container."+b.prefix===d){g();var e=a(".as3cf-bucket-manual h3").data("modal-title");a(".as3cf-bucket-manual h3").text(e),j()}}),j(),a("body").on("input keyup",".as3cf-create-bucket-form .as3cf-bucket-name",function(){var c=a(this).val(),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");n(c)?d.find("button[type=submit]").removeAttr("disabled"):d.find("button[type=submit]").attr("disabled",!0),o(c)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<3?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").removeAttr("disabled")})})}(jQuery,as3cfModal); \ No newline at end of file diff --git a/assets/package.json b/assets/package.json deleted file mode 100644 index 21d1b3cb..00000000 --- a/assets/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "amazon-s3-and-cloudfront", - "version": "0.1.0", - "devDependencies": { - "grunt": "^0.4.4", - "grunt-contrib-uglify": "^0.4.0", - "grunt-contrib-watch": "^0.6.0", - "grunt-contrib-compass": "^0.7.2", - "grunt-contrib-jshint": "~0.10.0" - } -} diff --git a/assets/sass/modal.scss b/assets/sass/modal.scss new file mode 100644 index 00000000..6f4ab722 --- /dev/null +++ b/assets/sass/modal.scss @@ -0,0 +1,80 @@ +/** + * Modals + */ +#as3cf-overlay { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + overflow: hidden; + overflow-y: auto; + z-index: 999999; // Needs to be higher than WP media modals +} + +#as3cf-modal { + display: none; + position: relative; + width: 600px; + margin: 100px auto; + padding: 30px; + background-color: #eee; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + font-size: 14px; + overflow: hidden; + z-index: 100000; + + .close-as3cf-modal { + color: #999; + cursor: pointer; + font-family: "Times New Roman", serif; + font-size: 26px; + font-weight: 200; + position: absolute; + right: 18px; + top: 18px; + + &:hover { + color: #666; + } + } + + h3 { + margin: 0 0 20px; + font-weight: normal; + line-height: 1; + } + + .error, + .notice, + .updated { + margin: 0 0 20px; + } + + .actions { + margin: 20px -30px -30px; + padding: 20px 30px; + border-top: none; + background-color: #e3e3e3; + overflow: hidden; + + .right { + margin-left: 15px; + + &:last-of-type { + margin-left: 0; + } + } + + button { + min-width: 90px; + } + } +} + +body.as3cf-modal-open { + overflow: hidden; + padding-right: 15px; +} \ No newline at end of file diff --git a/assets/sass/styles.scss b/assets/sass/styles.scss index c7cc7bea..ee273387 100644 --- a/assets/sass/styles.scss +++ b/assets/sass/styles.scss @@ -1,4 +1,43 @@ -.aws-main { + +/** + * AWS wrap + */ +.aws-main.wrap { + &> h2 { + float: left; + } + + .as3cf-notice, .as3cf-updated, .as3cf-error { + margin-bottom: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + .as3cf-error.fatal { + clear: both; + float: left; + } + + h2.nav-tab-wrapper { + float: none; + margin-bottom: 0; + width: 650px; + margin-top: 10px; + padding-left: 5px; + padding-right: 0; + + a.nav-tab-active { + color: #464646; + cursor: default; + } + + a:focus { + -webkit-box-shadow: none; + box-shadow: none; + } + } + .error { pre { background: #eaeaea; @@ -13,11 +52,23 @@ } } - .as3cf-notice, .error { - max-width: 935px; - } + &[data-tab="support"] { + .as3cf-notice, .error, .updated, .updated.show { + display: none; + } + .fatal .error, .dbrains-api-down { + display: block; + } + } + .as3cf-notice, .error, .updated { + max-width: 650px; + margin-top: 15px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } - .updated { + .as3cf-updated { display: none; &.as3cf-notice, &.show { display: block; @@ -25,13 +76,38 @@ } } -.as3cf-settings { +/** + * Bucket select + */ +.as3cf-tab { + .as3cf-main-settings { + display: none; + } + + .as3cf-bucket-container { + display: block; + } + + &.as3cf-has-bucket { + .as3cf-main-settings { + display: block; + } + + .as3cf-bucket-container { + display: none; + } + } +} + +/** + * Settings + */ +.as3cf-tab { + display: none; position: relative; width: 650px; - min-height: 800px; - .as3cf-main-settings { - display: none; + .as3cf-main-settings { p { font-size: 13px; a { @@ -39,14 +115,6 @@ } } } - &.as3cf-has-bucket { - .as3cf-bucket-select { - display: none; - } - .as3cf-main-settings { - display: block; - } - } .object-prefix-desc { em { @@ -54,23 +122,27 @@ } } - .as3cf-url-preview-wrap { - background: #ffffff; - text-align: center; - padding: 20px 20px 10px; - max-width: 610px; - width: 100%; - .as3cf-url-preview { - margin-top: 5px; - overflow-x: scroll; - padding-bottom: 15px; - } - span { - color: #aaa; - text-transform: uppercase; - font-weight: bold; + .as3cf-url-preview-wrap { + background: #fff; + text-align: center; + padding: 20px 0 0; + max-width: 650px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + .as3cf-url-preview { + margin-top: 10px; + padding: 0 20px 10px; + overflow-x: scroll; + } + span { + color: #aaa; + text-transform: uppercase; + font-weight: bold; + } } - } .as3cf-ssl { p.info { @@ -100,183 +172,108 @@ } } - .as3cf-switch { - position: relative; - - &.disabled { - span { - cursor: default; - margin: 1px 0 0 0; - } - span.checked { - background: #F1F1F1; - border: 1px solid #dddddd; - color: #a3a3a3; - } - } - span { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - cursor: pointer; - float: left; - display: inline-block; - height: 100%; - font-size: 12px; - line-height: 20px; - border-radius: 2px; - -webkit-border-radius: 2px; - font-weight: bold; - padding: 4px 8px; - background: #F1F1F1; - border: 1px solid #dddddd; - color: #CCCCCC; - margin: 1px -1px 0; - z-index: 1; - - &.checked { - background: #ffffff; - border-color: #E4E4E4; - color: #373737; - padding: 5px 10px; - margin: 0; - z-index: 2; - } - } - input[type="checkbox"] { - position: absolute !important; - top: 0; - left: 0; - opacity: 0; - filter: alpha(opacity=0); - z-index: -1; - } - } - - .as3cf-setting { - &.hide{ - display: none; - } - } - - .as3cf-bucket-actions { - position: absolute; - right: 0; - top: 2px; + .as3cf-switch { + position: relative; + display: inline-block; + padding: 2px; + overflow: hidden; + border-radius: 2px; + -webkit-border-radius: 2px; + background-color: #d4d3d3; + cursor: pointer; + + &.on { + background-color: #ade7b5; + } - .as3cf-cancel-bucket-select-wrap { - display: none; - &:after { - content: " | " - } - } - } + span { + visibility: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + float: left; + display: inline-block; + height: 100%; + font-size: 12px; + line-height: 20px; + border-radius: 2px; + -webkit-border-radius: 2px; + font-weight: bold; + padding: 4px 8px; + background: #fff; + color: #8d8d8d; + z-index: 1; + + &.on { + color: #82d78b; + } + + &.checked { + visibility: visible; + } + } - .as3cf-bucket-select { - &.manual { - .as3cf-manual-bucket-toggle, - .as3cf-refresh-buckets, - .as3cf-bucket-list-wrapper { - display: none; - } - .as3cf-bucket-list-toggle, - .as3cf-manual-save-bucket-wrapper { - display: block; - } + &.disabled { + cursor: default; + background: #e6e6e6; - .as3cf-cancel-bucket-select-wrap { - &:after { - content: "" + span { + background: #f1f1f1; + color: #d6d6d6; + } } - } - } - - .as3cf-manual-bucket-toggle, - .as3cf-bucket-list-wrapper { - display: block; - } - .as3cf-refresh-buckets { - display: inline; + input[type="checkbox"] { + position: absolute !important; + top: 0; + left: 0; + opacity: 0; + filter: alpha(opacity=0); + z-index: -1; + } } - .as3cf-bucket-list-toggle, - .as3cf-manual-save-bucket-wrapper { + .as3cf-setting { + &.hide{ display: none; } } - .as3cf-bucket-list-wrapper { - background: #fff; - padding: 15px 20px; - margin-bottom: 2em; - - .as3cf-bucket-list { - max-height: 300px; - overflow: auto; - margin: 0; + h3 { + font-weight: normal; + text-transform: uppercase; + margin: 15px 0; + } - li { - font-size: 14px; - padding: 10px 0; - } - a { - color: #444; - text-decoration: none; - &:hover, &.selected { - color: #0074A2; - } - &.selected { - font-weight: bold; + .form-table { + margin: 0; + tr { + &.as3cf-border-bottom td { + border-bottom: 1px solid #ddd; + padding: 20px 0px; + } + &.as3cf-setting-title td { + padding-bottom: 0; + } + &.as3cf-setting-title:first-child td { + padding-top: 20px; + } + td { + padding: 15px 0; + &:first-child { + vertical-align: top; + min-width: 120px; + } + } } - .bucket { - float: left; - clear: both; - .dashicons { - margin-right: 5px; - } + h3 { + padding: 0; + margin: 0; } - .spinner { - float: left; - background-size: 15px 15px + h4 { + margin: 0; } - } - &.saving { - opacity: 0.5; - } - } - } - - input[type="text"].as3cf-bucket-name { - width: 80%; - } - - select.bucket { - margin-bottom: 5px; - width: 380px; - } - - .form-table { - td { - padding-left: 0; - padding-right: 0; - &:first-child { - vertical-align: top; - min-width: 120px; - } - } - h3 { - font-weight: normal; - text-transform: uppercase; - margin: 0; - } - h4 { - margin: 0; - } - .as3cf-border-bottom td { - border-bottom: 1px solid #ddd; } - } .as3cf-active-bucket { font-weight: bold; @@ -346,16 +343,178 @@ } } -.as3cf-banner { - img { +/** + * Media tab + */ +#tab-media { display: block; - } + + .as3cf-main-settings { + display: none; + } + + .as3cf-bucket-container { + display: block; + } + + &.as3cf-has-bucket { + .as3cf-main-settings { + display: block; + } + + .as3cf-bucket-container { + display: none; + } + } +} + +/** + * Bucket select + */ +.as3cf-bucket-container { + h3 { + line-height: 1.3; + text-transform: none; + } + + a:focus { + -webkit-box-shadow: none; + box-shadow: none; + outline: none; + } + + input[type=text] { + box-sizing: border-box; + width: 100%; + } + + select { + box-sizing: border-box; + width: 50%; + } + + .form-table { + td { + padding: 5px 0; + + &:first-child { + width: 100px; + line-height: 30px; + vertical-align: top; + } + } + } + + .as3cf-invalid-bucket-name { + font-size: 12px; + color: #a00; + } + + .bucket-actions { + margin: 15px 0; + border-top: 1px solid #ccc; + padding-top: 15px; + overflow: hidden; + + button, + .right { + float: right; + margin-right: 0; + } + + span { + display: inline-block; + margin-right: 20px; + line-height: 28px; + } + + .bucket-action-cancel { + color: #a00; + text-decoration: none; + + &:hover { + color: red; + } + } + } + + .as3cf-bucket-list { + padding: 15px; + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; + background-color: #fff; + font-size: 14px; + + li:last-of-type { + margin-bottom: 0; + } + + a { + color: #444; + text-decoration: none; + + &:hover { + color: #0074A2; + } + + &.selected { + font-weight: bold; + color: #0074A2; + } + + .dashicons { + margin-right: 5px; + } + } + } + + .as3cf-bucket-select, + .as3cf-bucket-create { + display: none; + } + + .bucket-actions.select { + display: none; + } +} + +.as3cf-tab { + display: none; +} + +#tab-media { + display: block; +} + +#tab-support { + .as3cf-sidebar { + top: 11px; + } + + .support-section { + border-bottom: 1px solid #ccc; + padding-bottom: 20px; + margin-bottom: 20px; + } + + .debug { + textarea { + width: 100%; + min-height: 200px; + font-family: Consolas, Monaco, monospace; + margin-bottom: 5px; + } + } } +/** + * Sidebar + */ .as3cf-sidebar { position: absolute; - top: 17px; - right: -312px; + top: 25px; + left: 670px; width: 292px; .block { @@ -497,3 +656,17 @@ margin-top: 50px; } } + +/** + * Misc + */ +.as3cf-banner { + img { + display: block; + } +} + +.aws-compatibility-notice.error { + clear: both; + margin: 5px 20px 5px 0; +} \ No newline at end of file diff --git a/classes/amazon-s3-and-cloudfront.php b/classes/amazon-s3-and-cloudfront.php index 15f34a2b..f24aaeca 100644 --- a/classes/amazon-s3-and-cloudfront.php +++ b/classes/amazon-s3-and-cloudfront.php @@ -1,16 +1,56 @@ plugin_title = __( 'Amazon S3 and CloudFront', 'as3cf' ); + self::$plugin_page = $this->plugin_slug; + $this->plugin_title = __( 'Offload S3', 'as3cf' ); $this->plugin_menu_title = __( 'S3 and CloudFront', 'as3cf' ); // fire up the plugin upgrade checker @@ -45,27 +86,66 @@ function init( $plugin_file_path ) { add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) ); add_action( 'wp_ajax_as3cf-save-bucket', array( $this, 'ajax_save_bucket' ) ); add_action( 'wp_ajax_as3cf-create-bucket', array( $this, 'ajax_create_bucket' ) ); - add_action( 'wp_ajax_as3cf-manual-save-bucket', array( $this, 'ajax_manual_save_bucket' ) ); + add_action( 'wp_ajax_as3cf-manual-save-bucket', array( $this, 'ajax_save_bucket' ) ); add_action( 'wp_ajax_as3cf-get-url-preview', array( $this, 'ajax_get_url_preview' ) ); + // Admin notices + add_action( 'admin_notices', array( $this, 'maybe_show_admin_notices' ) ); + add_action( 'network_admin_notices', array( $this, 'maybe_show_admin_notices' ) ); + add_action( 'shutdown', array( $this, 'save_admin_notices' ) ); + add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 99, 2 ); add_filter( 'wp_handle_upload_prefilter', array( $this, 'wp_handle_upload_prefilter' ), 1 ); - add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 100, 2 ); + add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 110, 2 ); add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 ); add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 ); + add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 ); add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 ); - add_filter( 'as3cf_get_attached_file_copy_back_to_local', array( $this, 'regenerate_thumbnails_get_attached_file' ) ); + add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 ); + + // include compatibility code for other plugins + new AS3CF_Plugin_Compatibility( $this ); load_plugin_textdomain( 'as3cf', false, dirname( plugin_basename( $plugin_file_path ) ) . '/languages/' ); + + // Register modal scripts and styles + $this->register_modal_assets(); + } + + /** + * Get the plugin title to be used in page headings + * + * @return string + */ + function get_plugin_page_title() { + return apply_filters( 'as3cf_settings_page_title', $this->plugin_title ); + } + + /** + * Get the plugin prefix in slug format, ie. replace underscores with hyphens + * + * @return string + */ + function get_plugin_prefix_slug() { + return str_replace( '_', '-', $this->plugin_prefix ); + } + + /** + * Get the nonce key for the settings form of the plugin + * + * @return string + */ + function get_settings_nonce_key() { + return $this->get_plugin_prefix_slug() . '-save-settings'; } /** * Accessor for a plugin setting with conditions to defaults and upgrades * * @param string $key - * @param string $default + * @param mixed $default * - * @return int|mixed|string|void + * @return int|mixed|string|WP_Error */ function get_setting( $key, $default = '' ) { // use settings from $_POST when generating URL preview via AJAX @@ -89,6 +169,11 @@ function get_setting( $key, $default = '' ) { return '1'; } + // Turn on object versioning by default + if ( 'object-versioning' == $key && ! isset( $settings['object-versioning'] ) ) { + return '1'; + } + // Default object prefix if ( 'object-prefix' == $key && ! isset( $settings['object-prefix'] ) ) { return $this->get_default_object_prefix(); @@ -115,9 +200,9 @@ function get_setting( $key, $default = '' ) { // Region of bucket if not already retrieved if ( 'region' == $key && ! isset( $settings['region'] ) ) { $bucket = $this->get_setting( 'bucket' ); - $region = $this->get_bucket_region( $bucket ); - if ( is_wp_error( $region ) ) { - $region = ''; + $region = $default; + if ( $bucket ) { + $region = $this->get_bucket_region( $bucket ); } return $region; @@ -156,7 +241,6 @@ function get_setting( $key, $default = '' ) { } if ( 'bucket' == $key && defined( 'AS3CF_BUCKET' ) ) { - return AS3CF_BUCKET; } @@ -165,16 +249,36 @@ function get_setting( $key, $default = '' ) { return apply_filters( 'as3cf_setting_' . $key, $value ); } + /** + * Setter for a plugin setting with custom hooks + * + * @param string $key + * @param mixed $value + */ + function set_setting( $key, $value ) { + // Run class specific hooks before the setting is saved + $this->pre_set_setting( $key, $value ); + + $value = apply_filters( 'as3cf_set_setting_' . $key, $value ); + + parent::set_setting( $key, $value ); + } + /** * Return the default object prefix * * @return string */ function get_default_object_prefix() { + if ( is_multisite() ) { + return 'wp-content/uploads/'; + } + $uploads = wp_upload_dir(); - $parts = parse_url( $uploads['baseurl'] ); + $parts = parse_url( $uploads['baseurl'] ); + $path = ltrim( $parts['path'], '/' ); - return substr( $parts['path'], 1 ) . '/'; + return trailingslashit( $path ); } /** @@ -186,24 +290,58 @@ function get_allowed_mime_types() { return apply_filters( 'as3cf_allowed_mime_types', get_allowed_mime_types() ); } + /** + * Wrapper for scheduling cron jobs + * + * @param string $hook + * @param null|string $interval Defaults to hook if not supplied + * @param array $args + */ + function schedule_event( $hook, $interval = null, $args = array() ) { + if ( is_null( $interval ) ) { + $interval = $hook; + } + if ( ! wp_next_scheduled( $hook ) ) { + wp_schedule_event( current_time( 'timestamp' ), $interval, $hook, $args ); + } + } + + /** + * Wrapper for clearing scheduled events for a specific cron job + * + * @param string $hook + */ + function clear_scheduled_event( $hook ) { + $timestamp = wp_next_scheduled( $hook ); + if ( $timestamp ) { + wp_unschedule_event( $timestamp, $hook ); + } + } + /** * Generate a preview of the URL of files uploaded to S3 * + * @param bool $escape * @param string $suffix * * @return string */ - function get_url_preview( $suffix = 'photo.jpg' ) { + function get_url_preview( $escape = true, $suffix = 'photo.jpg' ) { $scheme = $this->get_s3_url_scheme(); $bucket = $this->get_setting( 'bucket' ); $path = $this->get_file_prefix(); $region = $this->get_setting( 'region' ); + if ( is_wp_error( $region ) ) { + $region = ''; + } $domain = $this->get_s3_url_domain( $bucket, $region ); $url = $scheme . '://' . $domain . '/' . $path . $suffix; // replace hyphens with non breaking hyphens for formatting - $url = str_replace( '-', '‑', $url ); + if ( $escape ) { + $url = str_replace( '-', '‑', $url ); + } return $url; } @@ -216,11 +354,12 @@ function ajax_get_url_preview() { $url = $this->get_url_preview(); - echo json_encode( array( + $out = array( 'success' => '1', 'url' => $url, - ) ); - exit; + ); + + $this->end_ajax( $out ); } /** @@ -264,18 +403,20 @@ function prepare_intermediate_images_to_remove( $post_id, &$objects, $path ) { /** * Delete bulk objects from an S3 bucket * - * @param string $region - * @param string $bucket - * @param array $objects - * @param bool $log_error - * @param bool $return_on_error + * @param string $region + * @param string $bucket + * @param array $objects + * @param bool $log_error + * @param bool $return_on_error + * @param bool $force_new_s3_client if we are deleting in bulk, force new S3 client + * to cope with possible different regions */ - function delete_s3_objects( $region, $bucket, $objects, $log_error = false, $return_on_error = false ) { + function delete_s3_objects( $region, $bucket, $objects, $log_error = false, $return_on_error = false, $force_new_s3_client = false ) { try { - $this->get_s3client( $region )->deleteObjects( array( - 'Bucket' => $bucket, - 'Objects' => $objects, - ) ); + $this->get_s3client( $region, $force_new_s3_client )->deleteObjects( array( + 'Bucket' => $bucket, + 'Objects' => $objects, + ) ); } catch ( Exception $e ) { if ( $log_error ) { error_log( 'Error removing files from S3: ' . $e->getMessage() ); @@ -286,59 +427,112 @@ function delete_s3_objects( $region, $bucket, $objects, $log_error = false, $ret } } - function delete_attachment( $post_id ) { - if ( ! $this->is_plugin_setup() ) { - return; + /** + * Remove any HDiPi image versions from S3 if they exist + * + * @param string $region + * @param string $bucket + * @param array $objects array of image file that could possibly have HDiPi versions + */ + function maybe_remove_hdipi_images_from_s3( $region, $bucket, $objects ) { + if ( $objects ) { + $hidpi_images = array(); + foreach ( $objects as $object ) { + $hidpi_images[] = array( + 'Key' => $this->get_hidpi_file_path( $object['Key'] ) + ); + } + + // Try removing any @2x images but ignore any errors + $this->delete_s3_objects( $region, $bucket, $hidpi_images ); } + } - if ( ! ( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) { - return; + /** + * Removes an attachment's files from S3. + * + * @param int $post_id + * @param array $s3object + * @param null|string $file optional file override of $s3object['key'] + * @param bool $remove_backup_sizes remove previous edited image versions + * @param bool $log_error + * @param bool $return_on_error + * @param bool $force_new_s3_client if we are deleting in bulk, force new S3 client + * to cope with possible different regions + */ + function remove_attachment_files_from_s3( $post_id, $s3object, $file = null, $remove_backup_sizes = true, $log_error = false, $return_on_error = false, $force_new_s3_client = false ) { + if ( is_null( $file ) ) { + $file = $s3object['key']; } + $prefix = trailingslashit( dirname( $s3object['key'] ) ); + $bucket = $s3object['bucket']; $region = $this->get_s3object_region( $s3object ); if ( is_wp_error( $region ) ) { - error_log( 'Error retrieving region: ' . $region->get_error_message() ); $region = ''; } - $amazon_path = dirname( $s3object['key'] ); - $objects = array(); + $objects_to_remove = array(); - // remove intermediate images - $this->prepare_intermediate_images_to_remove( $post_id, $objects, $amazon_path ); - // remove backup images - $this->prepare_backup_size_images_to_remove( $post_id, $objects, $amazon_path ); + $objects_to_remove[] = array( + 'Key' => path_join( $prefix, basename( $file ) ) + ); + // get any image size files to remove + $this->prepare_intermediate_images_to_remove( $post_id, $objects_to_remove, $prefix ); + if ( $remove_backup_sizes ) { + // Remove backup images + $this->prepare_backup_size_images_to_remove( $post_id, $objects, $prefix ); + } + // remove any HDiPi iamhges + $this->maybe_remove_hdipi_images_from_s3( $region, $bucket, $objects_to_remove ); - // Try removing any @2x images but ignore any errors - if ( $objects ) { - $hidpi_images = array(); - foreach ( $objects as $object ) { - $hidpi_images[] = array( - 'Key' => $this->get_hidpi_file_path( $object['Key'] ) - ); - } + // finally delete the objects from S3 + $this->delete_s3_objects( $region, $bucket, $objects_to_remove, $log_error, $return_on_error, $force_new_s3_client ); + } - $this->delete_s3_objects( $region, $bucket, $hidpi_images ); + /** + * Removes an attachment and intermediate image size files from S3 + * + * @param int $post_id + * @param bool $force_new_s3_client if we are deleting in bulk, force new S3 client + * to cope with possible different regions + */ + function delete_attachment( $post_id, $force_new_s3_client = false ) { + if ( ! $this->is_plugin_setup() ) { + return; } - // add main file to be deleted - $objects[] = array( - 'Key' => $s3object['key'], - ); + if ( ! ( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) { + return; + } - $this->delete_s3_objects( $region, $bucket, $objects, true, true ); + $this->remove_attachment_files_from_s3( $post_id, $s3object, null, true, true, true, $force_new_s3_client ); delete_post_meta( $post_id, 'amazonS3_info' ); } + /** + * Handles the upload of the attachment to S3 when an attachment is updated using + * the 'wp_update_attachment_metadata' filter + * + * @param array $data meta data for attachment + * @param int $post_id + * + * @return array + */ function wp_update_attachment_metadata( $data, $post_id ) { - if ( ! $this->get_setting( 'copy-to-s3' ) || ! $this->is_plugin_setup() ) { + if ( ! $this->is_plugin_setup() ) { + return $data; + } + + if ( ! ( $old_s3object = $this->get_attachment_s3_info( $post_id ) ) && ! $this->get_setting( 'copy-to-s3' ) ) { + // abort if not already uploaded to S3 and the copy setting is off return $data; } // allow S3 upload to be cancelled for any reason - $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id ); + $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id, $old_s3object ); if ( false !== $pre ) { return $data; } @@ -352,16 +546,29 @@ function wp_update_attachment_metadata( $data, $post_id ) { /** * Upload attachment to S3 * - * @param $post_id - * @param $data + * @param int $post_id + * @param array|null $data + * @param string|null $file_path + * @param bool $force_new_s3_client if we are uploading in bulk, force new S3 client + * to cope with possible different regions * * @return array|WP_Error $s3object */ - function upload_attachment_to_s3( $post_id, $data = null ) { + function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false ) { if ( is_null( $data ) ) { $data = wp_get_attachment_metadata( $post_id, true ); } + if ( is_null( $file_path ) ) { + $file_path = get_attached_file( $post_id, true ); + } + + // Check file exists locally before attempting upload + if ( ! file_exists( $file_path ) ) { + return new WP_Error( 'exception', sprintf( __( 'File %s does not exist', 'as3cf' ), $file_path ) ); + } + + $file_name = basename( $file_path ); $type = get_post_mime_type( $post_id ); $allowed_types = $this->get_allowed_mime_types(); @@ -389,7 +596,7 @@ function upload_attachment_to_s3( $post_id, $data = null ) { } else { // derive prefix from various settings if ( isset( $data['file'] ) ) { - $time = untrailingslashit( dirname( $data['file'] ) ); + $time = $this->get_folder_time_from_url( $data['file'] ); } else { $time = $this->get_attachment_folder_time( $post_id ); $time = date( 'Y/m', $time ); @@ -400,11 +607,11 @@ function upload_attachment_to_s3( $post_id, $data = null ) { // use bucket from settings $bucket = $this->get_setting( 'bucket' ); $region = $this->get_setting( 'region' ); + if ( is_wp_error( $region ) ) { + $region = ''; + } } - $file_path = get_attached_file( $post_id, true ); - $file_name = basename( $file_path ); - $acl = apply_filters( 'wps3_upload_acl', $acl, $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon $acl = apply_filters( 'as3cf_upload_acl', $acl, $data, $post_id ); @@ -419,35 +626,24 @@ function upload_attachment_to_s3( $post_id, $data = null ) { $s3object['acl'] = $acl; } - $s3client = $this->get_s3client( $region ); + $s3client = $this->get_s3client( $region, $force_new_s3_client ); - $args = array( + $args = apply_filters( 'as3cf_object_meta', array( 'Bucket' => $bucket, 'Key' => $prefix . $file_name, 'SourceFile' => $file_path, 'ACL' => $acl, - ); + ), $post_id ); // If far future expiration checked (10 years) if ( $this->get_setting( 'expires' ) ) { $args['Expires'] = date( 'D, d M Y H:i:s O', time() + 315360000 ); } - // if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set - // then we need to remove the edited image versions - if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] && defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) { - $objects_to_remove = array(); - // edited main file - $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); - $objects_to_remove[] = array( - 'Key' => path_join( $prefix, basename( $meta['file'] ) ) - ); - // edited resized image files - $this->prepare_intermediate_images_to_remove( $post_id, $objects_to_remove, $prefix ); - $this->delete_s3_objects( $region, $bucket, $objects_to_remove, true ); - } + do_action( 'as3cf_upload_attachment_pre_remove', $post_id, $s3object, $prefix, $args ); $files_to_remove = array(); + if ( file_exists( $file_path ) ) { $files_to_remove[] = $file_path; try { @@ -469,22 +665,24 @@ function upload_attachment_to_s3( $post_id, $data = null ) { if ( isset( $data['thumb'] ) && $data['thumb'] ) { $path = str_replace( $file_name, $data['thumb'], $file_path ); - if ( file_exists( $path ) ) { + if ( file_exists( $path ) && ! in_array( $path, $files_to_remove ) ) { $additional_images[] = array( 'Key' => $prefix . $data['thumb'], 'SourceFile' => $path, ); - $files_to_remove[] = $path; + + $files_to_remove[] = $path; } } else if ( ! empty( $data['sizes'] ) ) { foreach ( $data['sizes'] as $size ) { $path = str_replace( $file_name, $size['file'], $file_path ); - if ( file_exists( $path ) ) { + if ( file_exists( $path ) && ! in_array( $path, $files_to_remove ) ) { $additional_images[] = array( 'Key' => $prefix . $size['file'], 'SourceFile' => $path, ); - $files_to_remove[] = $path; + + $files_to_remove[] = $path; } } } @@ -520,12 +718,26 @@ function upload_attachment_to_s3( $post_id, $data = null ) { } if ( $this->get_setting( 'remove-local-file' ) ) { + if ( isset( $_POST['action'] ) && 'image-editor' == sanitize_key( $_POST['action'] ) && defined( 'DOING_AJAX' ) && DOING_AJAX ) { + // remove original main image after edit + $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); + $original_file = str_replace( $file_name, basename( $meta['file'] ), $file_path ); + if ( file_exists( $original_file ) && ! in_array( $original_file, $files_to_remove ) ) { + $files_to_remove[] = $original_file; + } + } + $this->remove_local_files( $files_to_remove ); } return $s3object; } + /** + * Remove files from the local site + * + * @param array $file_paths array of files to remove + */ function remove_local_files( $file_paths ) { foreach ( $file_paths as $path ) { if ( ! @unlink( $path ) ) { @@ -556,6 +768,23 @@ function get_object_version_string( $post_id ) { return $object_version; } + /** + * Get the upload folder time from given URL + * + * @param string $url + * + * @return null|string + */ + function get_folder_time_from_url( $url ) { + preg_match( '@[0-9]{4}/[0-9]{2}@', $url, $matches ); + + if ( isset( $matches[0] ) ) { + return $matches[0]; + } + + return null; + } + // Media files attached to a post use the post's date // to determine the folder path they are placed in function get_attachment_folder_time( $post_id ) { @@ -625,6 +854,9 @@ function wp_handle_upload_prefilter( $file ) { $bucket = $this->get_setting( 'bucket' ); $region = $this->get_setting( 'region' ); + if ( is_wp_error( $region ) ) { + return $file; + } $s3client = $this->get_s3client( $region ); @@ -668,26 +900,29 @@ function is_plugin_setup() { * Generate a link to download a file from Amazon S3 using query string * authentication. This link is only valid for a limited amount of time. * - * @param int $post_id Post ID of the attachment - * @param int $expires Seconds for the link to live - * @param string $size Size of the image to get + * @param int $post_id Post ID of the attachment + * @param int $expires Seconds for the link to live + * @param string $size Size of the image to get + * @param array $headers Header overrides for request * * @return mixed|void|WP_Error */ - function get_secure_attachment_url( $post_id, $expires = null, $size = null ) { + function get_secure_attachment_url( $post_id, $expires = null, $size = null, $headers = array() ) { if ( is_null( $expires ) ) { $expires = self::DEFAULT_EXPIRES; } - return $this->get_attachment_url( $post_id, $expires, $size ); + return $this->get_attachment_url( $post_id, $expires, $size, null, $headers ); } /** * Return the scheme to be used in URLs * + * @param string|null $ssl + * * @return string */ - function get_s3_url_scheme() { - if ( $this->use_ssl() ) { + function get_s3_url_scheme( $ssl = null ) { + if ( $this->use_ssl( $ssl ) ) { $scheme = 'https'; } else { @@ -700,12 +935,16 @@ function get_s3_url_scheme() { /** * Determine when to use https in URLS * + * @param string|null $ssl + * * @return bool */ - function use_ssl( ) { + function use_ssl( $ssl = null ) { $use_ssl = false; - $ssl = $this->get_setting( 'ssl' ); + if ( is_null( $ssl ) ) { + $ssl = $this->get_setting( 'ssl' ); + } if ( 'request' == $ssl && is_ssl() ) { $use_ssl = true; @@ -734,8 +973,8 @@ function get_object_prefix() { /** * Get the file prefix * - * @param null $time - * @param null $post_id + * @param null|string $time + * @param null|int $post_id * * @return string */ @@ -753,12 +992,24 @@ function get_file_prefix( $time = null, $post_id = null ) { /** * Get the region specific prefix for S3 URL * - * @param $region + * @param string $region + * @param null|int $expires * * @return string */ - function get_s3_url_prefix( $region = '' ) { - $prefix = ( '' == $region ) ? 's3' : 's3-' . $region; + function get_s3_url_prefix( $region = '', $expires = null ) { + $prefix = 's3'; + + if ( '' !== $region ) { + $delimiter = '-'; + if ( 'eu-central-1' == $region && ! is_null( $expires ) ) { + // if we are creating a secure URL for a Frankfurt base file use the alternative delimiter + // http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region + $delimiter = '.'; + } + + $prefix .= $delimiter . $region; + } return $prefix; } @@ -766,24 +1017,35 @@ function get_s3_url_prefix( $region = '' ) { /** * Get the S3 url for the files * - * @param $bucket + * @param string $bucket * @param string $region * @param int $expires + * @param array $args Allows you to specify custom URL settings * * @return mixed|string|void */ - function get_s3_url_domain( $bucket, $region = '', $expires = null ) { - $domain = $this->get_setting( 'domain' ); + function get_s3_url_domain( $bucket, $region = '', $expires = null, $args = array() ) { + if ( ! isset( $args['cloudfront'] ) ) { + $args['cloudfront'] = $this->get_setting( 'cloudfront' ); + } - $prefix = $this->get_s3_url_prefix( $region ); + if ( ! isset( $args['domain'] ) ) { + $args['domain'] = $this->get_setting( 'domain' ); + } + + if ( ! isset( $args['ssl'] ) ) { + $args['ssl'] = $this->get_setting( 'ssl' ); + } + + $prefix = $this->get_s3_url_prefix( $region, $expires ); - if ( 'cloudfront' == $domain && is_null( $expires ) && $this->get_setting( 'cloudfront' ) ) { - $s3_domain = $this->get_setting( 'cloudfront' ); + if ( 'cloudfront' === $args['domain'] && is_null( $expires ) && $args['cloudfront'] ) { + $s3_domain = $args['cloudfront']; } - elseif ( 'virtual-host' == $domain ) { + elseif ( 'virtual-host' === $args['domain'] ) { $s3_domain = $bucket; } - elseif ( 'path' == $domain || $this->use_ssl() ) { + elseif ( 'path' === $args['domain'] || $this->use_ssl( $args['ssl'] ) ) { $s3_domain = $prefix . '.amazonaws.com/' . $bucket; } else { @@ -796,14 +1058,15 @@ function get_s3_url_domain( $bucket, $region = '', $expires = null ) { /** * Get the url of the file from Amazon S3 * - * @param int $post_id Post ID of the attachment - * @param int $expires Seconds for the link to live - * @param string $size Size of the image to get - * @param array $meta Pre retrieved _wp_attachment_metadata for the attachment + * @param int $post_id Post ID of the attachment + * @param int $expires Seconds for the link to live + * @param string $size Size of the image to get + * @param array $meta Pre retrieved _wp_attachment_metadata for the attachment + * @param array $headers Header overrides for request * * @return bool|mixed|void|WP_Error */ - function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null ) { + function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array() ) { if ( ! $this->get_setting( 'serve-from-s3' ) ) { return false; } @@ -843,7 +1106,7 @@ function get_attachment_url( $post_id, $expires = null, $size = null, $meta = nu if ( ! is_null( $expires ) ) { try { $expires = time() + $expires; - $secure_url = $this->get_s3client( $region )->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires ); + $secure_url = $this->get_s3client( $region )->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires, $headers ); } catch ( Exception $e ) { return new WP_Error( 'exception', $e->getMessage() ); @@ -924,15 +1187,37 @@ function encode_filename_in_path( $file ) { return $file_path . $file_name; } + /** + * Allow processes to update the file on S3 via update_attached_file() + * + * @param string $file + * @param int $attachment_id + * + * @return string + */ + function update_attached_file( $file, $attachment_id ) { + if ( ! $this->is_plugin_setup() ) { + return $file; + } + + if ( ! ( $s3object = $this->get_attachment_s3_info( $attachment_id ) ) ) { + return $file; + } + + $file = apply_filters( 'as3cf_update_attached_file', $file, $attachment_id, $s3object ); + + return $file; + } + /** * Return the S3 URL when the local file is missing * unless we know the calling process is and we are happy * to copy the file back to the server to be used * - * @param $file - * @param $attachment_id + * @param string $file + * @param int $attachment_id * - * @return bool|mixed|void|WP_Error + * @return string */ function get_attached_file( $file, $attachment_id ) { if ( file_exists( $file ) || ! $this->get_setting( 'serve-from-s3' ) ) { @@ -945,55 +1230,20 @@ function get_attached_file( $file, $attachment_id ) { $url = $this->get_attachment_url( $attachment_id ); - // the default behaviour is to return the S3 URL, however we can override this - // and copy back the file to the local server for certain processes - // where we know it will get removed again via wp_update_attachment_metadata - $copy_back_to_local = apply_filters( 'as3cf_get_attached_file_copy_back_to_local', false, $file, $attachment_id ); - if ( false === $copy_back_to_local ) { - // return S3 URL as a fallback - return $url; - } - - // fire up the filesystem API - $filesystem = WP_Filesystem(); - global $wp_filesystem; - if ( false === $filesystem || is_null( $wp_filesystem ) ) { - error_log( __( 'There was an error attempting to access the file system', 'as3cf' ) ); - - return $url; - } - - // download the file from S3 - $temp_file = download_url( $url ); - // copy the temp file to the attachments location - if ( ! $wp_filesystem->copy( $temp_file, $file ) ) { - // fallback to url - $file = $url; - } - // clear up temp file - @unlink( $temp_file ); + // return the URL by default + $file = apply_filters( 'as3cf_get_attached_file', $url, $file, $attachment_id, $s3object ); return $file; } /** - * Allow the Regenerate Thumbnails plugin to copy the S3 file back to the local - * server when the file is missing on the server via get_attached_file - * - * @param $copy_back_to_local + * Helper method for returning data to AJAX call * - * @return bool + * @param array $return */ - function regenerate_thumbnails_get_attached_file( $copy_back_to_local ) { - if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { - return $copy_back_to_local; - } - - if ( isset( $_POST['action'] ) && 'regeneratethumbnail' == sanitize_key( $_POST['action'] ) ) { // input var okay - return true; - } - - return $copy_back_to_local; + function end_ajax( $return = array() ) { + echo json_encode( $return ); + exit; } function verify_ajax_request() { @@ -1008,93 +1258,172 @@ function verify_ajax_request() { function ajax_check_bucket() { if ( ! isset( $_POST['bucket_name'] ) || ! ( $bucket = sanitize_text_field( $_POST['bucket_name'] ) ) ) { // input var okay - echo json_encode( array( 'error' => __( 'No bucket name provided.', 'as3cf' ) ) ); - exit; + $out = array( 'error' => __( 'No bucket name provided.', 'as3cf' ) ); + + $this->end_ajax( $out ); } - return $bucket; + return strtolower( $bucket ); } + /** + * Handler for AJAX callback to create a bucket in S3 + */ function ajax_create_bucket() { $this->verify_ajax_request(); $bucket = $this->ajax_check_bucket(); - $result = $this->create_bucket( $bucket ); - if ( is_wp_error( $result ) ) { - $out = array( 'error' => $result->get_error_message() ); + if ( defined( 'AS3CF_REGION' ) ) { + // Are we defining the region? + $region = AS3CF_REGION; } else { - $region = $this->save_bucket( $bucket ); - - if ( $region !== false ) { - $out = array( - 'success' => '1', - '_nonce' => wp_create_nonce( 'as3cf-create-bucket' ), - 'region' => $region, - ); - $out['can_write'] = $this->check_write_permission( $bucket, $region ); - } else { - $out = array( 'error' => __( 'Failed to retrieve bucket region.', 'as3cf' ) ); - } + // Are we specifying the region via the form? + $region = isset( $_POST['region'] ) ? sanitize_text_field( $_POST['region'] ) : null; // input var okay } - echo json_encode( $out ); - exit; - } + $result = $this->create_bucket( $bucket, $region ); + if ( is_wp_error( $result ) ) { + $out = array( 'error' => $result->get_error_message() ); - function create_bucket( $bucket_name ) { - try { - $this->get_s3client()->createBucket( array( 'Bucket' => $bucket_name ) ); + $this->end_ajax( $out ); } - catch ( Exception $e ) { - return new WP_Error( 'exception', $e->getMessage() ); - } - - return true; - } - - function ajax_save_bucket() { - $this->verify_ajax_request(); - $bucket = $this->ajax_check_bucket(); + // check if we were previously selecting a bucket manually via the input + $previous_manual_bucket_select = $this->get_setting( 'manual_bucket', false ); - $region = $this->save_bucket( $bucket ); + $region = $this->save_bucket( $bucket, $previous_manual_bucket_select, $region ); - if ( $region !== false ) { + if ( ! is_wp_error( $region ) ) { $out = array( 'success' => '1', + '_nonce' => wp_create_nonce( 'as3cf-create-bucket' ), 'region' => $region, ); - $out['can_write'] = $this->check_write_permission( $bucket, $region ); + + // Delete transient to force write check + delete_site_transient( $this->plugin_prefix . '_bucket_writable' ); + + $can_write = $this->check_write_permission( $bucket, $region ); + + if ( is_wp_error( $can_write ) ) { + $out = array( 'error' => $can_write->get_error_message() ); + } else { + $out['can_write'] = $can_write; + } } else { - $out = array( 'error' => __( 'Failed to retrieve bucket region.', 'as3cf' ) ); + $out = array( 'error' => $region->get_error_message() ); } - echo json_encode( $out ); - exit; + $this->end_ajax( $out ); + } + + /** + * Create an S3 bucket + * + * @param string $bucket_name + * @param null|string $region option location constraint + * + * @return bool|WP_Error + */ + function create_bucket( $bucket_name, $region = null ) { + try { + $args = array( 'Bucket' => $bucket_name ); + + if ( defined( 'AS3CF_REGION' ) ) { + // Make sure we always use the defined region + $region = AS3CF_REGION; + } + + if ( ! is_null( $region ) && self::DEFAULT_REGION !== $region ) { + $args['LocationConstraint'] = $region; + } + + $this->get_s3client()->createBucket( $args ); + } + catch ( Exception $e ) { + return new WP_Error( 'exception', $e->getMessage() ); + } + + return true; + } + + /** + * Handler for AJAX callback to save the selection of a bucket + */ + function ajax_save_bucket() { + $this->verify_ajax_request(); + + $bucket = $this->ajax_check_bucket(); + + $manual = false; + // are we inputting the bucket manually? + if ( isset( $_POST['action'] ) && false !== strpos( $_POST['action'], 'manual-save-bucket' ) ) { + $manual = true; + } + + $region = $this->save_bucket( $bucket, $manual ); + + if ( ! is_wp_error( $region ) ) { + $out = array( + 'success' => '1', + 'region' => $region, + ); + + // Delete transient to force write check + delete_site_transient( $this->plugin_prefix . '_bucket_writable' ); + + $can_write = $this->check_write_permission( $bucket, $region ); + + if ( is_wp_error( $can_write ) ) { + $out = array( 'error' => $can_write->get_error_message() ); + } else { + $out['can_write'] = $can_write; + } + } else { + $out = array( 'error' => $region->get_error_message() ); + } + + $this->end_ajax( $out ); + } + + /** + * Perform custom actions before the setting is saved + * + * @param string $key + * @param string $value + */ + function pre_set_setting( $key, $value ) { + if ( 'bucket' === $key && ! $this->get_setting( 'bucket' ) ) { + // first time bucket select - enable main options by default + $this->set_setting( 'copy-to-s3', '1' ); + $this->set_setting( 'serve-from-s3', '1' ); + } } /** * Save bucket and bucket's region * - * @param string $bucket_name - * @param bool $manual if we are entering the bucket via the manual input form + * @param string $bucket_name + * @param bool $manual if we are entering the bucket via the manual input form + * @param null|string $region * - * @return string|bool|WP_Error Region on success + * @return string|bool region on success */ - function save_bucket( $bucket_name, $manual = false ) { + function save_bucket( $bucket_name, $manual = false, $region = null ) { if ( $bucket_name ) { $this->get_settings(); - // first time bucket select - enable main options by default - if ( ! $this->get_setting( 'bucket' ) ) { - $this->set_setting( 'copy-to-s3', '1' ); - $this->set_setting( 'serve-from-s3', '1' ); - } + $this->set_setting( 'bucket', $bucket_name ); - $region = $this->get_bucket_region( $bucket_name ); - if ( is_wp_error( $region ) ) { - return false; + + if ( is_null( $region ) ) { + // retrieve the bucket region if not supplied + $region = $this->get_bucket_region( $bucket_name ); + if ( is_wp_error( $region ) ) { + return $region; + } } + $this->set_setting( 'region', $region ); if ( $manual ) { @@ -1112,30 +1441,40 @@ function save_bucket( $bucket_name, $manual = false ) { return false; } - function ajax_manual_save_bucket() { - $this->verify_ajax_request(); - - $bucket = $this->ajax_check_bucket(); - - $region = $this->save_bucket( $bucket, true ); + /** + * Get all AWS regions + * + * @return array + */ + function get_aws_regions() { + $regionEnum = new ReflectionClass( 'Aws\Common\Enum\Region' ); + $all_regions = $regionEnum->getConstants(); + + $regions = array(); + foreach ( $all_regions as $label => $region ) { + // Nicely format region name + if ( self::DEFAULT_REGION === $region ) { + $label = 'US Standard'; + } else { + $label = strtolower( $label ); + $label = str_replace( '_', ' ', $label ); + $label = ucwords( $label ); + } - if ( $region !== false ) { - $out = array( - 'success' => '1', - 'region' => $region, - ); - $out['can_write'] = $this->check_write_permission( $bucket, $region ); - } else { - $out = array( 'error' => __( 'Failed to retrieve bucket region.', 'as3cf' ) ); + $regions[ $region ] = $label; } - echo json_encode( $out ); - exit; + return $regions; } + /** + * Add the settings menu item + * + * @param Amazon_Web_Services $aws + */ function admin_menu( $aws ) { - $hook_suffix = $aws->add_page( $this->plugin_title, $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) ); - add_action( 'load-' . $hook_suffix , array( $this, 'plugin_load' ) ); + $this->hook_suffix = $aws->add_page( $this->get_plugin_page_title(), $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) ); + add_action( 'load-' . $this->hook_suffix , array( $this, 'plugin_load' ) ); } /** @@ -1167,7 +1506,7 @@ function get_s3client( $region = false, $force = false ) { /** * Get the region of a bucket * - * @param $bucket + * @param string $bucket * * @return string|WP_Error */ @@ -1176,7 +1515,8 @@ function get_bucket_region( $bucket ) { $region = $this->get_s3client()->getBucketLocation( array( 'Bucket' => $bucket ) ); } catch ( Exception $e ) { - error_log( sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() ) ); + $error_msg = sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() ); + error_log( $error_msg ); return new WP_Error( 'exception', $e->getMessage() ); } @@ -1247,12 +1587,10 @@ function ajax_get_buckets() { $out = array( 'success' => '1', 'buckets' => $result, - 'selected' => $this->get_setting( 'bucket' ) ); } - echo json_encode( $out ); - exit; + $this->end_ajax( $out ); } /** @@ -1277,7 +1615,7 @@ function get_buckets() { * @param string $bucket * @param string $region * - * @return bool + * @return bool|WP_Error */ function check_write_permission( $bucket = null, $region = null ) { if ( is_null( $bucket ) ) { @@ -1287,6 +1625,12 @@ function check_write_permission( $bucket = null, $region = null ) { } } + $is_bucket_writable = get_site_transient( $this->plugin_prefix . '_bucket_writable' ); + + if ( false !== $is_bucket_writable && $is_bucket_writable['bucket'] === $bucket ) { + return $is_bucket_writable['can_write']; + } + $file_name = 'as3cf-permission-check.txt'; $file_contents = __( 'This is a test file to check if the user has write permission to S3. Delete me if found.', 'as3cf' ); @@ -1304,6 +1648,10 @@ function check_write_permission( $bucket = null, $region = null ) { // need to set region for buckets in non default region if ( is_null( $region ) ) { $region = $this->get_setting( 'region' ); + + if ( is_wp_error( $region ) ) { + return $region; + } } // attempt to create the test file $this->get_s3client( $region, true )->putObject( $args ); @@ -1314,51 +1662,142 @@ function check_write_permission( $bucket = null, $region = null ) { ) ); $can_write = true; } catch ( Exception $e ) { + // if we encounter an error that isn't access denied, throw that error + if ( 'AccessDenied' != $e->getExceptionCode() ) { + $error_msg = sprintf( __( 'There was an error attempting to check the permissions of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() ); + error_log( $error_msg ); + + return new WP_Error( 'exception', $error_msg ); + } + // write permission not found $can_write = false; } + // Cache the result + $is_bucket_writable = array( + 'bucket' => $bucket, + 'can_write' => $can_write, + ); + set_site_transient( $this->plugin_prefix . '_bucket_writable', $is_bucket_writable, 5 * MINUTE_IN_SECONDS ); + return $can_write; } - function plugin_load() { + /** + * Render error messages in a view for bucket permission and access issues + */ + function render_bucket_permission_errors() { + $can_write = $this->check_write_permission(); + // catch any checking issues + if ( is_wp_error( $can_write ) ) { + $this->render_view( 'error-fatal', array( 'message' => $can_write->get_error_message() ) ); + $can_write = true; + } + // display a error message if the user does not have write permission to S3 bucket + $this->render_view( 'error-access', array( 'can_write' => $can_write ) ); + } + + /** + * Register modal scripts and styles so they can be enqueued later + */ + function register_modal_assets() + { $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $this->plugin_version; + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - $src = plugins_url( 'assets/css/styles.css', $this->plugin_file_path ); - wp_enqueue_style( 'as3cf-styles', $src, array(), $version ); + $src = plugins_url( 'assets/css/modal.css', $this->plugin_file_path ); + wp_register_style( 'as3cf-modal', $src, array(), $version ); + $src = plugins_url( 'assets/js/modal' . $suffix . '.js', $this->plugin_file_path ); + wp_register_script( 'as3cf-modal', $src, array( 'jquery' ), $version, true ); + } + + function plugin_load() { + $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $this->plugin_version; $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + $src = plugins_url( 'assets/css/styles.css', $this->plugin_file_path ); + wp_enqueue_style( 'as3cf-styles', $src, array( 'as3cf-modal' ), $version ); + $src = plugins_url( 'assets/js/script' . $suffix . '.js', $this->plugin_file_path ); - wp_enqueue_script( 'as3cf-script', $src, array( 'jquery' ), $version, true ); - - wp_localize_script( 'as3cf-script', 'as3cf_i18n', array( - 'create_bucket_prompt' => __( 'Bucket Name:', 'as3cf' ), - 'create_bucket_error' => __( 'Error creating bucket: ', 'as3cf' ), - 'create_bucket_nonce' => wp_create_nonce( 'as3cf-create-bucket' ), - 'manual_bucket_nonce' => wp_create_nonce( 'as3cf-manual-save-bucket' ), - 'get_buckets_error' => __( 'Error fetching buckets: ', 'as3cf' ), - 'get_buckets_nonce' => wp_create_nonce( 'as3cf-get-buckets' ), - 'save_bucket_error' => __( 'Error saving bucket: ', 'as3cf' ), - 'save_bucket_nonce' => wp_create_nonce( 'as3cf-save-bucket' ), - 'get_url_preview_nonce' => wp_create_nonce( 'as3cf-get-url-preview' ), - 'get_url_preview_error' => __( 'Error getting URL preview: ', 'as3cf' ), - 'save_alert' => __( 'The changes you made will be lost if you navigate away from this page', 'as3cf' ), - ) ); + wp_enqueue_script( 'as3cf-script', $src, array( 'jquery', 'as3cf-modal' ), $version, true ); + + wp_localize_script( 'as3cf-script', + 'as3cf', + array( + 'strings' => array( + 'create_bucket_error' => __( 'Error creating bucket: ', 'as3cf' ), + 'create_bucket_name_short' => __( 'Bucket name too short.', 'as3cf' ), + 'create_bucket_name_long' => __( 'Bucket name too long.', 'as3cf' ), + 'create_bucket_invalid_chars' => __( 'Invalid character. Bucket names can contain lowercase letters, numbers, periods and hyphens.', 'as3cf' ), + 'save_bucket_error' => __( 'Error saving bucket: ', 'as3cf' ), + 'get_buckets_error' => __( 'Error fetching buckets: ', 'as3cf' ), + 'get_url_preview_error' => __( 'Error getting URL preview: ', 'as3cf' ), + 'save_alert' => __( 'The changes you made will be lost if you navigate away from this page', 'as3cf' ) + ), + 'nonces' => array( + 'create_bucket' => wp_create_nonce( 'as3cf-create-bucket' ), + 'manual_bucket' => wp_create_nonce( 'as3cf-manual-save-bucket' ), + 'get_buckets' => wp_create_nonce( 'as3cf-get-buckets' ), + 'save_bucket' => wp_create_nonce( 'as3cf-save-bucket' ), + 'get_url_preview' => wp_create_nonce( 'as3cf-get-url-preview' ), + ), + 'is_pro' => $this->is_pro(), + ) + ); $this->handle_post_request(); + $this->http_prepare_download_log(); + + do_action( 'as3cf_plugin_load' ); } + /** + * Whitelist of settings allowed to be saved + * + * @return array + */ + function get_settings_whitelist() { + return array( + 'bucket', + 'region', + 'domain', + 'virtual-host', + 'expires', + 'permissions', + 'cloudfront', + 'object-prefix', + 'copy-to-s3', + 'serve-from-s3', + 'remove-local-file', + 'ssl', + 'hidpi-images', + 'object-versioning', + 'use-yearmonth-folders', + 'enable-object-prefix', + ); + } + + /** + * Handle the saving of the settings page + */ function handle_post_request() { + if ( empty( $_POST['plugin'] ) || $this->get_plugin_slug() != sanitize_key( $_POST['plugin'] ) ) { // input var okay + return; + } + if ( empty( $_POST['action'] ) || 'save' != sanitize_key( $_POST['action'] ) ) { // input var okay return; } - if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'as3cf-save-settings' ) ) { // input var okay - die( __( "Cheatin' eh?", 'amazon-web-services' ) ); + if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), $this->get_settings_nonce_key() ) ) { // input var okay + die( __( "Cheatin' eh?", 'as3cf' ) ); } - $post_vars = array( 'bucket', 'region', 'domain', 'virtual-host', 'expires', 'permissions', 'cloudfront', 'object-prefix', 'copy-to-s3', 'serve-from-s3', 'remove-local-file', 'ssl', 'hidpi-images', 'object-versioning', 'use-yearmonth-folders', 'enable-object-prefix' ); + do_action( 'as3cf_pre_save_settings' ); + + $post_vars = $this->get_settings_whitelist(); foreach ( $post_vars as $var ) { $this->remove_setting( $var ); @@ -1374,15 +1813,65 @@ function handle_post_request() { $this->save_settings(); - wp_redirect( 'admin.php?page=' . $this->plugin_slug . '&updated=1' ); + $url = $this->get_plugin_page_url( array( 'updated' => '1' ) ); + wp_redirect( $url ); exit; } + /** + * Helper method to return the settings page URL for the plugin + * + * @param array $args + * @param string $url_method To prepend to admin_url() + * @param bool $escape Should we escape the URL + * + * @return string + */ + function get_plugin_page_url( $args = array(), $url_method = 'network', $escape = true ) { + $default_args = array( + 'page' => self::$plugin_page, + ); + + $args = array_merge( $args, $default_args ); + + switch ( $url_method ) { + case 'self': + $base_url = self_admin_url( 'admin.php' ); + break; + case '': + $base_url = admin_url( 'admin.php' ); + break; + default: + $base_url = network_admin_url( 'admin.php' ); + } + + // Add a hash to the URL + $hash = false; + if ( isset( $args['hash'] ) ) { + $hash = $args['hash']; + unset( $args['hash'] ); + } else if ( $this->default_tab ) { + $hash = $this->default_tab; + } + + $url = add_query_arg( $args, $base_url ); + + if ( $hash ) { + $url .= '#' . $hash; + } + + if ( $escape ) { + $url = esc_url_raw( $url ); + } + + return $url; + } + /** * Display the main settings page for the plugin */ function render_page() { - $this->aws->render_view( 'header', array( 'page_title' => $this->plugin_title ) ); + $this->aws->render_view( 'header', array( 'page_title' => $this->get_plugin_page_title(), 'page' => 'as3cf' ) ); $aws_client = $this->aws->get_client(); @@ -1390,6 +1879,7 @@ function render_page() { $this->render_view( 'error-fatal', array( 'message' => $aws_client->get_error_message() ) ); } else { + $this->render_view( 'settings-tabs' ); do_action( 'as3cf_pre_settings_render' ); $this->render_view( 'settings' ); do_action( 'as3cf_post_settings_render' ); @@ -1398,6 +1888,20 @@ function render_page() { $this->aws->render_view( 'footer' ); } + /** + * Get the tabs available for the plugin settings page + * + * @return array + */ + function get_settings_tabs() { + $tabs = array( + 'media' => _x( 'Media Library', 'Show the media library tab', 'as3cf' ), + 'support' => _x( 'Support', 'Show the support tab', 'as3cf' ) + ); + + return apply_filters( 'as3cf_settings_tabs', $tabs ); + } + /** * Get the prefix path for the files * @@ -1406,17 +1910,16 @@ function render_page() { * @return string */ function get_dynamic_prefix( $time = null ) { - $uploads = wp_upload_dir( $time ); $prefix = ''; if ( $this->get_setting( 'use-yearmonth-folders' ) ) { - $prefix = str_replace( $this->get_base_upload_path(), '', $uploads['path'] ); + $uploads = wp_upload_dir( $time ); + $prefix = str_replace( $this->get_base_upload_path(), '', $uploads['path'] ); } // support legacy MS installs (<3.5 since upgraded) for subsites - if ( is_multisite() && 1 != ( $blog_id = get_current_blog_id() ) && strpos( $prefix, 'sites/' ) === false ) { - $details = get_blog_details( $blog_id ); - $sitename = basename( $details->siteurl ); - $legacy_ms_prefix = $sitename . '/files/'; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) && false === strpos( $prefix, 'sites/' ) ) { + $details = get_blog_details( get_current_blog_id() ); + $legacy_ms_prefix = 'sites/' . $details->blog_id . '/'; $legacy_ms_prefix = apply_filters( 'as3cf_legacy_ms_subsite_prefix', $legacy_ms_prefix, $details ); $prefix = '/' . trailingslashit( ltrim( $legacy_ms_prefix, '/' ) ) . ltrim( $prefix, '/' ); } @@ -1463,13 +1966,419 @@ function get_blog_ids() { $blog_ids = array(); foreach ( $blogs as $blog ) { - if ( 1 == $blog['blog_id'] ) { - // ignore the first blog which doesn't have the ID in the table prefix - continue; - } $blog_ids[] = $blog['blog_id']; } return $blog_ids; } + + /** + * Check whether the pro addon is installed. + * + * @return bool + */ + function is_pro() { + if ( ! class_exists( 'Amazon_S3_And_CloudFront_Pro' ) ) { + return false; + } + + return true; + } + + /** + * Apply ACL to an attachment and associated files + * + * @param int $post_id + * @param object $s3object + * @param string $acl + */ + function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) { + // set ACL as private + $args = array( + 'ACL' => $acl, + 'Bucket' => $s3object['bucket'], + 'Key' => $s3object['key'], + ); + + $region = ( isset( $s3object['region'] ) ) ? $s3object['region'] : false; + $s3client = $this->get_s3client( $region, true ); + + try { + $s3client->PutObjectAcl( $args ); + $s3object['acl'] = $acl; + + // Add attachment to ACL update notice + $message = $this->make_acl_admin_notice_text( $s3object ); + $this->set_admin_notice( $message ); + + // update S3 meta data + if ( $acl == self::DEFAULT_ACL ) { + unset( $s3object['acl'] ); + } + update_post_meta( $post_id, 'amazonS3_info', $s3object ); + } catch ( Exception $e ) { + error_log( 'Error setting ACL to ' . $acl . ' for ' . $s3object['key'] . ': ' . $e->getMessage() ); + } + } + + /** + * Make admin notice text for when object ACL has changed + * + * @param array $s3object + * + * @return string + */ + function make_acl_admin_notice_text( $s3object ) { + $filename = basename( $s3object['key'] ); + $acl = $this->get_acl_display_name( $s3object['acl'] ); + + return sprintf( __( 'The file %s has been given %s permissions on Amazon S3.', 'as3cf' ), "{$filename}", "{$acl}" ); + } + + /** + * Set admin notice + * + * @param string $message + * @param string $type info, updated, error + * @param bool $dismissible + * @param bool $inline + */ + function set_admin_notice( $message, $type = 'info', $dismissible = true, $inline = false ) { + self::$admin_notices[] = array( + 'message' => $message, + 'type' => $type, + 'dismissible' => $dismissible, + 'inline' => $inline, + ); + } + + /** + * Save admin notices to transients before shutdown + */ + function save_admin_notices() { + if ( ! empty( self::$admin_notices ) ) { + set_site_transient( 'as3cf_notices', self::$admin_notices ); + } + } + + /** + * Maybe show notices on admin page + */ + function maybe_show_admin_notices() { + if ( $notices = get_site_transient( 'as3cf_notices' ) ) { + foreach ( $notices as $notice ) { + if ( 'info' === $notice['type'] ) { + $notice['type'] = 'notice-info'; + } + + $args = array( + 'message' => $notice['message'], + 'type' => $notice['type'], + 'dismissible' => $notice['dismissible'], + 'inline' => $notice['inline'], + ); + + $this->render_view( 'notice', $args ); + } + + delete_site_transient( 'as3cf_notices' ); + } + } + + /** + * Diagnostic information for the support tab + * + * @param bool $escape + */ + function output_diagnostic_info( $escape = true ) { + global $table_prefix; + global $wpdb; + + echo 'site_url(): '; + echo esc_html( site_url() ); + echo "\r\n"; + + echo 'home_url(): '; + echo esc_html( home_url() ); + echo "\r\n"; + + echo 'Database Name: '; + echo esc_html( $wpdb->dbname ); + echo "\r\n"; + + echo 'Table Prefix: '; + echo esc_html( $table_prefix ); + echo "\r\n"; + + echo 'WordPress: '; + echo bloginfo( 'version' ); + if ( is_multisite() ) { + echo ' Multisite'; + } + echo "\r\n"; + + echo 'Web Server: '; + echo esc_html( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '' ); + echo "\r\n"; + + echo 'PHP: '; + if ( function_exists( 'phpversion' ) ) { + echo esc_html( phpversion() ); + } + echo "\r\n"; + + echo 'MySQL: '; + echo esc_html( empty( $wpdb->use_mysqli ) ? mysql_get_server_info() : mysqli_get_server_info( $wpdb->dbh ) ); + echo "\r\n"; + + echo 'ext/mysqli: '; + echo empty( $wpdb->use_mysqli ) ? 'no' : 'yes'; + echo "\r\n"; + + echo 'WP Memory Limit: '; + echo esc_html( WP_MEMORY_LIMIT ); + echo "\r\n"; + + echo 'Blocked External HTTP Requests: '; + if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) { + echo 'None'; + } else { + $accessible_hosts = ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) ? WP_ACCESSIBLE_HOSTS : ''; + + if ( empty( $accessible_hosts ) ) { + echo 'ALL'; + } else { + echo 'Partially (Accessible Hosts: ' . esc_html( $accessible_hosts ) . ')'; + } + } + echo "\r\n"; + + echo 'WP Locale: '; + echo esc_html( get_locale() ); + echo "\r\n"; + + echo 'Debug Mode: '; + echo esc_html( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 'Yes' : 'No' ); + echo "\r\n"; + + echo 'WP Max Upload Size: '; + echo esc_html( size_format( wp_max_upload_size() ) ); + echo "\r\n"; + + echo 'PHP Time Limit: '; + if ( function_exists( 'ini_get' ) ) { + echo esc_html( ini_get( 'max_execution_time' ) ); + } + echo "\r\n"; + + echo 'PHP Error Log: '; + if ( function_exists( 'ini_get' ) ) { + echo esc_html( ini_get( 'error_log' ) ); + } + echo "\r\n"; + + echo 'fsockopen: '; + if ( function_exists( 'fsockopen' ) ) { + echo 'Enabled'; + } else { + echo 'Disabled'; + } + echo "\r\n"; + + echo 'OpenSSL: '; + if ( $this->open_ssl_enabled() ) { + echo esc_html( OPENSSL_VERSION_TEXT ); + } else { + echo 'Disabled'; + } + echo "\r\n"; + + echo 'cURL: '; + if ( function_exists( 'curl_init' ) ) { + echo 'Enabled'; + } else { + echo 'Disabled'; + } + echo "\r\n"; + + echo 'Zlib Compression: '; + if ( function_exists( 'gzcompress' ) ) { + echo 'Enabled'; + } else { + echo 'Disabled'; + } + echo "\r\n\r\n"; + + echo 'Bucket: '; + echo $this->get_setting( 'bucket' ); + echo "\r\n"; + echo 'Region: '; + $region = $this->get_setting( 'region' ); + if ( ! is_wp_error( $region ) ) { + echo $region; + } + echo "\r\n"; + echo 'Copy Files to S3: '; + echo $this->on_off( 'copy-to-s3' ); + echo "\r\n"; + echo 'Rewrite File URLs: '; + echo $this->on_off( 'serve-from-s3' ); + echo "\r\n"; + echo "\r\n"; + + echo 'URL Preview: '; + echo $this->get_url_preview( $escape ); + echo "\r\n"; + echo "\r\n"; + + echo 'Domain: '; + echo $this->get_setting( 'domain' ); + echo "\r\n"; + echo 'Enable Path: '; + echo $this->on_off( 'enable-object-prefix' ); + echo "\r\n"; + echo 'Custom Path: '; + echo $this->get_setting( 'object-prefix' ); + echo "\r\n"; + echo 'Use Year/Month: '; + echo $this->on_off( 'use-yearmonth-folders' ); + echo "\r\n"; + echo 'SSL: '; + echo $this->get_setting( 'ssl' ); + echo "\r\n"; + echo 'Remove Files From Server: '; + echo $this->on_off( 'remove-local-file' ); + echo "\r\n"; + echo 'Object Versioning: '; + echo $this->on_off( 'object-versioning' ); + echo "\r\n"; + echo 'Far Future Expiration Header: '; + echo $this->on_off( 'expires' ); + echo "\r\n"; + echo 'Copy HiDPI (@2x) Images: '; + echo $this->on_off( 'hidpi-images' ); + echo "\r\n\r\n"; + + do_action( 'as3cf_diagnostic_info' ); + if ( has_action( 'as3cf_diagnostic_info' ) ) { + echo "\r\n"; + } + + echo "Active Plugins:\r\n"; + $active_plugins = (array) get_option( 'active_plugins', array() ); + + if ( is_multisite() ) { + $network_active_plugins = wp_get_active_network_plugins(); + $active_plugins = array_map( array( $this, 'remove_wp_plugin_dir' ), $network_active_plugins ); + } + + foreach ( $active_plugins as $plugin ) { + $this->print_plugin_details( WP_PLUGIN_DIR . '/' . $plugin ); + } + } + + /** + * Helper for displaying settings + * + * @param $key setting key + * + * @return string + */ + function on_off( $key ) { + $value = $this->get_setting( $key, 0 ); + + return ( 1 == $value ) ? 'On' : 'Off'; + } + + /** + * Helper to display plugin details + * + * @param $plugin_path + * @param string $suffix + */ + function print_plugin_details( $plugin_path, $suffix = '' ) { + $plugin_data = get_plugin_data( $plugin_path ); + if ( empty( $plugin_data['Name'] ) ) { + return; + } + + printf( "%s%s (v%s) by %s\r\n", $plugin_data['Name'], $suffix, $plugin_data['Version'], strip_tags( $plugin_data['AuthorName'] ) ); + } + + /** + * Helper to remove the plugin directory from the plugin path + * + * @param string $path + * + * @return string + */ + function remove_wp_plugin_dir( $path ) { + $plugin = str_replace( WP_PLUGIN_DIR, '', $path ); + + return substr( $plugin, 1 ); + } + + /** + * Check for as3cf-download-log and related nonce and if found begin the + * download of the diagnostic log + * + * @return void + */ + function http_prepare_download_log() { + if ( isset( $_GET['as3cf-download-log'] ) && wp_verify_nonce( $_GET['nonce'], 'as3cf-download-log' ) ) { + ob_start(); + $this->output_diagnostic_info( false ); + $log = ob_get_clean(); + $url = parse_url( home_url() ); + $host = sanitize_file_name( $url['host'] ); + $filename = sprintf( '%s-diagnostic-log-%s.txt', $host, date( 'YmdHis' ) ); + header( 'Content-Description: File Transfer' ); + header( 'Content-Type: application/octet-stream' ); + header( 'Content-Length: ' . strlen( $log ) ); + header( 'Content-Disposition: attachment; filename=' . $filename ); + echo $log; + exit; + } + } + + /** + * Return human friendly ACL name + * + * @param string $acl + * + * @return string + */ + function get_acl_display_name( $acl ) { + return ucwords( str_replace( '-', ' ', $acl ) ); + } + + /** + * Detect if OpenSSL is enabled + * + * @return bool + */ + function open_ssl_enabled() { + if ( defined( 'OPENSSL_VERSION_TEXT' ) ) { + return true; + } else { + return false; + } + } + + /** + * Is the current blog ID that specified in wp-config.php + * + * @param int $blog_id + * + * @return bool + */ + function is_current_blog( $blog_id ) { + $default = defined( 'BLOG_ID_CURRENT_SITE' ) ? BLOG_ID_CURRENT_SITE : 1; + + if ( $default === $blog_id ) { + return true; + } + + return false; + } } diff --git a/classes/as3cf-compatibility-check.php b/classes/as3cf-compatibility-check.php deleted file mode 100644 index cab0e480..00000000 --- a/classes/as3cf-compatibility-check.php +++ /dev/null @@ -1,105 +0,0 @@ -plugin_file_path = $plugin_file_path; - $this->aws_plugin_version_required = $aws_plugin_version_required; - - add_action( 'admin_notices', array( $this, 'hook_admin_notices' ) ); - add_action( 'network_admin_notices', array( $this, 'hook_admin_notices' ) ); - } - - function is_compatible() { - return $this->get_error_msg() ? false : true; - } - - function get_error_msg() { - static $msg; - - if ( ! is_null( $msg ) ) { - return $msg; - } - - $hide_notice_msg = '
    ' . __( 'You can deactivate the Amazon S3 and CloudFront plugin to get rid of this notice.', 'as3cf' ); - - if ( ! class_exists( 'Amazon_Web_Services' ) ) { - $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled as it requires the Amazon Web Services plugin.', 'as3cf' ), 'http://wordpress.org/extend/plugins/amazon-web-services/' ); - - if ( file_exists( WP_PLUGIN_DIR . '/amazon-web-services/amazon-web-services.php' ) ) { - $msg .= ' ' . __( 'It appears to be installed already.', 'as3cf' ); - $activate_url = wp_nonce_url( network_admin_url( 'plugins.php?action=activate&plugin=amazon-web-services/amazon-web-services.php' ), 'activate-plugin_amazon-web-services/amazon-web-services.php' ); - $msg .= ' ' . _x( 'Activate it now', 'Activate plugin', 'as3cf' ) . ''; - } - else { - $install_url = wp_nonce_url( network_admin_url( 'update.php?action=install-plugin&plugin=amazon-web-services' ), 'install-plugin_amazon-web-services' ); - $msg .= ' ' . sprintf( __( 'Install it and activate.', 'as3cf' ), $install_url ); - } - - $msg .= $hide_notice_msg; - - return $msg; - } - - $aws_plugin_version = isset( $GLOBALS['aws_meta']['amazon-web-services']['version'] ) ? $GLOBALS['aws_meta']['amazon-web-services']['version'] : 0; - - if ( ! version_compare( $aws_plugin_version, $this->aws_plugin_version_required, '>=' ) ) { - $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled as it requires version %s or later of the Amazon Web Services plugin.', 'as3cf' ), $this->aws_plugin_version_required, 'http://wordpress.org/extend/plugins/amazon-web-services/' ); - - if ( $aws_plugin_version ) { - $msg .= ' ' . sprintf( __( 'You currently have version %s installed.', 'as3cf' ), $aws_plugin_version ); - } - - $update_url = wp_nonce_url( network_admin_url( 'update.php?action=upgrade-plugin&plugin=amazon-web-services/amazon-web-services.php' ), 'upgrade-plugin_amazon-web-services/amazon-web-services.php' ); - $msg .= ' ' . __( 'Update to the latest version', 'as3cf' ) . ''; - - $msg .= $hide_notice_msg; - - return $msg; - } - - $as3cf_plugin_version_required = $GLOBALS['aws_meta']['amazon-web-services']['supported_addon_versions']['amazon-s3-and-cloudfront']; - $as3cf_plugin_version = $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version']; - - if ( ! version_compare( $as3cf_plugin_version, $as3cf_plugin_version_required, '>=' ) ) { - $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled because it will not work with the version of the Amazon Web Services plugin installed. Amazon S3 and CloudFront %s or later is required.', 'as3cf' ), $as3cf_plugin_version_required ); - - $plugin_basename = plugin_basename( __FILE__ ); - $update_url = wp_nonce_url( network_admin_url( 'update.php?action=upgrade-plugin&plugin=' . $plugin_basename ), 'upgrade-plugin_' . $plugin_basename ); - $msg .= ' ' . __( 'Update Amazon S3 and CloudFront to the latest version', 'as3cf' ) . ''; - - $msg .= $hide_notice_msg; - - return $msg; - } - - $msg = false; - return $msg; - } - - function hook_admin_notices() { - if ( is_multisite() ) { - if ( ! current_user_can( 'manage_network_plugins' ) ) { - return; // Don't show notices if the user can't manage network plugins - } - } - else { - // Don't show notices if user doesn't have plugin management privileges - $caps = array( 'activate_plugins', 'update_plugins', 'install_plugins' ); - foreach ( $caps as $cap ) { - if ( ! current_user_can( $cap ) ) { - return; - } - } - } - - $error_msg = $this->get_error_msg(); - - if ( ! $error_msg ) { - return; - } - - printf( '

    %s

    ', $error_msg ); - } -} \ No newline at end of file diff --git a/classes/as3cf-plugin-compatibility.php b/classes/as3cf-plugin-compatibility.php new file mode 100644 index 00000000..bbb4f054 --- /dev/null +++ b/classes/as3cf-plugin-compatibility.php @@ -0,0 +1,229 @@ +as3cf = $as3cf; + + $this->compatibility_init(); + } + + /** + * Register the compatibility hooks + */ + function compatibility_init() { + /* + * WP_Image_Editor + * /wp-includes/class-wp-image-editor.php + */ + add_action( 'as3cf_upload_attachment_pre_remove', array( $this, 'image_editor_remove_files' ), 10, 4 ); + add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 ); + + /* + * WP_Customize_Control + * /wp-includes/class-wp-customize_control.php + */ + add_filter( 'attachment_url_to_postid', array( $this, 'customizer_background_image' ), 10, 2 ); + + /* + * Regenerate Thumbnails + * https://wordpress.org/plugins/regenerate-thumbnails/ + */ + add_filter( 'as3cf_get_attached_file', array( $this, 'regenerate_thumbnails_download_file' ), 10, 4 ); + } + + /** + * Allow the WordPress Image Editor to remove edited version of images + * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set + * + * @param int $post_id + * @param array $s3object + * @param string $prefix + * @param array $args + */ + function image_editor_remove_files( $post_id, $s3object, $prefix, $args ) { + if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] && defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) { + $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); + $this->as3cf->remove_attachment_files_from_s3( $post_id, $s3object, $meta['file'] ); + } + } + + /** + * Allow the WordPress Image Editor to edit files that have been copied to S3 + * but removed from the local server, by copying them back temporarily + * + * @param string $url + * @param string $file + * @param int $attachment_id + * @param array $s3_object + * + * @return string + */ + function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { + if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + return $url; + } + + // When the image-editor restores the original it requests the edited image, + // but we actually need to copy back the original image at this point + // for the restore to be successful and edited images to be deleted from S3 + // via image_editor_remove_files() + if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] ) { + $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true ); + $original_filename = $backup_sizes['full-orig']['file']; + + $orig_s3 = $s3_object; + $orig_s3['key'] = dirname( $s3_object['key'] ) . '/' . $original_filename; + $orig_file = dirname( $file ) . '/' . $original_filename; + + // Copy the original file back to the server for the restore process + $this->copy_s3_file_to_server( $orig_s3, $orig_file ); + + // Copy the edited file back to the server as well, it will be cleaned up later + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; + } + + // must be the image-editor process + if ( isset( $_POST['action'] ) && 'image-editor' == sanitize_key( $_POST['action'] ) ) { // input var okay + $callers = debug_backtrace(); + foreach ( $callers as $caller ) { + if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) { + // check this has been called by '_load_image_to_edit_path' so as only to copy back once + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; + } + } + } + + return $url; + } + + /** + * Show the correct background image in the customizer + * + * @param int|null $post_id + * @param string $url + * + * @return int|null + */ + function customizer_background_image( $post_id, $url ) { + if ( ! is_null( $post_id ) ) { + return $post_id; + } + $url = parse_url( $url ); + + if ( ! isset( $url['path'] ) ) { + return $post_id; // URL path can't be determined + } + + $key1 = ltrim( $url['path'], '/' ); + $length1 = strlen( $key1 ); + + // URLs may contain the bucket name within the path, therefore we must + // also perform the search with the first path segment removed + $parts = explode( '/', $key1 ); + unset( $parts[0] ); + + $key2 = implode( '/', $parts ); + $length2 = strlen( $key2 ); + + global $wpdb; + $sql = " + SELECT `post_id` + FROM `{$wpdb->prefix}postmeta` + WHERE `{$wpdb->prefix}postmeta`.`meta_key` = 'amazonS3_info' + AND ( `{$wpdb->prefix}postmeta`.`meta_value` LIKE '%s:3:\"key\";s:{$length1}:\"{$key1}\";%' + OR `{$wpdb->prefix}postmeta`.`meta_value` LIKE '%s:3:\"key\";s:{$length2}:\"{$key2}\";%' ) + "; + + if ( $id = $wpdb->get_var( $sql ) ) { + return $id; + } + + return $post_id; // No attachment found on S3 + } + + /** + * Allow the Regenerate Thumbnails plugin to copy the S3 file back to the local + * server when the file is missing on the server via get_attached_file + * + * @param string $url + * @param string $file + * @param int $attachment_id + * @param array $s3_object + * + * @return string + */ + function regenerate_thumbnails_download_file( $url, $file, $attachment_id, $s3_object ) { + if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + return $url; + } + + if ( isset( $_POST['action'] ) && 'regeneratethumbnail' == sanitize_key( $_POST['action'] ) ) { // input var okay + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; + } + + return $url; + } + + /** + * Download a file from S3 if the file does not exist locally and places it where + * the attachment's file should be. + * + * @param array $s3_object + * @param string $file + * + * @return string|bool File if downloaded, false on failure + */ + protected function copy_s3_file_to_server( $s3_object, $file ) { + try { + $this->as3cf->get_s3client( $s3_object['region'], true )->getObject( + array( + 'Bucket' => $s3_object['bucket'], + 'Key' => $s3_object['key'], + 'SaveAs' => $file, + ) + ); + } catch ( Exception $e ) { + error_log( sprintf( __( 'There was an error attempting to download the file %s from S3: %s', 'as3cf' ), $s3_object['key'], $e->getMessage() ) ); + + return false; + } + + return $file; + } +} \ No newline at end of file diff --git a/classes/as3cf-upgrade.php b/classes/as3cf-upgrade.php index fc72e251..ccca87b5 100644 --- a/classes/as3cf-upgrade.php +++ b/classes/as3cf-upgrade.php @@ -77,24 +77,33 @@ function maybe_init_upgrade() { return; } + // Do we actually have S3 meta data without regions to update? + // No need to bother for fresh sites, or media not uploaded to S3 + if ( 0 == $this->count_all_attachments_without_region() ) { + $this->as3cf->set_setting( 'post_meta_version', 1 ); + $this->as3cf->save_settings(); + + return; + } + // Initialize the upgrade $this->save_session( array( 'status' => self::STATUS_RUNNING ) ); - $this->schedule_event(); + $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY ); } /** * Adds notices about issues with upgrades allowing user to restart them */ function maybe_display_notices() { - $action_url = self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() . '&action=restart_update_meta_with_region' ); - $msg_type = 'notice'; + $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update_meta_with_region' ), 'self' ); + $msg_type = 'notice-info'; switch ( $this->get_upgrade_status() ) { case self::STATUS_RUNNING : $msg = sprintf( __( 'Running Metadata Update — We’re going through all the Media Library items uploaded to S3 and updating the metadata with the bucket region it is served from. This will allow us to serve your files from the proper S3 region subdomain (e.g. s3-us-west-2.amazonaws.com). This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server’s performance.', 'as3cf' ), $this->cron_interval_in_minutes ); $action_text = __( 'Pause Update', 'as3cf' ); - $action_url = self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() . '&action=pause_update_meta_with_region' ); + $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update_meta_with_region' ), 'self' ); break; case self::STATUS_PAUSED : $msg = __( 'Metadata Update Paused — Updating Media Library metadata has been paused.', 'as3cf' ); @@ -111,7 +120,12 @@ function maybe_display_notices() { $msg .= ' ' . $action_text . ''; - $this->as3cf->render_view( $msg_type, array( 'message' => $msg ) ); + $args = array( + 'message' => $msg, + 'type' => $msg_type, + ); + + $this->as3cf->render_view( 'notice', $args ); } function maybe_handle_action() { @@ -130,7 +144,7 @@ function maybe_handle_action() { */ function action_restart_update_meta_with_region() { $this->change_status_request( self::STATUS_RUNNING ); - $this->schedule_event(); + $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY ); } /** @@ -151,7 +165,8 @@ function change_status_request( $status ) { $session['status'] = $status; $this->save_session( $session ); - wp_redirect( self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() ) ); + $url = $this->as3cf->get_plugin_page_url( array(), 'self' ); + wp_redirect( $url ); } /** @@ -170,63 +185,29 @@ function cron_schedules( $schedules ) { return $schedules; } - - /** - * Wrapper for scheduling the cron job - */ - function schedule_event() { - if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { - wp_schedule_event( current_time( 'timestamp' ), self::CRON_SCHEDULE_KEY, self::CRON_HOOK ); - } - } - - /** - * Wrapper for clearing scheduled events for a specific cron job - */ - function clear_scheduled_event() { - $timestamp = wp_next_scheduled( self::CRON_HOOK ); - if ( $timestamp ) { - wp_unschedule_event( $timestamp, self::CRON_HOOK ); - } - } - + /** * Cron jon to update the region of the bucket in s3 metadata */ function cron_update_meta_with_region() { // Check if the cron should even be running if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 || $this->get_upgrade_status() != self::STATUS_RUNNING ) { - $this->clear_scheduled_event(); + $this->as3cf->clear_scheduled_event( self::CRON_HOOK ); return; } - global $wpdb; - $prefix = $wpdb->prefix; - // set the batch size limit for the query $limit = apply_filters( 'as3cf_update_meta_with_region_batch_size', 500 ); $all_limit = $limit; - $table_prefixes = array(); $session = $this->get_session(); // find the blog IDs that have been processed so we can skip them $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array(); $error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0; - if ( ! in_array( 1, $processed_blog_ids ) ) { - $table_prefixes[1] = $prefix; - } - - if ( is_multisite() ) { - $blog_ids = $this->as3cf->get_blog_ids(); - foreach ( $blog_ids as $blog_id ) { - if ( in_array( $blog_id, $processed_blog_ids ) ) { - continue; - } - $table_prefixes[ $blog_id ] = $prefix . $blog_id . '_'; - } - } + // get the table prefixes for all the blogs + $table_prefixes = $this->get_all_blog_table_prefixes( $processed_blog_ids ); $all_attachments = array(); $all_count = 0; @@ -254,7 +235,7 @@ function cron_update_meta_with_region() { $this->as3cf->set_setting( 'post_meta_version', 1 ); $this->as3cf->remove_setting( 'update_meta_with_region_session' ); $this->as3cf->save_settings(); - $this->clear_scheduled_event(); + $this->as3cf->clear_scheduled_event( self::CRON_HOOK ); return; } @@ -268,7 +249,7 @@ function cron_update_meta_with_region() { // loop through and update s3 meta with region foreach ( $all_attachments as $blog_id => $attachments ) { - if ( 1 != $blog_id && is_multisite() ) { + if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) { switch_to_blog( $blog_id ); } @@ -299,7 +280,7 @@ function cron_update_meta_with_region() { } } - if ( 1 != $blog_id && is_multisite() ) { + if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) { restore_current_blog(); } } @@ -310,7 +291,26 @@ function cron_update_meta_with_region() { $this->save_session( $session ); } - /* + /** + * Get a count of all attachments without region in their S3 metadata + * for the whole site + * + * @return int + */ + function count_all_attachments_without_region() { + // get the table prefixes for all the blogs + $table_prefixes = $this->get_all_blog_table_prefixes(); + $all_count = 0; + + foreach ( $table_prefixes as $blog_id => $table_prefix ) { + $count = $this->count_attachments_without_region( $table_prefix ); + $all_count += $count; + } + + return $all_count; + } + + /** * Get the current status of the upgrade * See STATUS_* constants in the class declaration above. */ @@ -324,7 +324,7 @@ function get_upgrade_status() { return $session['status']; } - /* + /** * Retrieve session data from plugin settings * * @return array @@ -333,7 +333,7 @@ function get_session() { return $this->as3cf->get_setting( 'update_meta_with_region_session', array() ); } - /* + /** * Store data to be used between requests in plugin settings * * @param $session array of session data to store @@ -344,23 +344,90 @@ function save_session( $session ) { } /** - * Get all attachments that don't have region in their S3 meta + * Get all the table prefixes for the blogs in the site. MS compatible * - * @param unknown $prefix - * @param unknown $limit + * @param array $exclude_blog_ids blog ids to exclude + * + * @return array associative array with blog ID as key, prefix as value + */ + function get_all_blog_table_prefixes( $exclude_blog_ids = array() ) { + global $wpdb; + $prefix = $wpdb->prefix; + + $table_prefixes = array(); + + if ( ! in_array( 1, $exclude_blog_ids ) ) { + $table_prefixes[1] = $prefix; + } + + if ( is_multisite() ) { + $blog_ids = $this->as3cf->get_blog_ids(); + foreach ( $blog_ids as $blog_id ) { + if ( in_array( $blog_id, $exclude_blog_ids ) ) { + continue; + } + $table_prefixes[ $blog_id ] = $wpdb->get_blog_prefix( $blog_id ); + } + } + + return $table_prefixes; + } + + /** + * Get all attachments that don't have region in their S3 meta data for a blog + * + * @param string $prefix + * @param int $limit * * @return mixed */ function get_attachments_without_region( $prefix, $limit ) { + $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit ); + + return $attachments; + } + + /** + * Get a count of attachments that don't have region in their S3 meta data for a blog + * @param $prefix + * + * @return int + */ + function count_attachments_without_region( $prefix ) { + $count = $this->get_attachments_without_region_results( $prefix, true ); + + return $count; + } + + /** + * Wrapper for database call to get attachments without region + * + * @param string $prefix + * @param bool $count return count of attachments + * @param null|int $limit + * + * @return mixed + */ + function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) { global $wpdb; - $sql = $wpdb->prepare( - "SELECT `post_id` as `ID`, `meta_value` AS 's3object' - FROM `{$prefix}postmeta` + + $sql = " FROM `{$prefix}postmeta` WHERE `meta_key` = 'amazonS3_info' - AND `meta_value` NOT LIKE '%%\"region\"%%' - LIMIT %d", - $limit - ); + AND `meta_value` NOT LIKE '%%\"region\"%%'"; + + if ( $count ) { + $sql = 'SELECT COUNT(*)' . $sql; + + return $wpdb->get_var( $sql ); + } + + $sql = "SELECT `post_id` as `ID`, `meta_value` AS 's3object'" . $sql; + + if ( ! is_null( $limit ) ) { + $sql .= ' LIMIT %d'; + + $sql = $wpdb->prepare( $sql, $limit ); + } return $wpdb->get_results( $sql, OBJECT ); } diff --git a/classes/wp-aws-compatibility-check.php b/classes/wp-aws-compatibility-check.php new file mode 100644 index 00000000..4dd8f449 --- /dev/null +++ b/classes/wp-aws-compatibility-check.php @@ -0,0 +1,424 @@ +plugin_name = $plugin_name; + $this->plugin_slug = $plugin_slug; + $this->plugin_file_path = $plugin_file_path; + $this->parent_plugin_name = $parent_plugin_name; + $this->parent_plugin_slug = $parent_plugin_slug; + $this->parent_plugin_required_version = $parent_plugin_required_version; + $this->parent_plugin_filename = $parent_plugin_filename; + $this->deactivate_if_not_compatible = $deactivate_if_not_compatible; + $this->parent_plugin_url = $parent_plugin_url; + + add_action( 'admin_notices', array( $this, 'hook_admin_notices' ) ); + add_action( 'network_admin_notices', array( $this, 'hook_admin_notices' ) ); + } + + /** + * Is the plugin compatible? + * + * @return bool + */ + function is_compatible() { + $compatible = $this->get_error_msg() ? false : true; + + $GLOBALS['aws_meta'][ $this->plugin_slug ]['compatible'] = $compatible; + + return $compatible; + } + + /** + * Get the basename for the plugin + * + * @return string + */ + function get_plugin_basename() { + return plugin_basename( $this->plugin_file_path ); + } + + /** + * Get the name of the parent plugin + * + * @return string + */ + function get_parent_plugin_name() { + if ( $this->parent_plugin_name ) { + return $this->parent_plugin_name; + } + + return ''; + } + + /** + * Get the class of the parent plugin + * + * @return mixed|string + */ + function get_parent_plugin_class() { + if ( $this->parent_plugin_slug ) { + $class = ucwords( str_replace( '-', ' ', $this->parent_plugin_slug ) ); + + return str_replace( ' ', '_', $class ); + } + + return ''; + } + + /** + * Get the filename of the main parent plugin file + * + * @return string + */ + function get_parent_plugin_filename() { + if ( ! is_null( $this->parent_plugin_slug ) ) { + $filename = $this->parent_plugin_slug; + if ( ! is_null( $this->parent_plugin_filename ) ) { + $filename = basename( $this->parent_plugin_filename, '.php' ); + } + + return $filename . '.php'; + } + + return ''; + } + + /** + * Get the basename of the parent plugin {slug}/{slug}.php + * + * @return string + */ + function get_parent_plugin_basename() { + if ( ! is_null( $this->parent_plugin_slug ) ) { + $file_name = $this->get_parent_plugin_filename(); + + return $this->parent_plugin_slug . '/' . $file_name; + } + + return ''; + } + + /** + * Get the URL for the parent plugin. Defaults to a wordpress.org URL. + * + * @return null|string + */ + function get_parent_plugin_url() { + if ( ! is_null( $this->parent_plugin_slug ) ) { + $url = 'http://wordpress.org/extend/plugins/' . $this->parent_plugin_slug . '/'; + if ( ! is_null( $this->parent_plugin_url ) ) { + $url = $this->parent_plugin_url; + } + + return $url; + } + + return ''; + } + + /** + * Generate a URL to perform core actions on for a plugin + * + * @param string $action Such as activate, deactivate, install, upgrade + * @param string|null $basename + * + * @return string + */ + function get_plugin_action_url( $action, $basename = null ) { + if ( is_null( $basename ) ) { + $basename = $this->get_plugin_basename(); + } + + $nonce_action = $action . '-plugin_' . $basename; + $page = 'plugins'; + + if ( in_array( $action, array( 'upgrade', 'install' ) ) ) { + $page = 'update'; + $action .= '-plugin'; + } + + $url = wp_nonce_url( network_admin_url( $page . '.php?action=' . $action . '&plugin=' . $basename ), $nonce_action ); + + return $url; + } + + /** + * Set the error message to be returned for the admin notice + * + * @param string $message + * + * @return string + */ + function set_error_msg( $message ) { + // Replace the space between the last two words with   to prevent typographic widows + $message = preg_replace( '/\s([\w]+[.,!\:;\\"-?]{0,1})$/', ' \\1', $message, 1 ); + + $this->error_message = $message; + + return $this->error_message; + } + + /** + * Check if the parent plugin is active and enabled, ie. not disabled due to + * compatibility issues up the chain. + * + * @return bool + */ + function is_parent_plugin_enabled() { + $class = $this->get_parent_plugin_class(); + if ( ! class_exists( $class ) ) { + // Class not even loaded + return false; + } + + // call_user_func overcomes parse errors on PHP versions < 5.3 + if ( method_exists( $class, 'is_compatible' ) && ! call_user_func( array( $class, 'is_compatible' ) ) ) { + // The plugin is active but not compatible + return false; + } + + return true; + } + + /** + * Get the compatibility error message + * + * @return string|void + */ + function get_error_msg() { + if ( is_null( $this->parent_plugin_slug ) ) { + return false; + } + + if ( ! is_null( $this->error_message ) ) { + return $this->error_message; + } + + $plugin_basename = $this->get_plugin_basename(); + $parent_basename = $this->get_parent_plugin_basename(); + $parent_plugin_link_html = sprintf( '%s', $this->get_parent_plugin_url(), $this->get_parent_plugin_name() ); + + $deactivate_url = $this->get_plugin_action_url( 'deactivate', $plugin_basename ); + $deactivate_link = sprintf( '%s', $deactivate_url, __( 'deactivate', 'as3cf' ) ); + $hide_notice_msg = '
    ' . sprintf( __( 'You can %s the %s plugin to get rid of this notice.', 'as3cf' ), $deactivate_link, $this->plugin_name ) . ''; + + if ( ! $this->is_parent_plugin_enabled() ) { + $msg = sprintf( __( '%s has been disabled as it requires the %s plugin.', 'as3cf' ), $this->plugin_name, $parent_plugin_link_html ); + + if ( file_exists( WP_PLUGIN_DIR . '/' . $parent_basename ) ) { + if ( isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['compatible'] ) && ! $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['compatible'] ) { + $msg = rtrim( $msg, '.' ) . ', ' . __( 'which is currently disabled.', 'as3cf' ); + } else { + $msg .= ' ' . __( 'It appears to be installed already.', 'as3cf' ); + $activate_url = $this->get_plugin_action_url( 'activate', $parent_basename ); + $msg .= ' ' . _x( 'Activate it now.', 'Activate plugin', 'as3cf' ) . ''; + } + } else { + $install_url = 'https://deliciousbrains.com/my-account/'; + if ( is_null( $this->parent_plugin_url ) ) { + $install_url = $this->get_plugin_action_url( 'install', $this->parent_plugin_slug ); + } + $msg .= ' ' . sprintf( __( 'Install and activate it.', 'as3cf' ), $install_url ); + } + + $msg .= $hide_notice_msg; + + return $this->set_error_msg( $msg ); + } + + $current_parent_plugin_version = isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] ) ? $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] : 0; + + if ( ! version_compare( $current_parent_plugin_version, $this->parent_plugin_required_version, '>=' ) ) { + $msg = sprintf( __( '%s has been disabled as it requires version %s or later of the %s plugin.', 'as3cf' ), $this->plugin_name, $this->parent_plugin_required_version, $parent_plugin_link_html ); + + if ( $current_parent_plugin_version ) { + $msg .= ' ' . sprintf( __( 'You currently have version %s installed.', 'as3cf' ), $current_parent_plugin_version ); + } + + global $as3cfpro; + if ( ! empty( $as3cfpro ) && $as3cfpro->get_plugin_slug() === $this->parent_plugin_slug ) { + // Don't show update link for addons of a licensed plugin where the license is invalid + if ( ! $as3cfpro->is_valid_licence() ) { + $msg .= ' ' . sprintf( __( 'A valid license for %s is required to update.', 'as3cf' ), $this->get_parent_plugin_name() ); + $msg .= $hide_notice_msg; + + return $this->set_error_msg( $msg ); + } + } + + $update_url = $this->get_plugin_action_url( 'upgrade', $parent_basename ); + $msg .= ' ' . __( 'Update to the latest version', 'as3cf' ) . ''; + + $msg .= $hide_notice_msg; + + return $this->set_error_msg( $msg ); + } + + if ( ! isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'] ) ) { + return false; + } + + if ( ! isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'][ $this->plugin_slug ] ) ) { + $msg = sprintf( __( '%1$s has been disabled because it is not a supported addon of the %2$s plugin.', 'as3cf' ), $this->plugin_name, $this->get_parent_plugin_name() ); + + return $this->set_error_msg( $msg ); + } + + $this_plugin_version_required = $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'][ $this->plugin_slug ]; + $this_plugin_version = $GLOBALS['aws_meta'][ $this->plugin_slug ]['version']; + + if ( ! version_compare( $this_plugin_version, $this_plugin_version_required, '>=' ) ) { + $msg = sprintf( __( '%1$s has been disabled because it will not work with the version of the %2$s plugin installed. %1$s %3$s or later is required.', 'as3cf' ), $this->plugin_name, $this->get_parent_plugin_name(), $this_plugin_version_required ); + + $update_url = $this->get_plugin_action_url( 'upgrade', $plugin_basename ); + $msg .= ' ' . sprintf( __( 'Update %s to the latest version', 'as3cf' ), $this->plugin_name ) . ''; + + $msg .= $hide_notice_msg; + + return $this->set_error_msg( $msg ); + } + + return false; + } + + /** + * Check plugin capabilities for a user + * + * @return bool + */ + function check_capabilities() { + if ( is_multisite() ) { + if ( ! current_user_can( 'manage_network_plugins' ) ) { + return false; // Don't allow if the user can't manage network plugins + } + } else { + // Don't allow if user doesn't have plugin management privileges + $caps = array( 'activate_plugins', 'update_plugins', 'install_plugins' ); + foreach ( $caps as $cap ) { + if ( ! current_user_can( $cap ) ) { + return false; + } + } + } + + return true; + } + + /** + * Display compatibility notices to users who can manage plugins + */ + function hook_admin_notices() { + if ( ! $this->check_capabilities() ){ + return; + } + + $this->get_admin_notice(); + } + + /** + * Get the admin notice to be displayed + */ + function get_admin_notice() { + $error_msg = $this->get_error_msg(); + + if ( ! $error_msg ) { + return; + } + + if ( $this->deactivate_if_not_compatible ) { + $deactivated_msg = sprintf( __( 'The %s plugin has been deactivated.', 'as3cf' ), $this->plugin_name ); + + $error_msg = $deactivated_msg . ' ' . $error_msg; + $this->render_notice( $error_msg ); + + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + deactivate_plugins( $this->plugin_file_path ); + } else { + $this->render_notice( $error_msg ); + } + } + + /** + * Render the notice HTML + * + * @param string $message + */ + function render_notice( $message ) { + printf( '

    %s

    ', $message ); + } + } +} \ No newline at end of file diff --git a/classes/wp-aws-uninstall.php b/classes/wp-aws-uninstall.php new file mode 100644 index 00000000..8555ef4f --- /dev/null +++ b/classes/wp-aws-uninstall.php @@ -0,0 +1,181 @@ +options = $options; + $this->postmeta = $postmeta; + $this->crons = $crons; + $this->transients = $transients; + + $this->set_blog_ids(); + + $this->delete_options(); + $this->delete_postmeta(); + $this->clear_crons(); + $this->delete_transients(); + } + + /** + * Set the blog id(s) for a site + */ + private function set_blog_ids() { + $blog_ids[] = 1; + if ( function_exists( 'is_multisite' ) && is_multisite() ) { + $args = array( + 'limit' => false, + 'spam' => 0, + 'deleted' => 0, + 'archived' => 0, + ); + $blogs = wp_get_sites( $args ); + $blog_ids = wp_list_pluck( $blogs, 'blog_id' ); + } + + $this->blog_ids = $blog_ids; + } + + /** + * Check and ensure a property has been filled with an array + * + * @param string $property + * + * @return bool + */ + private function check_property( $property ) { + if ( empty( $this->$property ) ) { + return false; + } + + if ( ! is_array( $this->$property ) ) { + // Convert any strings to an array + $this->$property = array( $this->$property ); + } + + return true; + } + + /** + * Delete site wide options + */ + public function delete_options() { + if ( ! $this->check_property( 'options' ) ) { + return; + } + + foreach ( $this->options as $option ) { + delete_site_option( $option ); + } + } + + /** + * Delete post meta data for all blogs + */ + public function delete_postmeta() { + if ( ! $this->check_property( 'postmeta' ) ) { + return; + } + + global $wpdb; + + foreach ( $this->blog_ids as $blog_id ) { + $prefix = $wpdb->get_blog_prefix( $blog_id ); + + foreach ( $this->postmeta as $postmeta ) { + $sql = $wpdb->prepare( "DELETE FROM {$prefix}postmeta WHERE meta_key = %s", $postmeta ); + $wpdb->query( $sql ); + } + } + } + + /** + * Clear any scheduled cron jobs + */ + public function clear_crons() { + if ( ! $this->check_property( 'crons' ) ) { + return; + } + + foreach ( $this->crons as $cron ) { + $timestamp = wp_next_scheduled( $cron ); + if ( $timestamp ) { + wp_unschedule_event( $timestamp, $cron ); + } + } + } + + /** + * Delete site wide transients + */ + public function delete_transients() { + if ( ! $this->check_property( 'transients' ) ) { + return; + } + + foreach ( $this->transients as $transient ) { + delete_site_transient( $transient ); + } + } + } +} \ No newline at end of file diff --git a/include/functions.php b/include/functions.php index 9af9885e..6049df96 100644 --- a/include/functions.php +++ b/include/functions.php @@ -11,4 +11,4 @@ function as3cf_get_secure_attachment_url( $post_id, $expires = 900, $size = null global $as3cf; return $as3cf->get_secure_attachment_url( $post_id, $expires, $size ); -} +} \ No newline at end of file diff --git a/languages/amazon-s3-and-cloudfront-en.pot b/languages/amazon-s3-and-cloudfront-en.pot new file mode 100644 index 00000000..46d553a1 --- /dev/null +++ b/languages/amazon-s3-and-cloudfront-en.pot @@ -0,0 +1,580 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: amazon-s3-and-cloudfront\n" +"Report-Msgid-Bugs-To: nom@deliciousbrains.com\n" +"POT-Creation-Date: 2015-07-08 12:57-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:79 +msgid "Offload S3" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:80 +msgid "S3 and CloudFront" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:568 +#, php-format +msgid "File %s does not exist" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:577 +#, php-format +msgid "Mime type %s is not allowed" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:653 +#, php-format +msgid "Error uploading %s to S3: %s" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1251 +msgid "Cheatin’ eh?" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1255 +msgid "You do not have sufficient permissions to access this page." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1261 +msgid "No bucket name provided." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1518 +#, php-format +msgid "There was an error attempting to get the region of the bucket %s: %s" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1635 +msgid "" +"This is a test file to check if the user has write permission to S3. Delete " +"me if found." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1667 +#, php-format +msgid "" +"There was an error attempting to check the permissions of the bucket %s: %s" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1730 +msgid "Error creating bucket: " +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1731 +msgid "Bucket name too short." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1732 +msgid "Bucket name too long." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1733 +msgid "" +"Invalid character. Bucket names can contain lowercase letters, numbers, " +"periods and hyphens." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1734 +msgid "Error saving bucket: " +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1735 +msgid "Error fetching buckets: " +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1736 +msgid "Error getting URL preview: " +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1737 +msgid "The changes you made will be lost if you navigate away from this page" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1795 +msgid "Cheatin' eh?" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1898 +msgctxt "Show the media library tab" +msgid "Media Library" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1899 +msgctxt "Show the support tab" +msgid "Support" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:2035 +#, php-format +msgid "The file %s has been given %s permissions on Amazon S3." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-plugin-compatibility.php:222 +#, php-format +msgid "There was an error attempting to download the file %s from S3: %s" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:104 +#, php-format +msgid "" +"Running Metadata Update — We’re going through " +"all the Media Library items uploaded to S3 and updating the metadata with " +"the bucket region it is served from. This will allow us to serve your files " +"from the proper S3 region subdomain (e." +"g. s3-us-west-2.amazonaws.com). This will be done quietly in the " +"background, processing a small batch of Media Library items every %d " +"minutes. There should be no noticeable impact on your server’s " +"performance." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:105 +msgid "Pause Update" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:109 +msgid "" +"Metadata Update Paused — Updating Media Library " +"metadata has been paused." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:110 +msgid "Restart Update" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:113 +msgid "" +"Error Updating Metadata — We ran into some errors " +"attempting to update the metadata for all your Media Library items that have " +"been uploaded to S3. Please check your error log for details." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:114 +msgid "Try Run It Again" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:183 +#, php-format +msgid "Every %d Minutes" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:276 +msgid "deactivate" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:277 +#, php-format +msgid "You can %s the %s plugin to get rid of this notice." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:280 +#, php-format +msgid "%s has been disabled as it requires the %s plugin." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:284 +msgid "which is currently disabled." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:286 +msgid "It appears to be installed already." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:288 +msgctxt "Activate plugin" +msgid "Activate it now." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:295 +#, php-format +msgid "Install and activate it." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:306 +#, php-format +msgid "" +"%s has been disabled as it requires version %s or later of the %s plugin." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:309 +#, php-format +msgid "You currently have version %s installed." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:316 +#, php-format +msgid "A valid license for %s is required to update." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:324 +msgid "Update to the latest version" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:336 +#, php-format +msgid "" +"%1$s has been disabled because it is not a supported addon of the %2$s " +"plugin." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:345 +#, php-format +msgid "" +"%1$s has been disabled because it will not work with the version of the %2$s " +"plugin installed. %1$s %3$s or later is required." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:348 +#, php-format +msgid "Update %s to the latest version" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:403 +#, php-format +msgid "The %s plugin has been deactivated." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:3 +msgid "Change bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:3 +msgid "What bucket would you like to use?" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:5 +msgid "Existing bucket name" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:7 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:12 +msgid "Saving..." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:7 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:12 +msgid "Save Bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:8 +msgid "Browse existing buckets" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:9 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:26 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:31 +msgid "Create new bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:13 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:21 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:61 +msgid "Cancel" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:18 +msgid "Select bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:19 +msgid "Loading..." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:22 +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:27 +msgid "Refresh" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:25 +msgid "Enter bucket name" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:37 +msgid "Bucket Name:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:40 +msgid "Bucket Name" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:47 +msgid "Region:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:60 +msgid "Creating..." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:60 +msgid "Create New Bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-setting.php:7 +msgid "Bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/bucket-setting.php:11 +msgid "Change" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/debug-info.php:2 +msgid "Diagnostic Info" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/debug-info.php:11 +msgctxt "Download to your computer" +msgid "Download" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:5 +msgid "Domain:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:23 +msgid "Bucket name as subdomain" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:28 +msgid "Bucket name in path" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:33 +msgid "Bucket name as domain" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:38 +msgid "CloudFront or custom domain" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/error-access.php:4 +msgid "Access Denied to Bucket" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/error-access.php:6 +#, php-format +msgid "" +"This could indicate your S3 policy is Read-Only. You need to go to Identity and Access Management in your AWS console and manage the " +"policy for the user you're using for this plugin. Your policy should look " +"something like the following:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:10 +msgid "Settings saved." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:44 +msgid "Enable/Disable the Plugin" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:51 +msgid "Copy Files to S3" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:52 +msgid "" +"When a file is uploaded to the Media Library, copy it to S3. Existing files " +"are not copied to S3." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:60 +msgid "Rewrite File URLs" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:61 +msgid "" +"For Media Library files that have been copied to S3, rewrite the URLs so " +"that they are served from S3/CloudFront instead of your server." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:65 +msgid "Configure File URLs" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:83 +msgid "Path" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:85 +msgid "By default the path is the same as your local WordPress files:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:98 +msgid "Year/Month" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:100 +msgid "Add the Year/Month in the URL." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:106 +msgid "SSL:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:114 +msgid "Same as request" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:115 +msgid "When the request is https://, use https:// for the file URL as well." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:119 +msgid "Always SSL" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:120 +msgid "Forces https:// to be used." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:121 +msgid "" +"You cannot use the \"Bucket as a subdomain\" domain option when using SSL." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:125 +msgid "Always non-SSL" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:126 +msgid "Forces http:// to be used." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:132 +msgid "Advanced Options" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:139 +msgid "Remove Files From Server" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:140 +msgid "Once a file has been copied to S3, remove it from the local server." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:148 +msgid "Object Versioning" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:150 +msgid "" +"Append a timestamp to the S3 file path. Recommended when using CloudFront so " +"you don't have to worry about cache invalidation." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:152 +#: builds/amazon-s3-and-cloudfront/view/settings.php:165 +msgid "More info" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:162 +msgid "Far Future Expiration Header" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:163 +msgid "" +"Implements a \"Never Expire\" caching policy for browsers by setting an " +"Expires header for 10 years in the future. Should be used in conjunction " +"with object versioning above." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:175 +msgid "Copy HiDPI (@2x) Images" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:176 +#, php-format +msgid "" +"When uploading a file to S3, checks if there's a file of the same name with " +"an @2x suffix and copies it to S3 as well. Works with the WP " +"Retina 2x plugin." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/settings.php:182 +msgid "Save Changes" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:6 +msgid "Pro Version?" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:11 +msgid "" +"We're working on a pro version that will include the following features:" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:15 +msgid "Copy existing Media Library to S3" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:16 +msgid "Serve theme JS & CSS from S3/CloudFront" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:17 +msgid "" +"WooCommerce & EDD integration" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:18 +msgid "Awesome email support" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:22 +msgid "Your Email" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:26 +msgid "First Name" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:30 +msgid "Last Name" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:37 +msgid "Send me news about a pro version" +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/sidebar.php:41 +msgid "" +"We promise we will not use your email for anything else and you can " +"unsubscribe with 1-click anytime." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:2 +msgid "As this is a free plugin, we do not provide support." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:4 +#, php-format +msgid "" +"You may ask the WordPress community for help by posting to the WordPress.org support forum. Response time can range from a few days " +"to a few weeks and will likely be from a non-developer." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:6 +#, php-format +msgid "" +"If you want a timely response via email from a developer " +"who works on this plugin, upgrade and send us an email." +msgstr "" + +#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:8 +#, php-format +msgid "" +"If you've found a bug, please submit an issue on GitHub." +msgstr "" diff --git a/languages/amazon-s3-and-cloudfront.pot b/languages/amazon-s3-and-cloudfront.pot deleted file mode 100644 index 4afa8181..00000000 --- a/languages/amazon-s3-and-cloudfront.pot +++ /dev/null @@ -1,457 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Amazon S3 and CloudFront\n" -"POT-Creation-Date: 2015-01-29 12:40-0000\n" -"PO-Revision-Date: 2015-01-29 12:40-0000\n" -"Last-Translator: Delicious Brains \n" -"Language-Team: Delicious Brains \n" -"Language: en\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.7\n" -"X-Poedit-Basepath: ..\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;" -"esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;" -"_nx_noop:3c,1,2;__ngettext_noop:1,2\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Poedit-SearchPath-0: .\n" -"X-Poedit-SearchPathExcluded-0: vendor\n" -"X-Poedit-SearchPathExcluded-1: assets\n" - -#: classes/amazon-s3-and-cloudfront.php:38 -msgid "Amazon S3 and CloudFront" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:39 -msgid "S3 and CloudFront" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:370 -#, php-format -msgid "Mime type %s is not allowed" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:457 -#, php-format -msgid "Error uploading %s to S3: %s" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:961 -msgid "There was an error attempting to access the file system" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1001 -msgid "Cheatin’ eh?" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1005 -msgid "You do not have sufficient permissions to access this page." -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1011 -msgid "No bucket name provided." -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1037 -#: classes/amazon-s3-and-cloudfront.php:1070 -#: classes/amazon-s3-and-cloudfront.php:1129 -msgid "Failed to retrieve bucket region." -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1179 -#, php-format -msgid "There was an error attempting to get the region of the bucket %s: %s" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1291 -msgid "" -"This is a test file to check if the user has write permission to S3. Delete " -"me if found." -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1336 -msgid "Bucket Name:" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1337 -msgid "Error creating bucket: " -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1340 -msgid "Error fetching buckets: " -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1342 -msgid "Error saving bucket: " -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1345 -msgid "Error getting URL preview: " -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1346 -msgid "The changes you made will be lost if you navigate away from this page" -msgstr "" - -#: classes/amazon-s3-and-cloudfront.php:1358 -msgid "Cheatin' eh?" -msgstr "" - -#: classes/as3cf-compatibility-check.php:25 -msgid "" -"You can deactivate the Amazon S3 and CloudFront plugin to get rid of this " -"notice." -msgstr "" - -#: classes/as3cf-compatibility-check.php:28 -#, php-format -msgid "" -"Amazon S3 and CloudFront has been disabled as it requires the Amazon Web Services " -"plugin." -msgstr "" - -#: classes/as3cf-compatibility-check.php:31 -msgid "It appears to be installed already." -msgstr "" - -#: classes/as3cf-compatibility-check.php:33 -msgctxt "Activate plugin" -msgid "Activate it now" -msgstr "" - -#: classes/as3cf-compatibility-check.php:37 -#, php-format -msgid "Install it and activate." -msgstr "" - -#: classes/as3cf-compatibility-check.php:48 -#, php-format -msgid "" -"Amazon S3 and CloudFront has been disabled as it requires version %s or " -"later of the Amazon " -"Web Services plugin." -msgstr "" - -#: classes/as3cf-compatibility-check.php:51 -#, php-format -msgid "You currently have version %s installed." -msgstr "" - -#: classes/as3cf-compatibility-check.php:55 -msgid "Update to the latest version" -msgstr "" - -#: classes/as3cf-compatibility-check.php:66 -#, php-format -msgid "" -"Amazon S3 and CloudFront has been disabled because it will not work with the " -"version of the Amazon Web Services plugin installed. Amazon " -"S3 and CloudFront %s or later is required." -msgstr "" - -#: classes/as3cf-compatibility-check.php:70 -msgid "Update Amazon S3 and CloudFront to the latest version" -msgstr "" - -#: classes/as3cf-upgrade.php:95 -#, php-format -msgid "" -"Running Metadata Update — We’re going through " -"all the Media Library items uploaded to S3 and updating the metadata with " -"the bucket region it is served from. This will allow us to serve your files " -"from the proper S3 region subdomain (e." -"g. s3-us-west-2.amazonaws.com). This will be done quietly in the " -"background, processing a small batch of Media Library items every %d " -"minutes. There should be no noticeable impact on your server’s " -"performance." -msgstr "" - -#: classes/as3cf-upgrade.php:96 -msgid "Pause Update" -msgstr "" - -#: classes/as3cf-upgrade.php:100 -msgid "" -"Metadata Update Paused — Updating Media Library " -"metadata has been paused." -msgstr "" - -#: classes/as3cf-upgrade.php:101 -msgid "Restart Update" -msgstr "" - -#: classes/as3cf-upgrade.php:104 -msgid "" -"Error Updating Metadata — We ran into some errors " -"attempting to update the metadata for all your Media Library items that have " -"been uploaded to S3. Please check your error log for details." -msgstr "" - -#: classes/as3cf-upgrade.php:105 -msgid "Try Run It Again" -msgstr "" - -#: classes/as3cf-upgrade.php:168 -#, php-format -msgid "Every %d Minutes" -msgstr "" - -#: view/settings.php:19 -msgid "Settings saved." -msgstr "" - -#: view/settings.php:35 -msgid "S3 Policy is Read-Only" -msgstr "" - -#: view/settings.php:37 -#, php-format -msgid "" -"You need to go to Identity and Access Management in your " -"AWS console and manage the policy for the user you're using for this plugin. " -"Your policy should look something like the following:" -msgstr "" - -#: view/settings.php:52 -msgid "Select an existing S3 bucket to use:" -msgstr "" - -#: view/settings.php:55 -msgid "Cancel" -msgstr "" - -#: view/settings.php:57 -msgid "Refresh" -msgstr "" - -#: view/settings.php:60 -msgid "" -"You can enter a bucket manually to avoid listing the buckets available. This " -"can be helpful if you have an IAM policy that does not allow bucket listing " -"or you have a large amount of buckets to load." -msgstr "" - -#: view/settings.php:63 -msgid "Enter Bucket" -msgstr "" - -#: view/settings.php:64 -msgid "Select Bucket" -msgstr "" - -#: view/settings.php:68 view/settings.php:79 -msgid "Bucket Name" -msgstr "" - -#: view/settings.php:69 -msgid "Saving..." -msgstr "" - -#: view/settings.php:69 -msgid "Save" -msgstr "" - -#: view/settings.php:73 -msgid "Loading..." -msgstr "" - -#: view/settings.php:76 -msgid "Or create a new bucket:" -msgstr "" - -#: view/settings.php:80 -msgid "Creating..." -msgstr "" - -#: view/settings.php:80 -msgid "Create" -msgstr "" - -#: view/settings.php:91 -msgid "Bucket" -msgstr "" - -#: view/settings.php:95 -msgid "Change" -msgstr "" - -#: view/settings.php:102 -msgid "Enable/Disable the Plugin" -msgstr "" - -#: view/settings.php:109 -msgid "Copy Files to S3" -msgstr "" - -#: view/settings.php:110 -msgid "" -"When a file is uploaded to the Media Library, copy it to S3. Existing files " -"are not copied to S3." -msgstr "" - -#: view/settings.php:118 -msgid "Rewrite File URLs" -msgstr "" - -#: view/settings.php:119 -msgid "" -"For Media Library files that have been copied to S3, rewrite the URLs so " -"that they are served from S3/CloudFront instead of your server." -msgstr "" - -#: view/settings.php:123 -msgid "Configure File URLs" -msgstr "" - -#: view/settings.php:137 -msgid "Domain:" -msgstr "" - -#: view/settings.php:183 -msgid "Path" -msgstr "" - -#: view/settings.php:185 -msgid "By default the path is the same as your local WordPress files:" -msgstr "" - -#: view/settings.php:198 -msgid "Year/Month" -msgstr "" - -#: view/settings.php:200 -msgid "Add the Year/Month in the URL." -msgstr "" - -#: view/settings.php:206 -msgid "SSL" -msgstr "" - -#: view/settings.php:214 -msgid "Same as request" -msgstr "" - -#: view/settings.php:215 -msgid "When the request is https://, use https:// for the file URL as well." -msgstr "" - -#: view/settings.php:219 -msgid "Always SSL" -msgstr "" - -#: view/settings.php:220 -msgid "Forces https:// to be used." -msgstr "" - -#: view/settings.php:221 -msgid "" -"You cannot use the \"Bucket as a subdomain\" domain option when using SSL." -msgstr "" - -#: view/settings.php:225 -msgid "Always non-SSL" -msgstr "" - -#: view/settings.php:226 -msgid "Forces http:// to be used." -msgstr "" - -#: view/settings.php:232 -msgid "Advanced Options" -msgstr "" - -#: view/settings.php:239 -msgid "Remove Files From Server" -msgstr "" - -#: view/settings.php:240 -msgid "Once a file has been copied to S3, remove it from the local server." -msgstr "" - -#: view/settings.php:248 -msgid "Object Versioning" -msgstr "" - -#: view/settings.php:250 -msgid "" -"Append a timestamp to the S3 file path. Recommended when using CloudFront so " -"you don't have to worry about cache invalidation." -msgstr "" - -#: view/settings.php:252 view/settings.php:265 -msgid "More info" -msgstr "" - -#: view/settings.php:262 -msgid "Far Future Expiration Header" -msgstr "" - -#: view/settings.php:263 -msgid "" -"Implements a \"Never Expire\" caching policy for browsers by setting an " -"Expires header for 10 years in the future. Should be used in conjunction " -"with object versioning above." -msgstr "" - -#: view/settings.php:275 -msgid "Copy HiDPI (@2x) Images" -msgstr "" - -#: view/settings.php:276 -#, php-format -msgid "" -"When uploading a file to S3, checks if there's a file of the same name with " -"an @2x suffix and copies it to S3 as well. Works with the WP " -"Retina 2x plugin." -msgstr "" - -#: view/settings.php:282 -msgid "Save Changes" -msgstr "" - -#: view/sidebar.php:6 -msgid "Pro Version?" -msgstr "" - -#: view/sidebar.php:11 -msgid "" -"We're working on a pro version that will include the following features:" -msgstr "" - -#: view/sidebar.php:15 -msgid "Copy existing Media Library to S3" -msgstr "" - -#: view/sidebar.php:16 -msgid "Serve theme JS & CSS from S3/CloudFront" -msgstr "" - -#: view/sidebar.php:17 -msgid "" -"WooCommerce & EDD integration" -msgstr "" - -#: view/sidebar.php:18 -msgid "Awesome email support" -msgstr "" - -#: view/sidebar.php:22 -msgid "Your Name" -msgstr "" - -#: view/sidebar.php:26 -msgid "Your Email" -msgstr "" - -#: view/sidebar.php:30 -msgid "Send me news about a pro version" -msgstr "" - -#: view/sidebar.php:34 -msgid "" -"We promise we will not use your email for anything else and you can " -"unsubscribe with 1-click anytime." -msgstr "" diff --git a/readme.txt b/readme.txt index 8dd543f5..3eae192b 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ -=== Amazon S3 and Cloudfront === +=== WP Offload S3 === Contributors: bradt Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC Tags: uploads, amazon, s3, mirror, admin, media, cdn, cloudfront Requires at least: 3.5 -Tested up to: 4.1 -Stable tag: 0.8.2 +Tested up to: 4.2.2 +Stable tag: 0.9 License: GPLv3 Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. @@ -59,6 +59,36 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin == Changelog == += 0.9 - 2015-07-08 = +* New: Plugin rebranded to WP Offload S3 +* New: Support tab added to _Offload S3_ screen containing diagnostic information +* New: Compatibility with the [Media Replace](https://wordpress.org/plugins/enable-media-replace/) plugin +* New: Select bucket region when creating a new bucket +* New: Toggle switches redesigned +* Improvement: Compatibility with release candidate of Pro plugin +* Improvement: Example IAM policy more secure +* Improvement: Set default bucket region using the `AS3CF_REGION` constant +* Improvement: Added `as3cf_object_meta` filter for developers +* Improvement: Bucket selection moved to modal window +* Improvement: Don't allow bucket names to contain invalid characters on creation +* Improvement: More verbose error messages on bucket selection +* Improvement: Settings link added to plugin row on _Plugins_ screen +* Improvement: Object versioning enabled by default +* Improvement: Uninstall routines added +* Improvement: JavaScript coding standards +* Improvement: Cache result when checking S3 bucket permissions +* Bug fix: Bucket region errors result in blank WP Offload S3 screen +* Bug fix: Editing an image when _Remove Files From Server_ option is enabled results in error +* Bug fix: Metadata upgrade procedure triggered on new installs +* Bug fix: File URLs when uploaded to a subdirectory result in incorrect S3 URLs +* Bug fix: Errors logged when trying to delete non-existent HiDPI images +* Bug fix: SignatureDoesNotMatch errors on regions with v4 authentication +* Bug fix: Customizer background image not editable +* Bug fix: Error when creating buckets with US Standard region +* Bug fix: Notices appearing incorrectly on some admin screens +* Bug fix: Subsite upload paths repeated on multisite installs +* Bug fix: Handle multisite installs where `BLOG_ID_CURRENT_SITE` is not 1 + = 0.8.2 - 2015-01-31 = * New: Input bucket in settings to avoid listing all buckets * New: Specify bucket with 'AS3CF_BUCKET' constant diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 00000000..126de7ff --- /dev/null +++ b/uninstall.php @@ -0,0 +1,27 @@ +"> +
    +

    +
    + +

    + + + +

    +

    + + +

    +
    +
    +
    +

    +
      +

      + + +

      +

      + + + +

      +
      +
      +

      +
      + + + + + + + + + + + + +
      + + + +

      +
      + + + +
      +

      + + +

      +
      +
      + \ No newline at end of file diff --git a/view/bucket-setting.php b/view/bucket-setting.php new file mode 100644 index 00000000..eba4e9f6 --- /dev/null +++ b/view/bucket-setting.php @@ -0,0 +1,28 @@ + + + +

      + + + + + + + get_setting( 'region' ); + if ( is_wp_error( $region ) ) { + $region = ''; + } ?> + + get_setting( 'manual_bucket' ) ? 'manual' : ''; ?> + + + + diff --git a/view/checkbox.php b/view/checkbox.php index ee0da28a..f80f9fa1 100644 --- a/view/checkbox.php +++ b/view/checkbox.php @@ -1,9 +1,9 @@ get_setting( $key ); $class = ( isset( $class ) ) ? 'class="' . $class . '"' : ''; -$disabled = ( isset( $disabled ) && $disabled ) ? 'disabled' : ''; +$disabled = ( isset( $disabled ) && $disabled ) ? ' disabled' : ''; ?> -
      +
      ON OFF diff --git a/view/debug-info.php b/view/debug-info.php new file mode 100644 index 00000000..aa6010a1 --- /dev/null +++ b/view/debug-info.php @@ -0,0 +1,12 @@ +
      +

      + + wp_create_nonce( 'as3cf-download-log' ), + 'as3cf-download-log' => '1', + 'hash' => 'support', + ); + $url = $this->get_plugin_page_url( $args, 'network', false ); ?> + +
      \ No newline at end of file diff --git a/view/domain-setting.php b/view/domain-setting.php new file mode 100644 index 00000000..3cdd9391 --- /dev/null +++ b/view/domain-setting.php @@ -0,0 +1,45 @@ + + + +

      + + + get_setting( 'domain' ); + $subdomain_disabled = ''; + $subdomain_class = ''; + if ( 'https' == $this->get_setting( 'ssl' ) ) { + if ( 'subdomain' == $domain ) { + $domain = 'path'; + } + $subdomain_disabled = 'disabled="disabled"'; + $subdomain_class = 'disabled'; + } + ?> +
      + + + + +
      + + \ No newline at end of file diff --git a/view/error-access.php b/view/error-access.php new file mode 100644 index 00000000..db27daa6 --- /dev/null +++ b/view/error-access.php @@ -0,0 +1,25 @@ +
      +

      + + + — + Identity and Access Management in your AWS console and manage the policy for the user you\'re using for this plugin. Your policy should look something like the following:', 'as3cf' ), 'https://console.aws.amazon.com/iam/home' ); ?> +

      +
      {
      +  "Statement": [
      +    {
      +      "Effect": "Allow",
      +      "Action": [
      +        "s3:CreateBucket",
      +        "s3:DeleteObject",
      +        "s3:Put*",
      +        "s3:Get*",
      +        "s3:List*"
      +      ],
      +      "Resource": [
      +        "arn:aws:s3:::*"
      +      ]
      +    }
      +  ]
      +}
      +
      \ No newline at end of file diff --git a/view/error-fatal.php b/view/error-fatal.php index 40a1c28a..1db162fe 100644 --- a/view/error-fatal.php +++ b/view/error-fatal.php @@ -1,5 +1,5 @@ -
      - - render_view( 'error', compact( 'message' ) ); ?> - +
      +
      +

      +
      \ No newline at end of file diff --git a/view/error.php b/view/error.php deleted file mode 100644 index 1b28ccb1..00000000 --- a/view/error.php +++ /dev/null @@ -1,3 +0,0 @@ -
      -

      -
      diff --git a/view/notice.php b/view/notice.php index 86716b4a..2659f9d6 100644 --- a/view/notice.php +++ b/view/notice.php @@ -1,3 +1,8 @@ -
      + +

      -
      +
      \ No newline at end of file diff --git a/view/settings-tabs.php b/view/settings-tabs.php new file mode 100644 index 00000000..f48f89ea --- /dev/null +++ b/view/settings-tabs.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/view/settings.php b/view/settings.php index af45364a..c1f6bb46 100644 --- a/view/settings.php +++ b/view/settings.php @@ -1,104 +1,46 @@ get_setting( 'bucket' ); ?> -
      - - - -
      +$prefix = $this->get_plugin_prefix_slug(); +?> +
      +

      + +

      +
      +get_setting( 'bucket' ); ?> +
      + check_write_permission(); - // catch any file system issues -if ( is_wp_error( $can_write ) ) { - $this->render_view( 'error-fatal', array( 'message' => $can_write->get_error_message() ) ); - $can_write = true; -} -// display a error message if the user does not have write permission to S3 -?> -
      -

      - - - — - Identity and Access Management in your AWS console and manage the policy for the user you\'re using for this plugin. Your policy should look something like the following:', 'as3cf' ), 'https://console.aws.amazon.com/iam/home' ); ?> -

      -
      {
      -  "Version": "2012-10-17",
      -  "Statement": [
      -	{
      -	  "Effect": "Allow",
      -	  "Action": "s3:*",
      -	  "Resource": "*"
      -	}
      -  ]
      -}
      -
      - get_setting( 'manual_bucket' ) ? 'manual' : ''; ?> -
      -

      -
      - - - - -
      -

      - -

      -

      - - -

      -
      -
      - - -
      -
      -
      -
        -
      -
      -

      -
      - - - -
      -
      + do_action( 'as3cf_media_pre_tab_render' ); + $this->render_bucket_permission_errors(); ?>
      - + + get_settings_nonce_key() ) ?> + - - - - - + render_view( 'bucket-setting', + array( + 'prefix' => $prefix, + 'selected_bucket' => $selected_bucket, + 'tr_class' => 'as3cf-border-bottom', + ) + ); ?> + @@ -119,7 +61,7 @@

      - + @@ -132,49 +74,7 @@ - - - - + render_view( 'domain-setting', array( 'tr_class' => 'configure-url url-preview' ) ); ?> - + @@ -279,11 +179,20 @@

      - - - - - - -

      -

      -
      - get_setting( 'domain' ); - $subdomain_disabled = ''; - $subdomain_class = ''; - if ( 'https' == $this->get_setting( 'ssl' ) ) { - if ( 'subdomain' == $domain ) { - $domain = 'path'; - } - $subdomain_disabled = 'disabled="disabled"'; - $subdomain_class = 'disabled'; - } - ?> -
      - - - - -
      -
      render_view( 'checkbox', array( 'key' => 'enable-object-prefix', 'class' => 'sub-toggle' ) ); ?> @@ -182,7 +82,7 @@

      - + get_default_object_prefix(); // xss ok ?>

      @@ -203,7 +103,7 @@

      -

      +

      - +

      - render_view( 'sidebar' ); ?> + render_view( 'bucket-select', array( 'prefix' => $prefix, 'selected_bucket' => $selected_bucket ) ); ?> +
      + +render_view( 'support' ); ?> -
      \ No newline at end of file + + +is_pro() ) { + $this->render_view( 'sidebar' ); +} +?> \ No newline at end of file diff --git a/view/sidebar.php b/view/sidebar.php index 440392d9..d2ec8269 100644 --- a/view/sidebar.php +++ b/view/sidebar.php @@ -2,7 +2,7 @@
      -