ShortPixel Image Optimizer - Version 3.0.0

Version Description

  • Major update
  • when validating the API Key on a multisite a message with instructions on how to add the API Key in wp-config.php is displayed
  • check when an optimized image cannot be saved and stop bulk processing (if running)
  • restore backup is not displayed when option is not activated
  • change image status in the Media Library ShortPixel Compression column imediately after the image is reduced, not only after reloading the page. Add spinner to the "Image waiting to be processed" status.
  • images with relative URLs are converted to absolute URL so they can be processed by the plugin
  • proper handling of images with non-standard latin chars inside
  • better average compression computation
  • rewritten handleImageUpload
  • removed MUST_HAVE_KEY
  • changed isProcessable
  • and others :)
Download this release

Release Info

Developer ShortPixel
Plugin Icon 128x128 ShortPixel Image Optimizer
Version 3.0.0
Comparing to
See all releases

Code changes from version 2.1.10 to 3.0.0

css/short-pixel.css ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ li.shortpixel-toolbar-processing > a.ab-item > div,
2
+ #wpadminbar li.shortpixel-toolbar-processing > a.ab-item > div{
3
+ background-image: url("../img/loading-dark-big.gif");
4
+ background-position: center center;
5
+ background-repeat: no-repeat;
6
+ height: 33px;
7
+ margin-top: -1px;
8
+ padding: 0 3px;
9
+ }
10
+ li.shortpixel-toolbar-processing > a.ab-item > div > img,
11
+ #wpadminbar li.shortpixel-toolbar-processing > a.ab-item > div > img{
12
+ margin-right: 2px;
13
+ margin-top: 6px;
14
+ }
15
+ li.shortpixel-toolbar-processing > a.ab-item > div > span.shp-alert,
16
+ #wpadminbar li.shortpixel-toolbar-processing > a.ab-item > div > span.shp-alert{
17
+ display:none;
18
+ }
19
+ li.shortpixel-toolbar-processing.shortpixel-alert > a.ab-item > div > span.shp-alert,
20
+ #wpadminbar li.shortpixel-toolbar-processing.shortpixel-alert > a.ab-item > div > span.shp-alert{
21
+ display: inline;
22
+ font-size: 26px;
23
+ color: red;
24
+ font-weight: bold;
25
+ vertical-align: top;
26
+ }
27
+ li.shortpixel-toolbar-processing.shortpixel-alert > a.ab-item > div,
28
+ #wpadminbar li.shortpixel-toolbar-processing.shortpixel-alert > a.ab-item > div{
29
+ background-image: none;
30
+ }
31
+ li.shortpixel-hide {
32
+ display:none;
33
+ }
34
+ .bulk-progress {
35
+ padding: 20px 32px 17px;
36
+ background-color: #ffffff;
37
+ border: 1px dotted #c4c2c2;
38
+ }
39
+ .bulk-progress.bulk-stats > div{
40
+ display:inline-block;
41
+ }
42
+ .bulk-progress.bulk-stats > div.label{
43
+ width: 320px;
44
+ }
45
+ .progress {
46
+ background-color: #ecedee;
47
+ height: 30px;
48
+ position: relative;
49
+ width: 60%;
50
+ display: inline-block;
51
+ margin-right: 28px;
52
+ }
53
+ .progress .progress-img {
54
+ position: absolute;
55
+ top: -10px;
56
+ z-index: 2;
57
+ margin-left: -35px;
58
+ line-height: 48px;
59
+ font-size: 22px;
60
+ font-weight: bold;
61
+ }
62
+ .progress .progress-img span {
63
+ vertical-align: top;
64
+ margin-left: -7px;
65
+ }
66
+ .progress .progress-left {
67
+ background-color: #1cbecb;
68
+ bottom: 0;
69
+ left: 0;
70
+ position: absolute;
71
+ top: 0;
72
+ z-index: 1;
73
+ font-size: 22px;
74
+ font-weight: bold;
75
+ line-height: 28px;
76
+ text-align: center;
77
+ color: #ffffff;
78
+ }
79
+ .bulk-estimate {
80
+ font-size: 20px;
81
+ line-height: 30px;
82
+ vertical-align: top;
83
+ display: inline-block;
84
+ }
85
+ .wp-core-ui .button-secondary.bulk-cancel {
86
+ float: right;
87
+ height: 30px;
88
+ }
89
+ .short-pixel-block-title {
90
+ font-size: 22px;
91
+ margin-bottom: 15px;
92
+ }
93
+ .bulk-slider-container {
94
+ margin-top: 20px;
95
+ display: none;
96
+ min-height: 500px;
97
+ }
98
+ .bulk-slider-container h2{
99
+ margin-bottom: 15px;
100
+ }
101
+ .bulk-slider .bulk-slide {
102
+ position: absolute;
103
+ width: 100%;
104
+ }
105
+ .bulk-slider .img-original,
106
+ .bulk-slider .img-optimized{
107
+ display:inline-block;
108
+ margin-right:20px;
109
+ text-align: center;
110
+ }
111
+ .bulk-slider .img-original div,
112
+ .bulk-slider .img-optimized div{
113
+ max-height: 450px;
114
+ overflow: hidden;
115
+ }
116
+ .bulk-slider .img-original img,
117
+ .bulk-slider .img-optimized img{
118
+ max-width: 300px;
119
+ }
120
+ .bulk-slider .img-info{
121
+ display:inline-block;
122
+ vertical-align: top;
123
+ font-size: 48px;
124
+ max-width: 150px;
125
+ }
img/loading-dark-big.gif ADDED
Binary file
img/loading-dark.gif ADDED
Binary file
img/loading.gif ADDED
Binary file
img/shortpixel-alert.png ADDED
Binary file
img/shortpixel.png ADDED
Binary file
img/slider.png ADDED
Binary file
js/short-pixel.js ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Short Pixel WordPress Plugin javascript
3
+ */
4
+
5
+ jQuery(document).ready(function($){
6
+ //are we on media list?
7
+ if( jQuery('table.wp-list-table.media').length > 0) {
8
+ //register a bulk action
9
+ jQuery('select[name^="action"] option:last-child').before('<option value="short-pixel-bulk">Optimize with ShortPixel</option>');
10
+ }
11
+ //check if bulk processing
12
+ checkQuotaExceededAlert();
13
+ checkBulkProgress();
14
+ });
15
+
16
+ var ShortPixel = function() {
17
+
18
+ function setOptions(options) {
19
+ for(var opt in options) {
20
+ ShortPixel[opt] = options[opt];
21
+ }
22
+ }
23
+
24
+ return {
25
+ setOptions: setOptions
26
+ }
27
+ }();
28
+
29
+ function showToolBarAlert($status, $message) {
30
+ switch($status) {
31
+ case ShortPixel.STATUS_QUOTA_EXCEEDED:
32
+ jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-alert");
33
+ jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-quota-exceeded");
34
+ jQuery("li.shortpixel-toolbar-processing a").attr("href", "http://shortpixel.com/login/" + ShortPixel.API_KEY);
35
+ jQuery("li.shortpixel-toolbar-processing a div").attr("title", "ShortPixel quota exceeded. Click to top-up");
36
+ break;
37
+ case ShortPixel.STATUS_FAIL:
38
+ jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-alert shortpixel-processing");
39
+ jQuery("li.shortpixel-toolbar-processing a div").attr("title", $message);
40
+ break;
41
+ }
42
+ jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
43
+ }
44
+ function hideToolBarAlert () {
45
+ jQuery("li.shortpixel-toolbar-processing.shortpixel-processing").addClass("shortpixel-hide");
46
+ }
47
+
48
+ function hideQuotaExceededToolBarAlert () {
49
+ jQuery("li.shortpixel-toolbar-processing.shortpixel-quota-exceeded").addClass("shortpixel-hide");
50
+ }
51
+
52
+ function checkQuotaExceededAlert() {
53
+ if(typeof shortPixelQuotaExceeded != 'undefined') {
54
+ if(shortPixelQuotaExceeded == 1) {
55
+ showToolBarAlert(ShortPixel.STATUS_QUOTA_EXCEEDED);
56
+ } else {
57
+ hideQuotaExceededToolBarAlert();
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * JavaScript image processing - this method gets executed on every footer load and afterwards
63
+ * calls itself until receives an Empty queue message
64
+ */
65
+ function checkBulkProgress() {
66
+ //if I'm not the bulk processor, check every 20 sec. if the bulk processor is running, otherwise take the role
67
+ if(ShortPixel.bulkProcessor == true || typeof localStorage.bulkTime == 'undefined' || Math.floor(Date.now() / 1000) - localStorage.bulkTime > 90) {
68
+ ShortPixel.bulkProcessor = true;
69
+ localStorage.bulkTime = Math.floor(Date.now() / 1000);
70
+ console.log(localStorage.bulkTime);
71
+ checkBulkProcessingCallApi();
72
+ } else {
73
+ console.log("not the bulk processor");
74
+ setTimeout(checkBulkProgress, 5000);
75
+ }
76
+ }
77
+
78
+ function checkBulkProcessingCallApi(){
79
+ var data = { 'action': 'shortpixel_image_processing' };
80
+ // since WP 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
81
+ jQuery.post(ajaxurl, data, function(response)
82
+ {
83
+ if(response.length > 0) {
84
+ var data = JSON.parse(response);
85
+ var id = data["ImageID"];
86
+
87
+ var isBulkPage = (jQuery("div.short-pixel-bulk-page").length > 0);
88
+
89
+ switch (data["Status"]) {
90
+ case ShortPixel.STATUS_QUOTA_EXCEEDED:
91
+ setCellMessage(id, data["Message"] + " | <a href=\"https://shortpixel.com/login/"
92
+ + ShortPixel.API_KEY + ")\">Extend Quota</a>");
93
+ showToolBarAlert(ShortPixel.STATUS_QUOTA_EXCEEDED);
94
+ break;
95
+ case ShortPixel.STATUS_FAIL:
96
+ setCellMessage(id, data["Message"]);
97
+ if(isBulkPage) {
98
+ showToolBarAlert(ShortPixel.STATUS_FAIL, data["Message"]);
99
+ }
100
+ console.log(data["Message"]);
101
+ break;
102
+ case ShortPixel.STATUS_EMPTY_QUEUE:
103
+ console.log(data["Message"]);
104
+ clearBulkProcessor(); //nothing to process, leave the role. Next page load will check again
105
+ hideToolBarAlert();
106
+ var progress = jQuery("#bulk-progress");
107
+ if(isBulkPage && progress.length && data["BulkStatus"] != '2') {
108
+ progressUpdate(100, "Bulk finished!");
109
+ jQuery("a.bulk-cancel").attr("disabled", "disabled");
110
+ hideSlider();
111
+ //showStats();
112
+ setTimeout(function(){
113
+ window.location.reload();
114
+ }, 3000);
115
+ }
116
+ break;
117
+ case ShortPixel.STATUS_SUCCESS:
118
+ var percent = data["PercentImprovement"];
119
+ setCellMessage(id, "Reduced by <span class='percent'>" + percent + "%</span> | <a href=\"admin.php?action=shortpixel_restore_backup&attachment_ID=" + id + ")\">Restore backup</a>");
120
+ var animator = new PercentageAnimator("#sp-msg-" + id + " span.percent", percent);
121
+ animator.animate(percent);
122
+ if(isBulkPage && typeof data["Thumb"] !== 'undefined' && data["PercentImprovement"] > 0) {
123
+ progressUpdate(data["BulkPercent"], data["BulkMsg"]);
124
+ if(data["Thumb"].length > 0){
125
+ sliderUpdate(id, data["Thumb"], data["BkThumb"], data["PercentImprovement"]);
126
+ }
127
+ }
128
+ //fall through
129
+ case ShortPixel.STATUS_ERROR: //for error and skip also we retry
130
+ case ShortPixel.STATUS_SKIP:
131
+ console.log('Server response: ' + response);
132
+ setTimeout(checkBulkProgress, 2000);
133
+ }
134
+ }
135
+ });
136
+ }
137
+
138
+ function clearBulkProcessor(){
139
+ ShortPixel.bulkProcessor = false; //nothing to process, leave the role. Next page load will check again
140
+ localStorage.bulkTime = 0;
141
+ }
142
+
143
+ function setCellMessage(id, message){
144
+ var msg = jQuery("#sp-msg-" + id);
145
+ if(typeof msg !== "undefined") {
146
+ msg.html(message);
147
+ }
148
+ }
149
+
150
+ function manualOptimization(id) {
151
+ setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/shortpixel-image-optimiser/img/loading.gif'>Image waiting to be processed");
152
+ jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
153
+ var data = { action : 'shortpixel_manual_optimization',
154
+ image_id: id};
155
+ jQuery.get(ajaxurl, data, function(response) {
156
+ data = JSON.parse(response);
157
+ if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
158
+ setTimeout(checkBulkProgress, 2000);
159
+ } else {
160
+ setCellMessage(id, "This content is not processable.");
161
+ }
162
+ //aici e aici
163
+ });
164
+ }
165
+
166
+ function PercentageAnimator(outputSelector, targetPercentage) {
167
+ this.animationSpeed = 10;
168
+ this.increment = 2;
169
+ this.curPercentage = 0;
170
+ this.targetPercentage = targetPercentage;
171
+ this.outputSelector = outputSelector;
172
+
173
+ this.animate = function(percentage) {
174
+ this.targetPercentage = percentage;
175
+ setTimeout(PercentageTimer.bind(null, this), this.animationSpeed);
176
+ }
177
+ }
178
+
179
+ function PercentageTimer(animator) {
180
+ if (animator.curPercentage - animator.targetPercentage < -animator.increment) {
181
+ animator.curPercentage += animator.increment;
182
+ } else if (animator.curPercentage - animator.targetPercentage > animator.increment) {
183
+ animator.curPercentage -= animator.increment;
184
+ } else {
185
+ animator.curPercentage = animator.targetPercentage;
186
+ }
187
+
188
+ jQuery(animator.outputSelector).text(animator.curPercentage + "%");
189
+
190
+ if (animator.curPercentage != animator.targetPercentage) {
191
+ setTimeout(PercentageTimer.bind(null,animator), animator.animationSpeed)
192
+ }
193
+ }
194
+
195
+ function progressUpdate(percent, message) {
196
+ var progress = jQuery("#bulk-progress");
197
+ if(progress.length) {
198
+ jQuery(".progress-left", progress).css("width", percent + "%");
199
+ jQuery(".progress-img", progress).css("left", percent + "%");
200
+ if(percent > 24) {
201
+ jQuery(".progress-img span", progress).html("");
202
+ jQuery(".progress-left", progress).html(percent + "%");
203
+ } else {
204
+ jQuery(".progress-img span", progress).html(percent + "%");
205
+ jQuery(".progress-left", progress).html("");
206
+ }
207
+ jQuery(".bulk-estimate").html(message);
208
+ }
209
+ }
210
+
211
+ function sliderUpdate(id, thumb, bkThumb, percent){
212
+ var oldSlide = jQuery(".bulk-slider div.bulk-slide:first-child");
213
+ var newSlide = oldSlide.clone();
214
+ newSlide.attr("id", "slide-" + id);
215
+ if(oldSlide.attr("id") != "empty-slide") {
216
+ newSlide.hide();
217
+ }
218
+ oldSlide.css("z-index", 1000);
219
+ jQuery(".bulk-img-opt", newSlide).attr("src", thumb);
220
+ jQuery(".bulk-img-orig", newSlide).attr("src", bkThumb);
221
+ jQuery(".bulk-opt-percent", newSlide).text(percent + "%");
222
+
223
+ jQuery(".bulk-slider").append(newSlide);
224
+ if(oldSlide.attr("id") == "empty-slide") {
225
+ oldSlide.remove();
226
+ jQuery(".bulk-slider-container").css("display", "block");
227
+ } else {
228
+ oldSlide.animate({ left: oldSlide.width() }, 'slow', 'swing', function(){
229
+ oldSlide.remove();
230
+ newSlide.fadeIn("slow");
231
+ });
232
+ }
233
+ }
234
+
235
+ function hideSlider() {
236
+ jQuery(".bulk-slider-container").css("display", "none");
237
+ }
238
+
239
+ function showStats() {
240
+ var statsDiv = jQuery(".bulk-stats");
241
+ if(statsDiv.length > 0) {
242
+
243
+ }
244
+ }
readme.txt CHANGED
@@ -1,14 +1,15 @@
1
  === ShortPixel Image Optimizer ===
2
 
3
  Contributors: AlexSP
4
- Tags: picture, optimization, image editor, pngout, upload speed, shortpixel, compression, jpegmini, webp, lossless, cwebp, media, tinypng, jpegtran,image, image optimisation, shrink, picture, photo, optimize photos, compress, performance, tinypng, crunch, pngquant, attachment, optimize, pictures,fast, images, image files, image quality, lossy, upload, kraken, resize, seo, smushit, optipng, kraken image optimizer, ewww, photo optimization, gifsicle, image optimizer, images, krakenio, png, gmagick, image optimize, pdf, pdf optimisation, pdf optimization, optimize pdf, optimise pdf, shrink pdf, jpg, jpeg, jpg optimisation, jpg optimization, optimize jpg, optimise jpg, shrink jpg, gif, animated gif, optimize gif, optimise gif
 
5
  Requires at least: 3.0.0 or higher
6
  Tested up to: 4.2
7
- Stable tag: 2.1.10
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
11
- The ShortPixel plugin reduces the images' size making your website load faster. Image quality is preserved using advanced compression technology.
12
 
13
  == Description ==
14
 
@@ -25,7 +26,7 @@ ShortPixel uses powerful algorithms that enable your website to load faster, use
25
  * **Backup and restore originals:** if you ever want to return to the original version, images are automatically stored in a backup folder on your hosting servers.
26
  * **Bulk image optimization:** Crunch your image gallery, and downsize your website. This feature may take up to several hours, depending on the number and size of existing images.
27
 
28
- On the https://ShortPixel.com website, we offer free access to the ShrtPixel API which you can use for further image optimization purposes.
29
 
30
  == Installation ==
31
 
@@ -118,9 +119,20 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
118
 
119
  == Changelog ==
120
 
121
- = 2.1.10 =
122
-
123
- * added a fix so relative paths to images are turned to absolute paths
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  = 2.1.9 =
126
 
@@ -146,7 +158,7 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
146
  * when quota is exceeded user can more easily increase it
147
  * extra warning regarding the number of thumbs available additionally to main images
148
  * improved counting of images
149
- * check if https works if not use http for communcations with the API
150
  * better handling of error messages when API service cannot be contacted
151
 
152
  = 2.1.5 =
@@ -159,7 +171,7 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
159
  = 2.1.4 =
160
 
161
  * fixed global variable issue for some variables
162
- * fixed API Key validation that occured for some of the hosting providers out there when HTTPS was used
163
 
164
  = 2.1.3 =
165
 
@@ -167,7 +179,7 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
167
  * removed forgotten debug message
168
  * changed "optimised" to "optimized". Welcome USA :)
169
  * improved bulk handling and also "cancel" and "resume" options
170
- * fixed confilct with wpmandrill on wp_mail function
171
 
172
  = 2.1.2 =
173
 
@@ -206,7 +218,7 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
206
  * fixed issue with "missing" images
207
  * save plugin version for easier debugging
208
  * list mode is set for media library for first time run
209
- * fixed bug that prevented backuped files to remove when the original was removed
210
 
211
  = 2.0.6 =
212
 
@@ -324,7 +336,7 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
324
  = 1.4.0 =
325
 
326
  * Bulk image processing improved so it can optimize all the images in background while admin page is open
327
- * small changes in readme.txt descrption
328
 
329
  = 1.3.5 =
330
 
1
  === ShortPixel Image Optimizer ===
2
 
3
  Contributors: AlexSP
4
+ Tags: picture, optimization, image editor, pngout, upload speed, shortpixel, compression, jpegmini, webp, lossless, cwebp, media, jpegtran,image, image optimisation, shrink, picture, photo, optimize photos, compress, performance, tinypng, crunch, pngquant, attachment, optimize, pictures,fast, images, image files, image quality, lossy, upload, kraken, resize, seo, smushit, optipng, kraken image optimizer, ewww, photo optimization, gifsicle, image optimizer, images, krakenio, png, gmagick, image optimize, pdf, pdf optimisation, pdf optimization, optimize pdf, optimise pdf, shrink pdf, jpg, jpeg, jpg optimisation, jpg optimization, optimize jpg, optimise jpg, shrink jpg, gif, animated gif, optimize gif, optimise gif, optimizer, optimiser, compresion, optimization, cruncher, image cruncher, compress png, compress jpg, compress jpeg, faster loading times, image optimiser, improve pagerank, optimise, optimize animated gif, optimise jpeg, optimize jpeg, optimize png, optimise png, tinyjpg, short pixel, shortpixel
5
+
6
  Requires at least: 3.0.0 or higher
7
  Tested up to: 4.2
8
+ Stable tag: 3.0.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
+ Fast, easy-to-use and lightweight plugin that optimizes images & PDFs. Preserve a high visual quality of images and make your website load faster.
13
 
14
  == Description ==
15
 
26
  * **Backup and restore originals:** if you ever want to return to the original version, images are automatically stored in a backup folder on your hosting servers.
27
  * **Bulk image optimization:** Crunch your image gallery, and downsize your website. This feature may take up to several hours, depending on the number and size of existing images.
28
 
29
+ On the https://ShortPixel.com website, we offer free access to the ShortPixel API which you can use for further image optimization purposes.
30
 
31
  == Installation ==
32
 
119
 
120
  == Changelog ==
121
 
122
+ = 3.0.0 =
123
+
124
+ * Major update
125
+ * when validating the API Key on a multisite a message with instructions on how to add the API Key in wp-config.php is displayed
126
+ * check when an optimized image cannot be saved and stop bulk processing (if running)
127
+ * restore backup is not displayed when option is not activated
128
+ * change image status in the Media Library ShortPixel Compression column imediately after the image is reduced, not only after reloading the page. Add spinner to the "Image waiting to be processed" status.
129
+ * images with relative URLs are converted to absolute URL so they can be processed by the plugin
130
+ * proper handling of images with non-standard latin chars inside
131
+ * better average compression computation
132
+ * rewritten handleImageUpload
133
+ * removed MUST_HAVE_KEY
134
+ * changed isProcessable
135
+ * and others :)
136
 
137
  = 2.1.9 =
138
 
158
  * when quota is exceeded user can more easily increase it
159
  * extra warning regarding the number of thumbs available additionally to main images
160
  * improved counting of images
161
+ * check if https works if not use http for communications with the API
162
  * better handling of error messages when API service cannot be contacted
163
 
164
  = 2.1.5 =
171
  = 2.1.4 =
172
 
173
  * fixed global variable issue for some variables
174
+ * fixed API Key validation that occurred for some of the hosting providers out there when HTTPS was used
175
 
176
  = 2.1.3 =
177
 
179
  * removed forgotten debug message
180
  * changed "optimised" to "optimized". Welcome USA :)
181
  * improved bulk handling and also "cancel" and "resume" options
182
+ * fixed conflict with wpmandrill on wp_mail function
183
 
184
  = 2.1.2 =
185
 
218
  * fixed issue with "missing" images
219
  * save plugin version for easier debugging
220
  * list mode is set for media library for first time run
221
+ * fixed bug that prevented backup-ed files to remove when the original was removed
222
 
223
  = 2.0.6 =
224
 
336
  = 1.4.0 =
337
 
338
  * Bulk image processing improved so it can optimize all the images in background while admin page is open
339
+ * small changes in readme.txt description
340
 
341
  = 1.3.5 =
342
 
shortpixel_api.php CHANGED
@@ -1,412 +1,433 @@
1
  <?php
2
  if ( !function_exists( 'download_url' ) ) {
3
- require_once( ABSPATH . 'wp-admin/includes/file.php' );
4
  }
5
 
6
- class shortpixel_api {
7
-
8
- private $_apiKey = '';
9
- private $_compressionType = '';
10
- private $_CMYKtoRGBconversion = '';
11
- private $_maxAttempts = 10;
12
- private $_apiEndPoint = 'https://api.shortpixel.com/v2/reducer.php';
13
-
14
- public function setCompressionType($compressionType) {
15
- $this->_compressionType = $compressionType;
16
- }
17
-
18
- public function setCMYKtoRGB($CMYK2RGB) {
19
- $this->_CMYKtoRGBconversion = $CMYK2RGB;
20
- }
21
- public function getCompressionType() {
22
- return $this->_compressionType;
23
- }
24
-
25
- public function setApiKey($apiKey) {
26
- $this->_apiKey = $apiKey;
27
- }
28
-
29
- public function getApiKey() {
30
- return $this->_apiKey;
31
- }
32
-
33
- public function __construct($apiKey, $compressionType, $CMYK2RGB) {
34
- $this->_apiKey = $apiKey;
35
- $this->setCompressionType($compressionType);
36
- $this->setCMYKtoRGB($CMYK2RGB);
37
- add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
38
- }
39
-
40
- public function processImageAction($url, $filePaths, $ID, $time) {
41
- $this->processImage($url, $filePaths, $ID, $time);
42
- }
43
-
44
- public function doRequests($urls, $filePath, $ID = null) {
45
- if ( !is_array($urls) )
46
- $response = $this->doBulkRequest(array($urls), true);
47
- else
48
- $response = $this->doBulkRequest($urls, true);
49
-
50
- if(is_object($response) && get_class($response) == 'WP_Error') {
51
- if ( isset($response->errors['http_request_failed'][0]) )
52
- return $response->errors['http_request_failed'][0] . ". If problem persists please <a href='https://shortpixel.com/contact'>contact us</a>";
53
- else
54
- return "There was an error. If persists please <a href='https://shortpixel.com/contact'>contact us</a>";
55
- }
56
-
57
- return $response;
58
- }
59
-
60
- public function doBulkRequest($imageList = array(), $blocking = false) {
61
- if(!is_array($imageList)) return false;
62
-
63
- $requestParameters = array(
64
- 'plugin_version' => PLUGIN_VERSION,
65
- 'key' => $this->_apiKey,
66
- 'lossy' => $this->_compressionType,
67
- 'cmyk2rgb' => $this->_CMYKtoRGBconversion,
68
- 'urllist' => $imageList
69
- );
70
-
71
- $arguments = array(
72
- 'method' => 'POST',
73
- 'timeout' => 45,
74
- 'redirection' => 3,
75
- 'sslverify' => false,
76
- 'httpversion' => '1.0',
77
- 'blocking' => $blocking,
78
- 'headers' => array(),
79
- 'body' => json_encode($requestParameters),
80
- 'cookies' => array()
81
- );
82
-
83
- $response = wp_remote_post($this->_apiEndPoint, $arguments );
84
-
85
- if(is_wp_error( $response ))
86
- $response = wp_remote_post(str_replace('https://', 'http://', $this->_apiEndPoint), $arguments );
87
-
88
- return $response;
89
- }
90
-
91
- public function parseResponse($response) {
92
- $data = $response['body'];
93
- $data = $this->parseJSON($data);
94
- return $data;
95
- }
96
-
97
- //handles the processing of the image using the ShortPixel API
98
- public function processImage($url, $filePaths, $ID = null, $startTime = 0) {
99
-
100
- if($startTime == 0) { $startTime = time(); }
101
- $apiRetries = get_option('wp-short-pixel-api-retries');
102
- if(time() - $startTime > MAX_EXECUTION_TIME) {//keeps track of time
103
- if ( $apiRetries > MAX_API_RETRIES )//we tried to process this time too many times, giving up...
104
- {
105
- $meta = wp_get_attachment_metadata($ID);
106
- $meta['ShortPixelImprovement'] = 'Timed out while processing.';
107
- unset($meta['ShortPixel']['WaitingProcessing']);
108
- wp_update_attachment_metadata($ID, $meta);
109
- //also decrement last ID for queries so bulk won't hang in such cases
110
- $startQueryID = get_option("wp-short-pixel-query-id-start");
111
- update_option("wp-short-pixel-query-id-start", $startQueryID - 1);
112
- }
113
- else
114
- {//we'll try again next time user visits a page on admin panel
115
- $apiRetries++;
116
- update_option('wp-short-pixel-api-retries', $apiRetries);
117
- exit('Timed out while processing. (pass '.$apiRetries.')');
118
- }
119
- }
120
-
121
- $response = $this->doRequests($url, $filePaths, $ID);//send requests to API
122
-
123
- if(! is_array($response)) {
124
- return $response;
125
- }
126
-
127
- if($response['response']['code'] != 200) {//response <> 200 -> there was an error apparently?
128
- printf('ShortPixel API service accesibility error. Please try again later.');
129
- return false;
130
- }
131
-
132
- $data = (array)$this->parseResponse($response);//get the actual response from API, convert it to an array if it is an object
133
-
134
- if ( isset($data[0]) )//API returned image details
135
- {
136
- $firstImage = $data[0];//extract as object first image
137
- foreach ( $data as $imageObject )
138
- { //this part makes sure that all the sizes were processed and ready to be downloaded
139
- if ( $imageObject->Status->Code == 0 || $imageObject->Status->Code == 1 )
140
- {
141
- sleep(2);
142
- return $this->processImage($url, $filePaths, $ID, $startTime);
143
- }
144
- }
145
-
146
- switch($firstImage->Status->Code) {
147
- case 1:
148
- //handle image has been scheduled
149
- sleep(1);
150
- return $this->processImage($url, $filePaths, $ID, $startTime);
151
- break;
152
- case 2:
153
- //handle image has been processed
154
- update_option( 'wp-short-pixel-quota-exceeded', 0);//reset the quota exceeded flag
155
- $this->handleSuccess($data, $url, $filePaths, $ID);
156
- break;
157
- default:
158
- //handle error
159
- if ( isset($data[0]->Status->Message) )
160
- return $data[0]->Status->Message;
161
- }
162
- }
163
- else//API returned an error
164
- {
165
- switch($data['Status']->Code) {
166
- case -403:
167
- update_option("wp-short-pixel-query-id-start", 0);//update max and min ID
168
- update_option("wp-short-pixel-query-id-stop", 0);
169
- @delete_option('bulkProcessingStatus');
170
- update_option( 'wp-short-pixel-quota-exceeded', 1);
171
- return 'Quota exceeded';
172
- break;
173
- case -401:
174
- return 'Wrong API Key</br>';
175
- break;
176
- case -302:
177
- return 'Images does not exists</br>';
178
- break;
179
- default:
180
- //handle error
181
- if ( isset($data[0]->Status->Message) )
182
- return $data[0]->Status->Message;
183
- }
184
-
185
- }
186
-
187
- return $data;
188
- }
189
-
190
-
191
- public function handleSuccess($callData, $url, $filePath, $ID) {
192
-
193
- $counter = 0;
194
- if($this->_compressionType)
195
- {
196
- $fileType = "LossyURL";
197
- $fileSize = "LossySize";
198
- }
199
- else
200
- {
201
- $fileType = "LosslessURL";
202
- $fileSize = "LoselessSize";
203
- }
204
-
205
- foreach ( $callData as $fileData )//download each file from array and process it
206
- {
207
- if ( $fileData->Status->Code == 2 ) //file was processed OK
208
- {
209
- if ( $counter == 0 )//save percent improvement for main file
210
- $percentImprovement = $fileData->PercentImprovement;
211
-
212
- $correctFileSize = $fileData->$fileSize;
213
- $tempFiles[$counter] = download_url(urldecode($fileData->$fileType));
214
-
215
- if(is_wp_error( $tempFiles[$counter] )) //also tries with http instead of https
216
- {
217
- sleep(1);
218
- $tempFiles[$counter] = download_url(str_replace('https://', 'http://', urldecode($fileData->$fileType)));
219
- }
220
-
221
- if ( is_wp_error( $tempFiles[$counter] ) ) {
222
- @unlink($tempFiles[$counter]);
223
- return sprintf("Error downloading file (%s)", $tempFiles[$counter]->get_error_message());
224
- die;
225
- }
226
-
227
- //check response so that download is OK
228
- if( filesize($tempFiles[$counter]) != $correctFileSize) {
229
- return sprintf("Error downloading file - incorrect file size");
230
- die;
231
- }
232
-
233
- if (!file_exists($tempFiles[$counter])) {
234
- return sprintf("Unable to locate downloaded file (%s)", $tempFiles[$counter]);
235
- die;
236
- }
237
- }
238
- else //there was an error while trying to download a file
239
- $tempFiles[$counter] = "";
240
- $counter++;
241
- }
242
-
243
- //generate SubDir for this file
244
- $meta = wp_get_attachment_metadata($ID);
245
- if ( empty($meta['file']) )//file has no metadata attached (like PDF files uploaded before SP plugin)
246
- {
247
- $attachedFilePath = get_attached_file($ID);
248
- $SubDir = $this->returnSubDir($attachedFilePath);
249
- }
250
- else
251
- {
252
- $SubDir = $this->returnSubDir($meta['file']);
253
- $source = $filePath;
254
- }
255
-
256
- //if backup is enabled
257
- if( get_option('wp-short-backup_images') )
258
- {
259
- $imageIndex = 0;
260
- $uploadDir = wp_upload_dir();
261
- $source = $filePath;
262
-
263
- if(!file_exists(SP_BACKUP_FOLDER) && !mkdir(SP_BACKUP_FOLDER, 0777, true)) {
264
- return sprintf("Backup folder does not exist and it could not be created");
265
- }
266
-
267
- //create backup dir if needed
268
- @mkdir( SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir, 0777, true);
269
- $destination[$imageIndex] = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . basename($source[$imageIndex]);//for main file
270
- if ( !empty($meta['file']) )
271
- {
272
- foreach ( $meta['sizes'] as $pictureDetails )//generate paths for all the version of an image
273
- {
274
- $imageIndex++;
275
- $source[$imageIndex] = $uploadDir['basedir'] . DIRECTORY_SEPARATOR . $SubDir . $pictureDetails['file'];
276
- $destination[$imageIndex] = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . basename($source[$imageIndex]);
277
- }
278
- }
279
-
280
-
281
- if(is_writable(SP_BACKUP_FOLDER)) {
282
- if(!file_exists($destination[0])) //do not overwrite backup files
283
- {
284
- foreach ( $source as $imageIndex => $fileSource )
285
- {
286
- $fileDestination = $destination[$imageIndex];
287
- @copy($fileSource, $fileDestination);
288
- }
289
- }
290
- } else {
291
- $meta = wp_get_attachment_metadata($ID);
292
- $meta['ShortPixelImprovement'] = 'Cannot save file in backup directory';
293
- wp_update_attachment_metadata($ID, $meta);
294
- return sprintf("Backup folder exists but is not writable");
295
- }
296
-
297
- }//end backup section
298
-
299
-
300
- $counter = 0;
301
- $writeFailed = 0;
302
- foreach ( $tempFiles as $tempFile )//overwrite the original files with the optimized ones
303
- {
304
- $sourceFile = $tempFile;
305
- $destinationFile = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $SubDir . basename($url[$counter]);
306
-
307
- if ( $sourceFile <> "" && file_exists($sourceFile) )
308
- {
309
- @unlink( $destinationFile );
310
- $success = @rename( $sourceFile, $destinationFile );
311
-
312
- if (!$success) {
313
- $copySuccess = @copy($sourceFile, $destinationFile);
314
- unlink($sourceFile);
315
- }
316
- }
317
- else
318
- {
319
- $success = 0;
320
- $copySuccess = 0;
321
- }
322
-
323
- //save data to counters
324
- if( $success || $copySuccess)
325
- {
326
- //update statistics
327
- $fileData = $callData[$counter];
328
- $savedSpace = $fileData->OriginalSize - $fileData->LossySize;
329
-
330
- update_option(
331
- 'wp-short-pixel-savedSpace',
332
- get_option('wp-short-pixel-savedSpace') + $savedSpace
333
- );
334
- $averageCompression = get_option('wp-short-pixel-averageCompression') * get_option('wp-short-pixel-fileCount');
335
- $averageCompression += $fileData->PercentImprovement;
336
- $averageCompression = $averageCompression / (get_option('wp-short-pixel-fileCount') + 1);
337
- update_option('wp-short-pixel-averageCompression', $averageCompression);
338
- update_option('wp-short-pixel-fileCount', get_option('wp-short-pixel-fileCount')+1);
339
- }
340
- else
341
- $writeFailed++;//the file couldn't have been overwritten, we'll let the user know about this
342
- $counter++;
343
- }
344
-
345
- //update metadata
346
- if(isset($ID)) {
347
- $meta = wp_get_attachment_metadata($ID);
348
- if ( $writeFailed == 0 )
349
- $meta['ShortPixelImprovement'] = round($percentImprovement,2);
350
- else
351
- $meta['ShortPixelImprovement'] = 'Cannot write optimized file';
352
- wp_update_attachment_metadata($ID, $meta);
353
- }
354
-
355
- //we reset the retry counter in case of success
356
- update_option('wp-short-pixel-api-retries', 0);
357
- //set this file as processed -> we decrement the cursor
358
- update_option("wp-short-pixel-query-id-start", $ID - 1);//update max ID
359
-
360
- }//end handleSuccess
361
-
362
- static public function returnSubDir($file)//return subdir for that particular attached file
363
- {
364
- $MultisiteExtraPath = "";
365
-
366
- if ( function_exists('is_multisite') && function_exists('get_current_blog_id') )
367
- {//for dealing with WP Multisites
368
- $BlogID = get_current_blog_id();
369
- if ( is_multisite() && $BlogID > 1 ) //is a WP Multisite
370
- $MultisiteExtraPath = "sites" . DIRECTORY_SEPARATOR . $BlogID . DIRECTORY_SEPARATOR;
371
- }
372
-
373
- $uploadDir = wp_upload_dir();
374
-
375
- if ( !isset($file) || strpos($file, "/") === false )
376
- $SubDir = "";
377
- else
378
- $SubDir = $MultisiteExtraPath . trim(substr($file,0,strrpos($file,"/")+1));
379
-
380
- //remove upload dir from the URL if needed
381
- $SubDir = str_ireplace($uploadDir['basedir'] . DIRECTORY_SEPARATOR ,"", $SubDir);
382
-
383
- return $SubDir;
384
- }
385
-
386
- static public function returnSubDirURL($file)//return subdir for that particular attached file
387
- {
388
-
389
- $uploadDir = wp_upload_dir();
390
-
391
- if ( !isset($file) || strpos($file, "/") === false )
392
- $SubDir = "";
393
- else
394
- $SubDir = trim(substr($file,0,strrpos($file,"/")+1));
395
-
396
- //remove upload dir from the URL if needed
397
- $SubDir = str_ireplace($uploadDir['basedir'] . DIRECTORY_SEPARATOR ,"", $SubDir);
398
-
399
- return $SubDir;
400
- }
401
-
402
- public function parseJSON($data) {
403
- if ( function_exists('json_decode') ) {
404
- $data = json_decode( $data );
405
- } else {
406
- require_once( 'JSON/JSON.php' );
407
- $json = new Services_JSON( );
408
- $data = $json->decode( $data );
409
- }
410
- return $data;
411
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  }
1
  <?php
2
  if ( !function_exists( 'download_url' ) ) {
3
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
4
  }
5
 
6
+ class ShortPixelAPI {
7
+
8
+ const STATUS_SUCCESS = 1;
9
+ const STATUS_UNCHANGED = 0;
10
+ const STATUS_ERROR = -1;
11
+ const STATUS_FAIL = -2;
12
+ const STATUS_QUOTA_EXCEEDED = -3;
13
+ const STATUS_SKIP = -4;
14
+ const STATUS_NOT_FOUND = -5;
15
+
16
+ private $_apiKey = '';
17
+ private $_compressionType = '';
18
+ private $_CMYKtoRGBconversion = '';
19
+ private $_maxAttempts = 10;
20
+ private $_apiEndPoint = 'https://api.shortpixel.com/v2/reducer.php';
21
+
22
+ public function setCompressionType($compressionType) {
23
+ $this->_compressionType = $compressionType;
24
+ }
25
+
26
+ public function setCMYKtoRGB($CMYK2RGB) {
27
+ $this->_CMYKtoRGBconversion = $CMYK2RGB;
28
+ }
29
+ public function getCompressionType() {
30
+ return $this->_compressionType;
31
+ }
32
+
33
+ public function setApiKey($apiKey) {
34
+ $this->_apiKey = $apiKey;
35
+ }
36
+
37
+ public function getApiKey() {
38
+ return $this->_apiKey;
39
+ }
40
+
41
+ public function __construct($apiKey, $compressionType, $CMYK2RGB) {
42
+ $this->_apiKey = $apiKey;
43
+ $this->setCompressionType($compressionType);
44
+ $this->setCMYKtoRGB($CMYK2RGB);
45
+ add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
46
+ }
47
+
48
+ public function processImageAction($url, $filePaths, $ID, $time) {
49
+ $this->processImage($URLs, $PATHs, $ID, $time);
50
+ }
51
+
52
+ public function doRequests($URLs, $Blocking, $ID) {
53
+
54
+ $requestParameters = array(
55
+ 'plugin_version' => PLUGIN_VERSION,
56
+ 'key' => $this->_apiKey,
57
+ 'lossy' => $this->_compressionType,
58
+ 'cmyk2rgb' => $this->_CMYKtoRGBconversion,
59
+ 'urllist' => $URLs
60
+ );
61
+ $arguments = array(
62
+ 'method' => 'POST',
63
+ 'timeout' => 45,
64
+ 'redirection' => 3,
65
+ 'sslverify' => false,
66
+ 'httpversion' => '1.0',
67
+ 'blocking' => $Blocking,
68
+ 'headers' => array(),
69
+ 'body' => json_encode($requestParameters),
70
+ 'cookies' => array()
71
+ );
72
+
73
+ $response = wp_remote_post($this->_apiEndPoint, $arguments );
74
+
75
+ //only if $Blocking is true analyze the response
76
+ if ( $Blocking )
77
+ {
78
+ //there was an error, save this error inside file's SP optimization field
79
+ if ( is_object($response) && get_class($response) == 'WP_Error' )
80
+ {
81
+ $errorMessage = $response->errors['http_request_failed'][0];
82
+ $errorCode = 503;
83
+ }
84
+ elseif ( isset($response['response']['code']) && $response['response']['code'] <> 200 )
85
+ {
86
+ $errorMessage = $response['response']['code'] . " - " . $response['response']['message'];
87
+ $errorCode = $response['response']['code'];
88
+ }
89
+
90
+ if ( isset($errorMessage) )
91
+ {//set details inside file so user can know what happened
92
+ $meta = wp_get_attachment_metadata($ID);
93
+ $meta['ShortPixelImprovement'] = 'Error: <i>' . $errorMessage . '</i>';
94
+ unset($meta['ShortPixel']['WaitingProcessing']);
95
+ wp_update_attachment_metadata($ID, $meta);
96
+ return array("response" => array("code" => $errorCode, "message" => $errorMessage ));
97
+ }
98
+
99
+ return $response;//this can be an error or a good response
100
+ }
101
+
102
+ return $response;
103
+ }
104
+
105
+ public function parseResponse($response) {
106
+ $data = $response['body'];
107
+ $data = $this->parseJSON($data);
108
+ return (array)$data;
109
+ }
110
+
111
+ //handles the processing of the image using the ShortPixel API
112
+ public function processImage($URLs, $PATHs, $ID = null, $startTime = 0)
113
+ {
114
+
115
+ $PATHs = self::CheckAndFixImagePaths($PATHs);//check for images to make sure they exist on disk
116
+ if ( $PATHs === false )
117
+ return array("Status" => self::STATUS_SKIP, "Message" => 'The file(s) do not exist on disk, Image #$ID');
118
+
119
+ //tries multiple times (till timeout almost reached) to fetch images.
120
+ if($startTime == 0) {
121
+ $startTime = time();
122
+ }
123
+ $apiRetries = get_option('wp-short-pixel-api-retries');
124
+ if( time() - $startTime > MAX_EXECUTION_TIME)
125
+ {//keeps track of time
126
+ if ( $apiRetries > MAX_API_RETRIES )//we tried to process this time too many times, giving up...
127
+ {
128
+ $meta = wp_get_attachment_metadata($ID);
129
+ $meta['ShortPixelImprovement'] = 'Timed out while processing.';
130
+ unset($meta['ShortPixel']['WaitingProcessing']);
131
+ update_option('wp-short-pixel-api-retries', 0);//fai added to solve a bug?
132
+ wp_update_attachment_metadata($ID, $meta);
133
+ return array("Status" => self::STATUS_SKIP, "Message" => 'Skip this image, tries the next one.');
134
+ }
135
+ else
136
+ {//we'll try again next time user visits a page on admin panel
137
+ $apiRetries++;
138
+ update_option('wp-short-pixel-api-retries', $apiRetries);
139
+ return array("Status" => self::STATUS_ERROR, "Message" => 'Timed out while processing. (pass '.$apiRetries.')');
140
+ }
141
+ }
142
+ $response = $this->doRequests($URLs, true, $ID);//send requests to API
143
+ WPShortPixel::log("FAIPASS:" . json_encode($response));//fai remove
144
+ WPShortPixel::log("FAIPASS:" . json_encode($URLs));//fai remove
145
+ WPShortPixel::log("FAIPASS:" . $apiRetries);//fai remove
146
+
147
+ if($response['response']['code'] != 200)//response <> 200 -> there was an error apparently?
148
+ return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed.");
149
+
150
+ $APIresponse = $this->parseResponse($response);//get the actual response from API, its an array
151
+
152
+ if ( isset($APIresponse[0]) )//API returned image details
153
+ {
154
+ foreach ( $APIresponse as $imageObject )//this part makes sure that all the sizes were processed and ready to be downloaded
155
+ {
156
+ if ( $imageObject->Status->Code == 0 || $imageObject->Status->Code == 1 )
157
+ {
158
+ sleep(1);
159
+ return $this->processImage($URLs, $PATHs, $ID, $startTime);
160
+ }
161
+ }
162
+
163
+ $firstImage = $APIresponse[0];//extract as object first image
164
+ switch($firstImage->Status->Code)
165
+ {
166
+ case 2:
167
+ //handle image has been processed
168
+ update_option( 'wp-short-pixel-quota-exceeded', 0);//reset the quota exceeded flag
169
+ return $this->handleSuccess($APIresponse, $URLs, $PATHs, $ID);
170
+ break;
171
+ default:
172
+ //handle error
173
+ if ( !file_exists($PATHs[0]) )
174
+ return array("Status" => self::STATUS_NOT_FOUND, "Message" => "File not found on disk.");
175
+ elseif ( isset($APIresponse[0]->Status->Message) )
176
+ return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed.");
177
+
178
+ return "Unknown error";
179
+ break;
180
+ }
181
+ }
182
+
183
+ switch($APIresponse['Status']->Code)
184
+ {
185
+
186
+ case -403:
187
+ @delete_option('bulkProcessingStatus');
188
+ update_option( 'wp-short-pixel-quota-exceeded', 1);
189
+ return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => "Quota exceeded.");
190
+ break;
191
+ }
192
+
193
+ //sometimes the response array can be different
194
+ if ( is_numeric($APIresponse['Status']->Code) )
195
+ return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse['Status']->Message);
196
+ else
197
+ return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse[0]->Status->Message);
198
+
199
+ }
200
+
201
+ public function handleDownload($fileData,$counter){
202
+ //var_dump($fileData);
203
+ if($this->_compressionType)
204
+ {
205
+ $fileType = "LossyURL";
206
+ $fileSize = "LossySize";
207
+ }
208
+ else
209
+ {
210
+ $fileType = "LosslessURL";
211
+ $fileSize = "LoselessSize";
212
+ }
213
+
214
+ //if there is no improvement in size then we do not download this file
215
+ if ( $fileData->OriginalSize == $fileData->$fileSize )
216
+ return array("Status" => self::STATUS_UNCHANGED, "Message" => "File wasn't optimized so we do not download it.");
217
+
218
+ $correctFileSize = $fileData->$fileSize;
219
+ $tempFiles[$counter] = download_url(urldecode($fileData->$fileType));
220
+ //var_dump($tempFiles);
221
+
222
+ if(is_wp_error( $tempFiles[$counter] )) //also tries with http instead of https
223
+ {
224
+ $tempFiles[$counter] = download_url(str_replace('https://', 'http://', urldecode($fileData->$fileType)));
225
+ }
226
+ //on success we return this
227
+ $returnMessage = array("Status" => self::STATUS_SUCCESS, "Message" => $tempFiles[$counter]);
228
+
229
+ if ( is_wp_error( $tempFiles[$counter] ) ) {
230
+ @unlink($tempFiles[$counter]);
231
+ $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => "Error downloading file " . $tempFiles[$counter]->get_error_message());
232
+ }
233
+ //check response so that download is OK
234
+ elseif( filesize($tempFiles[$counter]) != $correctFileSize) {
235
+ @unlink($tempFiles[$counter]);
236
+ $size = filesize($tempFiles[$counter]);
237
+ $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => "Error downloading file - incorrect file size (downloaded: {$size}, correct: {$correctFileSize} )");
238
+ }
239
+ elseif (!file_exists($tempFiles[$counter])) {
240
+ $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => "Unable to locate downloaded file " . $tempFiles[$counter]);
241
+ }
242
+ return $returnMessage;
243
+ }
244
+
245
+ public function handleSuccess($APIresponse, $URLs, $PATHs, $ID) {
246
+ $counter = $savedSpace = $originalSpace = $optimizedSpace = $averageCompression = 0;
247
+
248
+ //download each file from array and process it
249
+ foreach ( $APIresponse as $fileData )
250
+ {
251
+ if ( $fileData->Status->Code == 2 ) //file was processed OK
252
+ {
253
+ if ( $counter == 0 )//save percent improvement for main file
254
+ $percentImprovement = $fileData->PercentImprovement;
255
+ else //count thumbnails only
256
+ update_option( 'wp-short-pixel-thumbnail-count', get_option('wp-short-pixel-thumbnail-count') + 1 );
257
+ $downloadResult = $this->handleDownload($fileData,$counter);
258
+ //when the status is STATUS_UNCHANGED we just skip the array line for that one
259
+ if ( $downloadResult['Status'] == self::STATUS_SUCCESS ) {
260
+ $tempFiles[$counter] = $downloadResult['Message'];
261
+ }
262
+ elseif ( $downloadResult['Status'] <> self::STATUS_UNCHANGED )
263
+ return array("Status" => $downloadResult['Status'], "Message" => $downloadResult['Message']);
264
+ }
265
+ else //there was an error while trying to download a file
266
+ $tempFiles[$counter] = "";
267
+
268
+ $counter++;
269
+ }
270
+
271
+ //figure out in what SubDir files should land
272
+ $SubDir = $this->returnSubDir(get_attached_file($ID));
273
+
274
+ //if backup is enabled - we try to save the images
275
+ if( get_option('wp-short-backup_images') )
276
+ {
277
+ $uploadDir = wp_upload_dir();
278
+ $source = $PATHs;//array with final paths for this files
279
+
280
+ if( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) ) {//creates backup folder if it doesn't exist
281
+ return array("Status" => self::STATUS_FAIL, "Message" => "Backup folder does not exist and it cannot be created");
282
+ }
283
+ //create subdir in backup folder if needed
284
+ @mkdir( SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir, 0777, true);
285
+
286
+ foreach ( $source as $fileID => $filePATH )//create destination files array
287
+ {
288
+ $destination[$fileID] = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . self::MB_basename($source[$fileID]);
289
+ }
290
+
291
+ //now that we have original files and where we should back them up we attempt to do just that
292
+ if(is_writable(SP_BACKUP_FOLDER))
293
+ {
294
+ foreach ( $destination as $fileID => $filePATH )
295
+ {
296
+ if ( !file_exists($filePATH) )
297
+ {
298
+ if ( !@copy($source[$fileID], $destination[$fileID]) )
299
+ {//file couldn't have been saved in backup folder
300
+ ShortPixelAPI::SaveMessageinMetadata($ID, 'Cannot save file <i>' . self::MB_basename($source[$fileID]) . '</i> in backup directory');
301
+ return array("Status" => self::STATUS_FAIL, "Message" => 'Cannot save file <i>' . self::MB_basename($source[$fileID]) . '</i> in backup directory');
302
+ }
303
+ }
304
+ }
305
+ } else {//cannot write to the backup dir, return with an error
306
+ ShortPixelAPI::SaveMessageinMetadata($ID, 'Cannot save file in backup directory');
307
+ return array("Status" => self::STATUS_FAIL, "Message" => 'Cannot save file in backup directory');
308
+ }
309
+
310
+ }//end backup section
311
+
312
+
313
+ $writeFailed = 0;
314
+
315
+ if ( !empty($tempFiles) )
316
+ {
317
+ //overwrite the original files with the optimized ones
318
+ foreach ( $tempFiles as $tempFileID => $tempFilePATH )
319
+ {
320
+ if ( file_exists($tempFilePATH) && file_exists($PATHs[$tempFileID]) && is_writable($PATHs[$tempFileID]) )
321
+ {
322
+ copy($tempFilePATH, $PATHs[$tempFileID]);
323
+ @unlink($tempFilePATH);
324
+ }
325
+ else
326
+ $writeFailed++;
327
+
328
+ if ( $writeFailed > 0 )//there was an error
329
+ {
330
+ ShortPixelAPI::SaveMessageinMetadata($ID, 'Error: optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.');
331
+ update_option('bulkProcessingStatus', "error");
332
+ return array("Status" => self::STATUS_FAIL, "Message" => 'Error: optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.');
333
+ }
334
+ else
335
+ {//all files were copied, optimization data regarding the savings locally in DB
336
+ $fileType = ( $this->_compressionType ) ? "LossySize" : "LoselessSize";
337
+ $savedSpace += $APIresponse[$tempFileID]->OriginalSize - $APIresponse[$tempFileID]->$fileType;
338
+ $originalSpace += $APIresponse[$tempFileID]->OriginalSize;
339
+ $optimizedSpace += $APIresponse[$tempFileID]->$fileType;
340
+ $averageCompression += $fileData->PercentImprovement;
341
+
342
+ //add the number of files with < 5% optimization
343
+ if ( ( ( 1 - $APIresponse[$tempFileID]->$fileType/$APIresponse[$tempFileID]->OriginalSize ) * 100 ) < 5 )
344
+ update_option( 'wp-short-pixel-files-under-5-percent', get_option('wp-short-pixel-files-under-5-percent') + 1);
345
+
346
+ }
347
+ }
348
+ }
349
+ //old average counting
350
+ update_option('wp-short-pixel-savedSpace', get_option('wp-short-pixel-savedSpace') + $savedSpace);
351
+ $averageCompression = get_option('wp-short-pixel-averageCompression') * get_option('wp-short-pixel-fileCount');
352
+ $averageCompression = $averageCompression / (get_option('wp-short-pixel-fileCount') + count($APIresponse));
353
+ update_option('wp-short-pixel-averageCompression', $averageCompression);
354
+ update_option('wp-short-pixel-fileCount', get_option('wp-short-pixel-fileCount') + count($APIresponse));
355
+ //new average counting
356
+ update_option('wp-short-pixel-total-original', get_option('wp-short-pixel-total-original') + $originalSpace);
357
+ update_option('wp-short-pixel-total-optimized', get_option('wp-short-pixel-total-optimized') + $optimizedSpace);
358
+ //update metadata for this file
359
+ $meta = wp_get_attachment_metadata($ID);
360
+ $meta['ShortPixelImprovement'] = round($percentImprovement,2);
361
+ wp_update_attachment_metadata($ID, $meta);
362
+ //we reset the retry counter in case of success
363
+ update_option('wp-short-pixel-api-retries', 0);
364
+
365
+ return array("Status" => self::STATUS_SUCCESS, "Message" => 'Success: No pixels remained unsqueezed :-)', "PercentImprovement" => $percentImprovement);
366
+ }//end handleSuccess
367
+
368
+ static public function returnSubDir($file)//return subdir for that particular attached file
369
+ {
370
+ $Atoms = explode("/", $file);
371
+ $Counter = count($Atoms);
372
+ $SubDir = $Atoms[$Counter-3] . DIRECTORY_SEPARATOR . $Atoms[$Counter-2] . DIRECTORY_SEPARATOR;
373
+
374
+ return $SubDir;
375
+ }
376
+
377
+ //a basename alternative that deals OK with multibyte charsets (e.g. Arabic)
378
+ static public function MB_basename($Path){
379
+ $Separator = " qq ";
380
+ $Path = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
381
+ $Base = basename($Path);
382
+ $Base = str_replace($Separator, "", $Base);
383
+ return $Base;
384
+ }
385
+
386
+ //sometimes, the paths to the files as defined in metadata are wrong, we try to automatically correct them
387
+ static public function CheckAndFixImagePaths($PATHs){
388
+
389
+ $ErrorCount = 0;
390
+ $uploadDir = wp_upload_dir();
391
+ $Tmp = explode("/", $uploadDir['basedir']);
392
+ $TmpCount = count($Tmp);
393
+ $StichString = $Tmp[$TmpCount-2] . "/" . $Tmp[$TmpCount-1];
394
+ //files exist on disk?
395
+ foreach ( $PATHs as $Id => $File )
396
+ {
397
+ //we try again with a different path
398
+ if ( !file_exists($File) ){
399
+ $NewFile = $uploadDir['basedir'] . substr($File,strpos($File, $StichString)+strlen($StichString));
400
+ if ( file_exists($NewFile) )
401
+ $PATHs[$Id] = $NewFile;
402
+ else
403
+ $ErrorCount++;
404
+ }
405
+ }
406
+
407
+ if ( $ErrorCount > 0 )
408
+ return false;
409
+ else
410
+ return $PATHs;
411
+
412
+ }
413
+
414
+
415
+ static private function SaveMessageinMetadata($ID, $Message)
416
+ {
417
+ $meta = wp_get_attachment_metadata($ID);
418
+ $meta['ShortPixelImprovement'] = $Message;
419
+ unset($meta['ShortPixel']['WaitingProcessing']);
420
+ wp_update_attachment_metadata($ID, $meta);
421
+ }
422
+
423
+ public function parseJSON($data) {
424
+ if ( function_exists('json_decode') ) {
425
+ $data = json_decode( $data );
426
+ } else {
427
+ require_once( 'JSON/JSON.php' );
428
+ $json = new Services_JSON( );
429
+ $data = $json->decode( $data );
430
+ }
431
+ return $data;
432
+ }
433
  }
shortpixel_queue.php ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelQueue {
4
+
5
+ private $ctrl;
6
+ private $startBulkId;
7
+ private $stopBulkId;
8
+ private $bulkCount;
9
+ private $bulkSkippedCount;
10
+ private $bulkAlreadyDoneCount;
11
+ private $lastBulkStartTime;
12
+ private $lastBulkSuccessTime;
13
+ private $bulkRunningTime;
14
+
15
+ //handling older
16
+ public function ShortPixelQueue($controller) {
17
+ $this->__construct($controller);
18
+ }
19
+
20
+ public function __construct($controller) {
21
+ $this->ctrl = $controller;
22
+ //init the option if needed
23
+ if(!isset($_SESSION["wp-short-pixel-priorityQueue"])) {
24
+ //take the priority list from the options (we persist there the priority IDs from the previous session)
25
+ $prioQueueOpt = WPShortPixel::getOpt( 'wp-short-pixel-priorityQueue', array());//here we save the IDs for the files that need to be processed after an image upload for example
26
+ $_SESSION["wp-short-pixel-priorityQueue"] = array();
27
+ foreach($prioQueueOpt as $ID) {
28
+ $meta = wp_get_attachment_metadata($ID);
29
+ WPShortPixel::log("INIT: Item $ID from options has metadata: " .json_encode($meta));
30
+ if(!isset($meta['ShortPixelImprovement'])) {
31
+ $this->push($ID);
32
+ }
33
+ }
34
+ update_option('wp-short-pixel-priorityQueue', $_SESSION["wp-short-pixel-priorityQueue"]);
35
+ WPShortPixel::log("INIT: Session queue not found, updated from Options with "
36
+ .json_encode($_SESSION["wp-short-pixel-priorityQueue"]));
37
+ }
38
+
39
+ $this->startBulkId = WPShortPixel::getOpt( 'wp-short-pixel-query-id-start', 0);//current query ID used for postmeta queries
40
+ $this->stopBulkId = WPShortPixel::getOpt( 'wp-short-pixel-query-id-stop', 0);//min ID used for postmeta queries
41
+ $this->bulkCount = WPShortPixel::getOpt( "wp-short-pixel-bulk-count", 0);
42
+ $this->bulkSkippedCount = WPShortPixel::getOpt( "wp-short-pixel-bulk-skipped-count", 0);
43
+ $this->bulkAlreadyDoneCount = WPShortPixel::getOpt( "wp-short-pixel-bulk-done-count", 0);
44
+ $this->lastBulkStartTime = WPShortPixel::getOpt( 'wp-short-pixel-last-bulk-start-time', 0);//time of the last start of the bulk.
45
+ $this->lastBulkSuccessTime = WPShortPixel::getOpt( 'wp-short-pixel-last-bulk-success-time', 0);//time of the last start of the bulk.
46
+ $this->bulkRunningTime = WPShortPixel::getOpt( 'wp-short-pixel-bulk-running-time', 0);//how long the bulk ran that far.
47
+ }
48
+
49
+ public function get() {
50
+ return $_SESSION["wp-short-pixel-priorityQueue"];//get_option("wp-short-pixel-priorityQueue");
51
+ }
52
+
53
+ public function push($ID)//add an ID to priority queue
54
+ {
55
+ $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"]; //get_option("wp-short-pixel-priorityQueue");
56
+ WPShortPixel::log("PUSH: Push ID $ID into queue ".json_encode($priorityQueue));
57
+ array_push($priorityQueue, $ID);
58
+ $prioQ = array_unique($priorityQueue);
59
+ $_SESSION["wp-short-pixel-priorityQueue"] = $prioQ;
60
+ //push also to the options queue, in case the session gets killed retrieve frm there
61
+ update_option('wp-short-pixel-priorityQueue', $prioQ);
62
+
63
+ WPShortPixel::log("PUSH: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
64
+ }
65
+
66
+ public function getFirst($count = 1)//return the first values added to priority queue
67
+ {
68
+ $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"];//self::getOpt("wp-short-pixel-priorityQueue", array());
69
+ $count = min(count($priorityQueue), $count);
70
+ return(array_slice($priorityQueue, count($priorityQueue) - $count, $count));
71
+ }
72
+
73
+ public function remove($ID)//remove an ID from priority queue
74
+ {
75
+ $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"];//get_option("wp-short-pixel-priorityQueue");
76
+ WPShortPixel::log("REM: Remove ID $ID from queue ".json_encode($priorityQueue));
77
+ $newPriorityQueue = array();
78
+ $found = false;
79
+ foreach($priorityQueue as $item) {
80
+ if($item != $ID) {
81
+ $newPriorityQueue[] = $item;
82
+ } else {
83
+ $found = true;
84
+ }
85
+ }
86
+ //update_option("wp-short-pixel-priorityQueue", $newPriorityQueue);
87
+ $_SESSION["wp-short-pixel-priorityQueue"] = $newPriorityQueue;
88
+ WPShortPixel::log("REM: " . ($found ? "Updated: " : "Not found") . json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
89
+ return $found;
90
+ }
91
+
92
+ public function bulkRunning() {
93
+ //$bulkProcessingStatus = get_option('bulkProcessingStatus');
94
+ return $this->startBulkId > $this->stopBulkId;
95
+ }
96
+
97
+ public function bulkPaused() {
98
+ WPShortPixel::log("Bulk Paused: " . get_option( 'wp-short-pixel-cancel-pointer'));
99
+ return WPShortPixel::getOpt( 'wp-short-pixel-cancel-pointer', 0);
100
+ }
101
+
102
+ public function bulkRan() {
103
+ return WPShortPixel::getOpt("wp-short-pixel-bulk-ever-ran", 0) != 0;
104
+ }
105
+
106
+ public function processing() {
107
+ WPShortPixel::log("QUEUE: processing(): get:" . json_encode($this->get()));
108
+ return $this->bulkRunning() || count($this->get());
109
+ }
110
+
111
+ public function getFlagBulkId() {
112
+ return WPShortPixel::getOpt("wp-short-pixel-flag-id",0);
113
+ }
114
+
115
+ public function getStartBulkId() {
116
+ return $this->startBulkId;
117
+ }
118
+
119
+ public function resetStartBulkId() {
120
+ $this->setStartBulkId($this->ctrl->getMaxMediaId());
121
+ }
122
+
123
+ public function setStartBulkId($start){
124
+ $this->startBulkId = $start;
125
+ update_option("wp-short-pixel-query-id-start", $this->startBulkId);
126
+ }
127
+
128
+ public function getStopBulkId() {
129
+ return $this->stopBulkId;
130
+ }
131
+
132
+ public function resetStopBulkId() {
133
+ $this->stopBulkId = $this->ctrl->getMinMediaId();
134
+ update_option("wp-short-pixel-query-id-stop", $this->stopBulkId);
135
+ }
136
+
137
+ public function resetBulkCount() {
138
+ $res = $this->ctrl->countAllProcessableFiles($this->getFlagBulkId(), $this->stopBulkId);
139
+ $this->bulkCount = $res["mainFiles"];
140
+ update_option("wp-short-pixel-bulk-count", $this->bulkCount);
141
+ }
142
+
143
+ public function resetBulkAlreadyDoneCount() {
144
+ $res = $this->ctrl->countAllProcessedFiles($this->getFlagBulkId(), $this->stopBulkId);
145
+ $this->bulkAlreadyDoneCount = $res["mainFiles"];
146
+ update_option("wp-short-pixel-bulk-done-count", $this->bulkAlreadyDoneCount);
147
+ }
148
+
149
+ public function resetBulkSkippedCount() {
150
+ $this->bulkSkippedCount = 0;
151
+ update_option("wp-short-pixel-bulk-skipped-count", $this->bulkSkippedCount);
152
+ }
153
+
154
+ public function flagBulkStart() {
155
+ update_option("wp-short-pixel-flag-id", $this->startBulkId);
156
+ delete_option('bulkProcessingStatus');
157
+ add_option('bulkProcessingStatus', 'running');//set bulk flag
158
+ }
159
+
160
+ public function startBulk() {
161
+ $this->resetStartBulkId(); //start downwards from the biggest item ID
162
+ $this->resetStopBulkId();
163
+ $this->flagBulkStart(); //we use this to detect new added files while bulk is running
164
+ $this->resetBulkCount(); //call this only after the resets above;
165
+ $this->resetBulkAlreadyDoneCount();//call this only after the resets above;
166
+ $this->resetBulkSkippedCount();
167
+ update_option( 'wp-short-pixel-bulk-ever-ran', 1);
168
+ }
169
+
170
+ public function pauseBulk() {
171
+ update_option( 'wp-short-pixel-cancel-pointer', $this->startBulkId);//we save this so we can resume bulk processing
172
+ WPShortPixel::log("PAUSE: Pointer = ".get_option( 'wp-short-pixel-cancel-pointer'));
173
+ $this->stopBulk();
174
+ }
175
+
176
+ public function stopBulk() {
177
+ $this->startBulkId = $this->ctrl->getMaxMediaId();
178
+ $this->stopBulkId = $this->startBulkId;
179
+ update_option("wp-short-pixel-query-id-start", $this->startBulkId);
180
+ update_option("wp-short-pixel-query-id-stop", $this->stopBulkId);
181
+ delete_option('bulkProcessingStatus');
182
+ $this->resetBulkSkippedCount();
183
+ return WPShortPixel::getOpt('wp-short-pixel-bulk-ever-ran', 0);
184
+ }
185
+
186
+ public function resumeBulk() {
187
+ $this->startBulkId = get_option( 'wp-short-pixel-cancel-pointer');
188
+ update_option("wp-short-pixel-query-id-start", $this->startBulkId);//start downwards from the biggest item ID
189
+ $this->stopBulkId = $this->ctrl->getMinMediaId();
190
+ update_option("wp-short-pixel-query-id-stop", $this->stopBulkId);
191
+ //update_option("wp-short-pixel-flag-id", $this->startBulkId);//we use to detect new added files while bulk is running
192
+ add_option('bulkProcessingStatus', 'running');//set bulk flag
193
+ $this->resetBulkSkippedCount();
194
+ delete_option( 'wp-short-pixel-cancel-pointer');
195
+ WPShortPixel::log("Resumed: (pause says: " . $this->bulkPaused() . ") Start from: " . $this->startBulkId . " to " . $this->stopBulkId);
196
+ }
197
+
198
+ public function addBulkSkipped($skipped) {
199
+ $this->bulkSkippedCount += $skipped;
200
+ update_option( "wp-short-pixel-bulk-skipped-count", $this->bulkSkippedCount);
201
+ }
202
+
203
+ public function markBulkComplete() {
204
+ delete_option('bulkProcessingStatus');
205
+ delete_option( 'wp-short-pixel-cancel-pointer');
206
+ }
207
+
208
+ public function resetBulk() {
209
+ delete_option('bulkProcessingStatus');
210
+ delete_option( 'wp-short-pixel-cancel-pointer');
211
+ $this->startBulkId = $this->stopBulkId = $this->ctrl->getMaxMediaId();
212
+ update_option( 'wp-short-pixel-query-id-stop', $this->startBulkId );
213
+ update_option( 'wp-short-pixel-query-id-start', $this->startBulkId );
214
+ update_option('wp-short-pixel-bulk-running-time', 0);
215
+ update_option('wp-short-pixel-last-bulk-start-time', 0);
216
+ update_option('wp-short-pixel-last-bulk-success-time', 0);
217
+ }
218
+
219
+ public function logBulkProgress() {
220
+ $t = time();
221
+ if($t - $this->lastBulkSuccessTime > 120) { //if break longer than two minutes we mark a pause in the bulk
222
+ $this->bulkRunningTime += ($this->lastBulkSuccessTime - $this->lastBulkStartTime);
223
+ update_option('wp-short-pixel-bulk-running-time', $this->bulkRunningTime);
224
+ $this->lastBulkStartTime = $this->lastBulkSuccessTime = $t;
225
+ update_option('wp-short-pixel-last-bulk-start-time', $t);
226
+ update_option('wp-short-pixel-last-bulk-success-time', $t);
227
+ } else {
228
+ $this->lastBulkSuccessTime = $t;
229
+ update_option('wp-short-pixel-last-bulk-success-time', $t);
230
+ }
231
+ }
232
+
233
+ public function getBulkPercent() {
234
+ WPShortPixel::log("QUEUE.getBulkPercent: Flag:" . get_option("wp-short-pixel-flag-id"). " Start:" . $this->startBulkId . " Already done: " . $this->bulkAlreadyDoneCount . " Skipped: " . $this->bulkSkippedCount . " Bulk count:" . $this->bulkCount);
235
+ WPShortPixel::log(" calculated percent:" . round((get_option("wp-short-pixel-flag-id") - $this->startBulkId + $this->bulkAlreadyDoneCount - $this->bulkSkippedCount) / $this->bulkCount * 100));
236
+
237
+ if($this->bulkCount == 0) return 100;
238
+ // return maximum 99%
239
+ return min(99, round((get_option("wp-short-pixel-flag-id") - $this->startBulkId + $this->bulkAlreadyDoneCount - $this->bulkSkippedCount) / $this->bulkCount * 100));
240
+ }
241
+
242
+ public function getDeltaBulkPercent() {
243
+ WPShortPixel::log("QUEUE.getDeltaBulkPercent: Flag:" . get_option("wp-short-pixel-flag-id"). " Start:" . $this->startBulkId . " Already done: " . $this->bulkAlreadyDoneCount . " Bulk count:" . $this->bulkCount);
244
+ return $this->getBulkPercent() - round($this->bulkAlreadyDoneCount / $this->bulkCount * 100);
245
+ }
246
+
247
+ public function getTimeRemaining (){
248
+ $p = $this->getBulkPercent();
249
+ $pAlready = round($this->bulkAlreadyDoneCount / $this->bulkCount * 100);
250
+ // die("" . ($this->lastBulkSuccessTime - $this->lastBulkStartTime));
251
+ if(($p - $pAlready) == 0) return 0;
252
+ return round(((100 - $p) / ($p - $pAlready)) * ($this->bulkRunningTime + $this->lastBulkSuccessTime - $this->lastBulkStartTime)/60);
253
+ }
254
+ }
shortpixel_view.php ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelView {
4
+
5
+ private $ctrl;
6
+
7
+ //handling older
8
+ public function ShortPixelView($controller) {
9
+ $this->__construct($controller);
10
+ }
11
+
12
+ public function __construct($controller) {
13
+ $this->ctrl = $controller;
14
+ }
15
+
16
+ public function displayQuotaExceededAlert($quotaData)
17
+ { ?>
18
+ <br/>
19
+ <div style="background-color: #fff; border-left: 4px solid #ff0000; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;">
20
+ <h3>Quota Exceeded</h3>
21
+ <p>
22
+ The plugin has optimized <?=number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric'])?> images and stopped because it reached the available quota limit.
23
+ </p>
24
+ <p>
25
+ It’s simple to upgrade, just click here to <a href='https://shortpixel.com/login/<?=$this->ctrl->getApiKey()?>' target='_blank'>log into your account</a> and see the options in the billing page.
26
+ </p>
27
+ <p>
28
+ You can immediately start processing 5,000 images/month for &#36;4,99, choose another plan that suits you or <a href='https://shortpixel.com/contact' target='_blank'>contact us</a> for larger compression needs.
29
+ </p>
30
+ </div> <?php
31
+ }
32
+
33
+ public function displayApiKeyAlert()
34
+ { ?>
35
+ <p>In order to start the optimization process, you need to validate your API key in the <a href="options-general.php?page=wp-shortpixel">ShortPixel Settings</a> page in your WordPress Admin.</p>
36
+ <p>If you don’t have an API Key, you can get one delivered to your inbox, for free.</p>
37
+ <p>Please <a href="https://shortpixel.com/wp-apikey" target="_blank">sign up</a> to get your API key.</p>
38
+ <?php
39
+ }
40
+
41
+ public function displayBulkProcessingForm($imageCount, $imgProcessedCount, $thumbsProcessedCount, $under5PercentCount, $bulkRan, $averageCompression, $filesOptimized, $savedSpace, $percent) {
42
+ ?>
43
+ <div class="wrap short-pixel-bulk-page">
44
+ <h1>Bulk Image Optimization by ShortPixel</h1>
45
+ <?php
46
+ if ( !$bulkRan ) { ?>
47
+ <p>You have <?=number_format($imageCount['mainFiles'])?> images in your Media Library and <?=number_format($imageCount['totalFiles'] - $imageCount['mainFiles'])?> smaller thumbnails, associated to these images.</p>
48
+ <?php if($imgProcessedCount["totalFiles"] > 0) { ?>
49
+ <p>From these, <?=number_format($imgProcessedCount['mainFiles'])?> images and <?=number_format($imgProcessedCount['totalFiles'] - $imgProcessedCount['mainFiles'])?> thumbnails were already processed by ShorPixel</p>
50
+ <?php } ?>
51
+ <p>If the box below is checked, <b>ShortPixel will process a total of <?=number_format($imageCount['totalFiles'] - $imgProcessedCount['totalFiles'])?> images.</b> However, images with less than 5% optimization will not be counted out of your quota, so the final number of counted images could be smaller.</p>
52
+ <p>Thumbnails are important because they are displayed on most of your website's pages and they may generate more traffic than the originals. Optimizing thumbnails will improve your overall website speed. However, if you don't want to optimize thumbnails, please uncheck the box below.</p>
53
+
54
+ <form action='' method='POST' >
55
+ <input type='checkbox' name='thumbnails' <?=$this->ctrl->processThumbnails() ? "checked":""?>> Include thumbnails
56
+ <p>The plugin will replace the original images with the optimized ones starting with the newest images added to your Media Library. You will be able to pause the process anytime.</p>
57
+ <?=$this->ctrl->backupImages() ? "<p>Your original images will be stored in a separate back-up folder.</p>" : ""?>
58
+ <input type='submit' name='bulkProcess' id='bulkProcess' class='button button-primary' value='Start Optimizing'>
59
+ </form>
60
+ <?php
61
+ } elseif($percent) // bulk is paused
62
+ { ?>
63
+ <p>Bulk processing is paused. You can continue optimizing your Media Gallery from where you left, by clicking the button below. Already optimized images will not be reprocessed.</p>
64
+ <?=$this->displayBulkProgressBar(false, $percent, "")?>
65
+ <p>Please see below the optimization status so far:</p>
66
+ <?=$this->displayBulkStats($filesOptimized, $thumbsProcessedCount, $under5PercentCount, $averageCompression, $savedSpace)?>
67
+ <?php
68
+ } else { ?>
69
+ <p>Congratulations, your media library has been successfully optimized!</p>
70
+ <p>Go to the ShortPixel <a href='" . get_admin_url() . "options-general.php?page=wp-shortpixel#facts'>Stats</a> and see all your websites' optimized stats.
71
+
72
+ <?=$this->displayBulkStats($filesOptimized, $thumbsProcessedCount, $under5PercentCount, $averageCompression, $savedSpace)?>
73
+ <p>Download your detailed <a href="https://api.shortpixel.com/v2/report.php?key=<?=$this->ctrl->getApiKey()?>">Optimization Report</a> to check your image optimization statistics for the last 40 days</p>
74
+ <p>Restart the optimization process for new images added to your library:
75
+ <form action='' method='POST' >
76
+ <input type='checkbox' name='thumbnails' <?=$this->ctrl->processThumbnails() ? "checked":""?>> Include thumbnails<br><br>
77
+ <input type='submit' name='bulkProcess' id='bulkProcess' class='button button-primary' value='Restart Optimizing'>
78
+ </form>
79
+ <?php } ?>
80
+ </div>
81
+ <?php
82
+ }
83
+
84
+ public function displayBulkProcessingRunning($percent, $message) {
85
+ ?>
86
+ <div class="wrap short-pixel-bulk-page">
87
+ <h1>Bulk Image Optimization by ShortPixel</h1>
88
+ <p>Bulk optimization has started. This process will take some time, depending on the number of images in your library. In the meantime, you can continue using the admin as usual. However, if you close the WordPress admin, the bulk processing will pause until you open it again. </p>
89
+ <?=$this->displayBulkProgressBar(true, $percent, $message)?>
90
+ <div class="bulk-progress bulk-slider-container">
91
+ <div style="margin-bottom: 10px;"><span class="short-pixel-block-title">Just optimized:</span></div>
92
+ <div class="bulk-slider">
93
+ <div class="bulk-slide" id="empty-slide">
94
+ <div class="img-original">
95
+ <div><img class="bulk-img-orig" src=""></div>
96
+ <div>Original image</div>
97
+ </div>
98
+ <div class="img-optimized">
99
+ <div><img class="bulk-img-opt" src=""></div>
100
+ <div>Optimized image</div>
101
+ </div>
102
+ <div class="img-info">
103
+ <div style="font-size: 14px; line-height: 10px; margin-bottom:16px;">Optimized by:</div>
104
+ <span class="bulk-opt-percent"></span>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ <?php
111
+ }
112
+
113
+ public function displayBulkProgressBar($running, $percent, $message) {
114
+ $percentBefore = $percentAfter = '';
115
+ if($percent > 24) {
116
+ $percentBefore = $percent . "%";
117
+ } else {
118
+ $percentAfter = $percent . "%";
119
+ }
120
+ ?>
121
+ <div class="bulk-progress">
122
+ <div id="bulk-progress" class="progress" >
123
+ <div class="progress-img" style="left: <?=$percent?>%;">
124
+ <img src="<?=WP_PLUGIN_URL?>/shortpixel-image-optimiser/img/slider.png">
125
+ <span><?=$percentAfter?></span>
126
+ </div>
127
+ <div class="progress-left" style="width: <?=$percent?>%"><?=$percentBefore?></div>
128
+ </div>
129
+ <div class="bulk-estimate">
130
+ &nbsp;<?=$message?>
131
+ </div>
132
+ <form action='' method='POST' style="display:inline;">
133
+ <input type="submit" class="button button-secondary bulk-cancel" onclick="clearBulkProcessor();"
134
+ name="<?=$running ? "bulkProcessPause" : "bulkProcessResume"?>" value="<?=$running ? "Pause" : "Resume Processing"?>"/>
135
+ </form>
136
+ </div>
137
+ <?php
138
+ }
139
+
140
+ public function displayBulkStats($filesOptimized, $thumbsProcessedCount, $under5PercentCount, $averageCompression, $savedSpace) {
141
+ ?>
142
+ <div class="bulk-progress bulk-stats">
143
+ <div class="label">Images/PDFs processed with ShortPixel:</div><div><?=number_format($filesOptimized - $thumbsProcessedCount)?></div><br>
144
+ <div class="label">Thumbnails processed with ShortPixel:</div><div><?=number_format($thumbsProcessedCount)?></div><br>
145
+ <div class="label totals">Total:</div><div><?=number_format($filesOptimized)?></div><br>
146
+ <div class="label totals">Images & thumbs with less than 5% optimization (free):</div><div><?=number_format($under5PercentCount)?></div><br>
147
+ <div class="label totals">Used quota:</div><div><?=number_format($filesOptimized - $under5PercentCount)?></div><br>
148
+ <div class="label">Average optimization:</div><div><?=$averageCompression?>%</div><br>
149
+ <div class="label">Saved space:</div><div><?=$savedSpace?></div>
150
+ </div>
151
+ <?php
152
+ }
153
+
154
+ }
wp-shortpixel.php CHANGED
@@ -1,1556 +1,1526 @@
1
- <?php
2
- /**
3
- * Plugin Name: ShortPixel Image Optimizer
4
- * Plugin URI: https://shortpixel.com/
5
- * Description: ShortPixel is an image compression tool that helps improve your website performance. The plugin optimizes images automatically using both lossy and lossless compression. Resulting, smaller, images are no different in quality from the original. To install: 1) Click the "Activate" link to the left of this description. 2) <a href="https://shortpixel.com/wp-apikey" target="_blank">Free Sign up</a> for your unique API Key . 3) Check your email for your API key. 4) Use your API key to activate ShortPixel plugin in the 'Plugins' menu in WordPress. 5) Done!
6
- * Version: 2.1.10
7
- * Author: ShortPixel
8
- * Author URI: https://shortpixel.com
9
- */
10
-
11
- require_once('shortpixel_api.php');
12
- require_once( ABSPATH . 'wp-admin/includes/image.php' );
13
- include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
14
- if ( !is_plugin_active( 'wpmandrill/wpmandrill.php' ) ) {
15
- require_once( ABSPATH . 'wp-includes/pluggable.php' );//to avoid conflict with wpmandrill plugin
16
- }
17
-
18
- define('PLUGIN_VERSION', "2.1.10");
19
- define('SP_DEBUG', false);
20
- define('SP_LOG', false);
21
- define('SP_MAX_TIMEOUT', 10);
22
- define('SP_BACKUP_FOLDER', WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'ShortpixelBackups');
23
- define('MUST_HAVE_KEY', true);
24
- define('MAX_API_RETRIES', 5);
25
- $MAX_EXECUTION_TIME = ini_get('max_execution_time');
26
- if ( is_numeric($MAX_EXECUTION_TIME) )
27
- define('MAX_EXECUTION_TIME', $MAX_EXECUTION_TIME - 5 ); //in seconds
28
- else
29
- define('MAX_EXECUTION_TIME', 25 );
30
- define("SP_MAX_RESULTS_QUERY", 6);
31
-
32
- class WPShortPixel {
33
-
34
- private $_apiInterface = null;
35
- private $_apiKey = '';
36
- private $_compressionType = 1;
37
- private $_processThumbnails = 1;
38
- private $_CMYKtoRGBconversion = 1;
39
- private $_backupImages = 1;
40
- private $_verifiedKey = false;
41
-
42
- public function __construct() {
43
-
44
- $this->populateOptions();
45
- define('QUOTA_EXCEEDED', "Quota Exceeded. <a href='https://shortpixel.com/login/".$this->_apiKey."' target='_blank'>Extend Quota</a>");
46
-
47
- $this->setDefaultViewModeList();//set default mode as list. only @ first run
48
-
49
- $this->_apiInterface = new shortpixel_api($this->_apiKey, $this->_compressionType, $this->_CMYKtoRGBconversion);
50
-
51
- //add hook for image upload processing
52
- add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleImageUpload' ), 10, 2 );
53
- add_filter( 'manage_media_columns', array( &$this, 'columns' ) );//add media library column header
54
- add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));//for plugin settings page
55
-
56
- //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
57
- add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );//generate the media library column
58
-
59
- //add settings page
60
- add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );//display SP in Settings menu
61
- add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
62
- add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
63
-
64
- //when plugin is activated run this
65
- register_activation_hook( __FILE__, array( &$this, 'shortPixelActivatePlugin' ) );
66
- register_deactivation_hook( __FILE__, array( &$this, 'shortPixelDeactivatePlugin' ) );
67
-
68
- //automatic optimization
69
- add_action( 'admin_footer', array( &$this, 'my_action_javascript') );
70
- add_action( 'wp_ajax_my_action', array( &$this, 'handleImageProcessing') );
71
-
72
- //manual optimization
73
- add_action('admin_action_shortpixel_manual_optimize', array(&$this, 'handleManualOptimization'));
74
- //backup restore
75
- add_action('admin_action_shortpixel_restore_backup', array(&$this, 'handleRestoreBackup'));
76
-
77
- $this->migrateBackupFolder();
78
- }
79
-
80
-
81
- public function populateOptions() {
82
-
83
- if(get_option('wp-short-pixel-apiKey') !== false) {
84
- $this->_apiKey = get_option('wp-short-pixel-apiKey');
85
- } else {
86
- add_option( 'wp-short-pixel-apiKey', '', '', 'yes' );
87
- }
88
-
89
- if(get_option('wp-short-pixel-verifiedKey') !== false) {
90
- $this->_verifiedKey = get_option('wp-short-pixel-verifiedKey');
91
- }
92
-
93
- if(get_option('wp-short-pixel-compression') !== false) {
94
- $this->_compressionType = get_option('wp-short-pixel-compression');
95
- } else {
96
- add_option('wp-short-pixel-compression', $this->_compressionType, '', 'yes');
97
- }
98
-
99
- if(get_option('wp-short-process_thumbnails') !== false) {
100
- $this->_processThumbnails = get_option('wp-short-process_thumbnails');
101
- } else {
102
- add_option('wp-short-process_thumbnails', $this->_processThumbnails, '', 'yes' );
103
- }
104
-
105
- if(get_option('wp-short-pixel_cmyk2rgb') !== false) {
106
- $this->_CMYKtoRGBconversion = get_option('wp-short-pixel_cmyk2rgb');
107
- } else {
108
- add_option('wp-short-pixel_cmyk2rgb', $this->_CMYKtoRGBconversion, '', 'yes' );
109
- }
110
-
111
- if(get_option('wp-short-backup_images') !== false) {
112
- $this->_backupImages = get_option('wp-short-backup_images');
113
- } else {
114
- add_option('wp-short-backup_images', $this->_backupImages, '', 'yes' );
115
- }
116
-
117
- if(get_option('wp-short-pixel-fileCount') === false) {
118
- add_option( 'wp-short-pixel-fileCount', 0, '', 'yes' );
119
- }
120
-
121
- if(get_option('wp-short-pixel-savedSpace') === false) {
122
- add_option( 'wp-short-pixel-savedSpace', 0, '', 'yes' );
123
- }
124
-
125
- if(get_option('wp-short-pixel-averageCompression') === false) {
126
- add_option( 'wp-short-pixel-averageCompression', 0, '', 'yes' );
127
- }
128
-
129
- if(get_option('wp-short-pixel-api-retries') === false) {//sometimes we need to retry processing/downloading a file multiple times
130
- add_option( 'wp-short-pixel-api-retries', 0, '', 'yes' );
131
- }
132
-
133
- if(get_option('wp-short-pixel-query-id-start') === false) {//current query ID used for postmeta queries
134
- add_option( 'wp-short-pixel-query-id-start', 0, '', 'yes' );
135
- }
136
-
137
- if(get_option('wp-short-pixel-query-id-stop') === false) {//min ID used for postmeta queries
138
- add_option( 'wp-short-pixel-query-id-stop', 0, '', 'yes' );
139
- }
140
-
141
- if(get_option('wp-short-pixel-quota-exceeded') === false) {//min ID used for postmeta queries
142
- add_option( 'wp-short-pixel-quota-exceeded', 0, '', 'yes' );
143
- }
144
-
145
- if(get_option('wp-short-pixel-protocol') === false) {//min ID used for postmeta queries
146
- add_option( 'wp-short-pixel-protocol', 'https', '', 'yes' );
147
- }
148
-
149
- }
150
-
151
- public function shortPixelActivatePlugin()//reset some params to avoid troubles for plugins that were activated/deactivated/activated
152
- {
153
- global $startQueryID,$endQueryID;
154
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
155
- delete_option('bulkProcessingStatus');
156
- delete_option( 'wp-short-pixel-cancel-pointer');
157
- update_option( 'wp-short-pixel-query-id-stop', $endQueryID );
158
- update_option( 'wp-short-pixel-query-id-start', $startQueryID );
159
- }
160
-
161
- public function shortPixelDeactivatePlugin()//reset some params to avoid troubles for plugins that were activated/deactivated/activated
162
- {
163
- delete_option('bulkProcessingStatus');
164
- delete_option( 'wp-short-pixel-cancel-pointer');
165
- update_option( 'wp-short-pixel-query-id-stop', 0 );
166
- update_option( 'wp-short-pixel-query-id-start', 0 );
167
- }
168
-
169
- //set default move as "list". only set once, it won't try to set the default mode again.
170
- public function setDefaultViewModeList()
171
- {
172
- if(get_option('wp-short-pixel-view-mode') === false)
173
- {
174
- add_option('wp-short-pixel-view-mode', 1, '', 'yes' );
175
- if ( function_exists('get_currentuserinfo') )
176
- {
177
- global $current_user;
178
- get_currentuserinfo();
179
- $currentUserID = $current_user->ID;
180
- update_user_meta($currentUserID, "wp_media_library_mode", "list");
181
- }
182
- }
183
-
184
- }
185
-
186
-
187
- static function log($message) {
188
- if(SP_DEBUG) {
189
- echo "{$message}</br>";
190
- }
191
- }
192
-
193
- function my_action_javascript() { ?>
194
- <script type="text/javascript" >
195
- jQuery(document).ready(sendRequest());
196
- function sendRequest() {
197
- var data = { 'action': 'my_action' };
198
- // since WP 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
199
- jQuery.post(ajaxurl, data, function(response) {
200
- if(response.search('Empty queue') >= 0 || response.search('Error processing image') >= 0 || response.search('Missing API Key') >= 0) {
201
- console.log('Queue is empty');
202
- } else {
203
- console.log('Server response: ' + response);
204
- sendRequest();
205
- }
206
- });
207
- }
208
- </script> <?php
209
- }
210
-
211
- function wp_load_admin_js() {
212
- add_action('admin_print_footer_scripts', array(&$this, 'add_bulk_actions_via_javascript'));
213
- }
214
-
215
- function add_bulk_actions_via_javascript() {
216
- ?>
217
- <script type="text/javascript">
218
- jQuery(document).ready(function($){
219
- $('select[name^="action"] option:last-child').before('<option value="2">Bulk Optimize</option>');
220
- });
221
- </script>
222
- <?php }
223
-
224
- //handling older
225
- public function WPShortPixel() {
226
- $this->__construct();
227
- }
228
-
229
- public function handleImageUpload($meta, $ID = null) {
230
-
231
- if(MUST_HAVE_KEY && $this->_verifiedKey)
232
- {
233
- $bulkProcessingStatus = get_option('bulkProcessingStatus');
234
- //update variable to keep track of this new attachment but only if bulk isn't running
235
- if( $bulkProcessingStatus <> 'running' )
236
- update_option("wp-short-pixel-query-id-start", $ID);
237
-
238
- self::log("Processing image id {$ID}");
239
-
240
- //turn relative paths to absolute
241
- if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) )
242
- {//no absolute URLs used -> we implement a hack
243
- $url = get_site_url() . wp_get_attachment_url($ID);//get the file URL
244
- }
245
- else
246
- $url = wp_get_attachment_url($ID);//get the file URL
247
-
248
-
249
- $path = get_attached_file($ID);
250
- if(self::isProcessable($path) != false)
251
- {
252
- if ( empty($meta) && $bulkProcessingStatus <> 'running' )//here's a PDF file most likely, while bulk not running
253
- {
254
- $meta['ShortPixel']['WaitingProcessing'] = true;
255
- return $meta;
256
- }
257
- elseif ( empty($meta) && $bulkProcessingStatus == 'running' )//while bulk running
258
- {
259
- return $meta;
260
- }
261
-
262
-
263
- $urlList[] = $url;
264
- $filePath[] = $path;
265
- //send request for thumbs as well, if needed
266
- if( !empty($meta['sizes']) )
267
- {
268
- foreach($meta['sizes'] as $thumbnailInfo)
269
- {
270
- $urlList[] = str_replace(basename($url), $thumbnailInfo['file'], $url);
271
- $filePath[] = str_replace(basename($path), $thumbnailInfo['file'], $path);
272
- }
273
- $this->_apiInterface->doRequests($urlList, $filePath);//send a processing request right after a file was uploaded
274
- }
275
- else//file is PDF maybe?
276
- {
277
- $uploadFilePath = get_attached_file($ID);
278
- if ( strtolower(substr($uploadFilePath,strrpos($uploadFilePath, ".")+1)) == "pdf" ) //is a PDF file
279
- {
280
- $filePath[0] = $uploadFilePath;
281
- $this->_apiInterface->doRequests($urlList, $filePath);//send a processing request right after a file was uploaded
282
- }
283
- }
284
-
285
- if ( $bulkProcessingStatus <> 'running' )
286
- $meta['ShortPixel']['WaitingProcessing'] = true;
287
- return $meta;
288
- }
289
- else
290
- {
291
- $meta['ShortPixelImprovement'] = 'Optimization N/A';
292
- return $meta;
293
- }
294
- }
295
- }
296
-
297
- public function handleImageProcessing($ID = null) {
298
- if(MUST_HAVE_KEY && $this->_verifiedKey == false) {
299
- echo "Missing API Key";
300
- die();
301
- }
302
- //query database for first found entry that needs processing //
303
- global $wpdb,$startQueryID,$endQueryID;
304
-
305
- //////////////////
306
-
307
- $startQueryID = get_option('wp-short-pixel-query-id-start');
308
- $endQueryID = get_option('wp-short-pixel-query-id-stop');
309
-
310
- if ( $startQueryID <= $endQueryID )
311
- {
312
- delete_option('bulkProcessingStatus');
313
- echo 'Empty queue ' . $startQueryID . '->' . $endQueryID;
314
- die;
315
- }
316
-
317
- sleep(1);
318
- $queryPostMeta = "SELECT * FROM " . $wpdb->prefix . "postmeta
319
- WHERE ( post_id <= $startQueryID AND post_id > $endQueryID ) AND (
320
- meta_key = '_wp_attached_file'
321
- OR meta_key = '_wp_attachment_metadata'
322
- )
323
- ORDER BY post_id DESC
324
- LIMIT " . SP_MAX_RESULTS_QUERY;
325
-
326
- $resultsPostMeta = $wpdb->get_results($queryPostMeta);
327
-
328
- if ( empty($resultsPostMeta) )//no results
329
- {
330
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
331
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max and min ID
332
- update_option("wp-short-pixel-query-id-stop", $endQueryID);
333
- echo 'Empty results ' . $startQueryID . '->' . $endQueryID;
334
- die;
335
- }
336
-
337
- $idList = array();
338
- $countMeta = 0;
339
- foreach ( $resultsPostMeta as $itemMetaData )
340
- {
341
- if ( $countMeta == 0 )
342
- {
343
- $metaCurrentFile = wp_get_attachment_metadata($itemMetaData->post_id);
344
- $meta = $metaCurrentFile;
345
- }
346
- else
347
- $meta = wp_get_attachment_metadata($itemMetaData->post_id);
348
-
349
- $meta['ShortPixelImprovement'] = ( isset($meta['ShortPixelImprovement']) ) ? $meta['ShortPixelImprovement'] : "";
350
- $filePath = get_attached_file($itemMetaData->post_id);
351
- $fileExtension = strtolower(substr($filePath,strrpos($filePath,".")+1));
352
-
353
- if ( ( $itemMetaData->meta_key == "_wp_attachment_metadata" && $meta['ShortPixelImprovement'] <> "Optimization N/A" && !is_numeric($meta['ShortPixelImprovement']) ) || $fileExtension == "pdf" )//Optimization N/A = is an unsupported file format
354
- {
355
- $idList[] = $itemMetaData;
356
- }
357
-
358
- $countMeta++;
359
- }
360
-
361
-
362
- if ( isset($idList[0]) )
363
- {
364
- $meta = $metaCurrentFile; //assign the saved meta of the file position [0]
365
- $meta = wp_get_attachment_metadata($idList[0]->post_id);
366
- $filePath = get_attached_file($idList[0]->post_id);
367
- $fileExtension = strtolower(substr($filePath,strrpos($filePath,".")+1));
368
-
369
- if ( !self::isProcessable($filePath) )//file has a non-supported extension, we skip it
370
- {
371
- $startQueryID = $idList[0]->post_id - 1;
372
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
373
- die();
374
- }
375
- elseif( !empty($meta) && !isset($meta['ShortPixel']['WaitingProcessing']) ) //possibly the file wasn't processed in the first pass so we'll wait for it to be completed
376
- {
377
- //if ( isset($idList[1]) )
378
- $startQueryID = ( $idList[0]->post_id );
379
- //else
380
- // $startQueryID = ( $idList[0]->post_id - 1 );
381
-
382
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
383
- }
384
- elseif ( empty($meta) && $fileExtension <> "pdf" )//file is not an image or PDF so we just skip to the next batch
385
- {
386
- $startQueryID = $startQueryID - 1; //SP_MAX_RESULTS_QUERY;
387
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
388
- die();
389
- }
390
- else //file was NOT processed in the first pass
391
- {
392
-
393
- $startQueryID = $idList[0]->post_id;
394
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
395
- }
396
- }
397
- elseif ( $startQueryID > $endQueryID )
398
- {
399
- if ( isset($resultsPostMeta[2]) && is_numeric($resultsPostMeta[2]->post_id) )
400
- $startQueryID = $resultsPostMeta[2]->post_id;
401
- else
402
- $startQueryID = $startQueryID - 1;
403
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
404
- die();
405
- }
406
-
407
-
408
- //////////////////////
409
-
410
- if( empty($idList) && $startQueryID <= $endQueryID ) { //die but before set the $endQueryID so only new files will be processed
411
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
412
- update_option('wp-short-pixel-query-id-stop', $endQueryID);
413
- delete_option('bulkProcessingStatus');
414
- echo 'Empty queue - '.$endQueryID; die;
415
- }
416
-
417
- //send a couple of pre-process requests (if available/needed)
418
- if ( isset($idList[1]) )
419
- {
420
- $itemDetails = $this->returnURLsAndPaths($idList[1]);
421
- $this->_apiInterface->doRequests($itemDetails['imageURLs'], $itemDetails['imagePaths']);
422
- }
423
-
424
- //send a couple of pre-process requests (if available/needed)
425
- if ( isset($idList[2]) )
426
- {
427
- $itemDetails = $this->returnURLsAndPaths($idList[1]);
428
- $this->_apiInterface->doRequests($itemDetails['imageURLs'], $itemDetails['imagePaths']);
429
- }
430
-
431
-
432
- //send a request for the latest item
433
- $itemDetails = $this->returnURLsAndPaths($idList[0]);
434
- $meta = $itemDetails['meta'];
435
- $ID = $itemDetails['ID'];
436
- $result = $this->_apiInterface->processImage($itemDetails['imageURLs'], $itemDetails['imagePaths'], $ID);//use the API connection to send processing requests for these files.
437
-
438
- if(is_string($result)) {//there was an error?
439
- if(isset($meta['ShortPixel']['BulkProcessing'])) { unset($meta['ShortPixel']['BulkProcessing']); }
440
- if(isset($meta['ShortPixel']['WaitingProcessing'])) { unset($meta['ShortPixel']['WaitingProcessing']); }
441
- $meta['ShortPixelImprovement'] = $result;
442
- wp_update_attachment_metadata($ID, $meta);
443
- echo "Error processing image: " . $result;
444
- //also decrement last ID for queries so bulk won't hang in such cases
445
- $startQueryID = get_option("wp-short-pixel-query-id-start");
446
- update_option("wp-short-pixel-query-id-start", $startQueryID - 1);
447
- die;
448
- }
449
-
450
- //$processThumbnails = get_option('wp-short-process_thumbnails');
451
- if ( isset($meta['ShortPixel']) )
452
- {
453
- if ( isset($meta['ShortPixel']['WaitingProcessing']) )
454
- unset($meta['ShortPixel']['WaitingProcessing']);
455
-
456
- if( isset($meta['ShortPixel']['BulkProcessing']) )
457
- unset($meta['ShortPixel']['BulkProcessing']);
458
- }
459
-
460
- $meta['ShortPixelImprovement'] = round($result[0]->PercentImprovement,2);
461
- wp_update_attachment_metadata($ID, $meta);
462
- echo "\nProcessing done succesfully for image #{$ID}";
463
-
464
- //set the next ID to be processed (skip to the next valid value)
465
- if ( isset($isList[1]) )
466
- {
467
- $startQueryID = $idList[1]->post_id;
468
- update_option("wp-short-pixel-query-id-start", $startQueryID);//update max ID
469
- }
470
-
471
- die();
472
- }
473
-
474
-
475
- //return urls and paths to be used but other functions
476
- public function returnURLsAndPaths($itemDetails)
477
- {
478
- global $wpdb;
479
-
480
- $imageIndex=0;
481
- $ID = $itemDetails->post_id;
482
- //turn relative paths to absolute
483
- if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) )
484
- {//no absolute URLs used -> we implement a hack
485
- $imageURL = get_site_url() . wp_get_attachment_url($ID);//get the file URL
486
- }
487
- else
488
- $imageURL = wp_get_attachment_url($ID);//get the file URL
489
-
490
- $imagePath = get_attached_file($ID);
491
- $meta = wp_get_attachment_metadata($ID);
492
- $uploadDir = wp_upload_dir();
493
-
494
- //generate absolute URL if needed
495
- if ( !parse_url($uploadDir['baseurl'], PHP_URL_SCHEME) )
496
- $uploadDir['baseurl'] = get_site_url() . $uploadDir['baseurl'];
497
-
498
-
499
- if ( empty($meta['file']) )//file has no metadata attached (like PDF files uploaded before SP plugin)
500
- {
501
- $SubDir = $this->_apiInterface->returnSubDir($imagePath);
502
- $SubDirURL = $this->_apiInterface->returnSubDirURL($imagePath);
503
- }
504
- else
505
- {
506
- $SubDir = $this->_apiInterface->returnSubDir($meta['file']);
507
- $SubDirURL = $this->_apiInterface->returnSubDirURL($imagePath);
508
- }
509
-
510
- $imageURLs[] = $uploadDir['baseurl'] . DIRECTORY_SEPARATOR . $SubDirURL . basename($imagePath);//URL to PDF file
511
- $imagePaths[] = $uploadDir['basedir'] . DIRECTORY_SEPARATOR . $SubDir . basename($imagePath);
512
-
513
- $processThumbnails = get_option('wp-short-process_thumbnails');
514
-
515
- if ( isset($meta['file']) && $processThumbnails )//handle the rest of the thumbnails generated by WP
516
- {
517
- $imageIndex = 0;
518
- foreach ( $meta['sizes'] as $pictureDetails )
519
- {
520
- $imageIndex++;
521
- $imageURLs[$imageIndex] = $uploadDir['baseurl'] . DIRECTORY_SEPARATOR . $SubDir . basename($pictureDetails['file']);
522
- $imagePaths[$imageIndex] = $uploadDir['basedir'] . DIRECTORY_SEPARATOR . $SubDir . basename($pictureDetails['file']);
523
- }
524
- }
525
-
526
- if ( isset($imageURLs) )
527
- return array("imageURLs" => $imageURLs, "imagePaths" => $imagePaths, "meta" => $meta, "ID" => $ID);
528
- else
529
- return false;
530
-
531
- }//end returnURLsAndPaths
532
-
533
-
534
- public function handleManualOptimization() {
535
- $attachmentID = intval($_GET['attachment_ID']);
536
-
537
- //turn relative paths to absolute when needed
538
- if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) )
539
- {//no absolute URLs used -> we implement a hack
540
- $urlList[] = get_site_url() . wp_get_attachment_url($attachmentID);//get the file URL
541
- }
542
- else
543
- $urlList[] = wp_get_attachment_url($attachmentID);//get the file URL
544
-
545
- $filePath[] = get_attached_file($attachmentID);
546
- $meta = wp_get_attachment_metadata($attachmentID);
547
-
548
- $processThumbnails = get_option('wp-short-process_thumbnails');
549
-
550
- //process all files (including thumbs)
551
- if($processThumbnails && !empty($meta['sizes'])) {
552
- //we generate an array with the URLs that need to be handled
553
- $SubDir = $this->_apiInterface->returnSubDir($meta['file']);
554
- foreach($meta['sizes'] as $thumbnailInfo)
555
- {
556
- $urlList[]= str_replace(basename($filePath[0]), $thumbnailInfo['file'], $urlList[0]);
557
- $filePath[] = str_replace(basename($filePath[0]), $thumbnailInfo['file'], $filePath[0]);
558
- }
559
- }
560
-
561
- $result = $this->_apiInterface->processImage($urlList, $filePath, $attachmentID);//request to process all the images
562
-
563
- if ( !is_array($result) )//there was an error, we save it in ShortPixelImprovement data
564
- $this->handleError($attachmentID, $result);
565
-
566
- // store the referring webpage location
567
- $sendback = wp_get_referer();
568
- // sanitize the referring webpage location
569
- $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
570
- // send the user back where they came from
571
- wp_redirect($sendback);
572
- // we are done,
573
- }
574
-
575
- //save error in file's meta data
576
- public function handleError($ID, $result)
577
- {
578
- $meta = wp_get_attachment_metadata($ID);
579
- $meta['ShortPixelImprovement'] = $result;
580
- wp_update_attachment_metadata($ID, $meta);
581
- }
582
-
583
- public function handleRestoreBackup() {
584
- $attachmentID = intval($_GET['attachment_ID']);
585
-
586
- $file = get_attached_file($attachmentID);
587
- $meta = wp_get_attachment_metadata($attachmentID);
588
- $uploadDir = wp_upload_dir();
589
- $pathInfo = pathinfo($file);
590
-
591
- $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
592
- $SubDir = $this->_apiInterface->returnSubDir($file);
593
-
594
- //sometimes the month of original file and backup can differ
595
- if ( !file_exists(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . basename($file)) )
596
- $SubDir = date("Y") . "/" . date("m") . "/";
597
-
598
- try {
599
- //main file
600
- @rename(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . basename($file), $file);
601
-
602
- //overwriting thumbnails
603
- if( !empty($meta['file']) ) {
604
- foreach($meta["sizes"] as $size => $imageData) {
605
- $source = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . $imageData['file'];
606
- $destination = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
607
- @rename($source, $destination);
608
- }
609
- }
610
- unset($meta["ShortPixelImprovement"]);
611
- wp_update_attachment_metadata($attachmentID, $meta);
612
-
613
- } catch(Exception $e) {
614
- //what to do, what to do?
615
- }
616
- // store the referring webpage location
617
- $sendback = wp_get_referer();
618
- // sanitize the referring webpage location
619
- $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
620
- // send the user back where they came from
621
- wp_redirect($sendback);
622
- // we are done
623
- }
624
-
625
-
626
- public function handleDeleteAttachmentInBackup($ID) {
627
- $file = get_attached_file($ID);
628
- $meta = wp_get_attachment_metadata($ID);
629
- if(self::isProcessable($file) != false) {
630
- try {
631
- $uploadDir = wp_upload_dir();
632
- $SubDir = $this->_apiInterface->returnSubDir($file);
633
-
634
- @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . basename($file));
635
-
636
- if ( !empty($meta['file']) )
637
- {
638
- $filesPath = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir;//base BACKUP path
639
- //remove thumbs thumbnails
640
- if(isset($meta["sizes"])) {
641
- foreach($meta["sizes"] as $size => $imageData) {
642
- @unlink($filesPath . basename($imageData['file']));//remove thumbs
643
- }
644
- }
645
- }
646
-
647
- } catch(Exception $e) {
648
- //what to do, what to do?
649
- }
650
- }
651
- }
652
-
653
- public function registerSettingsPage() {
654
- add_options_page( 'ShortPixel Settings', 'ShortPixel', 'manage_options', 'wp-shortpixel', array($this, 'renderSettingsMenu'));
655
- }
656
-
657
- function registerAdminPage( ) {
658
- add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcess' ) );
659
- }
660
-
661
- public function bulkProcess() {
662
- global $wpdb,$startQueryID,$endQueryID;
663
-
664
- echo '<h1>Bulk Image Optimization by ShortPixel</h1>';
665
-
666
- if(MUST_HAVE_KEY && $this->_verifiedKey == false) {//invalid API Key
667
- echo "<p>In order to start processing your images, you need to validate your API key in the ShortPixel Settings. If you don’t have an API Key, you can get one delivered to your inbox.</p>";
668
- echo "<p>Don’t have an API Key yet? Get it now at <a href=\"https://shortpixel.com/wp-apikey\" target=\"_blank\">www.ShortPixel.com</a>, for free.</p>";
669
- return;
670
- }
671
-
672
- if(isset($_GET['cancel']))
673
- {//cancel an ongoing bulk processing, it might be needed sometimes
674
- $this->cancelProcessing();
675
- }
676
-
677
- if(isset($_POST["bulkProcess"]))
678
- {
679
- //set the thumbnails option
680
- if ( isset($_POST['thumbnails']) )
681
- update_option('wp-short-process_thumbnails', 1);
682
- else
683
- update_option('wp-short-process_thumbnails', 0);
684
-
685
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
686
- update_option("wp-short-pixel-query-id-start", $startQueryID);//start downwards from the biggest item ID
687
- update_option("wp-short-pixel-query-id-stop", 0);
688
- update_option("wp-short-pixel-flag-id", $startQueryID);//we use to detect new added files while bulk is running
689
- add_option('bulkProcessingStatus', 'running');//set bulk flag
690
- }//end bulk process was clicked
691
-
692
- if(isset($_POST["bulkProcessResume"]))
693
- {
694
- $startQueryID = get_option( 'wp-short-pixel-cancel-pointer');
695
- update_option("wp-short-pixel-query-id-start", $startQueryID);//start downwards from the biggest item ID
696
- update_option("wp-short-pixel-query-id-stop", 0);
697
- update_option("wp-short-pixel-flag-id", $startQueryID);//we use to detect new added files while bulk is running
698
- add_option('bulkProcessingStatus', 'running');//set bulk flag
699
- delete_option( 'wp-short-pixel-cancel-pointer');
700
- }//resume was clicked
701
-
702
- $bulkProcessingStatus = get_option('bulkProcessingStatus');
703
- $startQueryID = get_option('wp-short-pixel-query-id-start');
704
- $endQueryID = get_option('wp-short-pixel-query-id-stop');
705
-
706
- //figure out all the files that could be processed
707
- $qry = "SELECT count(*) FilesToBeProcessed FROM " . $wpdb->prefix . "postmeta
708
- WHERE meta_key = '_wp_attached_file' ";
709
- $allFiles = $wpdb->get_results($qry);
710
- //figure out the files that are left to be processed
711
- $qry_left = "SELECT count(*) FilesLeftToBeProcessed FROM " . $wpdb->prefix . "postmeta
712
- WHERE meta_key = '_wp_attached_file' AND post_id <= $startQueryID";
713
- $filesLeft = $wpdb->get_results($qry_left);
714
-
715
- if ( get_option('wp-short-pixel-quota-exceeded') )//quota exceeded, let the user know
716
- {
717
- $noticeHTML = "<br/><div style=\"background-color: #fff; border-left: 4px solid %s; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;\"><p>%s</p></div>";
718
- $quotaData = $this->getQuotaInformation();
719
-
720
- //maybe in the meantime the user added credits to their account?
721
-
722
- if ( $quotaData['APICallsQuotaNumeric'] > $quotaData['APICallsMadeNumeric'] )
723
- {
724
- update_option('wp-short-pixel-quota-exceeded','0');
725
- }
726
- else
727
- {
728
- printf($noticeHTML, '#ff0000', "<h3>Quota Exceeded</h3>
729
- <p>
730
- The plugin has optimized " . number_format($quotaData['APICallsMadeNumeric']) . " images and stopped because it reached the monthly limit.</p>
731
- <p>
732
- It’s simple to upgrade, just <a href='https://shortpixel.com/login/".$this->_apiKey."' target='_blank'>log into your account</a> and see the options in the billing page.</p>
733
- <p>
734
- You can immediately start processing 5,000 images/month for &#36;4,99 or <a href='https://shortpixel.com/contact' target='_blank'>contact us</a> for larger compression needs.
735
- </p>" );
736
-
737
- return;
738
-
739
- }
740
- }
741
-
742
-
743
- if( $filesLeft[0]->FilesLeftToBeProcessed > 0 && ( $startQueryID <> $endQueryID ) && $bulkProcessingStatus == "running" )//bulk processing was started and is still running
744
- {
745
-
746
- echo "<p>
747
- Bulk optimization has started. This process will take some time, depending on the number of images in your library. <BR>Do not worry about the slow speed, it is a necessary measure in order not to interfere with the normal functioning of your site.<BR><BR>
748
- This is a brief estimation of the bulk processing times:<BR>
749
- 1 to 100 images < 20 min <BR>
750
- 100 to 500 images < 2 hour<BR>
751
- 500 to 1000 images < 4 hours<BR>
752
- over 1000 images > 4 hours or more<BR><BR>
753
-
754
- The latest status of the processing will be displayed here every 30 seconds.<BR>
755
- In the meantime, you can continue using the admin as usual.<BR>
756
- However, <b>you musn’t close the WordPress admin</b>, or the bulk processing will stop.
757
- </p>";
758
- echo '
759
- <script type="text/javascript" >
760
- var bulkProcessingRunning = true;
761
- </script>
762
- ';
763
-
764
- $imagesLeft = $filesLeft[0]->FilesLeftToBeProcessed;
765
- $totalImages = $allFiles[0]->FilesToBeProcessed;
766
-
767
- echo "<p>{$imagesLeft} out of {$totalImages} images left to process.</p>";
768
-
769
- echo '
770
- <a class="button button-secondary" href="' . get_admin_url() . 'upload.php">Media Library</a>
771
- <a class="button button-secondary" href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk&cancel=1">Pause Processing</a>
772
- ';
773
-
774
- } else
775
- {
776
- $bulkProcessingStatus = get_option('bulkProcessingStatus');
777
- if(isset($bulkProcessingStatus) && $bulkProcessingStatus == 'running')
778
- {
779
- echo "<p>Bulk optimization was successful. ShortPixel has finished optimizing all your images.</p>
780
- <p>Go to the ShortPixel <a href='" . get_admin_url() . "options-general.php?page=wp-shortpixel#facts'>Stats</a> and see your website's optimized stats (in Settings > ShortPixel). </p>";
781
-
782
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
783
- $maxIDbeforeBulk = get_option("wp-short-pixel-flag-id");//what was the max id before bulk was started?
784
-
785
- if ( $startQueryID > $maxIDbeforeBulk )//basically we resume the processing for the files uploaded while bulk was running
786
- {
787
- update_option("wp-short-pixel-query-id-start", $startQueryID);
788
- update_option("wp-short-pixel-query-id-stop", $maxIDbeforeBulk);
789
- delete_option('bulkProcessingStatus');
790
- }
791
- else
792
- {
793
- update_option("wp-short-pixel-query-id-start", $startQueryID);
794
- update_option("wp-short-pixel-query-id-stop", $endQueryID);
795
- delete_option('bulkProcessingStatus');
796
- }
797
- }
798
-
799
- delete_option('bulkProcessingStatus');
800
- echo $this->getBulkProcessingForm();
801
- echo '
802
- <script type="text/javascript" >
803
- var bulkProcessingRunning = false;
804
- </script>
805
- ';
806
- }
807
-
808
- echo '
809
- <script type="text/javascript" >
810
- jQuery(document).ready(function() {
811
- if(bulkProcessingRunning) {
812
- console.log("Bulk processing running");
813
- setTimeout(function(){
814
- window.location = window.location.href;
815
- }, 30000);
816
- } else {
817
- console.log("No bulk processing is currently running");
818
- }
819
- });
820
- </script>
821
- ';
822
- }
823
- //end bulk processing
824
-
825
-
826
- public function cancelProcessing(){
827
- //cancel an ongoing bulk processing, it might be needed sometimes
828
- global $wpdb,$startQueryID,$endQueryID;
829
- $startQueryID = get_option('wp-short-pixel-query-id-start');
830
- add_option( 'wp-short-pixel-cancel-pointer', $startQueryID);//we save this so we can resume bulk processing
831
-
832
- $this->getMaxShortPixelId();//fetch data for endQueryID and startQueryID
833
- update_option("wp-short-pixel-query-id-start", $startQueryID);
834
- update_option("wp-short-pixel-query-id-stop", $endQueryID);
835
- delete_option('bulkProcessingStatus');
836
- echo "Empty queue";
837
- }
838
-
839
- public function renderSettingsMenu() {
840
- if ( !current_user_can( 'manage_options' ) ) {
841
- wp_die('You do not have sufficient permissions to access this page.');
842
- }
843
- echo '<h1>ShortPixel Plugin Settings</h1>';
844
- echo '<p>
845
- <a href="https://shortpixel.com" target="_blank">ShortPixel.com</a> |
846
- <a href="https://wordpress.org/plugins/shortpixel-image-optimiser/installation/" target="_blank">Installation </a> |
847
- <a href="https://shortpixel.com/contact" target="_blank">Support </a>
848
- </p>';
849
- echo '<p>New images uploaded to the Media Library will be optimized automatically.<br/>If you have existing images you would like to optimize, you can use the <a href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk">Bulk Optimization Tool</a>.</p>';
850
-
851
- $noticeHTML = "<br/><div style=\"background-color: #fff; border-left: 4px solid %s; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;\"><p>%s</p></div>";
852
-
853
- //by default we try to fetch the API Key from wp-config.php (if defined)
854
- if ( !isset($_POST['submit']) && !get_option('wp-short-pixel-verifiedKey') && strlen(SHORTPIXEL_API_KEY) == 20 )
855
- {
856
- $_POST['validate'] = "validate";
857
- $_POST['key'] = SHORTPIXEL_API_KEY;
858
- }
859
-
860
- if(isset($_POST['submit']) || isset($_POST['validate'])) {
861
-
862
- //handle API Key - common for submit and validate
863
- $_POST['key'] = trim(str_replace("*","",$_POST['key']));
864
-
865
- if ( strlen($_POST['key']) <> 20 )
866
- {
867
- $KeyLength = strlen($_POST['key']);
868
-
869
- printf($noticeHTML, '#ff0000', "The key you provided has " . $KeyLength . " characters. The API key should have 20 characters, letters and numbers only.<BR> <b>Please check that the API key is the same as the one you received in your confirmation email.</b><BR>
870
- If this problem persists, please contact us at <a href='mailto:support@shortpixel.com?Subject=API Key issues' target='_top'>support@shortpixel.com</a> or <a href='https://shortpixel.com/contact' target='_blank'>here</a>.");
871
- }
872
- else
873
- {
874
- $validityData = $this->getQuotaInformation($_POST['key'], true);
875
-
876
- $this->_apiKey = $_POST['key'];
877
- $this->_apiInterface->setApiKey($this->_apiKey);
878
- update_option('wp-short-pixel-apiKey', $_POST['key']);
879
- if($validityData['APIKeyValid']) {
880
- if(isset($_POST['validate'])) {
881
- //display notification
882
- if(in_array($_SERVER["SERVER_ADDR"], array("127.0.0.1","::1"))) {
883
- printf($noticeHTML, '#FFC800', "API Key is valid but your server seems to have a local address.
884
- Please make sure that your server is accessible from the Internet before using the API or otherwise we won't be able to optimize them.");
885
- } else {
886
- printf($noticeHTML, '#7ad03a', 'API Key valid!');
887
- }
888
- }
889
- update_option('wp-short-pixel-verifiedKey', true);
890
- $this->_verifiedKey = true;
891
- //test that the "uploads" have the right rights and also we can create the backup dir for ShortPixel
892
- if ( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) )
893
- printf($noticeHTML, '#ff0000', "There is something preventing us to create a new folder for backing up your original files.<BR>
894
- Please make sure that folder <b>" .
895
- WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads</b> has the necessary write and read rights." );
896
- } else {
897
- if(isset($_POST['validate'])) {
898
- //display notification
899
- printf($noticeHTML, '#ff0000', $validityData["Message"]);
900
- }
901
- update_option('wp-short-pixel-verifiedKey', false);
902
- $this->_verifiedKey = false;
903
- }
904
- }
905
-
906
-
907
- //if save button - we process the rest of the form elements
908
- if(isset($_POST['submit'])) {
909
- update_option('wp-short-pixel-compression', $_POST['compressionType']);
910
- $this->_compressionType = $_POST['compressionType'];
911
- $this->_apiInterface->setCompressionType($this->_compressionType);
912
- if(isset($_POST['thumbnails'])) { $this->_processThumbnails = 1; } else { $this->_processThumbnails = 0; }
913
- if(isset($_POST['backupImages'])) { $this->_backupImages = 1; } else { $this->_backupImages = 0; }
914
- if(isset($_POST['cmyk2rgb'])) { $this->_CMYKtoRGBconversion = 1; } else { $this->_CMYKtoRGBconversion = 0; }
915
- update_option('wp-short-process_thumbnails', $this->_processThumbnails);
916
- update_option('wp-short-backup_images', $this->_backupImages);
917
- update_option('wp-short-pixel_cmyk2rgb', $this->_CMYKtoRGBconversion);
918
- }
919
- }
920
-
921
-
922
- //empty backup
923
- if(isset($_POST['emptyBackup'])) {
924
- if(file_exists(SP_BACKUP_FOLDER)) {
925
-
926
- //extract all images from DB in an array. of course
927
- $attachments = null;
928
- $attachments = get_posts( array(
929
- 'numberposts' => -1,
930
- 'post_type' => 'attachment',
931
- 'post_mime_type' => 'image'
932
- ));
933
-
934
-
935
- //parse all images and set the right flag that the image has no backup
936
- foreach($attachments as $attachment)
937
- {
938
- if(self::isProcessable(get_attached_file($attachment->ID)) == false) continue;
939
-
940
- $meta = wp_get_attachment_metadata($attachment->ID);
941
- $meta['ShortPixel']['NoBackup'] = true;
942
- wp_update_attachment_metadata($attachment->ID, $meta);
943
- }
944
-
945
- //delete the actual files on disk
946
- $this->deleteDir(SP_BACKUP_FOLDER);//call a recursive function to empty files and sub-dirs in backup dir
947
- }
948
- }
949
-
950
- $checked = '';
951
- if($this->_processThumbnails) { $checked = 'checked'; }
952
-
953
- $checkedBackupImages = '';
954
- if($this->_backupImages) { $checkedBackupImages = 'checked'; }
955
-
956
- $cmyk2rgb = '';
957
- if($this->_CMYKtoRGBconversion) { $cmyk2rgb = 'checked'; }
958
-
959
-
960
- $formHTML = <<< HTML
961
- <form name='wp_shortpixel_options' action='' method='post' id='wp_shortpixel_options'>
962
- <table class="form-table">
963
- <tbody><tr>
964
- <th scope="row"><label for="key">API Key:</label></th>
965
- <td><input name="key" type="text" id="key" value="{$this->_apiKey}" class="regular-text">
966
- <input type="submit" name="validate" id="validate" class="button button-primary" title="Validate the provided API key" value="Validate">
967
- </td>
968
- </tr>
969
- HTML;
970
-
971
- if(!$this->_verifiedKey) {
972
-
973
- //if invalid key we display the link to the API Key
974
- $formHTML .= '<tr><td style="padding-left: 0px;" colspan="2">Don’t have an API Key? <a href="https://shortpixel.com/wp-apikey" target="_blank">Sign up, it’s free.</a></td></tr>';
975
- $formHTML .= '</form>';
976
- } else {
977
- //if valid key we display the rest of the options
978
- $formHTML .= <<< HTML
979
- <tr><th scope="row">
980
- <label for="compressionType">Compression type:</label>
981
- </th><td>
982
- HTML;
983
-
984
- if($this->_compressionType == 1) {
985
- $formHTML .= '<input type="radio" name="compressionType" value="1" checked>Lossy</br></br>';
986
- $formHTML .= '<input type="radio" name="compressionType" value="0" >Lossless';
987
- } else {
988
- $formHTML .= '<input type="radio" name="compressionType" value="1">Lossy</br></br>';
989
- $formHTML .= '<input type="radio" name="compressionType" value="0" checked>Lossless';
990
- }
991
-
992
- $formHTML .= <<<HTML
993
- </td>
994
- </tr>
995
- </tbody></table>
996
- <p style="color: #818181;">
997
- <b>Lossy compression: </b>lossy has a better compression rate than lossless compression.</br>The resulting image
998
- is not 100% identical with the original. Works well for photos taken with your camera.</br></br>
999
- <b>Lossless compression: </b> the shrunk image will be identical with the original and smaller in size.</br>Use this
1000
- when you do not want to lose any of the original image's details. Works best for technical drawings,
1001
- clip art and comics.
1002
- </p>
1003
- <table class="form-table">
1004
- <tbody><tr>
1005
- <th scope="row"><label for="thumbnails">Image thumbnails:</label></th>
1006
- <td><input name="thumbnails" type="checkbox" id="thumbnails" {$checked}> Apply compression also to image thumbnails.</td>
1007
- </tr>
1008
- <tr>
1009
- <th scope="row"><label for="backupImages">Image backup</label></th>
1010
- <td>
1011
- <input name="backupImages" type="checkbox" id="backupImages" {$checkedBackupImages}> Save and keep a backup of your original images in a separate folder.
1012
- </td>
1013
- </tr>
1014
- <tr>
1015
- <th scope="row"><label for="backupImages">CMYK to RGB conversion</label></th>
1016
- <td>
1017
- <input name="cmyk2rgb" type="checkbox" id="cmyk2rgb" {$cmyk2rgb}>Adjust your images for computer and mobile screen display.
1018
- </td>
1019
- </tr>
1020
- </tr>
1021
- </tbody></table>
1022
- <p class="submit">
1023
- <input type="submit" name="submit" id="submit" class="button button-primary" title="Save Changes" value="Save Changes">
1024
- <a class="button button-primary" title="Process all the images in your Media Library" href="upload.php?page=wp-short-pixel-bulk">Bulk Process</a>
1025
- </p>
1026
- </form>
1027
- <script>
1028
- var rad = document.wp_shortpixel_options.compressionType;
1029
- var prev = null;
1030
- for(var i = 0; i < rad.length; i++) {
1031
- rad[i].onclick = function() {
1032
-
1033
- if(this !== prev) {
1034
- prev = this;
1035
- }
1036
- alert('This type of optimization will apply to new uploaded images. <BR>Images that were already processed will not be re-optimized.');
1037
- };
1038
- }
1039
- </script>
1040
- HTML;
1041
- }
1042
-
1043
- echo $formHTML;
1044
-
1045
- if($this->_verifiedKey) {
1046
- $fileCount = number_format(get_option('wp-short-pixel-fileCount'));
1047
- $savedSpace = self::formatBytes(get_option('wp-short-pixel-savedSpace'),2);
1048
- $averageCompression = round(get_option('wp-short-pixel-averageCompression'),2);
1049
- $savedBandwidth = self::formatBytes(get_option('wp-short-pixel-savedSpace') * 10000,2);
1050
- $quotaData = $this->getQuotaInformation();
1051
- if (is_numeric($quotaData['APICallsQuota'])) {
1052
- $quotaData['APICallsQuota'] .= "/month";
1053
- }
1054
- $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
1055
- $remainingImages = number_format($quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] - $quotaData['APICallsMadeNumeric'] - $quotaData['APICallsMadeOneTimeNumeric'] );
1056
- $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
1057
-
1058
- $statHTML = <<< HTML
1059
- <a id="facts"></a>
1060
- <h3>Your ShortPixel Stats</h3>
1061
- <table class="form-table">
1062
- <tbody>
1063
- <tr>
1064
- <th scope="row"><label for="averagCompression">Average compression of your files:</label></th>
1065
- <td>$averageCompression%</td>
1066
- </tr>
1067
- <tr>
1068
- <th scope="row"><label for="savedSpace">Saved disk space by ShortPixel</label></th>
1069
- <td>$savedSpace</td>
1070
- </tr>
1071
- <tr>
1072
- <th scope="row"><label for="savedBandwidth">Bandwith* saved with ShortPixel:</label></th>
1073
- <td>$savedBandwidth</td>
1074
- </tr>
1075
- </tbody></table>
1076
-
1077
- <p style="padding-top: 0px; color: #818181;" >* Saved bandwidth is calculated at 10,000 impressions/image</p>
1078
-
1079
- <h3>Your ShortPixel Plan</h3>
1080
- <table class="form-table">
1081
- <tbody>
1082
- <tr>
1083
- <th scope="row" bgcolor="#ffffff"><label for="apiQuota">Your ShortPixel plan</label></th>
1084
- <td bgcolor="#ffffff">{$quotaData['APICallsQuota']}/month ( <a href="https://shortpixel.com/login/{$this->_apiKey}" target="_blank">Need More? See the options available</a> )
1085
- </tr>
1086
- <tr>
1087
- <th scope="row"><label for="usedQUota">One time credits:</label></th>
1088
- <td>{$quotaData['APICallsQuotaOneTimeNumeric']}</td>
1089
- </tr>
1090
- <tr>
1091
- <th scope="row"><label for="usedQUota">Number of images processed this month:</label></th>
1092
- <td>{$totalCallsMade}</td>
1093
- </tr>
1094
- <tr>
1095
- <th scope="row"><label for="remainingImages">Remaining** images in your plan: </label></th>
1096
- <td>{$remainingImages} images</td>
1097
- </tr>
1098
- </tbody></table>
1099
-
1100
- <p style="padding-top: 0px; color: #818181;" >** Increase your image quota by <a href="https://shortpixel.com/login/{$this->_apiKey}" target="_blank">upgrading</a> your ShortPixel plan.</p>
1101
-
1102
- <table class="form-table">
1103
- <tbody>
1104
- <tr>
1105
- <th scope="row"><label for="totalFiles">Total number of processed files:</label></th>
1106
- <td>{$fileCount}</td>
1107
- </tr>
1108
-
1109
-
1110
-
1111
- HTML;
1112
- if($this->_backupImages) {
1113
- $statHTML .= <<< HTML
1114
- <form action="" method="POST">
1115
- <tr>
1116
- <th scope="row"><label for="sizeBackup">Original images are stored in a backup folder. Your backup folder size is now:</label></th>
1117
- <td>
1118
- {$backupFolderSize}
1119
- <input type="submit" style="margin-left: 15px; vertical-align: middle;" class="button button-secondary" name="emptyBackup" value="Empty backups"/>
1120
- </td>
1121
- </tr>
1122
- </form>
1123
- HTML;
1124
- }
1125
-
1126
- $statHTML .= <<< HTML
1127
- </tbody></table>
1128
- HTML;
1129
-
1130
- echo $statHTML;
1131
-
1132
-
1133
- }
1134
-
1135
- }
1136
-
1137
- public function getBulkProcessingForm() {
1138
-
1139
- //image count
1140
- $imageCount = $this->countAllProcessableFiles();
1141
- $imageOnlyThumbs = $imageCount['totalFiles'] - $imageCount['mainFiles'];
1142
- //average compression
1143
- $averageCompression = round(get_option('wp-short-pixel-averageCompression'),2);
1144
-
1145
-
1146
- if ( $averageCompression == 0 )
1147
- {
1148
- $message = "</br>
1149
- There are {$imageCount['mainFiles']} images in your library. </br>
1150
- The plugin will replace the original images with the smaller, compressed ones.<BR>
1151
- ";
1152
-
1153
- if ( $this->_backupImages )
1154
- $message .= "A backup folder stores the originals in a separate place on your server.<BR>";
1155
-
1156
- $message .= "
1157
- Start the compression by clicking the button below.<BR>
1158
- </br>
1159
- <form action='' method='POST' >
1160
- <input type='submit' name='bulkProcess' id='bulkProcess' class='button button-primary' value='Compress all your images'>";
1161
- }
1162
- else
1163
- {
1164
- $message = "<BR>
1165
- <p>You have ".number_format($imageCount['mainFiles'])." images in your Media Library and ".number_format($imageOnlyThumbs)." smaller thumbnails, associated to these images.</p>
1166
- <p>Unless you uncheck the box below, <b>ShortPixel will process a total of ".number_format($imageCount['totalFiles'])." images.</b></p>";
1167
-
1168
- $message .= "<form action='' method='POST' >";
1169
- if ( $this->_processThumbnails )
1170
- $message .= "<input type='checkbox' name='thumbnails' checked> Include thumbnails";
1171
- else
1172
- $message .= "<input type='checkbox' name='thumbnails'> Include thumbnails";
1173
-
1174
- $message .= "<BR><BR>Optimizing thumbnails is important for your overall website speed. However, if you don't want to optimize thumbs, please uncheck the box above.";
1175
-
1176
- $message .= "<BR><BR><input type='submit' name='bulkProcess' id='bulkProcess' class='button button-primary' value='Start Compression'>";
1177
-
1178
- }
1179
-
1180
-
1181
- if ( get_option( 'wp-short-pixel-cancel-pointer') )//add also the resume bulk processing option
1182
- $message .= "&nbsp;&nbsp;&nbsp;<input type='submit' name='bulkProcessResume' id='bulkProcessResume' class='button button-primary' value='Resume process'>";
1183
-
1184
- $message .= "
1185
- </form>";
1186
- return $message;
1187
-
1188
- }
1189
-
1190
-
1191
- public function getQuotaInformation($apiKey = null, $appendUserAgent = false) {
1192
-
1193
- if(is_null($apiKey)) { $apiKey = $this->_apiKey; }
1194
-
1195
- $requestURL = 'https://api.shortpixel.com/v2/api-status.php';
1196
- $args = array('timeout'=> SP_MAX_TIMEOUT,
1197
- 'sslverify' => false,
1198
- 'body' => array('key' => $apiKey)
1199
- );
1200
-
1201
- if($appendUserAgent) {
1202
- $args['body']['useragent'] = "Agent" . urlencode($_SERVER['HTTP_USER_AGENT']);
1203
- }
1204
- $response = wp_remote_post($requestURL, $args);
1205
-
1206
- if(is_wp_error( $response )) //some hosting providers won't allow https:// POST connections so we try http:// as well
1207
- $response = wp_remote_post(str_replace('https://', 'http://', $requestURL), $args);
1208
-
1209
- if(is_wp_error( $response ))
1210
- $response = wp_remote_get(str_replace('https://', 'http://', $requestURL), $args);
1211
-
1212
- $defaultData = array(
1213
- "APIKeyValid" => false,
1214
- "Message" => 'API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to IP 176.9.106.46.<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ',
1215
- "APICallsMade" => 'Information unavailable. Please check your API key.',
1216
- "APICallsQuota" => 'Information unavailable. Please check your API key.');
1217
-
1218
- if(is_object($response) && get_class($response) == 'WP_Error') {
1219
-
1220
- $urlElements = parse_url($requestURL);
1221
- $portConnect = @fsockopen($urlElements['host'],8,$errno,$errstr,15);
1222
- if(!$portConnect)
1223
- $defaultData['Message'] .= "<BR>Debug info: <i>$errstr</i>";
1224
-
1225
- return $defaultData;
1226
- }
1227
-
1228
- if($response['response']['code'] != 200) {
1229
- return $defaultData;
1230
- }
1231
-
1232
- $data = $response['body'];
1233
- $data = $this->parseJSON($data);
1234
-
1235
- if(empty($data)) { return $defaultData; }
1236
-
1237
- if($data->Status->Code != 2) {
1238
- $defaultData['Message'] = $data->Status->Message;
1239
- return $defaultData;
1240
- }
1241
-
1242
- if ( ( $data->APICallsMade + $data->APICallsMadeOneTime ) < ( $data->APICallsQuota + $data->APICallsQuotaOneTime ) ) //reset quota exceeded flag -> user is allowed to process more images.
1243
- update_option('wp-short-pixel-quota-exceeded',0);
1244
- else
1245
- update_option('wp-short-pixel-quota-exceeded',1);//activate quota limiting
1246
-
1247
- return array(
1248
- "APIKeyValid" => true,
1249
- "APICallsMade" => number_format($data->APICallsMade) . ' images',
1250
- "APICallsQuota" => number_format($data->APICallsQuota) . ' images',
1251
- "APICallsMadeOneTime" => number_format($data->APICallsMadeOneTime) . ' images',
1252
- "APICallsQuotaOneTime" => number_format($data->APICallsQuotaOneTime) . ' images',
1253
- "APICallsMadeNumeric" => $data->APICallsMade,
1254
- "APICallsQuotaNumeric" => $data->APICallsQuota,
1255
- "APICallsMadeOneTimeNumeric" => $data->APICallsMadeOneTime,
1256
- "APICallsQuotaOneTimeNumeric" => $data->APICallsQuotaOneTime
1257
- );
1258
-
1259
-
1260
- }
1261
-
1262
- public function generateCustomColumn( $column_name, $id ) {
1263
- if( 'wp-shortPixel' == $column_name ) {
1264
- $data = wp_get_attachment_metadata($id);
1265
- $file = get_attached_file($id);
1266
- $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
1267
-
1268
- if ( empty($data) )
1269
- {
1270
- if ( $fileExtension <> "pdf" )
1271
- {
1272
- if(!$this->_verifiedKey)
1273
- print 'Invalid API Key. <a href="options-general.php?page=wp-shortpixel">Check your Settings</a>';
1274
- else
1275
- print 'Optimization N/A';
1276
- }
1277
- else
1278
- {
1279
- if ( get_option('wp-short-pixel-quota-exceeded') )
1280
- {
1281
- print QUOTA_EXCEEDED;
1282
- return;
1283
- }
1284
- else
1285
- {
1286
- print 'PDF not processed';
1287
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
1288
- return;
1289
- }
1290
- }
1291
- return;
1292
- }
1293
- elseif ( isset( $data['ShortPixelImprovement'] ) )
1294
- {
1295
- if(isset($meta['ShortPixel']['BulkProcessing']))
1296
- {
1297
- if ( get_option('wp-short-pixel-quota-exceeded') )
1298
- {
1299
- print QUOTA_EXCEEDED;
1300
- return;
1301
- }
1302
- else
1303
- {
1304
- print 'Waiting for bulk processing';
1305
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
1306
- return;
1307
- }
1308
- }
1309
-
1310
- if( is_numeric($data['ShortPixelImprovement']) && !isset($data['ShortPixel']['NoBackup']) ) {
1311
- print 'Reduced by ';
1312
- print $data['ShortPixelImprovement'] . '%';
1313
- print " | <a href=\"admin.php?action=shortpixel_restore_backup&amp;attachment_ID={$id}\">Restore backup</a>";
1314
- return;
1315
- }
1316
- elseif ( is_numeric($data['ShortPixelImprovement']) )
1317
- {
1318
- print 'Reduced by ';
1319
- print $data['ShortPixelImprovement'];
1320
- print '%';
1321
- return;
1322
- }
1323
- elseif ( $data['ShortPixelImprovement'] <> "Optimization N/A" )
1324
- {
1325
- if ( trim(strip_tags($data['ShortPixelImprovement'])) == "Quota exceeded" )
1326
- {
1327
- print QUOTA_EXCEEDED;
1328
- if ( !get_option('wp-short-pixel-quota-exceeded') )
1329
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Try again</a>";
1330
- return;
1331
- }
1332
- else
1333
- {
1334
- print $data['ShortPixelImprovement'];
1335
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Try again</a>";
1336
- return;
1337
- }
1338
- }
1339
- else
1340
- {
1341
- print "Optimization N/A";
1342
- return;
1343
- }
1344
-
1345
-
1346
- } elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
1347
- if ( get_option('wp-short-pixel-quota-exceeded') )
1348
- {
1349
- print QUOTA_EXCEEDED;
1350
- return;
1351
- }
1352
- else
1353
- {
1354
- print 'Image waiting to be processed';
1355
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
1356
- return;
1357
- }
1358
-
1359
- } elseif(isset($data['ShortPixel']['NoFileOnDisk'])) {
1360
- print 'Image does not exist';
1361
- return;
1362
- } else {
1363
-
1364
- if ( wp_attachment_is_image( $id ) )
1365
- {
1366
- if ( get_option('wp-short-pixel-quota-exceeded') )
1367
- {
1368
- print QUOTA_EXCEEDED;
1369
- return;
1370
- }
1371
- else
1372
- {
1373
- print 'Image not processed';
1374
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
1375
- return;
1376
- }
1377
- }
1378
- elseif ( $fileExtension == "pdf" )
1379
- {
1380
- if ( get_option('wp-short-pixel-quota-exceeded') )
1381
- {
1382
- print QUOTA_EXCEEDED;
1383
- return;
1384
- }
1385
- else
1386
- {
1387
- print 'PDF not processed';
1388
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
1389
- return;
1390
- }
1391
- }
1392
-
1393
- }
1394
- }
1395
- }
1396
-
1397
- public function columns( $defaults ) {
1398
- $defaults['wp-shortPixel'] = 'ShortPixel Compression';
1399
- return $defaults;
1400
- }
1401
-
1402
- public function generatePluginLinks($links) {
1403
- $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
1404
- array_unshift($links, $in);
1405
- return $links;
1406
- }
1407
-
1408
- public function parseJSON($data) {
1409
- if ( function_exists('json_decode') ) {
1410
- $data = json_decode( $data );
1411
- } else {
1412
- require_once( 'JSON/JSON.php' );
1413
- $json = new Services_JSON( );
1414
- $data = $json->decode( $data );
1415
- }
1416
- return $data;
1417
- }
1418
-
1419
-
1420
- static public function formatBytes($bytes, $precision = 2) {
1421
- $units = array('B', 'KB', 'MB', 'GB', 'TB');
1422
-
1423
- $bytes = max($bytes, 0);
1424
- $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
1425
- $pow = min($pow, count($units) - 1);
1426
-
1427
- $bytes /= pow(1024, $pow);
1428
-
1429
- return round($bytes, $precision) . ' ' . $units[$pow];
1430
- }
1431
-
1432
- static public function isProcessable($path) {
1433
- $pathParts = pathinfo($path);
1434
- if( isset($pathParts['extension']) && in_array(strtolower($pathParts['extension']), array('jpg', 'jpeg', 'gif', 'png', 'pdf'))) {
1435
- return true;
1436
- } else {
1437
- return false;
1438
- }
1439
- }
1440
-
1441
- public static function deleteDir($dirPath) {
1442
- if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') {
1443
- $dirPath .= '/';
1444
- }
1445
- $files = glob($dirPath . '*', GLOB_MARK);
1446
- foreach ($files as $file) {
1447
- if (is_dir($file)) {
1448
- self::deleteDir($file);
1449
- @rmdir($file);//remove empty dir
1450
- } else {
1451
- @unlink($file);//remove file
1452
- }
1453
- }
1454
- }
1455
-
1456
- static public function folderSize($path) {
1457
- $total_size = 0;
1458
- if(file_exists($path)) {
1459
- $files = scandir($path);
1460
- } else {
1461
- return $total_size;
1462
- }
1463
- $cleanPath = rtrim($path, '/'). '/';
1464
- foreach($files as $t) {
1465
- if ($t<>"." && $t<>"..")
1466
- {
1467
- $currentFile = $cleanPath . $t;
1468
- if (is_dir($currentFile)) {
1469
- $size = self::folderSize($currentFile);
1470
- $total_size += $size;
1471
- }
1472
- else {
1473
- $size = filesize($currentFile);
1474
- $total_size += $size;
1475
- }
1476
- }
1477
- }
1478
- return $total_size;
1479
- }
1480
-
1481
- public function getMaxShortPixelId() {
1482
- global $wpdb,$startQueryID,$endQueryID;
1483
- $queryMax = "SELECT max(post_id) as startQueryID FROM " . $wpdb->prefix . "postmeta";
1484
- $resultQuery = $wpdb->get_results($queryMax);
1485
- $startQueryID = $resultQuery[0]->startQueryID;
1486
- $endQueryID = $startQueryID;
1487
- }
1488
-
1489
- //count all the processable files in media library (while limiting the results to max 10000)
1490
- public function countAllProcessableFiles(){
1491
- global $wpdb;
1492
-
1493
- $totalFiles = 0;
1494
- $mainFiles = 0;
1495
- $filesList= $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "postmeta
1496
- WHERE ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' ) LIMIT 10000");
1497
-
1498
-
1499
- foreach ( $filesList as $file )
1500
- {
1501
- if ( $file->meta_key == "_wp_attached_file" )
1502
- {//count pdf files only
1503
- $extension = substr($file->meta_value, strrpos($file->meta_value,".") + 1 );
1504
- if ( $extension == "pdf" )
1505
- {
1506
- $totalFiles++;
1507
- $mainFiles++;
1508
- }
1509
- }
1510
- else
1511
- {
1512
- $attachment = unserialize($file->meta_value);
1513
- if ( isset($attachment['sizes']) )
1514
- $totalFiles += count($attachment['sizes']);
1515
-
1516
- if ( isset($attachment['file']) )
1517
- {
1518
- $totalFiles++;
1519
- $mainFiles++;
1520
- }
1521
- }
1522
- }
1523
-
1524
- return array("totalFiles" => $totalFiles, "mainFiles" => $mainFiles);
1525
- }
1526
-
1527
-
1528
- public function migrateBackupFolder() {
1529
- $oldBackupFolder = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'ShortpixelBackups';
1530
-
1531
- if(!file_exists($oldBackupFolder)) return; //if old backup folder does not exist then there is nothing to do
1532
-
1533
- if(!file_exists(SP_BACKUP_FOLDER)) {
1534
- //we check that the backup folder exists, if not we create it so we can copy into it
1535
- if(!mkdir(SP_BACKUP_FOLDER, 0777, true)) return;
1536
- }
1537
-
1538
- $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1539
- foreach($scannedDirectory as $file) {
1540
- @rename($oldBackupFolder.DIRECTORY_SEPARATOR.$file, SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.$file);
1541
- }
1542
- $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1543
- if(empty($scannedDirectory)) {
1544
- @rmdir($oldBackupFolder);
1545
- }
1546
-
1547
- return;
1548
- }
1549
-
1550
-
1551
- }
1552
-
1553
- $pluginInstance = new WPShortPixel();
1554
- global $pluginInstance;
1555
-
1556
- ?>
1
+ <?php
2
+ /**
3
+ * Plugin Name: ShortPixel Image Optimizer
4
+ * Plugin URI: https://shortpixel.com/
5
+ * Description: ShortPixel optimizes images automatically, while guarding the quality of your images. Check your <a href="options-general.php?page=wp-shortpixel" target="_blank">Settings &gt; ShortPixel</a> page on how to start optimizing your image library and make your website load faster.
6
+ * Version: 3.0.0
7
+ * Author: ShortPixel
8
+ * Author URI: https://shortpixel.com
9
+ */
10
+
11
+ require_once('shortpixel_api.php');
12
+ require_once('shortpixel_queue.php');
13
+ require_once('shortpixel_view.php');
14
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
15
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
16
+ if ( !is_plugin_active( 'wpmandrill/wpmandrill.php' ) ) {
17
+ require_once( ABSPATH . 'wp-includes/pluggable.php' );//to avoid conflict with wpmandrill plugin
18
+ }
19
+
20
+ define('SP_RESET_ON_ACTIVATE', false);
21
+
22
+ define('PLUGIN_VERSION', "3.0.0");
23
+ define('SP_MAX_TIMEOUT', 10);
24
+ define('SP_BACKUP', 'ShortpixelBackups');
25
+ define('SP_BACKUP_FOLDER', WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . SP_BACKUP);
26
+ define('MAX_API_RETRIES', 5);
27
+ $MAX_EXECUTION_TIME = ini_get('max_execution_time');
28
+ if ( is_numeric($MAX_EXECUTION_TIME) )
29
+ define('MAX_EXECUTION_TIME', $MAX_EXECUTION_TIME - 5 ); //in seconds
30
+ else
31
+ define('MAX_EXECUTION_TIME', 25 );
32
+ define("SP_MAX_RESULTS_QUERY", 6);
33
+
34
+ class WPShortPixel {
35
+
36
+ const BULK_EMPTY_QUEUE = 0;
37
+
38
+ private $_apiKey = '';
39
+ private $_compressionType = 1;
40
+ private $_processThumbnails = 1;
41
+ private $_CMYKtoRGBconversion = 1;
42
+ private $_backupImages = 1;
43
+ private $_verifiedKey = false;
44
+
45
+ private $_apiInterface = null;
46
+ private $prioQ = null;
47
+ private $view = null;
48
+
49
+ //handling older
50
+ public function WPShortPixel() {
51
+ $this->__construct();
52
+ }
53
+
54
+ public function __construct() {
55
+ session_start();
56
+
57
+ $this->populateOptions();
58
+
59
+ $this->_apiInterface = new ShortPixelAPI($this->_apiKey, $this->_compressionType, $this->_CMYKtoRGBconversion);
60
+ $this->prioQ = new ShortPixelQueue($this);
61
+ $this->view = new ShortPixelView($this);
62
+
63
+ define('QUOTA_EXCEEDED', "Quota Exceeded. <a href='https://shortpixel.com/login/".$this->_apiKey."' target='_blank'>Extend Quota</a>");
64
+
65
+ $this->setDefaultViewModeList();//set default mode as list. only @ first run
66
+
67
+ //add hook for image upload processing
68
+ add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleImageUpload' ), 10, 2 );
69
+ add_filter( 'manage_media_columns', array( &$this, 'columns' ) );//add media library column header
70
+ add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));//for plugin settings page
71
+
72
+ //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
73
+ add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );//generate the media library column
74
+
75
+ //add settings page
76
+ add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );//display SP in Settings menu
77
+ add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
78
+ add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
79
+ add_action( 'load-upload.php', array( &$this, 'handleCustomBulk'));
80
+
81
+ //when plugin is activated run this
82
+ register_activation_hook( __FILE__, array( &$this, 'shortPixelActivatePlugin' ) );
83
+ register_deactivation_hook( __FILE__, array( &$this, 'shortPixelDeactivatePlugin' ) );
84
+
85
+ //automatic optimization
86
+ add_action( 'wp_ajax_shortpixel_image_processing', array( &$this, 'handleImageProcessing') );
87
+ //manual optimization
88
+ add_action( 'wp_ajax_shortpixel_manual_optimization', array(&$this, 'handleManualOptimization'));
89
+ //backup restore
90
+ add_action('admin_action_shortpixel_restore_backup', array(&$this, 'handleRestoreBackup'));
91
+
92
+ //This adds the constants used in PHP to be available also in JS
93
+ add_action( 'admin_footer', array( &$this, 'shortPixelJS') );
94
+
95
+
96
+ //example toolbar by fai, to be configured
97
+ add_action( 'admin_bar_menu', array( &$this, 'toolbar_shortpixel_processing'), 999 );
98
+
99
+ $this->migrateBackupFolder();
100
+ }
101
+
102
+ public function populateOptions() {
103
+
104
+ $this->_apiKey = self::getOpt('wp-short-pixel-apiKey', '');
105
+ $this->_verifiedKey = self::getOpt('wp-short-pixel-verifiedKey', $this->_verifiedKey);
106
+ $this->_compressionType = self::getOpt('wp-short-pixel-compression', $this->_compressionType);
107
+ $this->_processThumbnails = self::getOpt('wp-short-process_thumbnails', $this->_processThumbnails);
108
+ $this->_CMYKtoRGBconversion = self::getOpt('wp-short-pixel_cmyk2rgb', $this->_CMYKtoRGBconversion);
109
+ $this->_backupImages = self::getOpt('wp-short-backup_images', $this->_backupImages);
110
+ // the following practically set defaults for options if they're not set
111
+ self::getOpt( 'wp-short-pixel-fileCount', 0);
112
+ self::getOpt( 'wp-short-pixel-thumbnail-count', 0);//amount of optimized thumbnails
113
+ self::getOpt( 'wp-short-pixel-files-under-5-percent', 0);//amount of optimized thumbnails
114
+ self::getOpt( 'wp-short-pixel-savedSpace', 0);
115
+ self::getOpt( 'wp-short-pixel-api-retries', 0);//sometimes we need to retry processing/downloading a file multiple times
116
+ self::getOpt( 'wp-short-pixel-quota-exceeded', 0);
117
+ self::getOpt( 'wp-short-pixel-total-original', 0);//amount of original data
118
+ self::getOpt( 'wp-short-pixel-total-optimized', 0);//amount of optimized
119
+ self::getOpt( 'wp-short-pixel-protocol', 'https');
120
+ }
121
+
122
+ public function shortPixelActivatePlugin()//reset some params to avoid trouble for plugins that were activated/deactivated/activated
123
+ {
124
+ $this->prioQ->resetBulk();
125
+ if(SP_RESET_ON_ACTIVATE === true && WP_DEBUG === true) { //force reset plugin counters, only on specific occasions and on test environments
126
+ update_option( 'wp-short-pixel-fileCount', 0);
127
+ update_option( 'wp-short-pixel-thumbnail-count', 0);
128
+ update_option( 'wp-short-pixel-files-under-5-percent', 0);
129
+ update_option( 'wp-short-pixel-savedSpace', 0);
130
+ update_option( 'wp-short-pixel-api-retries', 0);//sometimes we need to retry processing/downloading a file multiple times
131
+ update_option( 'wp-short-pixel-quota-exceeded', 0);
132
+ update_option( 'wp-short-pixel-total-original', 0);//amount of original data
133
+ update_option( 'wp-short-pixel-total-optimized', 0);//amount of optimized
134
+ update_option( 'wp-short-pixel-bulk-ever-ran', 0);
135
+ delete_option('wp-short-pixel-priorityQueue');
136
+ unset($_SESSION["wp-short-pixel-priorityQueue"]);
137
+ }
138
+ }
139
+
140
+ public function shortPixelDeactivatePlugin()//reset some params to avoid trouble for plugins that were activated/deactivated/activated
141
+ {
142
+ $this->prioQ->resetBulk();
143
+ }
144
+
145
+ //set default move as "list". only set once, it won't try to set the default mode again.
146
+ public function setDefaultViewModeList()
147
+ {
148
+ if(get_option('wp-short-pixel-view-mode') === false)
149
+ {
150
+ add_option('wp-short-pixel-view-mode', 1, '', 'yes' );
151
+ if ( function_exists('get_currentuserinfo') )
152
+ {
153
+ global $current_user;
154
+ get_currentuserinfo();
155
+ $currentUserID = $current_user->ID;
156
+ update_user_meta($currentUserID, "wp_media_library_mode", "list");
157
+ }
158
+ }
159
+
160
+ }
161
+
162
+ static function log($message) {
163
+ if (WP_DEBUG === true) {
164
+ if (is_array($message) || is_object($message)) {
165
+ error_log(print_r($message, true));
166
+ } else {
167
+ error_log($message);
168
+ }
169
+ }
170
+ }
171
+
172
+ function shortPixelJS() { ?>
173
+ <script type="text/javascript" >
174
+ jQuery(document).ready(function($){
175
+ ShortPixel.setOptions({
176
+ STATUS_SUCCESS: <?= ShortPixelAPI::STATUS_SUCCESS ?>,
177
+ STATUS_EMPTY_QUEUE: <?= self::BULK_EMPTY_QUEUE ?>,
178
+ STATUS_ERROR: <?= ShortPixelAPI::STATUS_ERROR ?>,
179
+ STATUS_FAIL: <?= ShortPixelAPI::STATUS_FAIL ?>,
180
+ STATUS_SKIP: <?= ShortPixelAPI::STATUS_SKIP ?>,
181
+ STATUS_QUOTA_EXCEEDED: <?= ShortPixelAPI::STATUS_QUOTA_EXCEEDED ?>,
182
+ WP_PLUGIN_URL: '<?= WP_PLUGIN_URL ?>',
183
+ API_KEY: "<?= $this->_apiKey ?>"
184
+ });
185
+ });
186
+ </script> <?php
187
+ wp_enqueue_style('short-pixel.css', plugins_url('/css/short-pixel.css',__FILE__) );
188
+ }
189
+
190
+ function toolbar_shortpixel_processing( $wp_admin_bar ) {
191
+ wp_enqueue_script('short-pixel.js', plugins_url('/js/short-pixel.js',__FILE__) );
192
+
193
+ $extraClasses = " shortpixel-hide";
194
+ $tooltip = "ShortPixel optimizing...";
195
+ $icon = "shortpixel.png";
196
+ $link = 'upload.php?page=wp-short-pixel-bulk';
197
+ $blank = "";
198
+ if($this->prioQ->processing()) {
199
+ $extraClasses = " shortpixel-processing";
200
+ }
201
+ self::log("TOOLBAR: Quota exceeded: " . self::getOpt( 'wp-short-pixel-quota-exceeded', 0));
202
+ if(self::getOpt( 'wp-short-pixel-quota-exceeded', 0)) {
203
+ $extraClasses = " shortpixel-alert shortpixel-quota-exceeded";
204
+ $tooltip = "ShortPixel quota exceeded. Click to top-up";
205
+ $link = "http://shortpixel.com/login/" . $this->_apiKey;
206
+ $blank = '_blank';
207
+ //$icon = "shortpixel-alert.png";
208
+ }
209
+ self::log("TB: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
210
+ .json_encode($this->prioQ->get()));
211
+
212
+ $args = array(
213
+ 'id' => 'shortpixel_processing',
214
+ 'title' => '<div title="' . $tooltip . '" ><img src="'
215
+ . WP_PLUGIN_URL . '/shortpixel-image-optimiser/img/' . $icon . '"><span class="shp-alert">!</span></div>',
216
+ 'href' => $link,
217
+ 'meta' => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
218
+ );
219
+ $wp_admin_bar->add_node( $args );
220
+ }
221
+
222
+ public static function getOpt($key, $default) {
223
+ if(get_option($key) === false) {
224
+ add_option( $key, $default, '', 'yes' );
225
+ }
226
+ return get_option($key);
227
+ }
228
+
229
+ public function handleCustomBulk() {
230
+ // 1. get the action
231
+ $wp_list_table = _get_list_table('WP_Media_List_Table');
232
+ $action = $wp_list_table->current_action();
233
+
234
+ switch($action) {
235
+ // 2. Perform the action
236
+ case 'short-pixel-bulk':
237
+ // security check
238
+ check_admin_referer('bulk-media');
239
+ if(!is_array($_GET['media'])) {
240
+ break;
241
+ }
242
+ $mediaIds = array_reverse($_GET['media']);
243
+ foreach( $mediaIds as $ID ) {
244
+ $meta = wp_get_attachment_metadata($ID);
245
+ if( (!isset($meta['ShortPixel']) || !isset($meta['ShortPixel']['WaitingProcessing']) || $meta['ShortPixel']['WaitingProcessing'] != true)
246
+ && (!isset($meta['ShortPixelImprovement']) || $meta['ShortPixelImprovement'] != 'Optimization N/A')) {
247
+ $this->prioQ->push($ID);
248
+ $meta['ShortPixel']['WaitingProcessing'] = true;
249
+ wp_update_attachment_metadata($ID, $meta);
250
+ }
251
+ }
252
+ break;
253
+ }
254
+ }
255
+
256
+ public function handleImageUpload($meta, $ID = null)
257
+ {
258
+ if( !$this->_verifiedKey) {// no API Key set/verified -> do nothing here, just return
259
+ return $meta;
260
+ }
261
+ //else
262
+ self::log("IMG: Auto-analyzing file ID #{$ID}");
263
+
264
+ if( self::isProcessable($ID) == false )
265
+ {//not a file that we can process
266
+ $meta['ShortPixelImprovement'] = 'Optimization N/A';
267
+ return $meta;
268
+ }
269
+ else
270
+ {//the kind of file we can process. goody.
271
+ $this->prioQ->push($ID);
272
+ $URLsAndPATHs = $this->getURLsAndPATHs($ID, $meta);
273
+ $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $ID);//send a processing request right after a file was uploaded, do NOT wait for response
274
+ self::log("IMG: sent: " . json_encode($URLsAndPATHs));
275
+ $meta['ShortPixel']['WaitingProcessing'] = true;
276
+ return $meta;
277
+ }
278
+
279
+ }//end handleImageUpload
280
+
281
+ public function getCurrentBulkItemsCount(){
282
+ global $wpdb;
283
+
284
+ $startQueryID = $this->prioQ->getFlagBulkId();
285
+ $endQueryID = $this->prioQ->getStopBulkId();
286
+
287
+ if ( $startQueryID <= $endQueryID ) {
288
+ return 0;
289
+ }
290
+ $queryPostMeta = "SELECT COUNT(DISTINCT post_id) items FROM " . $wpdb->prefix . "postmeta
291
+ WHERE ( post_id <= $startQueryID AND post_id > $endQueryID ) AND (
292
+ meta_key = '_wp_attached_file'
293
+ OR meta_key = '_wp_attachment_metadata' )";
294
+ $res = $wpdb->get_results($queryPostMeta);
295
+ return $res[0]->items;
296
+ }
297
+
298
+ public function getBulkItemsFromDb(){
299
+ global $wpdb;
300
+
301
+ $startQueryID = $this->prioQ->getStartBulkId();
302
+ $endQueryID = $this->prioQ->getStopBulkId();
303
+ $skippedAlreadyProcessed = 0;
304
+
305
+ if ( $startQueryID <= $endQueryID ) {
306
+ return false;
307
+ }
308
+ $idList = array();
309
+ for ($sanityCheck = 0, $crtStartQueryID = $startQueryID;
310
+ $crtStartQueryID > $endQueryID && count($idList) < 3; $sanityCheck++) {
311
+
312
+ self::log("GETDB: current StartID: " . $crtStartQueryID);
313
+
314
+ $queryPostMeta = "SELECT * FROM " . $wpdb->prefix . "postmeta
315
+ WHERE ( post_id <= $crtStartQueryID AND post_id > $endQueryID )
316
+ AND ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' )
317
+ ORDER BY post_id DESC
318
+ LIMIT " . SP_MAX_RESULTS_QUERY;
319
+ $resultsPostMeta = $wpdb->get_results($queryPostMeta);
320
+
321
+ if($sanityCheck > 1000) {
322
+ die("oops! $crtStartQueryID -- $startQueryID -> $endQueryID");
323
+ }
324
+ if ( empty($resultsPostMeta) ) {
325
+ $crtStartQueryID -= SP_MAX_RESULTS_QUERY;
326
+ continue;
327
+ }
328
+
329
+ foreach ( $resultsPostMeta as $itemMetaData ) {
330
+ $crtStartQueryID = $itemMetaData->post_id;
331
+ if(!in_array($crtStartQueryID, $idList) && self::isProcessable($crtStartQueryID)) {
332
+ $meta = wp_get_attachment_metadata($crtStartQueryID);
333
+ if(!isset($meta["ShortPixelImprovement"]) || !is_numeric($meta["ShortPixelImprovement"])) {
334
+ $idList[] = $crtStartQueryID;
335
+ } elseif($itemMetaData->meta_key == '_wp_attachment_metadata') { //count skipped
336
+ $skippedAlreadyProcessed++;
337
+ }
338
+ }
339
+ }
340
+ if(!count($idList) && $crtStartQueryID <= $startQueryID) {
341
+ //daca n-am adaugat niciuna pana acum, n-are sens sa mai selectez zona asta de id-uri in bulk-ul asta.
342
+ $leapStart = $this->prioQ->getStartBulkId();
343
+ $crtStartQueryID = $startQueryID = $itemMetaData->post_id - 1; //decrement it so we don't select it again
344
+ $res = self::countAllProcessedFiles($leapStart, $crtStartQueryID);
345
+ $skippedAlreadyProcessed += $res["mainFiles"];
346
+ $this->prioQ->setStartBulkId($startQueryID);
347
+ } else {
348
+ $crtStartQueryID--;
349
+ }
350
+ }
351
+ return array("ids" => $idList, "skipped" => $skippedAlreadyProcessed);
352
+ }
353
+
354
+ /**
355
+ * Get last added items from priority
356
+ * @return type
357
+ */
358
+ public function getFromPrioAndCheck() {
359
+ $ids = array();
360
+ $removeIds = array();
361
+
362
+ $idsPrio = $this->prioQ->get();
363
+ for($i = count($idsPrio) - 1, $cnt = 0; $i>=0 && $cnt < 3; $i--) {
364
+ $id = $idsPrio[$i];
365
+ if(wp_get_attachment_url($id)) {
366
+ $ids[] = $id; //valid ID
367
+ } else {
368
+ $removeIds[] = $id;//absent, to remove
369
+ }
370
+ }
371
+ foreach($removeIds as $rId){
372
+ self::log("HIP: Unfound ID $rID Remove from Priority Queue: ".json_encode(get_option($this->prioQ->get())));
373
+ $this->prioQ->remove($rId);
374
+ }
375
+ return $ids;
376
+ }
377
+
378
+ public function handleImageProcessing($ID = null) {
379
+ //die();
380
+ //0: check key
381
+ if( $this->_verifiedKey == false) {
382
+ echo "Missing API Key";
383
+ die();
384
+ }
385
+
386
+ self::log("HIP: 0 Priority Queue: ".json_encode($this->prioQ->get()));
387
+
388
+ //1: get 3 ids to process. Take them with priority from the queue
389
+ $ids = $this->getFromPrioAndCheck();
390
+ if(count($ids) < 3 ) { //take from bulk if bulk processing active
391
+ $bulkStatus = $this->prioQ->bulkRunning();
392
+ if($bulkStatus =='running') {
393
+ $res = $this->getBulkItemsFromDb();
394
+ $bulkItems = $res['ids'];
395
+ if($bulkItems){
396
+ $ids = array_merge ($ids, $bulkItems);
397
+ }
398
+ $this->prioQ->addBulkSkipped($res['skipped']);
399
+ }
400
+ }
401
+ if ($ids === false || count( $ids ) == 0 ){
402
+ $bulkEverRan = $this->prioQ->stopBulk();
403
+ $avg = self::getAverageCompression();
404
+ $fileCount = get_option('wp-short-pixel-fileCount');
405
+ die(json_encode(array("Status" => self::BULK_EMPTY_QUEUE,
406
+ "Message" => 'Empty queue ' . $this->prioQ->getStartBulkId() . '->' . $this->prioQ->getStopBulkId(),
407
+ "BulkStatus" => ($this->prioQ->bulkRunning()
408
+ ? "1" : ($this->prioQ->bulkPaused() ? "2" : "0")),
409
+ "AverageCompression" => $avg,
410
+ "FileCount" => $fileCount,
411
+ "BulkPercent" => $this->prioQ->getBulkPercent())));
412
+ }
413
+
414
+ self::log("HIP: 1 Prio Queue: ".json_encode($this->prioQ->get()));
415
+
416
+ //2: Send up to 3 files to the server for processing
417
+ for($i = 0; $i < min(3, count($ids)); $i++) {
418
+ $ID = $ids[$i];
419
+ $URLsAndPATHs = $this->sendToProcessing($ID);
420
+ if($i == 0) { //save for later use
421
+ $firstUrlAndPaths = $URLsAndPATHs;
422
+ }
423
+ }
424
+
425
+ self::log("HIP: 2 Prio Queue: ".json_encode($this->prioQ->get()));
426
+
427
+ //3: Retrieve the file for the first element of the list
428
+ $ID = $ids[0];
429
+ $result = $this->_apiInterface->processImage($firstUrlAndPaths['URLs'], $firstUrlAndPaths['PATHs'], $ID);
430
+ $result["ImageID"] = $ID;
431
+
432
+ self::log("HIP: 3 Prio Queue: ".json_encode($this->prioQ->get()));
433
+
434
+ //4: update counters and priority list
435
+ if( $result["Status"] == ShortPixelAPI::STATUS_SUCCESS) {
436
+ self::log("HIP: Image ID $ID optimized successfully: ".json_encode($result));
437
+ $prio = $this->prioQ->remove($ID);
438
+ if(!$prio && $ID <= $this->prioQ->getStartBulkId()) {
439
+ $this->prioQ->setStartBulkId($ID - 1);
440
+ $this->prioQ->logBulkProgress();
441
+
442
+ $deltaBulkPercent = $this->prioQ->getDeltaBulkPercent();
443
+ $msg = $this->bulkProgressMessage($deltaBulkPercent, $this->prioQ->getTimeRemaining());
444
+
445
+ $thumb = $bkThumb = "";
446
+ $percent = 0;
447
+ $meta = wp_get_attachment_metadata($ID);
448
+ if(isset($meta["ShortPixelImprovement"]) && isset($meta["file"])){
449
+ $percent = $meta["ShortPixelImprovement"];
450
+
451
+ $filePath = explode("/", $meta["file"]);
452
+ $uploadsUrl = content_url() . "/uploads/";
453
+ $urlPath = implode("/", array_slice($filePath, 0, count($filePath) - 1));
454
+ $thumb = (isset($meta["sizes"]["medium"]) ? $meta["sizes"]["medium"]["file"] : (isset($meta["sizes"]["thumbnail"]) ? $meta["sizes"]["thumbnail"]["file"]: ""));
455
+ if(strlen($thumb) && get_option('wp-short-backup_images')) {
456
+ $bkThumb = $uploadsUrl . SP_BACKUP . "/" . $urlPath . "/" . $thumb;
457
+ }
458
+ if(strlen($thumb)) {
459
+ $thumb = $uploadsUrl . $urlPath . "/" . $thumb;
460
+ }
461
+ $result["Thumb"] = $thumb;
462
+ $result["BkThumb"] = $bkThumb;
463
+ $result["BulkPercent"] = $this->prioQ->getBulkPercent();;
464
+ $result["BulkMsg"] = $msg;
465
+ }
466
+ }
467
+ }
468
+ elseif ($result["Status"] == ShortPixelAPI::STATUS_SKIP
469
+ || $result["Status"] == ShortPixelAPI::STATUS_FAIL) {
470
+ $prio = $this->prioQ->remove($ID);
471
+ if(!$prio && $ID <= $this->prioQ->getStartBulkId()) {
472
+ $this->prioQ->setStartBulkId($ID - 1);
473
+ }
474
+ }
475
+ die(json_encode($result));
476
+ }
477
+
478
+ private function sendToProcessing($ID) {
479
+ $URLsAndPATHs = $this->getURLsAndPATHs($ID);
480
+ $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $ID);//send a request, do NOT wait for response
481
+ $meta = wp_get_attachment_metadata($ID);
482
+ $meta['ShortPixel']['WaitingProcessing'] = true;
483
+ wp_update_attachment_metadata($ID, $meta);
484
+ return $URLsAndPATHs;
485
+ }
486
+
487
+ public function handleManualOptimization() {
488
+ $imageId = intval($_GET['image_id']);
489
+
490
+ if(self::isProcessable($imageId)) {
491
+ $this->prioQ->push($imageId);
492
+ $this->sendToProcessing($imageId);
493
+ $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
494
+ } else {
495
+ die(var_dump($pathParts));
496
+ }
497
+ die(json_encode($ret));
498
+
499
+ $urlList[] = wp_get_attachment_url($attachmentID);
500
+ $filePath[] = get_attached_file($attachmentID);
501
+ $meta = wp_get_attachment_metadata($attachmentID);
502
+
503
+ $processThumbnails = get_option('wp-short-process_thumbnails');
504
+
505
+ //process all files (including thumbs)
506
+ if($processThumbnails && !empty($meta['sizes'])) {
507
+ //we generate an array with the URLs that need to be handled
508
+ $SubDir = $this->_apiInterface->returnSubDir($meta['file']);
509
+ foreach($meta['sizes'] as $thumbnailInfo)
510
+ {
511
+ $urlList[]= str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $urlList[0]);
512
+ $filePath[] = str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $filePath[0]);
513
+ }
514
+ }
515
+
516
+ $result = $this->_apiInterface->processImage($urlList, $filePath, $attachmentID);//request to process all the images
517
+
518
+ if ( !is_array($result) )//there was an error, we save it in ShortPixelImprovement data
519
+ $this->handleError($attachmentID, $result);
520
+
521
+ // store the referring webpage location
522
+ $sendback = wp_get_referer();
523
+ // sanitize the referring webpage location
524
+ $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
525
+ // send the user back where they came from
526
+ wp_redirect($sendback);
527
+ // we are done,
528
+ }
529
+
530
+ //save error in file's meta data
531
+ public function handleError($ID, $result)
532
+ {
533
+ $meta = wp_get_attachment_metadata($ID);
534
+ $meta['ShortPixelImprovement'] = $result;
535
+ wp_update_attachment_metadata($ID, $meta);
536
+ }
537
+
538
+ public function handleRestoreBackup() {
539
+ $attachmentID = intval($_GET['attachment_ID']);
540
+
541
+ $file = get_attached_file($attachmentID);
542
+ $meta = wp_get_attachment_metadata($attachmentID);
543
+ $pathInfo = pathinfo($file);
544
+
545
+ $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
546
+ $SubDir = $this->_apiInterface->returnSubDir($file);
547
+
548
+ //sometimes the month of original file and backup can differ
549
+ if ( !file_exists(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file)) )
550
+ $SubDir = date("Y") . "/" . date("m") . "/";
551
+
552
+ try {
553
+ //main file
554
+ @rename(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file), $file);
555
+
556
+ //overwriting thumbnails
557
+ if( !empty($meta['file']) ) {
558
+ foreach($meta["sizes"] as $size => $imageData) {
559
+ $source = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . $imageData['file'];
560
+ $destination = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
561
+ @rename($source, $destination);
562
+ }
563
+ }
564
+ unset($meta["ShortPixelImprovement"]);
565
+ unset($meta['ShortPixel']['WaitingProcessing']);
566
+ wp_update_attachment_metadata($attachmentID, $meta);
567
+
568
+ } catch(Exception $e) {
569
+ //what to do, what to do?
570
+ }
571
+ // store the referring webpage location
572
+ $sendback = wp_get_referer();
573
+ // sanitize the referring webpage location
574
+ $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
575
+ // send the user back where they came from
576
+ wp_redirect($sendback);
577
+ // we are done
578
+ }
579
+
580
+
581
+ public function handleDeleteAttachmentInBackup($ID) {
582
+ $file = get_attached_file($ID);
583
+ $meta = wp_get_attachment_metadata($ID);
584
+
585
+ if(self::isProcessable($ID) != false)
586
+ {
587
+ $SubDir = $this->_apiInterface->returnSubDir($file);
588
+ try {
589
+ $SubDir = $this->_apiInterface->returnSubDir($file);
590
+
591
+ @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file));
592
+
593
+ if ( !empty($meta['file']) )
594
+ {
595
+ $filesPath = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir;//base BACKUP path
596
+ //remove thumbs thumbnails
597
+ if(isset($meta["sizes"])) {
598
+ foreach($meta["sizes"] as $size => $imageData) {
599
+ @unlink($filesPath . ShortPixelAPI::MB_basename($imageData['file']));//remove thumbs
600
+ }
601
+ }
602
+ }
603
+
604
+ } catch(Exception $e) {
605
+ //what to do, what to do?
606
+ }
607
+ }
608
+ }
609
+
610
+ public function registerSettingsPage() {
611
+ add_options_page( 'ShortPixel Settings', 'ShortPixel', 'manage_options', 'wp-shortpixel', array($this, 'renderSettingsMenu'));
612
+ }
613
+
614
+ function registerAdminPage( ) {
615
+ add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcess' ) );
616
+ }
617
+
618
+ public function checkQuotaAndAlert() {
619
+ $quotaData = $this->getQuotaInformation();
620
+ if ( !$quotaData['APIKeyValid']) {
621
+ return $quotaData;
622
+ }
623
+ if($quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] > $quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']) {
624
+ update_option('wp-short-pixel-quota-exceeded','0');
625
+ ?><script>var shortPixelQuotaExceeded = 0;</script><?php
626
+ }
627
+ else {
628
+ $this->view->displayQuotaExceededAlert($quotaData);
629
+ ?><script>var shortPixelQuotaExceeded = 1;</script><?php
630
+ }
631
+ return $quotaData;
632
+ }
633
+
634
+ public function bulkProcess() {
635
+ global $wpdb;
636
+
637
+ if( $this->_verifiedKey == false ) {//invalid API Key
638
+ $this->view->displayApiKeyAlert();
639
+ return;
640
+ }
641
+
642
+ $quotaData = $this->checkQuotaAndAlert();
643
+ if(self::getOpt('wp-short-pixel-quota-exceeded', 0) != 0) return;
644
+
645
+ if(isset($_POST['bulkProcessPause']))
646
+ {//pause an ongoing bulk processing, it might be needed sometimes
647
+ $this->prioQ->pauseBulk();
648
+ }
649
+
650
+ if(isset($_POST["bulkProcess"]))
651
+ {
652
+ //set the thumbnails option
653
+ if ( isset($_POST['thumbnails']) ) {
654
+ update_option('wp-short-process_thumbnails', 1);
655
+ } else {
656
+ update_option('wp-short-process_thumbnails', 0);
657
+ }
658
+ $this->prioQ->startBulk();
659
+ self::log("BULK: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
660
+ .json_encode($this->prioQ->get()));
661
+ }//end bulk process was clicked
662
+
663
+ if(isset($_POST["bulkProcessResume"]))
664
+ {
665
+ $this->prioQ->resumeBulk();
666
+ }//resume was clicked
667
+
668
+ //figure out all the files that could be processed
669
+ $qry = "SELECT count(*) FilesToBeProcessed FROM " . $wpdb->prefix . "postmeta
670
+ WHERE meta_key = '_wp_attached_file' ";
671
+ $allFiles = $wpdb->get_results($qry);
672
+ //figure out the files that are left to be processed
673
+ $qry_left = "SELECT count(*) FilesLeftToBeProcessed FROM " . $wpdb->prefix . "postmeta
674
+ WHERE meta_key = '_wp_attached_file' AND post_id <= " . $this->prioQ->getStartBulkId();
675
+ $filesLeft = $wpdb->get_results($qry_left);
676
+
677
+ if ( $filesLeft[0]->FilesLeftToBeProcessed > 0 && $this->prioQ->bulkRunning() )//bulk processing was started and is still running
678
+ {
679
+ $msg = $this->bulkProgressMessage($this->prioQ->getDeltaBulkPercent(), $this->prioQ->getTimeRemaining());
680
+ $this->view->displayBulkProcessingRunning($this->prioQ->getBulkPercent(), $msg);
681
+
682
+ // $imagesLeft = $filesLeft[0]->FilesLeftToBeProcessed;
683
+ // $totalImages = $allFiles[0]->FilesToBeProcessed;
684
+ // echo "<p>{$imagesLeft} out of {$totalImages} images left to process.</p>";
685
+ // echo ' <a class="button button-secondary" href="' . get_admin_url() . 'upload.php">Media Library</a> ';
686
+ } else
687
+ {
688
+ if($this->prioQ->bulkRan() && !$this->prioQ->bulkPaused()) {
689
+ $this->prioQ->markBulkComplete();
690
+ }
691
+
692
+ //image count
693
+ $imageCount = $this->countAllProcessableFiles();
694
+ $imgProcessedCount = $this->countAllProcessedFiles();
695
+ $imageOnlyThumbs = $imageCount['totalFiles'] - $imageCount['mainFiles'];
696
+ $thumbsProcessedCount = self::getOpt( 'wp-short-pixel-thumbnail-count', 0);//amount of optimized thumbnails
697
+ $under5PercentCount = self::getOpt( 'wp-short-pixel-files-under-5-percent', 0);//amount of under 5% optimized imgs.
698
+
699
+ //average compression
700
+ $averageCompression = self::getAverageCompression();
701
+ // $this->view->displayBulkProcessingForm($imageCount, $imageOnlyThumbs, $this->prioQ->bulkRan(), $averageCompression,
702
+ $this->view->displayBulkProcessingForm($imageCount, $imgProcessedCount, $thumbsProcessedCount, $under5PercentCount,
703
+ $this->prioQ->bulkRan(), $averageCompression, get_option('wp-short-pixel-fileCount'),
704
+ self::formatBytes(get_option('wp-short-pixel-savedSpace')), $this->prioQ->bulkPaused() ? $this->prioQ->getBulkPercent() : false);
705
+ }
706
+ }
707
+ //end bulk processing
708
+
709
+ public function bulkProgressMessage($percent, $minutes) {
710
+ $timeEst = "";
711
+ self::log("bulkProgressMessage(): percent: " . $percent);
712
+ if($percent < 1 || $minutes == 0) {
713
+ $timeEst = "";
714
+ } elseif( $minutes > 2880) {
715
+ $timeEst = "~ " . round($minutes / 1440) . " days left";
716
+ } elseif ($minutes > 240) {
717
+ $timeEst = "~ " . round($minutes / 60) . " hours left";
718
+ } elseif ($minutes > 60) {
719
+ $timeEst = "~ " . round($minutes / 60) . " hours " . round($minutes%60/10) * 10 . " min. left";
720
+ } elseif ($minutes > 20) {
721
+ $timeEst = "~ " . round($minutes / 10) * 10 . " minutes left";
722
+ } else {
723
+ $timeEst = "~ " . $minutes . " minutes left";
724
+ }
725
+ return $timeEst;
726
+ }
727
+
728
+ public function emptyBackup(){
729
+ if(file_exists(SP_BACKUP_FOLDER)) {
730
+
731
+ //extract all images from DB in an array. of course
732
+ $attachments = null;
733
+ $attachments = get_posts( array(
734
+ 'numberposts' => -1,
735
+ 'post_type' => 'attachment',
736
+ 'post_mime_type' => 'image'
737
+ ));
738
+
739
+
740
+ //parse all images and set the right flag that the image has no backup
741
+ foreach($attachments as $attachment)
742
+ {
743
+ if(self::isProcessable(get_attached_file($attachment->ID)) == false) continue;
744
+
745
+ $meta = wp_get_attachment_metadata($attachment->ID);
746
+ $meta['ShortPixel']['NoBackup'] = true;
747
+ wp_update_attachment_metadata($attachment->ID, $meta);
748
+ }
749
+
750
+ //delete the actual files on disk
751
+ $this->deleteDir(SP_BACKUP_FOLDER);//call a recursive function to empty files and sub-dirs in backup dir
752
+ }
753
+ }
754
+
755
+ public function renderSettingsMenu() {
756
+ if ( !current_user_can( 'manage_options' ) ) {
757
+ wp_die('You do not have sufficient permissions to access this page.');
758
+ }
759
+
760
+ $quotaData = $this->checkQuotaAndAlert();
761
+
762
+ echo '<h1>ShortPixel Plugin Settings</h1>';
763
+ echo '<p>
764
+ <a href="https://shortpixel.com" target="_blank">ShortPixel.com</a> |
765
+ <a href="https://wordpress.org/plugins/shortpixel-image-optimiser/installation/" target="_blank">Installation </a> |
766
+ <a href="https://shortpixel.com/contact" target="_blank">Support </a>
767
+ </p>';
768
+ echo '<p>New images uploaded to the Media Library will be optimized automatically.<br/>If you have existing images you would like to optimize, you can use the <a href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk">Bulk Optimization Tool</a>.</p>';
769
+
770
+ $noticeHTML = "<br/><div style=\"background-color: #fff; border-left: 4px solid %s; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;\"><p>%s</p></div>";
771
+
772
+ //by default we try to fetch the API Key from wp-config.php (if defined)
773
+ if ( !isset($_POST['submit']) && !get_option('wp-short-pixel-verifiedKey') && defined("SHORTPIXEL_API_KEY") && strlen(SHORTPIXEL_API_KEY) == 20 )
774
+ {
775
+ $_POST['validate'] = "validate";
776
+ $_POST['key'] = SHORTPIXEL_API_KEY;
777
+ }
778
+
779
+ if(isset($_POST['submit']) || isset($_POST['validate'])) {
780
+
781
+ //handle API Key - common for submit and validate
782
+ $_POST['key'] = trim(str_replace("*","",$_POST['key']));
783
+
784
+ if ( strlen($_POST['key']) <> 20 )
785
+ {
786
+ $KeyLength = strlen($_POST['key']);
787
+
788
+ printf($noticeHTML, '#ff0000', "The key you provided has " . $KeyLength . " characters. The API key should have 20 characters, letters and numbers only.<BR> <b>Please check that the API key is the same as the one you received in your confirmation email.</b><BR>
789
+ If this problem persists, please contact us at <a href='mailto:support@shortpixel.com?Subject=API Key issues' target='_top'>support@shortpixel.com</a> or <a href='https://shortpixel.com/contact' target='_blank'>here</a>.");
790
+ }
791
+ else
792
+ {
793
+ $validityData = $this->getQuotaInformation($_POST['key'], true);
794
+
795
+ $this->_apiKey = $_POST['key'];
796
+ $this->_apiInterface->setApiKey($this->_apiKey);
797
+ update_option('wp-short-pixel-apiKey', $_POST['key']);
798
+ if($validityData['APIKeyValid']) {
799
+ if(isset($_POST['validate'])) {
800
+ //display notification
801
+ if(in_array($_SERVER["SERVER_ADDR"], array("127.0.0.1","::1"))) {
802
+ printf($noticeHTML, '#FFC800', "API Key is valid but your server seems to have a local address.
803
+ Please make sure that your server is accessible from the Internet before using the API or otherwise we won't be able to optimize them.");
804
+ } else {
805
+
806
+ if ( function_exists("is_multisite") && is_multisite() )
807
+ printf($noticeHTML, '#7ad03a', "API Key valid! <br>You seem to be running a multisite, please note that API Key can also be configured in wp-config.php like this:<BR> <b>define('SHORTPIXEL_API_KEY', '".$this->_apiKey."');</b>");
808
+ else
809
+ printf($noticeHTML, '#7ad03a', 'API Key valid!');
810
+ }
811
+ }
812
+ update_option('wp-short-pixel-verifiedKey', true);
813
+ $this->_verifiedKey = true;
814
+ //test that the "uploads" have the right rights and also we can create the backup dir for ShortPixel
815
+ if ( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) )
816
+ printf($noticeHTML, '#ff0000', "There is something preventing us to create a new folder for backing up your original files.<BR>
817
+ Please make sure that folder <b>" .
818
+ WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads</b> has the necessary write and read rights." );
819
+ } else {
820
+ if(isset($_POST['validate'])) {
821
+ //display notification
822
+ printf($noticeHTML, '#ff0000', $validityData["Message"]);
823
+ }
824
+ update_option('wp-short-pixel-verifiedKey', false);
825
+ $this->_verifiedKey = false;
826
+ }
827
+ }
828
+
829
+
830
+ //if save button - we process the rest of the form elements
831
+ if(isset($_POST['submit'])) {
832
+ update_option('wp-short-pixel-compression', $_POST['compressionType']);
833
+ $this->_compressionType = $_POST['compressionType'];
834
+ $this->_apiInterface->setCompressionType($this->_compressionType);
835
+ if(isset($_POST['thumbnails'])) { $this->_processThumbnails = 1; } else { $this->_processThumbnails = 0; }
836
+ if(isset($_POST['backupImages'])) { $this->_backupImages = 1; } else { $this->_backupImages = 0; }
837
+ if(isset($_POST['cmyk2rgb'])) { $this->_CMYKtoRGBconversion = 1; } else { $this->_CMYKtoRGBconversion = 0; }
838
+ update_option('wp-short-process_thumbnails', $this->_processThumbnails);
839
+ update_option('wp-short-backup_images', $this->_backupImages);
840
+ update_option('wp-short-pixel_cmyk2rgb', $this->_CMYKtoRGBconversion);
841
+ }
842
+ }
843
+
844
+
845
+ //empty backup
846
+ if(isset($_POST['emptyBackup'])) {
847
+ $this->emptyBackup();
848
+ }
849
+
850
+ $checked = '';
851
+ if($this->_processThumbnails) { $checked = 'checked'; }
852
+
853
+ $checkedBackupImages = '';
854
+ if($this->_backupImages) { $checkedBackupImages = 'checked'; }
855
+
856
+ $cmyk2rgb = '';
857
+ if($this->_CMYKtoRGBconversion) { $cmyk2rgb = 'checked'; }
858
+
859
+
860
+ $formHTML = <<< HTML
861
+ <form name='wp_shortpixel_options' action='' method='post' id='wp_shortpixel_options'>
862
+ <table class="form-table">
863
+ <tbody><tr>
864
+ <th scope="row"><label for="key">API Key:</label></th>
865
+ <td><input name="key" type="text" id="key" value="{$this->_apiKey}" class="regular-text">
866
+ <input type="submit" name="validate" id="validate" class="button button-primary" title="Validate the provided API key" value="Validate">
867
+ </td>
868
+ </tr>
869
+ HTML;
870
+
871
+ if(!$this->_verifiedKey) {
872
+
873
+ //if invalid key we display the link to the API Key
874
+ $formHTML .= '<tr><td style="padding-left: 0px;" colspan="2">Don’t have an API Key? <a href="https://shortpixel.com/wp-apikey" target="_blank">Sign up, it’s free.</a></td></tr>';
875
+ $formHTML .= '</form>';
876
+ } else {
877
+ //if valid key we display the rest of the options
878
+ $formHTML .= <<< HTML
879
+ <tr><th scope="row">
880
+ <label for="compressionType">Compression type:</label>
881
+ </th><td>
882
+ HTML;
883
+
884
+ if($this->_compressionType == 1) {
885
+ $formHTML .= '<input type="radio" name="compressionType" value="1" checked>Lossy</br></br>';
886
+ $formHTML .= '<input type="radio" name="compressionType" value="0" >Lossless';
887
+ } else {
888
+ $formHTML .= '<input type="radio" name="compressionType" value="1">Lossy</br></br>';
889
+ $formHTML .= '<input type="radio" name="compressionType" value="0" checked>Lossless';
890
+ }
891
+
892
+ $formHTML .= <<<HTML
893
+ </td>
894
+ </tr>
895
+ </tbody></table>
896
+ <p style="color: #818181;">
897
+ <b>Lossy compression: </b>lossy has a better compression rate than lossless compression.</br>The resulting image
898
+ is not 100% identical with the original. Works well for photos taken with your camera.</br></br>
899
+ <b>Lossless compression: </b> the shrunk image will be identical with the original and smaller in size.</br>Use this
900
+ when you do not want to lose any of the original image's details. Works best for technical drawings,
901
+ clip art and comics.
902
+ </p>
903
+ <table class="form-table">
904
+ <tbody><tr>
905
+ <th scope="row"><label for="thumbnails">Image thumbnails:</label></th>
906
+ <td><input name="thumbnails" type="checkbox" id="thumbnails" {$checked}> Apply compression also to image thumbnails.</td>
907
+ </tr>
908
+ <tr>
909
+ <th scope="row"><label for="backupImages">Image backup</label></th>
910
+ <td>
911
+ <input name="backupImages" type="checkbox" id="backupImages" {$checkedBackupImages}> Save and keep a backup of your original images in a separate folder.
912
+ </td>
913
+ </tr>
914
+ <tr>
915
+ <th scope="row"><label for="backupImages">CMYK to RGB conversion</label></th>
916
+ <td>
917
+ <input name="cmyk2rgb" type="checkbox" id="cmyk2rgb" {$cmyk2rgb}>Adjust your images for computer and mobile screen display.
918
+ </td>
919
+ </tr>
920
+ </tr>
921
+ </tbody></table>
922
+ <p class="submit">
923
+ <input type="submit" name="submit" id="submit" class="button button-primary" title="Save Changes" value="Save Changes">
924
+ <a class="button button-primary" title="Process all the images in your Media Library" href="upload.php?page=wp-short-pixel-bulk">Bulk Process</a>
925
+ </p>
926
+ </form>
927
+ <script>
928
+ var rad = document.wp_shortpixel_options.compressionType;
929
+ var prev = null;
930
+ for(var i = 0; i < rad.length; i++) {
931
+ rad[i].onclick = function() {
932
+
933
+ if(this !== prev) {
934
+ prev = this;
935
+ }
936
+ alert('This type of optimization will apply to new uploaded images. <BR>Images that were already processed will not be re-optimized.');
937
+ };
938
+ }
939
+ </script>
940
+ HTML;
941
+ }
942
+
943
+ echo $formHTML;
944
+
945
+ if($this->_verifiedKey) {
946
+ $fileCount = number_format(get_option('wp-short-pixel-fileCount'));
947
+ $savedSpace = self::formatBytes(get_option('wp-short-pixel-savedSpace'),2);
948
+ $averageCompression = self::getAverageCompression();
949
+ $savedBandwidth = self::formatBytes(get_option('wp-short-pixel-savedSpace') * 10000,2);
950
+ if (is_numeric($quotaData['APICallsQuota'])) {
951
+ $quotaData['APICallsQuota'] .= "/month";
952
+ }
953
+ $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
954
+ $remainingImages = $quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] - $quotaData['APICallsMadeNumeric'] - $quotaData['APICallsMadeOneTimeNumeric'];
955
+ $remainingImages = ( $remainingImages < 0 ) ? 0 : number_format($remainingImages);
956
+ $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
957
+
958
+ $statHTML = <<< HTML
959
+ <a id="facts"></a>
960
+ <h3>Your ShortPixel Stats</h3>
961
+ <table class="form-table">
962
+ <tbody>
963
+ <tr>
964
+ <th scope="row"><label for="averagCompression">Average compression of your files:</label></th>
965
+ <td>$averageCompression%</td>
966
+ </tr>
967
+ <tr>
968
+ <th scope="row"><label for="savedSpace">Saved disk space by ShortPixel</label></th>
969
+ <td>$savedSpace</td>
970
+ </tr>
971
+ <tr>
972
+ <th scope="row"><label for="savedBandwidth">Bandwith* saved with ShortPixel:</label></th>
973
+ <td>$savedBandwidth</td>
974
+ </tr>
975
+ </tbody></table>
976
+
977
+ <p style="padding-top: 0px; color: #818181;" >* Saved bandwidth is calculated at 10,000 impressions/image</p>
978
+
979
+ <h3>Your ShortPixel Plan</h3>
980
+ <table class="form-table">
981
+ <tbody>
982
+ <tr>
983
+ <th scope="row" bgcolor="#ffffff"><label for="apiQuota">Your ShortPixel plan</label></th>
984
+ <td bgcolor="#ffffff">{$quotaData['APICallsQuota']}/month ( <a href="https://shortpixel.com/login/{$this->_apiKey}" target="_blank">Need More? See the options available</a> )
985
+ </tr>
986
+ <tr>
987
+ <th scope="row"><label for="usedQUota">One time credits:</label></th>
988
+ <td>{$quotaData['APICallsQuotaOneTimeNumeric']}</td>
989
+ </tr>
990
+ <tr>
991
+ <th scope="row"><label for="usedQUota">Number of images processed this month:</label></th>
992
+ <td>{$totalCallsMade} (<a href="https://api.shortpixel.com/v2/report.php?key={$this->_apiKey}" target="_blank">see report</a>)</td>
993
+ </tr>
994
+ <tr>
995
+ <th scope="row"><label for="remainingImages">Remaining** images in your plan: </label></th>
996
+ <td>{$remainingImages} images</td>
997
+ </tr>
998
+ </tbody></table>
999
+
1000
+ <p style="padding-top: 0px; color: #818181;" >** Increase your image quota by <a href="https://shortpixel.com/login/{$this->_apiKey}" target="_blank">upgrading</a> your ShortPixel plan.</p>
1001
+
1002
+ <table class="form-table">
1003
+ <tbody>
1004
+ <tr>
1005
+ <th scope="row"><label for="totalFiles">Total number of processed files:</label></th>
1006
+ <td>{$fileCount}</td>
1007
+ </tr>
1008
+
1009
+
1010
+
1011
+ HTML;
1012
+ if($this->_backupImages) {
1013
+ $statHTML .= <<< HTML
1014
+ <form action="" method="POST">
1015
+ <tr>
1016
+ <th scope="row"><label for="sizeBackup">Original images are stored in a backup folder. Your backup folder size is now:</label></th>
1017
+ <td>
1018
+ {$backupFolderSize}
1019
+ <input type="submit" style="margin-left: 15px; vertical-align: middle;" class="button button-secondary" name="emptyBackup" value="Empty backups"/>
1020
+ </td>
1021
+ </tr>
1022
+ </form>
1023
+ HTML;
1024
+ }
1025
+
1026
+ $statHTML .= <<< HTML
1027
+ </tbody></table>
1028
+ HTML;
1029
+
1030
+ echo $statHTML;
1031
+
1032
+
1033
+ }
1034
+
1035
+ }
1036
+
1037
+ public function getAverageCompression(){
1038
+ return get_option('wp-short-pixel-total-optimized') > 0
1039
+ ? round(( 1 - ( get_option('wp-short-pixel-total-optimized') / get_option('wp-short-pixel-total-original') ) ) * 100, 2)
1040
+ : 0;
1041
+ }
1042
+
1043
+ public function getQuotaInformation($apiKey = null, $appendUserAgent = false) {
1044
+
1045
+ if(is_null($apiKey)) { $apiKey = $this->_apiKey; }
1046
+
1047
+ $requestURL = 'https://api.shortpixel.com/v2/api-status.php';
1048
+ $args = array('timeout'=> SP_MAX_TIMEOUT,
1049
+ 'sslverify' => false,
1050
+ 'body' => array('key' => $apiKey)
1051
+ );
1052
+
1053
+ if($appendUserAgent) {
1054
+ $args['body']['useragent'] = "Agent" . urlencode($_SERVER['HTTP_USER_AGENT']);
1055
+ }
1056
+ $response = wp_remote_post($requestURL, $args);
1057
+
1058
+ if(is_wp_error( $response )) //some hosting providers won't allow https:// POST connections so we try http:// as well
1059
+ $response = wp_remote_post(str_replace('https://', 'http://', $requestURL), $args);
1060
+
1061
+ if(is_wp_error( $response ))
1062
+ $response = wp_remote_get(str_replace('https://', 'http://', $requestURL), $args);
1063
+
1064
+ $defaultData = array(
1065
+ "APIKeyValid" => false,
1066
+ "Message" => 'API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to IP 176.9.106.46.<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ',
1067
+ "APICallsMade" => 'Information unavailable. Please check your API key.',
1068
+ "APICallsQuota" => 'Information unavailable. Please check your API key.');
1069
+
1070
+ if(is_object($response) && get_class($response) == 'WP_Error') {
1071
+
1072
+ $urlElements = parse_url($requestURL);
1073
+ $portConnect = @fsockopen($urlElements['host'],8,$errno,$errstr,15);
1074
+ if(!$portConnect)
1075
+ $defaultData['Message'] .= "<BR>Debug info: <i>$errstr</i>";
1076
+
1077
+ return $defaultData;
1078
+ }
1079
+
1080
+ if($response['response']['code'] != 200) {
1081
+ return $defaultData;
1082
+ }
1083
+
1084
+ $data = $response['body'];
1085
+ $data = $this->parseJSON($data);
1086
+
1087
+ if(empty($data)) { return $defaultData; }
1088
+
1089
+ if($data->Status->Code != 2) {
1090
+ $defaultData['Message'] = $data->Status->Message;
1091
+ return $defaultData;
1092
+ }
1093
+
1094
+ if ( ( $data->APICallsMade + $data->APICallsMadeOneTime ) < ( $data->APICallsQuota + $data->APICallsQuotaOneTime ) ) //reset quota exceeded flag -> user is allowed to process more images.
1095
+ update_option('wp-short-pixel-quota-exceeded',0);
1096
+ else
1097
+ update_option('wp-short-pixel-quota-exceeded',1);//activate quota limiting
1098
+
1099
+ return array(
1100
+ "APIKeyValid" => true,
1101
+ "APICallsMade" => number_format($data->APICallsMade) . ' images',
1102
+ "APICallsQuota" => number_format($data->APICallsQuota) . ' images',
1103
+ "APICallsMadeOneTime" => number_format($data->APICallsMadeOneTime) . ' images',
1104
+ "APICallsQuotaOneTime" => number_format($data->APICallsQuotaOneTime) . ' images',
1105
+ "APICallsMadeNumeric" => $data->APICallsMade,
1106
+ "APICallsQuotaNumeric" => $data->APICallsQuota,
1107
+ "APICallsMadeOneTimeNumeric" => $data->APICallsMadeOneTime,
1108
+ "APICallsQuotaOneTimeNumeric" => $data->APICallsQuotaOneTime
1109
+ );
1110
+
1111
+
1112
+ }
1113
+
1114
+ public function generateCustomColumn( $column_name, $id ) {
1115
+ if( 'wp-shortPixel' == $column_name ) {
1116
+ $data = wp_get_attachment_metadata($id);
1117
+ $file = get_attached_file($id);
1118
+ $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
1119
+
1120
+ print "<div id='sp-msg-{$id}'>";
1121
+
1122
+ if ( empty($data) )
1123
+ {
1124
+ if ( $fileExtension <> "pdf" )
1125
+ {
1126
+ if(!$this->_verifiedKey)
1127
+ print 'Invalid API Key. <a href="options-general.php?page=wp-shortpixel">Check your Settings</a>';
1128
+ else
1129
+ print 'Optimization N/A';
1130
+ }
1131
+ else
1132
+ {
1133
+ if ( get_option('wp-short-pixel-quota-exceeded') )
1134
+ {
1135
+ print QUOTA_EXCEEDED;
1136
+ return;
1137
+ }
1138
+ else
1139
+ {
1140
+ print 'PDF not processed';
1141
+ print " | <a href=\"javascript:manualOptimization({$id})\">Optimize now</a>";
1142
+ return;
1143
+ }
1144
+ }
1145
+ }
1146
+ elseif ( isset( $data['ShortPixelImprovement'] ) )
1147
+ {
1148
+ if(isset($meta['ShortPixel']['BulkProcessing']))
1149
+ {
1150
+ if ( get_option('wp-short-pixel-quota-exceeded') )
1151
+ {
1152
+ print QUOTA_EXCEEDED;
1153
+ }
1154
+ else
1155
+ {
1156
+ print 'Waiting for bulk processing';
1157
+ print " | <a href=\"javascript:manualOptimization({$id})\">Optimize now</a>";
1158
+ }
1159
+ }
1160
+ elseif( is_numeric($data['ShortPixelImprovement']) && !isset($data['ShortPixel']['NoBackup']) ) {
1161
+
1162
+ if ( $data['ShortPixelImprovement'] < 5 )
1163
+ {
1164
+ print $data['ShortPixelImprovement'] . '%';
1165
+ print " optimized<BR> Bonus processing";
1166
+
1167
+ }
1168
+ else
1169
+ {
1170
+ print 'Reduced by ';
1171
+ print $data['ShortPixelImprovement'] . '%';
1172
+ }
1173
+ if ( get_option('wp-short-backup_images') ) //display restore backup option only when backup is active
1174
+ print " | <a href=\"admin.php?action=shortpixel_restore_backup&amp;attachment_ID={$id}\">Restore backup</a>";
1175
+ }
1176
+ elseif ( is_numeric($data['ShortPixelImprovement']) )
1177
+ {
1178
+ if ( $data['ShortPixelImprovement'] < 5 )
1179
+ {
1180
+ print $data['ShortPixelImprovement'] . '%';
1181
+ print " optimized<BR> Bonus processing";
1182
+
1183
+ }
1184
+ else
1185
+ {
1186
+ print 'Reduced by ';
1187
+ print $data['ShortPixelImprovement'] . '%';
1188
+ }
1189
+ }
1190
+ elseif ( $data['ShortPixelImprovement'] <> "Optimization N/A" )
1191
+ {
1192
+ if ( trim(strip_tags($data['ShortPixelImprovement'])) == "Quota exceeded" )
1193
+ {
1194
+ print QUOTA_EXCEEDED;
1195
+ if ( !get_option('wp-short-pixel-quota-exceeded') )
1196
+ print " | <a href=\"javascript:manualOptimization({$id})\">Try again</a>";
1197
+ }
1198
+ elseif ( trim(strip_tags($data['ShortPixelImprovement'])) == "Cannot write optimized file" )
1199
+ {
1200
+ print $data['ShortPixelImprovement'];
1201
+ print " - <a href='https://shortpixel.com/faq#cannot-write-optimized-file' target='_blank'>Why?</a>";
1202
+ }
1203
+ else
1204
+ {
1205
+ print $data['ShortPixelImprovement'];
1206
+ print " | <a href=\"javascript:manualOptimization({$id})\">Try again</a>";
1207
+ }
1208
+ }
1209
+ else
1210
+ {
1211
+ print "Optimization N/A";
1212
+ }
1213
+ } elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
1214
+ if ( get_option('wp-short-pixel-quota-exceeded') )
1215
+ {
1216
+ print QUOTA_EXCEEDED;
1217
+ }
1218
+ else
1219
+ {
1220
+ print "<img src=\"" . WP_PLUGIN_URL . "/shortpixel-image-optimiser/img/loading.gif\">Image waiting to be processed
1221
+ | <a href=\"javascript:manualOptimization({$id})\">Retry</a></div>";
1222
+ $this->prioQ->push($id); //should be there but just to make sure
1223
+ }
1224
+
1225
+ } elseif(isset($data['ShortPixel']['NoFileOnDisk'])) {
1226
+ print 'Image does not exist';
1227
+
1228
+ } else {
1229
+
1230
+ if ( wp_attachment_is_image( $id ) )
1231
+ {
1232
+ if ( get_option('wp-short-pixel-quota-exceeded') )
1233
+ {
1234
+ print QUOTA_EXCEEDED;
1235
+ }
1236
+ else
1237
+ {
1238
+ print 'Image not processed';
1239
+ print " | <a href=\"javascript:manualOptimization({$id})\">Optimize now</a>";
1240
+ }
1241
+ }
1242
+ elseif ( $fileExtension == "pdf" )
1243
+ {
1244
+ if ( get_option('wp-short-pixel-quota-exceeded') )
1245
+ {
1246
+ print QUOTA_EXCEEDED;
1247
+ }
1248
+ else
1249
+ {
1250
+ print 'PDF not processed';
1251
+ print " | <a href=\"javascript:manualOptimization({$id})\">Optimize now</a>";
1252
+ }
1253
+ }
1254
+ }
1255
+ print "</div>";
1256
+ }
1257
+ }
1258
+
1259
+ public function columns( $defaults ) {
1260
+ $defaults['wp-shortPixel'] = 'ShortPixel Compression';
1261
+ return $defaults;
1262
+ }
1263
+
1264
+ public function generatePluginLinks($links) {
1265
+ $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
1266
+ array_unshift($links, $in);
1267
+ return $links;
1268
+ }
1269
+
1270
+ public function parseJSON($data) {
1271
+ if ( function_exists('json_decode') ) {
1272
+ $data = json_decode( $data );
1273
+ } else {
1274
+ require_once( 'JSON/JSON.php' );
1275
+ $json = new Services_JSON( );
1276
+ $data = $json->decode( $data );
1277
+ }
1278
+ return $data;
1279
+ }
1280
+
1281
+
1282
+ static public function formatBytes($bytes, $precision = 2) {
1283
+ $units = array('B', 'KB', 'MB', 'GB', 'TB');
1284
+
1285
+ $bytes = max($bytes, 0);
1286
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
1287
+ $pow = min($pow, count($units) - 1);
1288
+
1289
+ $bytes /= pow(1024, $pow);
1290
+
1291
+ return round($bytes, $precision) . ' ' . $units[$pow];
1292
+ }
1293
+
1294
+ static public function isProcessable($ID) {
1295
+ $path = get_attached_file($ID);//get the full file PATH
1296
+ $pathParts = pathinfo($path);
1297
+ if( isset($pathParts['extension']) && in_array(strtolower($pathParts['extension']), array('jpg', 'jpeg', 'gif', 'png', 'pdf'))) {
1298
+ return true;
1299
+ } else {
1300
+ return false;
1301
+ }
1302
+ }
1303
+
1304
+
1305
+ //return an array with URL(s) and PATH(s) for this file
1306
+ public function getURLsAndPATHs($ID, $meta = NULL) {
1307
+
1308
+ if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) )
1309
+ {//no absolute URLs used -> we implement a hack
1310
+ $url = get_site_url() . wp_get_attachment_url($ID);//get the file URL
1311
+ }
1312
+ else
1313
+ $url = wp_get_attachment_url($ID);//get the file URL
1314
+
1315
+ $urlList[] = $url;
1316
+ $path = get_attached_file($ID);//get the full file PATH
1317
+ $filePath[] = $path;
1318
+ if ( $meta == NULL ) {
1319
+ $meta = wp_get_attachment_metadata($ID);
1320
+ }
1321
+
1322
+ //it is NOT a PDF file and thumbs are processable
1323
+ if ( strtolower(substr($filePath[0],strrpos($filePath[0], ".")+1)) != "pdf"
1324
+ && $this->_processThumbnails
1325
+ && isset($meta['sizes']) && is_array($meta['sizes']))
1326
+ {
1327
+ foreach( $meta['sizes'] as $thumbnailInfo )
1328
+ {
1329
+ $urlList[] = str_replace(ShortPixelAPI::MB_basename($urlList[0]), $thumbnailInfo['file'], $url);
1330
+ $filePath[] = str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $path);
1331
+ }
1332
+ }
1333
+ if(!isset($meta['sizes']) || !is_array($meta['sizes'])) {
1334
+ self::log("getURLsAndPATHs: no meta sizes for ID $ID : " . json_encode($meta));
1335
+ }
1336
+ return array("URLs" => $urlList, "PATHs" => $filePath);
1337
+ }
1338
+
1339
+
1340
+ public static function deleteDir($dirPath) {
1341
+ if (substr($dirPath, strlen($dirPath) - 1, 1) !=
1342
+ '/') {
1343
+ $dirPath .= '/';
1344
+ }
1345
+ $files = glob($dirPath . '*', GLOB_MARK);
1346
+ foreach ($files as $file) {
1347
+ if (is_dir($file)) {
1348
+ self::deleteDir($file);
1349
+ @rmdir($file);//remove empty dir
1350
+ } else {
1351
+ @unlink($file);//remove file
1352
+ }
1353
+ }
1354
+ }
1355
+
1356
+ static public function folderSize($path) {
1357
+ $total_size = 0;
1358
+ if(file_exists($path)) {
1359
+ $files = scandir($path);
1360
+ } else {
1361
+ return $total_size;
1362
+ }
1363
+ $cleanPath = rtrim($path, '/'). '/';
1364
+ foreach($files as $t) {
1365
+ if ($t<>"." && $t<>"..")
1366
+ {
1367
+ $currentFile = $cleanPath . $t;
1368
+ if (is_dir($currentFile)) {
1369
+ $size = self::folderSize($currentFile);
1370
+ $total_size += $size;
1371
+ }
1372
+ else {
1373
+ $size = filesize($currentFile);
1374
+ $total_size += $size;
1375
+ }
1376
+ }
1377
+ }
1378
+ return $total_size;
1379
+ }
1380
+
1381
+ public function getMaxMediaId() {
1382
+ global $wpdb;
1383
+ $queryMax = "SELECT max(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
1384
+ $resultQuery = $wpdb->get_results($queryMax);
1385
+ return $resultQuery[0]->QueryID;
1386
+ }
1387
+
1388
+ public function getMinMediaId() {
1389
+ global $wpdb;
1390
+ $queryMax = "SELECT min(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
1391
+ $resultQuery = $wpdb->get_results($queryMax);
1392
+ return $resultQuery[0]->QueryID;
1393
+ }
1394
+
1395
+ //count all the processable files in media library (while limiting the results to max 10000)
1396
+ public function countAllProcessableFiles($maxId = PHP_INT_MAX, $minId = 0){
1397
+ global $wpdb;
1398
+
1399
+ $totalFiles = 0;
1400
+ $mainFiles = 0;
1401
+ $limit = 500;
1402
+ $pointer = 0;
1403
+
1404
+ //count all the files, main and thumbs
1405
+ while ( 1 )
1406
+ {
1407
+ $filesList= $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "postmeta
1408
+ WHERE ( post_id <= $maxId AND post_id > $minId )
1409
+ AND ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' )
1410
+ LIMIT $pointer,$limit");
1411
+ if ( empty($filesList) ) //we parsed all the results
1412
+ break;
1413
+
1414
+ foreach ( $filesList as $file )
1415
+ {
1416
+ if ( $file->meta_key == "_wp_attached_file" )
1417
+ {//count pdf files only
1418
+ $extension = substr($file->meta_value, strrpos($file->meta_value,".") + 1 );
1419
+ if ( $extension == "pdf" )
1420
+ {
1421
+ $totalFiles++;
1422
+ $mainFiles++;
1423
+ }
1424
+ }
1425
+ else
1426
+ {
1427
+ $attachment = unserialize($file->meta_value);
1428
+ if ( isset($attachment['sizes']) )
1429
+ $totalFiles += count($attachment['sizes']);
1430
+
1431
+ if ( isset($attachment['file']) )
1432
+ {
1433
+ $totalFiles++;
1434
+ $mainFiles++;
1435
+ }
1436
+ }
1437
+ }
1438
+ unset($filesList);
1439
+ $pointer += $limit;
1440
+
1441
+ }//end while
1442
+
1443
+ return array("totalFiles" => $totalFiles, "mainFiles" => $mainFiles);
1444
+ }
1445
+
1446
+
1447
+ //count all the processable files in media library (while limiting the results to max 10000)
1448
+ public function countAllProcessedFiles($maxId = PHP_INT_MAX, $minId = 0){
1449
+ global $wpdb;
1450
+
1451
+ $processedMainFiles = $processedTotalFiles = 0;
1452
+ $limit = 500;
1453
+ $pointer = 0;
1454
+
1455
+ //count all the files, main and thumbs
1456
+ while ( 1 )
1457
+ {
1458
+ $filesList= $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "postmeta
1459
+ WHERE ( post_id <= $maxId AND post_id > $minId )
1460
+ AND ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' )
1461
+ LIMIT $pointer,$limit");
1462
+ if ( empty($filesList) ) {//we parsed all the results
1463
+ break;
1464
+ }
1465
+ foreach ( $filesList as $file )
1466
+ {
1467
+ if ( $file->meta_key == "_wp_attached_file" ) {
1468
+ continue;
1469
+ }
1470
+ $attachment = unserialize($file->meta_value);
1471
+ if ( isset($attachment['ShortPixelImprovement']) && $attachment['ShortPixelImprovement'] > 0 ) {
1472
+ $processedMainFiles++;
1473
+ $processedTotalFiles++;
1474
+ if ( isset($attachment['sizes']) ) {
1475
+ $processedTotalFiles += count($attachment['sizes']);
1476
+ }
1477
+ }
1478
+ }
1479
+ unset($filesList);
1480
+ $pointer += $limit;
1481
+
1482
+ }//end while
1483
+
1484
+ return array("totalFiles" => $processedTotalFiles, "mainFiles" => $processedMainFiles);
1485
+ }
1486
+
1487
+ public function migrateBackupFolder() {
1488
+ $oldBackupFolder = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'ShortpixelBackups';
1489
+
1490
+ if(!file_exists($oldBackupFolder)) return; //if old backup folder does not exist then there is nothing to do
1491
+
1492
+ if(!file_exists(SP_BACKUP_FOLDER)) {
1493
+ //we check that the backup folder exists, if not we create it so we can copy into it
1494
+ if(!mkdir(SP_BACKUP_FOLDER, 0777, true)) return;
1495
+ }
1496
+
1497
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1498
+ foreach($scannedDirectory as $file) {
1499
+ @rename($oldBackupFolder.DIRECTORY_SEPARATOR.$file, SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.$file);
1500
+ }
1501
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1502
+ if(empty($scannedDirectory)) {
1503
+ @rmdir($oldBackupFolder);
1504
+ }
1505
+
1506
+ return;
1507
+ }
1508
+
1509
+ public function getApiKey() {
1510
+ return $this->_apiKey;
1511
+ }
1512
+
1513
+ public function backupImages() {
1514
+ return $this->_backupImages;
1515
+ }
1516
+
1517
+ public function processThumbnails() {
1518
+ return $this->_processThumbnails;
1519
+ }
1520
+
1521
+ }
1522
+
1523
+ $pluginInstance = new WPShortPixel();
1524
+ global $pluginInstance;
1525
+
1526
+ ?>