diff --git a/README.md b/README.md index 9ca4aae9..bedae54c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Tags:** uploads, amazon, s3, mirror, admin, media, cdn, cloudfront **Requires at least:** 3.7 **Tested up to:** 4.3 -**Stable tag:** 0.9.6 +**Stable tag:** 0.9.7 **License:** GPLv3 Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. @@ -67,8 +67,25 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin ## Changelog ## -### 0.9.6 - 2015-10-01 ### +### 0.9.7 - 2015-10-26 ### +* Improvement: Improve compatibility with third party plugins when the _Remove Files From Server_ option is enabled +* Improvement: Fix inconsistent spacing on the WP Offload S3 settings screen +* Improvement: Validate _CloudFront or custom domain_ input field +* Improvement: Link to current S3 bucket added to WP Offload S3 settings screen +* Improvement: Show notice when neither GD or Imagick image libraries are not installed +* Improvement: Supply Cache-Control header to S3 when the _Far Future Expiration Header_ option is enabled +* Improvement: Additional information added to _Diagnostic Information_ +* Improvement: Added warning when _Remove Files From Server_ option is enabled +* Improvement: Filter added to allow additional image versions to be uploaded to S3 +* Bug fix: File size not stored in _wp_attachment_metadata_ when _Remove Files From Server_ option is enabled +* Bug fix: Uploads on Multisite installs allowed after surpassing upload limit +* Bug fix: Site icon in WordPress customizer returns 404 +* Bug fix: Image versions remain locally and on S3 after deletion, when the file name contains characters which require escaping +* Bug fix: Files with the same file name overwritten when __Remove Files From Server_ option is enabled +* Bug fix: Cron tasks incorrectly scheduled due to passing the wrong time to `wp_schedule_event` +* Bug fix: Default options not shown in the UI after first install +### 0.9.6 - 2015-10-01 ### * Improvement: Update text domains for translate.wordpress.org integration ### 0.9.5 - 2015-09-01 ### diff --git a/assets/css/styles.css b/assets/css/styles.css index 3b459498..9eb967cd 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -1 +1 @@ -.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"] .as3cf-notice.important,.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{min-height:900px}#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 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 input[type=text],.as3cf-sidebar .subscribe .field input[type=email]{width:100%}.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-banner@2x.jpg);background-size:292px 156px}}@media screen and (max-width: 1052px){.as3cf-sidebar{position:relative;top:auto;right:auto;margin-top:50px}}.as3cf-banner{margin-top:28px;width:292px;height:156px;display:block;background-image:url(../img/snail-banner.jpg);position:relative}.as3cf-banner h1{font-size:28px;color:#fff;font-weight:200;margin:0;position:absolute;bottom:25px;left:20px;text-decoration:none}.as3cf-upgrade-details{background-color:#73833b;padding:20px;color:#fff;font-size:13px;margin:0;display:block;text-decoration:none}.as3cf-upgrade-details p{margin:0}.as3cf-upgrade-details a{color:#fff;font-weight:bold;text-decoration:none;font-size:16px}.as3cf-upgrade-details a:hover{color:#fff;opacity:0.9}.as3cf-upgrade-details ul{margin-top:0;margin-left:16px;list-style-type:disc}.aws-compatibility-notice.error{clear:both;margin:5px 20px 5px 0}.as3cf-bucket-error span.title{font-weight:bold} +.aws-main.wrap>h1{float:left}.aws-main.wrap .as3cf-notice,.aws-main.wrap .as3cf-updated,.aws-main.wrap .as3cf-error{-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:15px;width:650px;margin-top:10px;padding:9px 0 0 5px}.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"] .as3cf-notice.important,.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 tr td .as3cf-notice:last-child{margin-bottom:0}.as3cf-tab .form-table tr:first-of-type td{padding-top:5px}.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 .as3cf-view-bucket{color:#444;text-decoration:none;margin-right:10px}.as3cf-tab .as3cf-view-bucket:hover,.as3cf-tab .as3cf-view-bucket:active{color:#00a0d2}.as3cf-tab .as3cf-view-bucket .dashicons-external{margin-top:-2px}.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 .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{min-height:900px}#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 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 input[type=text],.as3cf-sidebar .subscribe .field input[type=email]{width:100%}.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-banner@2x.jpg);background-size:292px 156px}}@media screen and (max-width: 1052px){.as3cf-sidebar{position:relative;top:auto;right:auto;margin-top:50px}}.as3cf-banner{margin-top:28px;width:292px;height:156px;display:block;background-image:url(../img/snail-banner.jpg);position:relative}.as3cf-banner h1{font-size:28px;color:#fff;font-weight:200;margin:0;position:absolute;bottom:25px;left:20px;text-decoration:none}.as3cf-upgrade-details{background-color:#73833b;padding:20px;color:#fff;font-size:13px;margin:0;display:block;text-decoration:none}.as3cf-upgrade-details p{margin:0}.as3cf-upgrade-details a{color:#fff;font-weight:bold;text-decoration:none;font-size:16px}.as3cf-upgrade-details a:hover{color:#fff;opacity:0.9}.as3cf-upgrade-details ul{margin-top:0;margin-left:16px;list-style-type:disc}.aws-compatibility-notice.error{clear:both;margin:5px 20px 5px 0}.as3cf-bucket-error span.title{font-weight:bold}.as3cf-invalid-bucket-name,.as3cf-validation-error{display:block;margin-top:2px;font-size:12px;color:#a00} diff --git a/assets/js/notice.js b/assets/js/notice.js new file mode 100644 index 00000000..24a2d850 --- /dev/null +++ b/assets/js/notice.js @@ -0,0 +1,24 @@ +(function( $ ) { + + $( 'body' ).on( 'click', '.as3cf-notice .notice-dismiss', function( e ) { + var id = $( this ).parents( '.as3cf-notice' ).attr( 'id' ); + if ( id ) { + var data = { + action : 'as3cf-dismiss-notice', + notice_id: id, + _nonce : as3cf_notice.nonces.dismiss_notice + }; + + $.ajax( { + url : ajaxurl, + type : 'POST', + dataType: 'JSON', + data : data, + error : function( jqXHR, textStatus, errorThrown ) { + alert( as3cf_notice.strings.dismiss_notice_error + errorThrown ); + } + } ); + } + } ); + +})( jQuery ); \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index b810f675..03dc2f79 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -32,6 +32,25 @@ $checkbox.attr( 'checked', switchOn ).trigger( 'change' ); } + /** + * Validate custom domain + * + * @param {object} $input + */ + function validateCustomDomain( $input ) { + var $error = $input.next( '.as3cf-validation-error' ); + var $submit = $( '#' + $activeTab.attr( 'id' ) + ' form button[type="submit"]' ); + var pattern = /[^a-zA-Z0-9\.\-]/; + + if ( pattern.test( $input.val() ) ) { + $error.show(); + $submit.attr( 'disabled', true ); + } else { + $error.hide(); + $submit.attr( 'disabled', false ); + } + } + as3cf.tabs = { defaultTab: 'media', /** @@ -348,7 +367,7 @@ var $manualBucketForm = $( '.as3cf-bucket-container.' + as3cfModal.prefix + ' .as3cf-manual-save-bucket-form' ); var $activeBucket = $( '#' + as3cfModal.prefix + '-active-bucket' ); - if ( 'as3cf' === as3cfModal.prefix && '' === $activeBucket.text() ) { + if ( 'as3cf' === as3cfModal.prefix && 0 === $activeBucket.text().trim().length ) { // first time bucket select - enable main options by default setCheckbox( 'copy-to-s3-wrap' ); setCheckbox( 'serve-from-s3-wrap' ); @@ -376,6 +395,8 @@ generateUrlPreview(); } + setBucketLink(); + as3cfModal.close( unlockBucketSelect ); }, @@ -479,6 +500,25 @@ }; + /** + * Get the link to the bucket on the AWS Console and update the DOM + * + * @returns {string} + */ + function setBucketLink() { + var bucket = $( '#' + as3cfModal.prefix + '-bucket' ).val(); + var $objectPrefix = $activeTab.find( 'input[name="object-prefix"]' ); + var prefix = $objectPrefix.val(); + + if ( '' !== prefix ) { + prefix = '&prefix=' + encodeURIComponent( prefix ); + } + + var url = as3cf.aws_bucket_link + bucket + prefix; + + $( '#' + as3cfModal.prefix + '-view-bucket' ).attr( 'href', url ); + } + /** * Generate URL preview */ @@ -525,6 +565,28 @@ as3cf.buckets.bucketSelectLock = false; } + /* + * Toggle the lost files notice + */ + function toggleLostFilesNotice() { + if ( $( '#remove-local-file' ).is( ':checked' ) && $( '#serve-from-s3' ).is( ':not(:checked)' ) ) { + $( '#as3cf-lost-files-notice' ).show(); + } else { + $( '#as3cf-lost-files-notice' ).hide(); + } + } + + /* + * Toggle the remove local files notice + */ + function toggleRemoveLocalNotice() { + if ( $( '#remove-local-file' ).is( ':checked' ) ) { + $( '#as3cf-remove-local-notice' ).show(); + } else { + $( '#as3cf-remove-local-notice' ).hide(); + } + } + $( document ).ready( function() { // Tabs @@ -630,6 +692,16 @@ generateUrlPreview(); } ); + toggleLostFilesNotice(); + $( '#serve-from-s3,#remove-local-file' ).on( 'change', function( e ) { + toggleLostFilesNotice(); + } ); + + toggleRemoveLocalNotice(); + $( '#remove-local-file' ).on( 'change', function( e ) { + toggleRemoveLocalNotice(); + } ); + // Don't allow 'enter' key to submit form on text input settings $( '.as3cf-setting input[type="text"]' ).keypress( function( event ) { if ( 13 === event.which ) { @@ -640,6 +712,28 @@ } ); + // Validate custom domain + $( 'input[name="cloudfront"]' ).on( 'keyup', function( e ) { + validateCustomDomain( $( this ) ); + } ); + + // Re-enable submit button on domain change + $( 'input[name="domain"]' ).on( 'change', function( e ) { + var $input = $( this ); + var $submit = $( '#' + $activeTab.attr( 'id' ) + ' form button[type="submit"]' ); + + if ( 'cloudfront' !== $input.val() ) { + $submit.attr( 'disabled', false ); + } else { + validateCustomDomain( $input.next( '.as3cf-setting' ).find( 'input[name="cloudfront"]' ) ); + } + } ); + + // Change bucket link when custom path changes + $( 'input[name="object-prefix"]' ).on( 'change', function( e ) { + setBucketLink(); + } ); + // Bucket select // -------------------- diff --git a/assets/js/script.min.js b/assets/js/script.min.js index 52c14f3e..24671a3b 100644 --- a/assets/js/script.min.js +++ b/assets/js/script.min.js @@ -1 +1 @@ -!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(){a(".as3cf-url-preview").html("Generating...");var b={_nonce:as3cf.nonces.get_url_preview};a.each(a("#tab-"+as3cf.tabs.defaultTab+" .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,c,d){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf.strings.get_url_preview_error+b.error)}})}function f(a){as3cf.buckets.bucketSelectLock=!1}var g,h={},i=/[^a-z0-9.-]/,j=a(".as3cf-tab");as3cf.tabs={defaultTab:"media",toggle:function(c,d){j.hide(),g=a("#tab-"+c),g.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),g.attr("data-prefix")&&(b.prefix=g.attr("data-prefix")),d||a(".as3cf-updated").removeClass("show")}},as3cf.buckets={validLength:3,bucketSelectLock:!1,loadList:function(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 this.scrollToSelected();d.html('
  • '+d.attr("data-working")+"
  • ");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b,c,f){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+'
  • ')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){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})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===g.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(),this.loadList()),c.find(".as3cf-bucket-error").hide();var d=a("#"+b.prefix+"-bucket").val();c.find(".as3cf-bucket-manual .as3cf-bucket-name").val(d),this.bucketSelectLock=!1},saveManual:function(){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(),h=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),g.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var i={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket},j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){e.text(h),j.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c,d,g){e.text(h),e.prop("disabled",!1),"undefined"!=typeof c.success?(j.set(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")):j.showError(as3cf.strings.save_bucket_error,c.error,"as3cf-bucket-manual")}})},saveSelected:function(c){var d=a(".as3cf-bucket-list");if(!this.bucketSelectLock){if(this.bucketSelectLock=!0,c.hasClass("selected"))return g.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"),h={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(b,c,f){d.removeClass("saving"),i.showError(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,h,j){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(i.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(i.showError(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"))}})}},disabledButtons:function(){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)}},showError:function(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(),this.bucketSelectLock=!1)},set:function(i,j,k){var l=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),m=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&""===m.text()){d("copy-to-s3-wrap"),d("serve-from-s3-wrap");var n=g.attr("id");h[n]=c(n)}a(".as3cf-error.fatal").hide(),m.text(i),l.find(".as3cf-bucket-name").val(i),a("#"+b.prefix+"-bucket").val(i),a("#"+b.prefix+"-region").val(j),a(".updated").not(".as3cf-notice").show(),g.addClass("as3cf-has-bucket"),g.find(".as3cf-can-write-error").toggle(!k),g.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&e(),b.close(f)},create:function(){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());var j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){f.text(h),j.showError(as3cf.strings.create_bucket_error,c,"as3cf-bucket-create")},success:function(b,c,e){f.text(h),f.prop("disabled",!1),"undefined"!=typeof b.success?(j.set(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)):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return a.length<3||a.length>63?!1:!0===i.test(a)?!1:!0},updateNameNotice:function(b){var c=null;!0===i.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),c&&b.length>0?a(".as3cf-invalid-bucket-name").html(c):a(".as3cf-invalid-bucket-name").html("")}},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 i=window.location.hash.substring(1);as3cf.tabs.toggle(i,!0)}else g=a("#tab-"+as3cf.tabs.defaultTab),a(".aws-main").attr("data-tab",as3cf.tabs.defaultTab);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}}),j.length&&j.each(function(a,b){h[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(h)){var b=g.attr("id");return c(b)!==h[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(b){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(b){a(this).hasClass("disabled")||d(a(this).attr("id"))}),j.on("change",".sub-toggle",function(b){var c=a(this).attr("id");a(".as3cf-setting."+c).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(b){var c=a(this).closest('input:radio[name="domain"]:checked'),d=c.val(),e=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),f="cloudfront"===d;e.toggleClass("hide",!f)}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(b){var c=a('input:radio[name="ssl"]:checked').val();if("https"===c){var d=a('input:radio[name="domain"]:checked').val();"subdomain"===d&&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(a){e()}),a('.as3cf-setting input[type="text"]').keypress(function(a){return 13===a.which?(a.preventDefault(),!1):void 0}),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(),as3cf.buckets.loadList()}),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(),as3cf.buckets.resetModal()}),a("body").on("click",".bucket-action-save",function(a){a.preventDefault(),as3cf.buckets.saveManual()}),a("body").on("click",'.as3cf-create-bucket-form button[type="submit"]',function(a){a.preventDefault(),as3cf.buckets.create()}),a("body").on("click",".bucket-action-refresh",function(a){a.preventDefault(),as3cf.buckets.loadList(!0)}),a("body").on("click",".as3cf-bucket-list a",function(b){b.preventDefault(),as3cf.buckets.saveSelected(a(this))}),a(".as3cf-bucket-container").on("click","a.js-link",function(b){return b.preventDefault(),window.open(a(this).attr("href")),!1}),a("body").on("as3cf-modal-open",function(c,d){if(".as3cf-bucket-container."+b.prefix===d){as3cf.buckets.resetModal();var e=a(".as3cf-bucket-manual h3").data("modal-title");a(".as3cf-bucket-manual h3").text(e),as3cf.buckets.disabledButtons()}}),as3cf.buckets.disabledButtons(),a("body").on("input keyup",".as3cf-create-bucket-form .as3cf-bucket-name",function(c){var d=a(this).val(),e=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(d)?e.find("button[type=submit]").removeAttr("disabled"):e.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(d)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(c){var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");d.find(".as3cf-bucket-name").val().length1)return a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"),void this.scrollToSelected();d.html('
  • '+d.attr("data-working")+"
  • ");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b,c,f){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+'
  • ')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){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})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===k.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(),this.loadList()),c.find(".as3cf-bucket-error").hide();var d=a("#"+b.prefix+"-bucket").val();c.find(".as3cf-bucket-manual .as3cf-bucket-name").val(d),this.bucketSelectLock=!1},saveManual:function(){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(),k.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},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(a,b,c){e.text(g),i.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c,d,h){e.text(g),e.prop("disabled",!1),"undefined"!=typeof c.success?(i.set(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")):i.showError(as3cf.strings.save_bucket_error,c.error,"as3cf-bucket-manual")}})},saveSelected:function(c){var d=a(".as3cf-bucket-list");if(!this.bucketSelectLock){if(this.bucketSelectLock=!0,c.hasClass("selected"))return k.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},h=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:g,error:function(b,c,f){d.removeClass("saving"),h.showError(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,i,j){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(h.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(h.showError(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"))}})}},disabledButtons:function(){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)}},showError:function(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(),this.bucketSelectLock=!1)},set:function(e,i,j){var m=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),n=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&0===n.text().trim().length){d("copy-to-s3-wrap"),d("serve-from-s3-wrap");var o=k.attr("id");l[o]=c(o)}a(".as3cf-error.fatal").hide(),n.text(e),m.find(".as3cf-bucket-name").val(e),a("#"+b.prefix+"-bucket").val(e),a("#"+b.prefix+"-region").val(i),a(".updated").not(".as3cf-notice").show(),k.addClass("as3cf-has-bucket"),k.find(".as3cf-can-write-error").toggle(!j),k.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&g(),f(),b.close(h)},create:function(){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());var j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){f.text(h),j.showError(as3cf.strings.create_bucket_error,c,"as3cf-bucket-create")},success:function(b,c,e){f.text(h),f.prop("disabled",!1),"undefined"!=typeof b.success?(j.set(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)):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return a.length<3||a.length>63?!1:!0===m.test(a)?!1:!0},updateNameNotice:function(b){var c=null;!0===m.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),c&&b.length>0?a(".as3cf-invalid-bucket-name").html(c):a(".as3cf-invalid-bucket-name").html("")}},a(document).ready(function(){var h=a(".wrap.aws-main .nav-tab-wrapper");if(a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(h),window.location.hash){var m=window.location.hash.substring(1);as3cf.tabs.toggle(m,!0)}else k=a("#tab-"+as3cf.tabs.defaultTab),a(".aws-main").attr("data-tab",as3cf.tabs.defaultTab);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}}),n.length&&n.each(function(a,b){l[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(l)){var b=k.attr("id");return c(b)!==l[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(b){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(b){a(this).hasClass("disabled")||d(a(this).attr("id"))}),n.on("change",".sub-toggle",function(b){var c=a(this).attr("id");a(".as3cf-setting."+c).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(b){var c=a(this).closest('input:radio[name="domain"]:checked'),d=c.val(),e=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),f="cloudfront"===d;e.toggleClass("hide",!f)}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(b){var c=a('input:radio[name="ssl"]:checked').val();if("https"===c){var d=a('input:radio[name="domain"]:checked').val();"subdomain"===d&&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(a){g()}),i(),a("#serve-from-s3,#remove-local-file").on("change",function(a){i()}),j(),a("#remove-local-file").on("change",function(a){j()}),a('.as3cf-setting input[type="text"]').keypress(function(a){return 13===a.which?(a.preventDefault(),!1):void 0}),a('input[name="cloudfront"]').on("keyup",function(b){e(a(this))}),a('input[name="domain"]').on("change",function(b){var c=a(this),d=a("#"+k.attr("id")+' form button[type="submit"]');"cloudfront"!==c.val()?d.attr("disabled",!1):e(c.next(".as3cf-setting").find('input[name="cloudfront"]'))}),a('input[name="object-prefix"]').on("change",function(a){f()}),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(),as3cf.buckets.loadList()}),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(),as3cf.buckets.resetModal()}),a("body").on("click",".bucket-action-save",function(a){a.preventDefault(),as3cf.buckets.saveManual()}),a("body").on("click",'.as3cf-create-bucket-form button[type="submit"]',function(a){a.preventDefault(),as3cf.buckets.create()}),a("body").on("click",".bucket-action-refresh",function(a){a.preventDefault(),as3cf.buckets.loadList(!0)}),a("body").on("click",".as3cf-bucket-list a",function(b){b.preventDefault(),as3cf.buckets.saveSelected(a(this))}),a(".as3cf-bucket-container").on("click","a.js-link",function(b){return b.preventDefault(),window.open(a(this).attr("href")),!1}),a("body").on("as3cf-modal-open",function(c,d){if(".as3cf-bucket-container."+b.prefix===d){as3cf.buckets.resetModal();var e=a(".as3cf-bucket-manual h3").data("modal-title");a(".as3cf-bucket-manual h3").text(e),as3cf.buckets.disabledButtons()}}),as3cf.buckets.disabledButtons(),a("body").on("input keyup",".as3cf-create-bucket-form .as3cf-bucket-name",function(c){var d=a(this).val(),e=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(d)?e.find("button[type=submit]").removeAttr("disabled"):e.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(d)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(c){var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");d.find(".as3cf-bucket-name").val().length h2 { + & > h1 { 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; @@ -21,11 +20,10 @@ h2.nav-tab-wrapper { float: none; - margin-bottom: 0; + margin-bottom: 15px; width: 650px; margin-top: 10px; - padding-left: 5px; - padding-right: 0; + padding: 9px 0 0 5px; a.nav-tab-active { color: #464646; @@ -247,6 +245,7 @@ .form-table { margin: 0; + tr { &.as3cf-border-bottom td { border-bottom: 1px solid #ddd; @@ -264,6 +263,16 @@ vertical-align: top; min-width: 120px; } + + .as3cf-notice:last-child { + margin-bottom: 0; + } + } + + &:first-of-type { + td { + padding-top: 5px; + } } } h3 { @@ -275,10 +284,21 @@ } } - .as3cf-active-bucket { - font-weight: bold; - margin-right: 10px; - } + .as3cf-active-bucket { + font-weight: bold; + margin-right: 10px; + } + .as3cf-view-bucket { + color: #444; + text-decoration: none; + margin-right: 10px; + &:hover, &:active { + color: #00a0d2; + } + .dashicons-external { + margin-top: -2px; + } + } .tooltip { position: relative; @@ -405,11 +425,6 @@ } } - .as3cf-invalid-bucket-name { - font-size: 12px; - color: #a00; - } - .bucket-actions { margin: 15px 0; border-top: 1px solid #ccc; @@ -714,4 +729,12 @@ span.title { font-weight: bold; } +} + +.as3cf-invalid-bucket-name, +.as3cf-validation-error { + display: block; + margin-top: 2px; + font-size: 12px; + color: #a00; } \ No newline at end of file diff --git a/classes/amazon-s3-and-cloudfront.php b/classes/amazon-s3-and-cloudfront.php index c0553d28..1ee4fc56 100644 --- a/classes/amazon-s3-and-cloudfront.php +++ b/classes/amazon-s3-and-cloudfront.php @@ -42,6 +42,11 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base { */ protected $default_tab = ''; + /** + * @var AS3CF_Notices + */ + public $notices; + /** * @var string */ @@ -52,6 +57,16 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base { */ protected static $buckets_check = array(); + /** + * @var array + */ + protected $encode_files = array(); + + /** + * @var AS3CF_Plugin_Compatibility + */ + public $plugin_compat; + const DEFAULT_ACL = 'public-read'; const PRIVATE_ACL = 'private'; const DEFAULT_EXPIRES = 900; @@ -70,6 +85,7 @@ function __construct( $plugin_file_path, $aws, $slug = null ) { parent::__construct( $plugin_file_path ); $this->aws = $aws; + $this->notices = AS3CF_Notices::get_instance( $this, $plugin_file_path ); $this->init( $plugin_file_path ); } @@ -95,15 +111,12 @@ function init( $plugin_file_path ) { 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' ), 110, 2 ); - add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 ); + add_filter( 'get_image_tag', array( $this, 'maybe_encode_get_image_tag' ), 10, 6 ); + add_filter( 'wp_get_attachment_image_src', array( $this, 'maybe_encode_wp_get_attachment_image_src' ), 10, 4 ); + add_filter( 'wp_prepare_attachment_for_js', array( $this, 'maybe_encode_wp_prepare_attachment_for_js' ), 10, 3 ); 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 ); @@ -111,7 +124,7 @@ function init( $plugin_file_path ) { add_filter( 'pre_get_space_used', array( $this, 'multisite_get_spaced_used' ) ); // include compatibility code for other plugins - new AS3CF_Plugin_Compatibility( $this ); + $this->plugin_compat = new AS3CF_Plugin_Compatibility( $this ); load_plugin_textdomain( 'amazon-s3-and-cloudfront', false, dirname( plugin_basename( $plugin_file_path ) ) . '/languages/' ); @@ -282,9 +295,28 @@ function set_setting( $key, $value ) { $value = apply_filters( 'as3cf_set_setting_' . $key, $value ); + // Remove disallowed characters from custom domain + if ( 'cloudfront' === $key ) { + $value = $this->sanitize_custom_domain( $value ); + } + parent::set_setting( $key, $value ); } + /** + * Sanitize custom domain + * + * @param string $domain + * + * @return string + */ + function sanitize_custom_domain( $domain ) { + $domain = preg_replace( '@^[a-zA-Z]*:\/\/@', '', $domain ); + $domain = preg_replace( '@[^a-zA-Z0-9\.\-]@', '', $domain ); + + return $domain; + } + /** * Return the default object prefix * @@ -323,7 +355,7 @@ function schedule_event( $hook, $interval = null, $args = array() ) { $interval = $hook; } if ( ! wp_next_scheduled( $hook ) ) { - wp_schedule_event( current_time( 'timestamp' ), $interval, $hook, $args ); + wp_schedule_event( time(), $interval, $hook, $args ); } } @@ -355,7 +387,7 @@ function get_url_preview( $escape = true, $suffix = 'photo.jpg' ) { if ( is_wp_error( $region ) ) { $region = ''; } - $domain = $this->get_s3_url_domain( $bucket, $region ); + $domain = $this->sanitize_custom_domain( $this->get_s3_url_domain( $bucket, $region ) ); $url = $scheme . '://' . $domain . '/' . $path . $suffix; @@ -490,7 +522,7 @@ function wp_update_attachment_metadata( $data, $post_id ) { } // upload attachment to S3 - $this->upload_attachment_to_s3( $post_id, $data ); + $data = $this->upload_attachment_to_s3( $post_id, $data ); return $data; } @@ -508,8 +540,25 @@ function wp_update_attachment_metadata( $data, $post_id ) { * @return array|WP_Error $s3object|$meta If meta is supplied, return it. Else return S3 meta */ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) { + $return_metadata = null; if ( is_null( $data ) ) { $data = wp_get_attachment_metadata( $post_id, true ); + } else { + // As we have passed in the meta, return it later + $return_metadata = $data; + } + + // Allow S3 upload to be hijacked / cancelled for any reason + $pre = apply_filters( 'as3cf_pre_upload_attachment', false, $post_id, $data ); + if ( false !== $pre ) { + if ( ! is_null( $return_metadata ) ) { + // If the attachment metadata is supplied, return it + return $data; + } + + $error_msg = is_string( $pre ) ? $pre : __( 'Upload aborted by filter \'as3cf_pre_upload_attachment\'', 'amazon-s3-and-cloudfront' ); + + return $this->return_upload_error( $error_msg ); } if ( is_null( $file_path ) ) { @@ -518,7 +567,9 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo // Check file exists locally before attempting upload if ( ! file_exists( $file_path ) ) { - return new WP_Error( 'exception', sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path ) ); + $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path ); + + return $this->return_upload_error( $error_msg, $return_metadata ); } $file_name = basename( $file_path ); @@ -527,7 +578,9 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo // check mime type of file is in allowed S3 mime types if ( ! in_array( $type, $allowed_types ) ) { - return new WP_Error( 'exception', sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $type ) ); + $error_msg = sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $type ); + + return $this->return_upload_error( $error_msg, $return_metadata ); } $acl = self::DEFAULT_ACL; @@ -590,7 +643,8 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo // 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 ); + $args['CacheControl'] = 'max-age=315360000'; + $args['Expires'] = date( 'D, d M Y H:i:s O', time() + 315360000 ); } $args = apply_filters( 'as3cf_object_meta', $args, $post_id ); @@ -605,9 +659,8 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo } catch ( Exception $e ) { $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() ); - error_log( $error_msg ); - return new WP_Error( 'exception', $error_msg ); + return $this->return_upload_error( $error_msg, $return_metadata ); } } @@ -627,8 +680,10 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo // Store in the attachment meta data for use by WP $data['filesize'] = $bytes; - // Update metadata with filesize - update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + if ( is_null( $return_metadata ) ) { + // Update metadata with filesize + update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + } // Add to the file size total $filesize_total += $bytes; @@ -688,16 +743,43 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo // Make sure we don't have a cached file sizes in the meta unset( $data['filesize'] ); - // Remove the filesize from the metadata - update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + if ( is_null( $return_metadata ) ) { + // Remove the filesize from the metadata + update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + } delete_post_meta( $post_id, 'wpos3_filesize_total' ); } } + do_action( 'wpos3_post_upload_attachment', $post_id, $s3object ); + + if ( ! is_null( $return_metadata ) ) { + // If the attachment metadata is supplied, return it + return $data; + } + return $s3object; } + /** + * Helper to return meta data on upload error + * + * @param string $error_msg + * @param array|null $return + * + * @return array|WP_Error + */ + protected function return_upload_error( $error_msg, $return = null ) { + if ( is_null( $return ) ) { + return new WP_Error( 'exception', $error_msg ); + } + + error_log( $error_msg ); + + return $return; + } + /** * Remove files from the local site * @@ -711,11 +793,31 @@ function remove_local_files( $file_paths ) { } } + /** + * Add HiDPi suffix to a file path + * + * @param string $orig_path + * + * @return string + */ function get_hidpi_file_path( $orig_path ) { $hidpi_suffix = apply_filters( 'as3cf_hidpi_suffix', '@2x' ); - $pathinfo = pathinfo( $orig_path ); - return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension']; + return $this->apply_file_suffix( $orig_path, $hidpi_suffix ); + } + + /** + * Helper to apply a suffix to a file path + * + * @param string $file + * @param string $suffix + * + * @return string + */ + function apply_file_suffix( $file, $suffix ) { + $pathinfo = pathinfo( $file ); + + return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $suffix . '.' . $pathinfo['extension']; } /** @@ -801,11 +903,6 @@ function wp_handle_upload_prefilter( $file ) { return $file; } - // only do this when we are removing local versions of files - if ( ! $this->get_setting( 'remove-local-file' ) ) { - return $file; - } - $filename = $file['name']; // sanitize the file name before we begin processing @@ -824,37 +921,146 @@ function wp_handle_upload_prefilter( $file ) { // rebuild filename with lowercase extension as S3 will have converted extension on upload $ext = strtolower( $ext ); $filename = $info['filename'] . $ext; + $time = current_time( 'mysql' ); - $time = current_time( 'timestamp' ); - $time = date( 'Y/m', $time ); + // Get time if uploaded in post screen + $post_id = filter_input( INPUT_POST, 'post_id', FILTER_VALIDATE_INT ); + if ( isset( $post_id ) ) { + $time = $this->get_post_time( $post_id ); + } - $prefix = ltrim( trailingslashit( $this->get_object_prefix() ), '/' ); - $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' ); + if ( ! $this->does_file_exist( $filename, $time ) ) { + // File doesn't exist locally or on S3, return it + return $file; + } + + $file['name'] = $this->generate_unique_filename( $name, $ext, $time ); + return $file; + } + + /** + * Get post time + * + * @param int $post_id + * + * @return string + */ + function get_post_time( $post_id ) { + $time = current_time( 'mysql' ); + + if ( ! $post = get_post( $post_id ) ) { + return $time; + } + + if ( substr( $post->post_date, 0, 4 ) > 0 ) { + $time = $post->post_date; + } + + return $time; + } + + /** + * Does file exist + * + * @param string $filename + * @param string $time + * + * @return bool + */ + function does_file_exist( $filename, $time ) { + if ( $this->does_file_exist_local( $filename, $time ) ) { + return true; + } + + if ( ! $this->get_setting( 'object-versioning' ) && $this->does_file_exist_s3( $filename, $time ) ) { + return true; + } + + return false; + } + + /** + * Does file exist local + * + * @param string $filename + * @param string $time + * + * @return bool + */ + function does_file_exist_local( $filename, $time ) { + global $wpdb; + + $path = wp_upload_dir( $time ); + $path = ltrim( $path['subdir'], '/' ); + + if ( '' !== $path ) { + $path = trailingslashit( $path ); + } + $file = $path . $filename; + + $sql = $wpdb->prepare( " + SELECT COUNT(*) + FROM $wpdb->postmeta + WHERE meta_key = %s + AND meta_value = %s + ", '_wp_attached_file', $file ); + + return (bool) $wpdb->get_var( $sql ); + } + + /** + * Does file exist s3 + * + * @param string $filename + * @param string $time + * + * @return bool + */ + function does_file_exist_s3( $filename, $time ) { $bucket = $this->get_setting( 'bucket' ); $region = $this->get_setting( 'region' ); + if ( is_wp_error( $region ) ) { - return $file; + return false; } $s3client = $this->get_s3client( $region ); + $prefix = ltrim( trailingslashit( $this->get_object_prefix() ), '/' ); + $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' ); - $number = ''; - while ( $s3client->doesObjectExist( $bucket, $prefix . $filename ) !== false ) { - $previous = $number; - ++$number; - if ( '' == $previous ) { - $filename = $name . $number . $ext; - } else { - $filename = str_replace( "$previous$ext", $number . $ext, $filename ); - } - } + return $s3client->doesObjectExist( $bucket, $prefix . $filename ); + } + + /** + * Generate unique filename + * + * @param string $name + * @param string $ext + * @param string $time + * + * @return string + */ + function generate_unique_filename( $name, $ext, $time ) { + $count = 1; + $filename = $name . $count . $ext; - $file['name'] = $filename; + while ( $this->does_file_exist( $filename, $time ) ) { + $count++; + $filename = $name . $count . $ext; + } - return $file; + return $filename; } + /** + * Get attachment url + * + * @param string $url + * @param int $post_id + * + * @return bool|mixed|void|WP_Error + */ function wp_get_attachment_url( $url, $post_id ) { $new_url = $this->get_attachment_url( $post_id ); if ( false === $new_url ) { @@ -867,6 +1073,13 @@ function wp_get_attachment_url( $url, $post_id ) { return $new_url; } + /** + * Get attachment s3 info + * + * @param int $post_id + * + * @return mixed + */ function get_attachment_s3_info( $post_id ) { return get_post_meta( $post_id, 'amazonS3_info', true ); } @@ -958,10 +1171,12 @@ function use_ssl( $ssl = null ) { /** * Get the custom object prefix if enabled * + * @param string $toggle_setting + * * @return string */ - function get_object_prefix() { - if ( $this->get_setting( 'enable-object-prefix' ) ) { + function get_object_prefix( $toggle_setting = 'enable-object-prefix' ) { + if ( $this->get_setting( $toggle_setting ) ) { $prefix = trim( $this->get_setting( 'object-prefix' ) ); } else { $prefix = ''; @@ -1121,50 +1336,100 @@ function get_attachment_url( $post_id, $expires = null, $size = null, $meta = nu } /** - * Override the attachment metadata + * Maybe encode attachment URLs when retrieving the image tag * - * @param unknown $data - * @param unknown $post_id + * @param string $html + * @param int $id + * @param string $alt + * @param string $title + * @param string $align + * @param string $size * - * @return mixed + * @return string */ - function wp_get_attachment_metadata( $data, $post_id ) { - return $this->maybe_encoded_file_of_resized_images( $data, $post_id ); + function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) { + if ( ! $this->get_setting( 'serve-from-s3' ) ) { + // Not serving S3 URLs + return $html; + } + + if ( ! $this->get_attachment_s3_info( $id ) ) { + // File not uploaded to S3 + return $html; + } + + preg_match( '@\ssrc=[\'\"]([^\'\"]*)[\'\"]@', $html, $matches ); + + if ( ! isset( $matches[1] ) ) { + // Can't establish img src + return $html; + } + + $img_src = $matches[1]; + $encoded_src = $this->encode_filename_in_path( $img_src ); + + return str_replace( $img_src, $encoded_src, $html ); } /** - * Encodes the file names for resized image files for an attachment where necessary + * Maybe encode URLs for images that represent an attachment * - * @param array $data - * @param int $post_id + * @param array|bool $image + * @param int $attachment_id + * @param string|array $size + * @param bool $icon * - * @return mixed Attachment meta data + * @return array */ - function maybe_encoded_file_of_resized_images( $data, $post_id ) { + function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) { if ( ! $this->get_setting( 'serve-from-s3' ) ) { - return $data; + // Not serving S3 URLs + return $image; } - if ( ! ( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) { - return $data; + if ( ! $this->get_attachment_s3_info( $attachment_id ) ) { + // File not uploaded to S3 + return $image; } - // we only need to encode the file name if url encoding is needed - $filename = basename( $s3object['key'] ); - if ( $filename == rawurlencode( $filename ) ) { - return $data; + if ( isset( $image[0] ) ) { + $image[0] = $this->encode_filename_in_path( $image[0] ); } - // we only need to encode resized image files - if ( ! isset( $data['sizes'] ) ) { - return $data; + return $image; + } + + /** + * Maybe encode URLs when outputting attachments in the media grid + * + * @param array $response + * @param int|object $attachment + * @param array $meta + * + * @return array + */ + function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) { + if ( ! $this->get_setting( 'serve-from-s3' ) ) { + // Not serving S3 URLs + return $response; } - foreach ( $data['sizes'] as $key => $size ) { - $data['sizes'][ $key ]['file'] = $this->encode_filename_in_path( $data['sizes'][ $key ]['file'] ); + if ( ! $this->get_attachment_s3_info( $attachment->ID ) ) { + // File not uploaded to S3 + return $response; } - return $data; + if ( isset( $response['url'] ) ) { + $response['url'] = $this->encode_filename_in_path( $response['url'] ); + } + + if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) { + foreach ( $response['sizes'] as $key => $value ) { + $response['sizes'][ $key ]['url'] = $this->encode_filename_in_path( $value['url'] ); + } + } + + return $response; } /** @@ -1176,11 +1441,40 @@ function maybe_encoded_file_of_resized_images( $data, $post_id ) { * @return string Encoded filename with path prefix untouched */ function encode_filename_in_path( $file ) { - $file_path = dirname( $file ); - $file_path = ( '.' != $file_path ) ? trailingslashit( $file_path ) : ''; - $file_name = rawurlencode( basename( $file ) ); + $url = parse_url( $file ); + + if ( ! isset( $url['path'] ) ) { + return $file; + } + + if ( in_array( $this->leading_slash_it( $url['path'] ), $this->encode_files ) ) { + // Already encoded return original file + return $file; + } + + $file_path = dirname( $file ); + $file_path = ( '.' !== $file_path ) ? trailingslashit( $file_path ) : ''; + $file_name = basename( $file ); + $encoded_file_name = rawurlencode( $file_name ); + $encoded_file_path = $file_path . $encoded_file_name; + + if ( $file_name !== $encoded_file_name ) { + $encoded_url = parse_url( $encoded_file_path ); + $this->encode_files[] = $this->leading_slash_it( $encoded_url['path'] ); + } + + return $file_path . $encoded_file_name; + } - return $file_path . $file_name; + /** + * Leading slash it + * + * @param string $path + * + * @return string + */ + function leading_slash_it( $path ) { + return '/' . ltrim( $path, '/\\' ); } /** @@ -1498,7 +1792,7 @@ function admin_menu( $aws ) { * @param bool|string $region specify region to client for signature * @param bool $force force return of new S3 client when swapping regions * - * @return mixed + * @return Aws\S3\S3Client */ function get_s3client( $region = false, $force = false ) { if ( is_null( $this->s3client ) || $force ) { @@ -1739,7 +2033,7 @@ function plugin_load() { wp_localize_script( 'as3cf-script', 'as3cf', array( - 'strings' => array( + 'strings' => array( 'create_bucket_error' => __( 'Error creating bucket', 'amazon-s3-and-cloudfront' ), 'create_bucket_name_short' => __( 'Bucket name too short.', 'amazon-s3-and-cloudfront' ), 'create_bucket_name_long' => __( 'Bucket name too long.', 'amazon-s3-and-cloudfront' ), @@ -1749,19 +2043,21 @@ function plugin_load() { 'get_url_preview_error' => __( 'Error getting URL preview: ', 'amazon-s3-and-cloudfront' ), 'save_alert' => __( 'The changes you made will be lost if you navigate away from this page', 'amazon-s3-and-cloudfront' ) ), - 'nonces' => array( + '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(), + 'is_pro' => $this->is_pro(), + 'aws_bucket_link' => $this->get_aws_bucket_link(), ) ); $this->handle_post_request(); $this->http_prepare_download_log(); + $this->check_for_gd_imagick(); do_action( 'as3cf_plugin_load' ); } @@ -1998,6 +2294,22 @@ function is_pro() { return true; } + /** + * Get the link to the bucket on the AWS console + * + * @param string $bucket + * @param string $prefix + * + * @return string + */ + function get_aws_bucket_link( $bucket = '', $prefix = '' ) { + if ( '' !== $prefix ) { + $prefix = '&prefix=' . urlencode( $prefix ); + } + + return 'https://console.aws.amazon.com/s3/home?bucket=' . $bucket . $prefix; + } + /** * Apply ACL to an attachment and associated files * @@ -2022,7 +2334,7 @@ function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) { // Add attachment to ACL update notice $message = $this->make_acl_admin_notice_text( $s3object ); - $this->set_admin_notice( $message ); + $this->notices->add_notice( $message ); // update S3 meta data if ( $acl == self::DEFAULT_ACL ) { @@ -2049,52 +2361,17 @@ function make_acl_admin_notice_text( $s3object ) { } /** - * 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 + * Check if PHP GD and Imagick is installed */ - 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 ); - } + function check_for_gd_imagick() { + $gd_enabled = $this->gd_enabled(); + $imagick_enabled = $this->imagick_enabled(); - delete_site_transient( 'as3cf_notices' ); + if( ! $gd_enabled && ! $imagick_enabled ) { + $this->notices->add_notice( + __( 'Image Manipulation Library Missing — Looks like you don\'t have an image manipulation library installed on this server and configured with PHP. You may run into trouble if you try to edit images. Please setup GD or ImageMagick.', 'amazon-s3-and-cloudfront' ), + array( 'flash' => false, 'only_show_to_user' => false, 'only_show_in_settings' => true ) + ); } } @@ -2148,6 +2425,12 @@ function output_diagnostic_info( $escape = true ) { echo empty( $wpdb->use_mysqli ) ? 'no' : 'yes'; echo "\r\n"; + echo 'PHP Memory Limit: '; + if ( function_exists( 'ini_get' ) ) { + echo esc_html( ini_get( 'memory_limit' ) ); + } + echo "\r\n"; + echo 'WP Memory Limit: '; echo esc_html( WP_MEMORY_LIMIT ); echo "\r\n"; @@ -2190,6 +2473,10 @@ function output_diagnostic_info( $escape = true ) { } echo "\r\n"; + echo 'WP Cron: '; + echo esc_html( ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ? 'Disabled' : 'Enabled' ); + echo "\r\n"; + echo 'fsockopen: '; if ( function_exists( 'fsockopen' ) ) { echo 'Enabled'; @@ -2220,6 +2507,23 @@ function output_diagnostic_info( $escape = true ) { } else { echo 'Disabled'; } + echo "\r\n"; + + echo 'PHP GD: '; + if ( $this->gd_enabled() ) { + $gd_info = gd_info(); + echo isset( $gd_info['GD Version'] ) ? esc_html( $gd_info['GD Version'] ) : 'Enabled'; + } else { + echo 'Disabled'; + } + echo "\r\n"; + + echo 'Imagick: '; + if ( $this->imagick_enabled() ) { + echo 'Enabled'; + } else { + echo 'Disabled'; + } echo "\r\n\r\n"; $media_counts = $this->diagnostic_media_counts(); @@ -2294,6 +2598,7 @@ function output_diagnostic_info( $escape = true ) { echo "Active Plugins:\r\n"; $active_plugins = (array) get_option( 'active_plugins', array() ); + $plugin_details = array(); if ( is_multisite() ) { $network_active_plugins = wp_get_active_network_plugins(); @@ -2301,7 +2606,24 @@ function output_diagnostic_info( $escape = true ) { } foreach ( $active_plugins as $plugin ) { - $this->print_plugin_details( WP_PLUGIN_DIR . '/' . $plugin ); + $plugin_details[] = $this->get_plugin_details( WP_PLUGIN_DIR . '/' . $plugin ); + } + + asort( $plugin_details ); + echo implode( '', $plugin_details ); + + $mu_plugins = wp_get_mu_plugins(); + if ( $mu_plugins ) { + $mu_plugin_details = array(); + echo "\r\n"; + echo "Must-use Plugins:\r\n"; + + foreach ( $mu_plugins as $mu_plugin ) { + $mu_plugin_details[] = $this->get_plugin_details( $mu_plugin ); + } + + asort( $mu_plugin_details ); + echo implode( '', $mu_plugin_details ); } } @@ -2323,14 +2645,16 @@ function on_off( $key ) { * * @param string $plugin_path * @param string $suffix + * + * @return string */ - function print_plugin_details( $plugin_path, $suffix = '' ) { + function get_plugin_details( $plugin_path, $suffix = '' ) { $plugin_data = get_plugin_data( $plugin_path ); if ( empty( $plugin_data['Name'] ) ) { - return; + return basename( $plugin_path ); } - printf( "%s%s (v%s) by %s\r\n", $plugin_data['Name'], $suffix, $plugin_data['Version'], strip_tags( $plugin_data['AuthorName'] ) ); + return sprintf( "%s%s (v%s) by %s\r\n", $plugin_data['Name'], $suffix, $plugin_data['Version'], strip_tags( $plugin_data['AuthorName'] ) ); } /** @@ -2393,6 +2717,32 @@ function open_ssl_enabled() { } } + /** + * Detect if PHP GD is enabled + * + * @return bool + */ + function gd_enabled() { + if ( extension_loaded( 'gd' ) && function_exists( 'gd_info' ) ) { + return true; + } else { + return false; + } + } + + /** + * Detect is Imagick is enabled + * + * @return bool + */ + function imagick_enabled() { + if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) && class_exists( 'ImagickPixel' ) ) { + return true; + } else { + return false; + } + } + /** * Is the current blog ID that specified in wp-config.php * @@ -2523,6 +2873,9 @@ public function get_attachment_file_paths( $attachment_id, $exists_locally = tru } } + // Allow other processes to add files to be uploaded + $paths = apply_filters( 'as3cf_attachment_file_paths', $paths, $attachment_id, $meta ); + // Remove duplicates $paths = array_unique( $paths ); @@ -2566,26 +2919,22 @@ function get_access_denied_notice_message( $single = true ) { * @return float|int */ function multisite_get_spaced_used( $space_used ) { - if ( false === ( $space_used = get_transient( 'wpos3_site_space_used' ) ) ) { - global $wpdb; + global $wpdb; - // Sum the total file size (including image sizes) for all S3 attachments - $sql = "SELECT SUM( meta_value ) AS bytes_total + // Sum the total file size (including image sizes) for all S3 attachments + $sql = "SELECT SUM( meta_value ) AS bytes_total FROM {$wpdb->postmeta} WHERE meta_key = 'wpos3_filesize_total'"; - $space_used = $wpdb->get_var( $sql ); + $space_used = $wpdb->get_var( $sql ); - // Get local upload sizes - $upload_dir = wp_upload_dir(); - $space_used += get_dirsize( $upload_dir['basedir'] ); - - if ( $space_used > 0 ) { - // Convert to bytes to MB - $space_used = $space_used / 1024 / 1024; - } + // Get local upload sizes + $upload_dir = wp_upload_dir(); + $space_used += get_dirsize( $upload_dir['basedir'] ); - set_transient( 'wpos3_site_space_used', $space_used, HOUR_IN_SECONDS ); + if ( $space_used > 0 ) { + // Convert to bytes to MB + $space_used = $space_used / 1024 / 1024; } return $space_used; diff --git a/classes/as3cf-notices.php b/classes/as3cf-notices.php new file mode 100644 index 00000000..b0f8e433 --- /dev/null +++ b/classes/as3cf-notices.php @@ -0,0 +1,324 @@ +as3cf = $as3cf; + $this->plugin_file_path = $plugin_file_path; + + add_action( 'admin_notices', array( $this, 'admin_notices' ) ); + add_action( 'network_admin_notices', array( $this, 'admin_notices' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_notice_scripts' ) ); + add_action( 'wp_ajax_as3cf-dismiss-notice', array( $this, 'ajax_dismiss_notice' ) ); + } + + /** + * As this class is a singelton it should not be clone-able + */ + protected function __clone() { + // Singleton + } + + /** + * Create a notice + * + * @param string $message + * @param array $args + * + * @return string notice ID + */ + public function add_notice( $message, $args = array() ) { + $defaults = array( + 'type' => 'info', + 'dismissible' => true, + 'inline' => false, + 'flash' => true, + 'only_show_to_user' => true, + 'only_show_in_settings' => false, + 'custom_id' => '', + ); + + $notice = array_intersect_key( array_merge( $defaults, $args ), $defaults ); + $notice['message'] = $message; + $notice['triggered_by'] = get_current_user_id(); + $notice['created_at'] = time(); + + if ( $notice['custom_id'] ) { + $notice['id'] = $notice['custom_id']; + } else { + $notice['id'] = apply_filters( 'as3cf_notice_id_prefix', 'as3cf-notice-' ) . sha1( $notice['message'] ); + } + + $this->save_notice( $notice ); + + return $notice['id']; + } + + /** + * Save a notice + * + * @param array $notice + */ + protected function save_notice( $notice ) { + $user_id = get_current_user_id(); + + if ( $notice['only_show_to_user'] ) { + $notices = get_user_meta( $user_id, 'as3cf_notices', true ); + } else { + $notices = get_site_transient( 'as3cf_notices' ); + } + + if ( ! is_array( $notices ) ) { + $notices = array(); + } + + if ( ! array_key_exists( $notice['id'], $notices ) ) { + $notices[ $notice['id'] ] = $notice; + + if ( $notice['only_show_to_user'] ) { + update_user_meta( $user_id, 'as3cf_notices', $notices ); + } else { + set_site_transient( 'as3cf_notices', $notices ); + } + } + } + + /** + * Remove a notice + * + * @param array $notice + */ + protected function remove_notice( $notice ) { + $user_id = get_current_user_id(); + + if ( $notice['only_show_to_user'] ) { + $notices = get_user_meta( $user_id, 'as3cf_notices', true ); + } else { + $notices = get_site_transient( 'as3cf_notices' ); + } + + if ( ! is_array( $notices ) ) { + $notices = array(); + } + + if ( array_key_exists( $notice['id'], $notices ) ) { + unset( $notices[ $notice['id'] ] ); + + if ( $notice['only_show_to_user'] ) { + if ( ! empty( $notices ) ) { + update_user_meta( $user_id, 'as3cf_notices', $notices ); + } else { + delete_user_meta( $user_id, 'as3cf_notices' ); + } + } else { + if ( ! empty( $notices ) ) { + set_site_transient( 'as3cf_notices', $notices ); + } else { + delete_site_transient( 'as3cf_notices' ); + } + } + } + } + + /** + * Remove a notice by it's ID + * + * @param string $notice_id + */ + public function remove_notice_by_id( $notice_id ) { + $notice = $this->find_notice_by_id( $notice_id ); + if ( $notice ) { + $this->remove_notice( $notice ); + } + } + + /** + * Dismiss a notice + * + * @param string $notice_id + */ + protected function dismiss_notice( $notice_id ) { + $user_id = get_current_user_id(); + + $notice = $this->find_notice_by_id( $notice_id ); + if ( $notice ) { + if ( $notice['only_show_to_user'] ) { + if ( ! empty( $notices ) ) { + unset( $notices[ $notice['id'] ] ); + update_user_meta( $user_id, 'as3cf_notices', $notices ); + } else { + delete_user_meta( $user_id, 'as3cf_notices' ); + } + } else { + $dismissed_notices = get_user_meta( $user_id, 'as3cf_dismissed_notices', true ); + if ( ! is_array( $dismissed_notices ) ) { + $dismissed_notices = array(); + } + + if ( ! in_array( $notice['id'], $dismissed_notices ) ) { + $dismissed_notices[] = $notice['id']; + update_user_meta( $user_id, 'as3cf_dismissed_notices', $dismissed_notices ); + } + } + } + } + + /** + * Find a notice by it's ID + * + * @param string $notice_id + * + * @return mixed + */ + protected function find_notice_by_id( $notice_id ) { + $user_id = get_current_user_id(); + + $user_notices = get_user_meta( $user_id, 'as3cf_notices', true ); + if ( ! is_array( $user_notices ) ) { + $user_notices = array(); + } + + $global_notices = get_site_transient( 'as3cf_notices' ); + if ( ! is_array( $global_notices ) ) { + $global_notices = array(); + } + $notices = array_merge( $user_notices, $global_notices ); + + if ( array_key_exists( $notice_id, $notices ) ) { + return $notices[ $notice_id ]; + } + + return null; + } + + /** + * Show the notices + */ + public function admin_notices() { + $user_id = get_current_user_id(); + $dismissed_notices = get_user_meta( $user_id, 'as3cf_dismissed_notices', true ); + if ( ! is_array( $dismissed_notices ) ) { + $dismissed_notices = array(); + } + + $user_notices = get_user_meta( $user_id, 'as3cf_notices', true ); + if ( is_array( $user_notices ) && ! empty( $user_notices ) ) { + foreach ( $user_notices as $notice ) { + $this->maybe_show_notice( $notice, $dismissed_notices ); + } + } + + $global_notices = get_site_transient( 'as3cf_notices' ); + if ( is_array( $global_notices ) && ! empty( $global_notices ) ) { + foreach ( $global_notices as $notice ) { + $this->maybe_show_notice( $notice, $dismissed_notices ); + } + } + } + + /** + * If it should be shown, display an individual notice + * + * @param array $notice + */ + protected function maybe_show_notice( $notice, $dismissed_notices ) { + $screen = get_current_screen(); + if ( $notice['only_show_in_settings'] && false === strpos( $screen->id, $this->as3cf->hook_suffix ) ) { + return; + } + + if ( ! $notice['only_show_to_user'] && in_array( $notice['id'], $dismissed_notices ) ) { + return; + } + + if ( 'info' === $notice['type'] ) { + $notice['type'] = 'notice-info'; + } + + $this->as3cf->render_view( 'notice', $notice ); + + if ( $notice['flash'] ) { + $this->remove_notice( $notice ); + } + } + + /** + * Enqueue notice scripts in the admin + */ + public function enqueue_notice_scripts() { + $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version']; + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + + // Enqueue notice.js globally as notices can be dismissed on any admin page + $src = plugins_url( 'assets/js/notice' . $suffix . '.js', $this->plugin_file_path ); + wp_enqueue_script( 'as3cf-notice', $src, array( 'jquery' ), $version, true ); + + wp_localize_script( 'as3cf-notice', 'as3cf_notice', array( + 'strings' => array( + 'dismiss_notice_error' => __( 'Error dismissing notice.', 'amazon-s3-and-cloudfront' ), + ), + 'nonces' => array( + 'dismiss_notice' => wp_create_nonce( 'as3cf-dismiss-notice' ), + ), + ) ); + } + + /** + * Handler for AJAX request to dismiss a notice + */ + public function ajax_dismiss_notice() { + $this->as3cf->verify_ajax_request(); + + if ( ! isset( $_POST['notice_id'] ) || ! ( $notice_id = sanitize_text_field( $_POST['notice_id'] ) ) ) { + $out = array( 'error' => __( 'Invalid notice ID.', 'amazon-s3-and-cloudfront' ) ); + $this->as3cf->end_ajax( $out ); + } + + $this->dismiss_notice( $notice_id ); + + $out = array( + 'success' => '1', + ); + $this->as3cf->end_ajax( $out ); + } + +} \ No newline at end of file diff --git a/classes/as3cf-plugin-compatibility.php b/classes/as3cf-plugin-compatibility.php index 7d1f49d7..dff14144 100644 --- a/classes/as3cf-plugin-compatibility.php +++ b/classes/as3cf-plugin-compatibility.php @@ -28,6 +28,14 @@ class AS3CF_Plugin_Compatibility { */ protected $as3cf; + /** + * @var array + */ + protected static $stream_wrappers = array(); + + /** + * @param Amazon_S3_And_CloudFront $as3cf + */ function __construct( $as3cf ) { $this->as3cf = $as3cf; @@ -38,6 +46,9 @@ function __construct( $as3cf ) { * Register the compatibility hooks */ function compatibility_init() { + // Turn on stream wrapper S3 file + add_filter( 'as3cf_get_attached_file', array( $this, 'get_stream_wrapper_file' ), 20, 4 ); + /* * Legacy filter * 'as3cf_get_attached_file_copy_back_to_local' @@ -51,14 +62,15 @@ function compatibility_init() { 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 ); add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'image_editor_remove_original_image' ), 10, 3 ); - add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_header_crop_download_file' ), 10, 4 ); - add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'customizer_header_crop_remove_original_image' ), 10, 3 ); + add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_crop_download_file' ), 10, 4 ); + add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'customizer_crop_remove_original_image' ), 10, 3 ); /* * 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/ @@ -93,6 +105,79 @@ function legacy_copy_back_to_local( $url, $file, $attachment_id, $s3_object ) { return $url; } + /** + * Is this an AJAX process? + * + * @return bool + */ + function is_ajax() { + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return true; + } + + return false; + } + + /** + * Check the current request is a specific one based on action and + * optional context + * + * @param string $action_key + * @param bool $ajax + * @param null|string $context_key + * + * @return bool + */ + function maybe_process_on_action( $action_key, $ajax, $context_key = null ) { + if ( $ajax !== $this->is_ajax() ) { + return false; + } + + $var_type = 'GET'; + + if ( isset( $_GET['action'] ) ) { + $action = $_GET['action']; + } else if ( isset( $_POST['action'] ) ) { + $var_type = 'POST'; + $action = $_POST['action']; + } else { + return false; + } + + $context_check = true; + if ( ! is_null( $context_key ) ) { + $global = constant( 'INPUT_' . $var_type ); + $context = filter_input( $global, 'context' ); + $context_check = ( $context_key === $context ); + } + + return ( $action_key === sanitize_key( $action ) && $context_check ); + } + + /** + * Generic method for copying back an S3 file to the server on a specific AJAX action + * + * @param string $action_key Action that must be in process + * @param bool $ajax Must the process be an AJAX one? + * @param string $url S3 URL + * @param string $file Local file path of image + * @param array $s3_object S3 meta data + * + * @return string + */ + function copy_image_to_server_on_action( $action_key, $ajax, $url, $file, $s3_object ) { + if ( false === $this->maybe_process_on_action( $action_key, $ajax ) ) { + return $url; + } + + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; + + return $url; + } + /** * Get the file path of the original image file before an update * @@ -145,7 +230,7 @@ function image_editor_remove_files( $post_id, $s3object, $prefix, $args ) { * @return string */ function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { - if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + if ( ! $this->is_ajax() ) { return $url; } @@ -199,7 +284,7 @@ function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { * @return array */ function image_editor_remove_original_image( $files, $post_id, $file_path ) { - if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + if ( ! $this->is_ajax() ) { return $files; } @@ -213,6 +298,23 @@ function image_editor_remove_original_image( $files, $post_id, $file_path ) { return $files; } + /** + * Generic check for Customizer crop actions + * + * @return bool + */ + protected function is_customizer_crop_action() { + $header_crop = $this->maybe_process_on_action( 'custom-header-crop', true ); + $identity_crop = $this->maybe_process_on_action( 'crop-image', true, 'site-icon' ); + + if ( ! $header_crop && ! $identity_crop ) { + // Not doing a Customizer action + return false; + } + + return true; + } + /** * Allow the WordPress Customizer to crop images that have been copied to S3 * but removed from the local server, by copying them back temporarily @@ -224,17 +326,15 @@ function image_editor_remove_original_image( $files, $post_id, $file_path ) { * * @return string */ - function customizer_header_crop_download_file( $url, $file, $attachment_id, $s3_object ) { - if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + function customizer_crop_download_file( $url, $file, $attachment_id, $s3_object ) { + if ( false === $this->is_customizer_crop_action() ) { return $url; } - if ( isset( $_POST['action'] ) && 'custom-header-crop' === 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; - }; - } + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; return $url; } @@ -249,16 +349,14 @@ function customizer_header_crop_download_file( $url, $file, $attachment_id, $s3_ * * @return array */ - function customizer_header_crop_remove_original_image( $files, $post_id, $file_path ) { - if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + function customizer_crop_remove_original_image( $files, $post_id, $file_path ) { + if ( false === $this->is_customizer_crop_action() ) { return $files; } - if ( isset( $_POST['action'] ) && 'custom-header-crop' === sanitize_key( $_POST['action'] ) ) { // input var okay - // remove original main image after edit - if ( ( $original_file = $this->get_original_image_file( $_POST['id'], $file_path ) ) ) { - $files[] = $original_file; - } + // remove original main image after edit + if ( ( $original_file = $this->get_original_image_file( $_POST['id'], $file_path ) ) ) { + $files[] = $original_file; } return $files; @@ -321,18 +419,7 @@ function customizer_background_image( $post_id, $url ) { * @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; + return $this->copy_image_to_server_on_action( 'regeneratethumbnail', true, $url, $file, $s3_object ); } /** @@ -361,4 +448,79 @@ protected function copy_s3_file_to_server( $s3_object, $file ) { return $file; } + + /** + * Register stream wrappers per region + * + * @param string $region + * + * @return mixed + */ + protected function register_stream_wrapper( $region ) { + $stored_region = ( '' === $region ) ? Amazon_S3_And_CloudFront::DEFAULT_REGION : $region; + + if ( in_array( $stored_region, self::$stream_wrappers ) ) { + return; + } + + $client = $this->as3cf->get_s3client( $region, true ); + $protocol = $this->get_stream_wrapper_protocol( $region ); + + // Register the region specific S3 stream wrapper to be used by plugins + AS3CF_Stream_Wrapper::register( $client, $protocol ); + + self::$stream_wrappers[] = $stored_region; + } + + /** + * Generate the stream wrapper protocol + * + * @param string $region + * + * @return string + */ + protected function get_stream_wrapper_protocol( $region ) { + $protocol = 's3'; + $protocol .= str_replace( '-', '', $region ); + + return $protocol; + } + + /** + * Generate an S3 stream wrapper compatible URL + * + * @param string $bucket + * @param string $key + * + * @return string + */ + function prepare_stream_wrapper_file( $bucket, $region, $key ) { + $protocol = $this->get_stream_wrapper_protocol( $region ); + + return $protocol . '://' . $bucket . '/' . $key; + } + + /** + * Allow access to the S3 file via the stream wrapper. + * This is useful for compatibility with plugins when attachments are removed from the + * local server after upload. + * + * @param string $url + * @param string $file + * @param int $attachment_id + * @param array $s3_object + * + * @return string + */ + public function get_stream_wrapper_file( $url, $file, $attachment_id, $s3_object ) { + if ( $url === $file ) { + // Abort if an earlier hook to get the file has been called and it has been copied back. + return $file; + } + + // Make sure the region stream wrapper is registered + $this->register_stream_wrapper( $s3_object['region'] ); + + return $this->prepare_stream_wrapper_file( $s3_object['bucket'], $s3_object['region'], $s3_object['key'] ); + } } \ No newline at end of file diff --git a/classes/as3cf-stream-wrapper.php b/classes/as3cf-stream-wrapper.php new file mode 100644 index 00000000..41cf8a61 --- /dev/null +++ b/classes/as3cf-stream-wrapper.php @@ -0,0 +1,96 @@ +getOptions(); + unset( $params['seekable'] ); + + return array( + 'Bucket' => $parts[0], + 'Key' => isset( $parts[1] ) ? $parts[1] : null, + ) + $params; + } + + /** + * Overrides so we don't check for stat on directories + * + * @param string $path + * @param int $flags + * + * @return array + */ + public function url_stat( $path, $flags ) { + $extension = pathinfo( $path, PATHINFO_EXTENSION ); + // If the path is a directory then return it as always existing. + if ( ! $extension ) { + return array( + 0 => 0, + 'dev' => 0, + 1 => 0, + 'ino' => 0, + 2 => 16895, + 'mode' => 16895, + 3 => 0, + 'nlink' => 0, + 4 => 0, + 'uid' => 0, + 5 => 0, + 'gid' => 0, + 6 => -1, + 'rdev' => -1, + 7 => 0, + 'size' => 0, + 8 => 0, + 'atime' => 0, + 9 => 0, + 'mtime' => 0, + 10 => 0, + 'ctime' => 0, + 11 => -1, + 'blksize' => -1, + 12 => -1, + 'blocks' => -1, + ); + } + + return parent::url_stat( $path, $flags ); + } + + /** + * Override the S3 Put Object arguments + * + * @return bool + */ + public function stream_flush() { + // Set the ACL as public by default + $this->params['ACL'] = Amazon_S3_And_CloudFront::DEFAULT_ACL; + + $this->params = apply_filters( 'wpos3_stream_flush_params', $this->params ); + + return parent::stream_flush(); + } +} \ No newline at end of file diff --git a/classes/as3cf-upgrade.php b/classes/as3cf-upgrade.php index 9a02a55c..e05dc5b1 100644 --- a/classes/as3cf-upgrade.php +++ b/classes/as3cf-upgrade.php @@ -285,7 +285,7 @@ function maybe_display_notices() { $action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' ); break; case self::STATUS_ERROR: - $msg = sprintf( __( 'Error Updating %s — We ran into some errors attempting to update the %s for all your Media Library items that have been uploaded to S3. Please check your error log for details.', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type ); + $msg = sprintf( __( 'Error Updating %s — We ran into some errors attempting to update the %s for all your Media Library items that have been uploaded to S3. Please check your error log for details. (#%d)', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type, $this->upgrade_id ); $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' ); $msg_type = 'error'; break; diff --git a/classes/wp-aws-compatibility-check.php b/classes/wp-aws-compatibility-check.php index e07356a1..d0ca5d2f 100644 --- a/classes/wp-aws-compatibility-check.php +++ b/classes/wp-aws-compatibility-check.php @@ -344,7 +344,7 @@ function get_error_msg() { } global $as3cfpro; - if ( ! empty( $as3cfpro ) && $as3cfpro->get_plugin_slug() === $this->parent_plugin_slug ) { + if ( ! empty( $as3cfpro ) && $as3cfpro->get_plugin_slug( true ) === $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.', 'amazon-s3-and-cloudfront' ), $this->get_parent_plugin_name() ); @@ -378,9 +378,18 @@ function get_error_msg() { 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.', 'amazon-s3-and-cloudfront' ), $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', 'amazon-s3-and-cloudfront' ), $this->plugin_name ) . ''; + $update_url = $this->get_plugin_action_url( 'upgrade', $plugin_basename ); + $upgrade_msg = ' ' . sprintf( __( 'Update %s to the latest version', 'amazon-s3-and-cloudfront' ), $this->plugin_name ) . ''; + global $as3cfpro; + if ( ! empty( $as3cfpro ) && $as3cfpro->get_plugin_slug( true ) === $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() ) { + $upgrade_msg = ' ' . sprintf( __( 'A valid license for %s is required to update.', 'amazon-s3-and-cloudfront' ), $this->get_parent_plugin_name() ); + } + } + + $msg .= $upgrade_msg; $msg .= $hide_notice_msg; return $this->set_error_msg( $msg ); diff --git a/languages/amazon-s3-and-cloudfront-en.pot b/languages/amazon-s3-and-cloudfront-en.pot index 917ecdd1..9663cbd3 100644 --- a/languages/amazon-s3-and-cloudfront-en.pot +++ b/languages/amazon-s3-and-cloudfront-en.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: amazon-s3-and-cloudfront\n" "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n" -"POT-Creation-Date: 2015-10-01 18:10+0100\n" +"POT-Creation-Date: 2015-10-26 13:57+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,120 +17,132 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: classes/amazon-s3-and-cloudfront.php:84 +#: classes/amazon-s3-and-cloudfront.php:100 msgid "Offload S3" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:85 +#: classes/amazon-s3-and-cloudfront.php:101 msgid "S3 and CloudFront" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:521 +#: classes/amazon-s3-and-cloudfront.php:559 +msgid "Upload aborted by filter 'as3cf_pre_upload_attachment'" +msgstr "" + +#: classes/amazon-s3-and-cloudfront.php:570 #, php-format msgid "File %s does not exist" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:530 +#: classes/amazon-s3-and-cloudfront.php:581 #, php-format msgid "Mime type %s is not allowed" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:607 +#: classes/amazon-s3-and-cloudfront.php:661 #, php-format msgid "Error uploading %s to S3: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1247 +#: classes/amazon-s3-and-cloudfront.php:1541 msgid "Cheatin’ eh?" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1251 +#: classes/amazon-s3-and-cloudfront.php:1545 msgid "You do not have sufficient permissions to access this page." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1257 +#: classes/amazon-s3-and-cloudfront.php:1551 msgid "No bucket name provided." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1532 +#: classes/amazon-s3-and-cloudfront.php:1826 msgid "Error Getting Bucket Region" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1533 +#: classes/amazon-s3-and-cloudfront.php:1827 #, php-format msgid "There was an error attempting to get the region of the bucket %s: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1653 +#: classes/amazon-s3-and-cloudfront.php:1947 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:1685 +#: classes/amazon-s3-and-cloudfront.php:1979 #, php-format msgid "" "There was an error attempting to check the permissions of the bucket %s: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1743 +#: classes/amazon-s3-and-cloudfront.php:2037 msgid "Error creating bucket" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1744 +#: classes/amazon-s3-and-cloudfront.php:2038 msgid "Bucket name too short." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1745 +#: classes/amazon-s3-and-cloudfront.php:2039 msgid "Bucket name too long." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1746 +#: classes/amazon-s3-and-cloudfront.php:2040 msgid "" "Invalid character. Bucket names can contain lowercase letters, numbers, " "periods and hyphens." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1747 +#: classes/amazon-s3-and-cloudfront.php:2041 msgid "Error saving bucket" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1748 +#: classes/amazon-s3-and-cloudfront.php:2042 msgid "Error fetching buckets" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1749 +#: classes/amazon-s3-and-cloudfront.php:2043 msgid "Error getting URL preview: " msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1750 +#: classes/amazon-s3-and-cloudfront.php:2044 msgid "The changes you made will be lost if you navigate away from this page" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1808 +#: classes/amazon-s3-and-cloudfront.php:2104 msgid "Cheatin' eh?" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1911 +#: classes/amazon-s3-and-cloudfront.php:2207 msgctxt "Show the media library tab" msgid "Media Library" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:1912 +#: classes/amazon-s3-and-cloudfront.php:2208 msgctxt "Show the support tab" msgid "Support" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2048 +#: classes/amazon-s3-and-cloudfront.php:2360 #, php-format msgid "The file %s has been given %s permissions on Amazon S3." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2549 +#: classes/amazon-s3-and-cloudfront.php:2372 +msgid "" +"Image Manipulation Library Missing — Looks like you " +"don't have an image manipulation library installed on this server and " +"configured with PHP. You may run into trouble if you try to edit images. " +"Please setup GD or ImageMagick." +msgstr "" + +#: classes/amazon-s3-and-cloudfront.php:2902 msgid "Quick Start Guide" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2551 +#: classes/amazon-s3-and-cloudfront.php:2904 #, php-format msgid "" "Looks like we don't have write access to this bucket. It's likely that the " @@ -139,7 +151,7 @@ msgid "" "correctly." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2553 +#: classes/amazon-s3-and-cloudfront.php:2906 #, php-format msgid "" "Looks like we don't have access to the buckets. It's likely that the user " @@ -147,7 +159,15 @@ msgid "" "Please see our %s for instructions on setting up permissions correctly." msgstr "" -#: classes/as3cf-plugin-compatibility.php:357 +#: classes/as3cf-notices.php:297 +msgid "Error dismissing notice." +msgstr "" + +#: classes/as3cf-notices.php:312 +msgid "Invalid notice ID." +msgstr "" + +#: classes/as3cf-plugin-compatibility.php:444 #: classes/upgrades/as3cf-meta-wp-error.php:72 #, php-format msgid "There was an error attempting to download the file %s from S3: %s" @@ -183,7 +203,7 @@ msgstr "" msgid "" "Error Updating %s — We ran into some errors " "attempting to update the %s for all your Media Library items that have been " -"uploaded to S3. Please check your error log for details." +"uploaded to S3. Please check your error log for details. (#%d)" msgstr "" #: classes/as3cf-upgrade.php:289 @@ -258,6 +278,7 @@ msgid "You currently have version %s installed." msgstr "" #: classes/wp-aws-compatibility-check.php:350 +#: classes/wp-aws-compatibility-check.php:388 #, php-format msgid "A valid license for %s is required to update." msgstr "" @@ -285,7 +306,7 @@ msgstr "" msgid "Update %s to the latest version" msgstr "" -#: classes/wp-aws-compatibility-check.php:442 +#: classes/wp-aws-compatibility-check.php:451 #, php-format msgid "The %s plugin has been deactivated." msgstr "" @@ -374,11 +395,15 @@ msgstr "" msgid "Bucket" msgstr "" -#: view/bucket-setting.php:11 +#: view/bucket-setting.php:12 +msgid "View in S3 console" +msgstr "" + +#: view/bucket-setting.php:16 msgid "Change" msgstr "" -#: view/bucket-setting.php:13 +#: view/bucket-setting.php:18 msgid "(defined in wp-config.php)" msgstr "" @@ -411,6 +436,10 @@ msgstr "" msgid "CloudFront or custom domain" msgstr "" +#: view/domain-setting.php:42 +msgid "Invalid character. Letters, numbers, periods and hyphens are allowed." +msgstr "" + #: view/error-access.php:4 msgid "Access Denied to Bucket" msgstr "" @@ -419,126 +448,141 @@ msgstr "" msgid "Settings saved." msgstr "" -#: view/settings.php:44 +#: view/settings.php:46 msgid "Enable/Disable the Plugin" msgstr "" -#: view/settings.php:51 +#: view/settings.php:53 msgid "Copy Files to S3" msgstr "" -#: view/settings.php:52 +#: view/settings.php:54 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:60 +#: view/settings.php:62 msgid "Rewrite File URLs" msgstr "" -#: view/settings.php:61 +#: view/settings.php:63 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:65 +#: view/settings.php:67 msgid "Configure File URLs" msgstr "" -#: view/settings.php:83 +#: view/settings.php:85 msgid "Path" msgstr "" -#: view/settings.php:85 +#: view/settings.php:87 msgid "By default the path is the same as your local WordPress files:" msgstr "" -#: view/settings.php:98 +#: view/settings.php:100 msgid "Year/Month" msgstr "" -#: view/settings.php:100 +#: view/settings.php:102 msgid "Add the Year/Month in the URL." msgstr "" -#: view/settings.php:106 +#: view/settings.php:108 msgid "SSL:" msgstr "" -#: view/settings.php:114 +#: view/settings.php:116 msgid "Same as request" msgstr "" -#: view/settings.php:115 +#: view/settings.php:117 msgid "When the request is https://, use https:// for the file URL as well." msgstr "" -#: view/settings.php:119 +#: view/settings.php:121 msgid "Always SSL" msgstr "" -#: view/settings.php:120 +#: view/settings.php:122 msgid "Forces https:// to be used." msgstr "" -#: view/settings.php:121 +#: view/settings.php:123 msgid "" "You cannot use the \"Bucket as a subdomain\" domain option when using SSL." msgstr "" -#: view/settings.php:125 +#: view/settings.php:127 msgid "Always non-SSL" msgstr "" -#: view/settings.php:126 +#: view/settings.php:128 msgid "Forces http:// to be used." msgstr "" -#: view/settings.php:132 +#: view/settings.php:134 msgid "Advanced Options" msgstr "" -#: view/settings.php:139 +#: view/settings.php:141 msgid "Remove Files From Server" msgstr "" -#: view/settings.php:140 +#: view/settings.php:142 msgid "Once a file has been copied to S3, remove it from the local server." msgstr "" -#: view/settings.php:148 +#: view/settings.php:144 +msgid "" +"Broken URLs — There will be broken URLs for files " +"that don't exist locally. You can fix this by enabling Rewrite File " +"URLs to use the S3 URLs." +msgstr "" + +#: view/settings.php:154 +#: view/settings.php:175 +#: view/settings.php:188 +msgid "More info" +msgstr "" + +#: view/settings.php:155 +#, php-format +msgid "" +"Warning — Some plugins depend on the file being " +"present on the local server and may not work when the file is removed. %s" +msgstr "" + +#: view/settings.php:171 msgid "Object Versioning" msgstr "" -#: view/settings.php:150 +#: view/settings.php:173 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:152 -#: view/settings.php:165 -msgid "More info" -msgstr "" - -#: view/settings.php:162 +#: view/settings.php:185 msgid "Far Future Expiration Header" msgstr "" -#: view/settings.php:163 +#: view/settings.php:186 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:175 +#: view/settings.php:198 msgid "Copy HiDPI (@2x) Images" msgstr "" -#: view/settings.php:176 +#: view/settings.php:199 #, php-format msgid "" "When uploading a file to S3, checks if there's a file of the same name with " @@ -546,7 +590,7 @@ msgid "" "Retina 2x plugin." msgstr "" -#: view/settings.php:182 +#: view/settings.php:205 msgid "Save Changes" msgstr "" diff --git a/readme.txt b/readme.txt index 49f97dd5..b0348524 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: bradt, deliciousbrains Tags: uploads, amazon, s3, mirror, admin, media, cdn, cloudfront Requires at least: 3.7 Tested up to: 4.3 -Stable tag: 0.9.6 +Stable tag: 0.9.7 License: GPLv3 Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. @@ -63,8 +63,25 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin == Changelog == -= 0.9.6 - 2015-10-01 = += 0.9.7 - 2015-10-26 = +* Improvement: Improve compatibility with third party plugins when the _Remove Files From Server_ option is enabled +* Improvement: Fix inconsistent spacing on the WP Offload S3 settings screen +* Improvement: Validate _CloudFront or custom domain_ input field +* Improvement: Link to current S3 bucket added to WP Offload S3 settings screen +* Improvement: Show notice when neither GD or Imagick image libraries are not installed +* Improvement: Supply Cache-Control header to S3 when the _Far Future Expiration Header_ option is enabled +* Improvement: Additional information added to _Diagnostic Information_ +* Improvement: Added warning when _Remove Files From Server_ option is enabled +* Improvement: Filter added to allow additional image versions to be uploaded to S3 +* Bug fix: File size not stored in _wp_attachment_metadata_ when _Remove Files From Server_ option is enabled +* Bug fix: Uploads on Multisite installs allowed after surpassing upload limit +* Bug fix: Site icon in WordPress customizer returns 404 +* Bug fix: Image versions remain locally and on S3 after deletion, when the file name contains characters which require escaping +* Bug fix: Files with the same file name overwritten when __Remove Files From Server_ option is enabled +* Bug fix: Cron tasks incorrectly scheduled due to passing the wrong time to `wp_schedule_event` +* Bug fix: Default options not shown in the UI after first install += 0.9.6 - 2015-10-01 = * Improvement: Update text domains for translate.wordpress.org integration = 0.9.5 - 2015-09-01 = diff --git a/view/bucket-setting.php b/view/bucket-setting.php index 2be3031c..3a0fe159 100644 --- a/view/bucket-setting.php +++ b/view/bucket-setting.php @@ -6,7 +6,12 @@

    - + + + + + +

    +

    diff --git a/view/notice.php b/view/notice.php index 2659f9d6..9d03d4e2 100644 --- a/view/notice.php +++ b/view/notice.php @@ -2,7 +2,9 @@ $type = ( isset( $type ) ) ? $type : 'notice-info'; $dismissible = ( isset( $dismissible ) ) ? $dismissible : false; $inline = ( isset( $inline ) ) ? $inline : false; +$id = ( isset( $id ) ) ? 'id="' . $id . '"' : ''; +$style = ( isset( $style ) ) ? $style : ''; ?> -
    +
    class="notice as3cf-notice " style="">

    \ No newline at end of file diff --git a/view/settings.php b/view/settings.php index 39a723cd..a68abb5e 100644 --- a/view/settings.php +++ b/view/settings.php @@ -11,7 +11,8 @@

    get_setting( 'bucket' ); ?> +$selected_bucket = $this->get_setting( 'bucket' ); +$selected_bucket_prefix = $this->get_object_prefix(); ?>