Kraken.io Image Optimizer - Version 1.0.3

Version Description

  • Bulk Actions menu in Media Library is now extended with "Krak 'em all", our Bulk Optimization feature.
  • Fixed a bug which caused old images' thumbnails to not be optimized.
  • Fixed a failure condition which occured only on WPEngine-hosted systems.
Download this release

Release Info

Developer karim79
Plugin Icon 128x128 Kraken.io Image Optimizer
Version 1.0.3
Comparing to
See all releases

Code changes from version 1.0.2.1 to 1.0.3

Files changed (7) hide show
  1. css/admin.css +69 -1
  2. css/jquery.modal.css +46 -0
  3. js/ajax.js +241 -14
  4. js/async.js +958 -0
  5. js/jquery.modal.min.js +5 -0
  6. kraken.php +67 -24
  7. readme.txt +14 -2
css/admin.css CHANGED
@@ -34,7 +34,7 @@
34
  float: left;
35
  clear: left;
36
  }
37
- .kraken_req {
38
  border: 1px solid #efab00;
39
  color: #222;
40
  padding: 3px 8px 4px;
@@ -52,6 +52,9 @@
52
  cursor: pointer;
53
  font-weight: bold;
54
  }
 
 
 
55
  .buttonWrap {
56
  position: relative;
57
  margin-bottom: 5px;
@@ -85,6 +88,17 @@ p.apiStatus {
85
  top: 5px;
86
  background: url('https://kraken.r.worldssl.net/assets/images/spinner.gif') no-repeat 0 0;
87
  }
 
 
 
 
 
 
 
 
 
 
 
88
  .krakenErrorWrap {
89
  margin-top: 10px;
90
  margin-bottom: 4px;
@@ -96,4 +110,58 @@ p.apiStatus {
96
  margin-left: 2px;
97
  font-weight: bold;
98
  color: #dd3d36;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
34
  float: left;
35
  clear: left;
36
  }
37
+ .kraken_req, .kraken_req_bulk {
38
  border: 1px solid #efab00;
39
  color: #222;
40
  padding: 3px 8px 4px;
52
  cursor: pointer;
53
  font-weight: bold;
54
  }
55
+ .kraken_req_bulk {
56
+ font-size: 16px;
57
+ }
58
  .buttonWrap {
59
  position: relative;
60
  margin-bottom: 5px;
88
  top: 5px;
89
  background: url('https://kraken.r.worldssl.net/assets/images/spinner.gif') no-repeat 0 0;
90
  }
91
+ .krakenBulkSpinner {
92
+ display: none;
93
+ padding: 0;
94
+ margin: 0;
95
+ width: 16px;
96
+ height: 16px;
97
+ background: url('https://kraken.r.worldssl.net/assets/images/spinner.gif') no-repeat 0 0;
98
+ }
99
+ .visible {
100
+ display: inline-block;
101
+ }
102
  .krakenErrorWrap {
103
  margin-top: 10px;
104
  margin-bottom: 4px;
110
  margin-left: 2px;
111
  font-weight: bold;
112
  color: #dd3d36;
113
+ }
114
+ .krakenBulkHeader {
115
+ font-size: 16px;
116
+ margin-bottom: 10px;
117
+ font-weight: bold;
118
+ }
119
+ #kraken-bulk-modal {
120
+ position: absolute !important;
121
+ top: 100px;
122
+ width: 785px;
123
+ }
124
+ #kraken-bulk-modal label {
125
+ margin-bottom: 11px;
126
+ }
127
+ #kraken-bulk {
128
+ width: 780px;
129
+ margin: 0 auto;
130
+ margin-bottom: 10px;
131
+ border-spacing: 1px;
132
+ border-collapse: separate;
133
+ }
134
+ .kraken-bulk-header td {
135
+ font-weight: bold;
136
+ font-size: 14px;
137
+ padding: 4px 0 4px 2px;
138
+ }
139
+ td.kraken-krakedsize {
140
+ text-align: left;
141
+ }
142
+ tr.kraken-item-row td {
143
+ padding: 2px;
144
+ height: 20px;
145
+ }
146
+ span.kraken-bulk-choose-type {
147
+ font-weight: bold;
148
+ margin-right: 5px;
149
+ }
150
+ #kraken-bulk-type-lossy {
151
+ margin-right: 5px;
152
+ }
153
+ .close-kraken-bulk {
154
+ font-size: 12px;
155
+ margin-left: 10px;
156
+ font-weight: bold;
157
+ cursor: pointer;
158
+ }
159
+ .radiosWrap {
160
+ display: inline-block;
161
+ margin: 0 0 5px 4px;
162
+ }
163
+ .kraken-bulk-small {
164
+ font-size: 12px;
165
+ display: inline-block;
166
+ margin: 0 0 4px 4px;
167
  }
css/jquery.modal.css ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .modal {
2
+ display: none;
3
+ width: 600px;
4
+ background: #fff;
5
+ padding: 15px 30px;
6
+ -webkit-border-radius: 8px;
7
+ -moz-border-radius: 8px;
8
+ -o-border-radius: 8px;
9
+ -ms-border-radius: 8px;
10
+ border-radius: 8px;
11
+ -webkit-box-shadow: 0 0 10px #000;
12
+ -moz-box-shadow: 0 0 10px #000;
13
+ -o-box-shadow: 0 0 10px #000;
14
+ -ms-box-shadow: 0 0 10px #000;
15
+ box-shadow: 0 0 10px #000;
16
+ }
17
+
18
+ /*
19
+ .modal a.close-modal {
20
+ position: absolute;
21
+ top: -12.5px;
22
+ right: -12.5px;
23
+ display: block;
24
+ width: 30px;
25
+ height: 30px;
26
+ text-indent: -9999px;
27
+ background: url(close.png) no-repeat 0 0;
28
+ }
29
+ */
30
+
31
+ .modal-spinner {
32
+ display: none;
33
+ width: 64px;
34
+ height: 64px;
35
+ position: fixed;
36
+ top: 50%;
37
+ left: 50%;
38
+ margin-right: -32px;
39
+ margin-top: -32px;
40
+ background: url(spinner.gif) #111 no-repeat center center;
41
+ -webkit-border-radius: 8px;
42
+ -moz-border-radius: 8px;
43
+ -o-border-radius: 8px;
44
+ -ms-border-radius: 8px;
45
+ border-radius: 8px;
46
+ }
js/ajax.js CHANGED
@@ -45,18 +45,19 @@ jQuery(document).ready(function ($) {
45
  fade: true,
46
  gravity: 'e'
47
  });
48
-
49
  var data = {
50
  action: 'kraken_request'
51
- }
52
-
53
- ,
54
 
55
  errorTpl = '<div class="krakenErrorWrap"><a class="krakenError">Failed! Hover here</a></div>'
56
-
57
- ,
 
 
58
 
59
- requestSuccess = function (data, textStatus, jqXHR) {
 
60
  var $button = $(this)
61
  , $parent = $(this).parent()
62
  , $cell = $(this).closest("td");
@@ -66,7 +67,6 @@ jQuery(document).ready(function ($) {
66
  $button.text("Image optimized");
67
 
68
  var type = data.type
69
- , originalSize = data.original_size
70
  , krakedSize = data.kraked_size
71
  , originalSize = data.original_size
72
  , savingsPercent = data.savings_percent
@@ -107,13 +107,13 @@ jQuery(document).ready(function ($) {
107
  opacity: 1
108
  });
109
  }
110
- },
111
 
112
- requestFail = function (jqXHR, textStatus, errorThrown) {
113
  $(this).removeAttr("disabled");
114
- },
115
 
116
- requestComplete = function (jqXHR, textStatus, errorThrown) {
117
  $(this).removeAttr("disabled");
118
  $(this)
119
  .parent()
@@ -121,6 +121,233 @@ jQuery(document).ready(function ($) {
121
  .css("display", "none");
122
  };
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  $(".kraken_req").click(function (e) {
126
  e.preventDefault();
@@ -133,7 +360,7 @@ jQuery(document).ready(function ($) {
133
  .text("Optimizing image...")
134
  .attr("disabled", true)
135
  .css({
136
- opacity: "0.5"
137
  });
138
 
139
 
@@ -147,7 +374,7 @@ jQuery(document).ready(function ($) {
147
  data: data,
148
  type: "post",
149
  dataType: "json",
150
- timeout: 120000,
151
  context: $button
152
  })
153
 
45
  fade: true,
46
  gravity: 'e'
47
  });
48
+
49
  var data = {
50
  action: 'kraken_request'
51
+ },
 
 
52
 
53
  errorTpl = '<div class="krakenErrorWrap"><a class="krakenError">Failed! Hover here</a></div>'
54
+ , $btnApplyBulkAction = $("#doaction")
55
+ , $btnApplyBulkAction2 = $("#doaction2")
56
+ , $topBulkActionDropdown = $(".tablenav.top .bulkactions select[name='action']")
57
+ , $bottomBulkActionDropdown = $(".tablenav.bottom .bulkactions select[name='action2']");
58
 
59
+
60
+ var requestSuccess = function (data, textStatus, jqXHR) {
61
  var $button = $(this)
62
  , $parent = $(this).parent()
63
  , $cell = $(this).closest("td");
67
  $button.text("Image optimized");
68
 
69
  var type = data.type
 
70
  , krakedSize = data.kraked_size
71
  , originalSize = data.original_size
72
  , savingsPercent = data.savings_percent
107
  opacity: 1
108
  });
109
  }
110
+ };
111
 
112
+ var requestFail = function (jqXHR, textStatus, errorThrown) {
113
  $(this).removeAttr("disabled");
114
+ };
115
 
116
+ var requestComplete = function (jqXHR, textStatus, errorThrown) {
117
  $(this).removeAttr("disabled");
118
  $(this)
119
  .parent()
121
  .css("display", "none");
122
  };
123
 
124
+ var opts = '<option value="kraken-bulk-lossy">' + "Krak 'em all" + '</option>';
125
+
126
+ $topBulkActionDropdown.find("option:last-child").before(opts);
127
+ $bottomBulkActionDropdown.find("option:last-child").before(opts);
128
+
129
+
130
+ var getBulkImageData = function () {
131
+ var $rows = $("tr[id^='post-']")
132
+ , $row = null
133
+ , postId = 0
134
+ , imageDateItem = {}
135
+ , $krakBtn = null
136
+ , btnData = {}
137
+ , originalSize = ''
138
+ , rv = [];
139
+ $rows.each(function () {
140
+ $row = $(this);
141
+ postId = this.id.replace(/^\D+/g, '');
142
+ if ($row.find("input[type='checkbox'][value='" + postId + "']:checked").length) {
143
+ $krakBtn = $row.find(".kraken_req");
144
+ if ($krakBtn.length) {
145
+ btnData = $krakBtn.data();
146
+ originalSize = $.trim($row.find('td.original_size').text());
147
+ btnData.originalSize = originalSize;
148
+ rv.push(btnData);
149
+ }
150
+ }
151
+ });
152
+ return rv;
153
+ };
154
+
155
+ var renderBulkImageSummary = function (bulkImageData) {
156
+ var modalOptions = {
157
+ zIndex: 4,
158
+ escapeClose: true,
159
+ clickClose: false,
160
+ closeText: 'close',
161
+ showClose: false
162
+ }
163
+ , setting = $("button.kraken_req").eq(0).data("setting")
164
+ , nImages = bulkImageData.length
165
+ , header = '<p class="krakenBulkHeader">Kraken Bulk Image Optimization</p>'
166
+ , krakEmAll = '<button class="kraken_req_bulk">Krak \'em all</button>'
167
+ , typeRadios = '<span class="radiosWrap"><span class="kraken-bulk-choose-type">Choose:</span>'
168
+ + '<input type="radio" id="kraken-bulk-type-lossy" value="Lossy" name="kraken-bulk-type"/>'
169
+ + '<label for="kraken-bulk-type-lossy">Lossy</label>&nbsp;'
170
+ + '<input type="radio" id="kraken-bulk-type-lossless" value="Lossless" name="kraken-bulk-type"/>'
171
+ + '<label for="kraken-bulk-type-lossless">Lossless</label></span>'
172
+ , $modal = $('<div id="kraken-bulk-modal" class="modal"></div>')
173
+ .html(header)
174
+ .append(typeRadios)
175
+ .append('<br /><small class="kraken-bulk-small">The following <strong>' + nImages + '</strong> images will be optimized by Kraken.io using the <strong class="bulkSetting">' + setting + '</strong> setting:</small><br />')
176
+ .appendTo("body")
177
+ .modal(modalOptions)
178
+ .bind($.modal.BEFORE_CLOSE, function (event, modal) {
179
+
180
+ })
181
+ .bind($.modal.OPEN, function (event, modal) {
182
+
183
+ })
184
+ .bind($.modal.CLOSE, function (event, modal) {
185
+ $("#kraken-bulk-modal").remove();
186
+ })
187
+ .css({
188
+ top: "10px",
189
+ marginTop: "40px"
190
+ });
191
+
192
+ if (setting === 'lossy') {
193
+ $("#kraken-bulk-type-lossy").attr("checked", true);
194
+ } else {
195
+ $("#kraken-bulk-type-lossless").attr("checked", true);
196
+ }
197
+
198
+ $bulkSettingSpan = $(".bulkSetting");
199
+ $("input[name='kraken-bulk-type']").change(function () {
200
+ var text = this.id === "kraken-bulk-type-lossy" ? "lossy" : "lossless";
201
+ $bulkSettingSpan.text(text);
202
+ });
203
+
204
+ // to prevent close on clicking overlay div
205
+ $(".jquery-modal.blocker").click(function (e) {
206
+ return false;
207
+ });
208
+
209
+ // otherwise media submenu shows through modal overlay
210
+ $("#menu-media ul.wp-submenu").css({
211
+ "z-index" : 1
212
+ });
213
+
214
+ var $table= $('<table id="kraken-bulk"></table>')
215
+ , $headerRow = $('<tr class="kraken-bulk-header"><td>File</td><td style="width:120px">Original Size</td><td style="width:120px">Kraked Size</td><td style="width:120px">Savings</td><td style="width:120px">% Savings</td></tr>');
216
+
217
+ $table.append($headerRow);
218
+ $.each(bulkImageData, function(index, element) {
219
+ $table.append('<tr class="kraken-item-row" data-krakenbulkid="' + element.id + '"><td class="kraken-filename">' + element.filename + '</td><td class="kraken-originalsize">' + element.originalSize + '</td><td class="kraken-krakedsize"><span class="krakenBulkSpinner hidden"></span></td><td class="kraken-savings"></td><td class="kraken-savingsPercent"></td></tr>');
220
+ });
221
+
222
+ $modal
223
+ .append($table)
224
+ .append(krakEmAll)
225
+ .append('<span class="close-kraken-bulk">Close Window</span>');
226
+
227
+ $(".close-kraken-bulk").click(function () {
228
+ $.modal.close();
229
+ });
230
+
231
+ if (!nImages) {
232
+ $(".kraken_req_bulk")
233
+ .attr("disabled", true)
234
+ .css({
235
+ opacity: 0.5
236
+ });
237
+ }
238
+ };
239
+
240
+ var bulkAction = function (bulkImageData) {
241
+
242
+ $bulkTable = $("#kraken-bulk");
243
+ var jqxhr = null;
244
+
245
+ var q = async.queue(function (task, callback) {
246
+ var id = task.id
247
+ , filename = task.filename;
248
+
249
+ var $row = $bulkTable.find("tr[data-krakenbulkid='" + id + "']")
250
+ , $krakedSizeColumn = $row.find(".kraken-krakedsize")
251
+ , $spinner = $krakedSizeColumn
252
+ .find(".krakenBulkSpinner")
253
+ .css({ display: "inline-block" })
254
+ , $savingsPercentColumn = $row.find(".kraken-savingsPercent")
255
+ , $savingsBytesColumn = $row.find(".kraken-savings");
256
+
257
+ jqxhr = $.ajax({
258
+ url: ajax_object.ajax_url,
259
+ data: { 'action' : 'kraken_request', 'id' : id, 'type' : $("input[name='kraken-bulk-type']:checked").val().toLowerCase() },
260
+ type: "post",
261
+ dataType: "json",
262
+ timeout: 120000
263
+ })
264
+ .done(function (data, textStatus, jqXHR) {
265
+ if (data.success && typeof data.error === 'undefined') {
266
+ var type = data.type
267
+ , originalSize = data.original_size
268
+ , krakedSize = data.kraked_size
269
+ , savingsPercent = data.savings_percent
270
+ , savingsBytes = data.saved_bytes;
271
+
272
+ $krakedSizeColumn.text(krakedSize);
273
+ $savingsPercentColumn.text(savingsPercent);
274
+ $savingsBytesColumn.text(savingsBytes);
275
+
276
+ var $button = $("button[id='krakenid-" + id + "']")
277
+ , $parent = $button.parent()
278
+ , $cell = $button.closest("td")
279
+ , $originalSizeColumn = $button.parent().prev("td.original_size")
280
+
281
+
282
+ $parent.fadeOut("fast", function () {
283
+ $cell.find(".noSavings, .krakenErrorWrap").remove();
284
+ krakedData = '<strong>' + krakedSize + '</strong><br /><small>Type:&nbsp;' + type + '</small><br /><small>Savings: ' + savingsPercent + '</small>';
285
+ if (typeof data.thumbs_data !== 'undefined') {
286
+ krakedData += '<br /><small>' + data.thumbs_data.length + ' thumbs optimized</small>';
287
+ }
288
+ $(this).replaceWith(krakedData);
289
+ $originalSizeColumn.html(originalSize);
290
+ $parent.remove();
291
+ });
292
+
293
+ } else if (data.error) {
294
+ if (data.error === 'This image can not be optimized any further') {
295
+ $krakedSizeColumn.text('No savings found.');
296
+ } else {
297
+
298
+ }
299
+ }
300
+
301
+ })
302
+
303
+ .fail(function () {
304
+
305
+ })
306
+
307
+ .always(function () {
308
+ $spinner.css({ display: "none" });
309
+ callback();
310
+ });
311
+ }, 5);
312
+
313
+ q.drain = function() {
314
+ $(".kraken_req_bulk")
315
+ .removeAttr("disabled")
316
+ .css({
317
+ opacity: 1
318
+ })
319
+ .text("Done")
320
+ .unbind("click")
321
+ .click(function () {
322
+ $.modal.close();
323
+ });
324
+ }
325
+
326
+ // add some items to the queue (batch-wise)
327
+ q.push(bulkImageData, function (err) {
328
+
329
+ });
330
+ };
331
+
332
+
333
+ $btnApplyBulkAction.add($btnApplyBulkAction2)
334
+ .click(function (e) {
335
+ if ($(this).prev("select").val() === 'kraken-bulk-lossy') {
336
+ e.preventDefault();
337
+ var bulkImageData = getBulkImageData();
338
+ renderBulkImageSummary(bulkImageData);
339
+
340
+ $('.kraken_req_bulk').click(function (e) {
341
+ e.preventDefault();
342
+ $(this)
343
+ .attr("disabled", true)
344
+ .css({
345
+ opacity: 0.5
346
+ });
347
+ bulkAction(bulkImageData);
348
+ });
349
+ }
350
+ });
351
 
352
  $(".kraken_req").click(function (e) {
353
  e.preventDefault();
360
  .text("Optimizing image...")
361
  .attr("disabled", true)
362
  .css({
363
+ opacity: 0.5
364
  });
365
 
366
 
374
  data: data,
375
  type: "post",
376
  dataType: "json",
377
+ timeout: 180000,
378
  context: $button
379
  })
380
 
js/async.js ADDED
@@ -0,0 +1,958 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*global setImmediate: false, setTimeout: false, console: false */
2
+ (function () {
3
+
4
+ var async = {};
5
+
6
+ // global on the server, window in the browser
7
+ var root, previous_async;
8
+
9
+ root = this;
10
+ if (root != null) {
11
+ previous_async = root.async;
12
+ }
13
+
14
+ async.noConflict = function () {
15
+ root.async = previous_async;
16
+ return async;
17
+ };
18
+
19
+ function only_once(fn) {
20
+ var called = false;
21
+ return function() {
22
+ if (called) throw new Error("Callback was already called.");
23
+ called = true;
24
+ fn.apply(root, arguments);
25
+ }
26
+ }
27
+
28
+ //// cross-browser compatiblity functions ////
29
+
30
+ var _each = function (arr, iterator) {
31
+ if (arr.forEach) {
32
+ return arr.forEach(iterator);
33
+ }
34
+ for (var i = 0; i < arr.length; i += 1) {
35
+ iterator(arr[i], i, arr);
36
+ }
37
+ };
38
+
39
+ var _map = function (arr, iterator) {
40
+ if (arr.map) {
41
+ return arr.map(iterator);
42
+ }
43
+ var results = [];
44
+ _each(arr, function (x, i, a) {
45
+ results.push(iterator(x, i, a));
46
+ });
47
+ return results;
48
+ };
49
+
50
+ var _reduce = function (arr, iterator, memo) {
51
+ if (arr.reduce) {
52
+ return arr.reduce(iterator, memo);
53
+ }
54
+ _each(arr, function (x, i, a) {
55
+ memo = iterator(memo, x, i, a);
56
+ });
57
+ return memo;
58
+ };
59
+
60
+ var _keys = function (obj) {
61
+ if (Object.keys) {
62
+ return Object.keys(obj);
63
+ }
64
+ var keys = [];
65
+ for (var k in obj) {
66
+ if (obj.hasOwnProperty(k)) {
67
+ keys.push(k);
68
+ }
69
+ }
70
+ return keys;
71
+ };
72
+
73
+ //// exported async module functions ////
74
+
75
+ //// nextTick implementation with browser-compatible fallback ////
76
+ if (typeof process === 'undefined' || !(process.nextTick)) {
77
+ if (typeof setImmediate === 'function') {
78
+ async.nextTick = function (fn) {
79
+ // not a direct alias for IE10 compatibility
80
+ setImmediate(fn);
81
+ };
82
+ async.setImmediate = async.nextTick;
83
+ }
84
+ else {
85
+ async.nextTick = function (fn) {
86
+ setTimeout(fn, 0);
87
+ };
88
+ async.setImmediate = async.nextTick;
89
+ }
90
+ }
91
+ else {
92
+ async.nextTick = process.nextTick;
93
+ if (typeof setImmediate !== 'undefined') {
94
+ async.setImmediate = function (fn) {
95
+ // not a direct alias for IE10 compatibility
96
+ setImmediate(fn);
97
+ };
98
+ }
99
+ else {
100
+ async.setImmediate = async.nextTick;
101
+ }
102
+ }
103
+
104
+ async.each = function (arr, iterator, callback) {
105
+ callback = callback || function () {};
106
+ if (!arr.length) {
107
+ return callback();
108
+ }
109
+ var completed = 0;
110
+ _each(arr, function (x) {
111
+ iterator(x, only_once(function (err) {
112
+ if (err) {
113
+ callback(err);
114
+ callback = function () {};
115
+ }
116
+ else {
117
+ completed += 1;
118
+ if (completed >= arr.length) {
119
+ callback(null);
120
+ }
121
+ }
122
+ }));
123
+ });
124
+ };
125
+ async.forEach = async.each;
126
+
127
+ async.eachSeries = function (arr, iterator, callback) {
128
+ callback = callback || function () {};
129
+ if (!arr.length) {
130
+ return callback();
131
+ }
132
+ var completed = 0;
133
+ var iterate = function () {
134
+ iterator(arr[completed], function (err) {
135
+ if (err) {
136
+ callback(err);
137
+ callback = function () {};
138
+ }
139
+ else {
140
+ completed += 1;
141
+ if (completed >= arr.length) {
142
+ callback(null);
143
+ }
144
+ else {
145
+ iterate();
146
+ }
147
+ }
148
+ });
149
+ };
150
+ iterate();
151
+ };
152
+ async.forEachSeries = async.eachSeries;
153
+
154
+ async.eachLimit = function (arr, limit, iterator, callback) {
155
+ var fn = _eachLimit(limit);
156
+ fn.apply(null, [arr, iterator, callback]);
157
+ };
158
+ async.forEachLimit = async.eachLimit;
159
+
160
+ var _eachLimit = function (limit) {
161
+
162
+ return function (arr, iterator, callback) {
163
+ callback = callback || function () {};
164
+ if (!arr.length || limit <= 0) {
165
+ return callback();
166
+ }
167
+ var completed = 0;
168
+ var started = 0;
169
+ var running = 0;
170
+
171
+ (function replenish () {
172
+ if (completed >= arr.length) {
173
+ return callback();
174
+ }
175
+
176
+ while (running < limit && started < arr.length) {
177
+ started += 1;
178
+ running += 1;
179
+ iterator(arr[started - 1], function (err) {
180
+ if (err) {
181
+ callback(err);
182
+ callback = function () {};
183
+ }
184
+ else {
185
+ completed += 1;
186
+ running -= 1;
187
+ if (completed >= arr.length) {
188
+ callback();
189
+ }
190
+ else {
191
+ replenish();
192
+ }
193
+ }
194
+ });
195
+ }
196
+ })();
197
+ };
198
+ };
199
+
200
+
201
+ var doParallel = function (fn) {
202
+ return function () {
203
+ var args = Array.prototype.slice.call(arguments);
204
+ return fn.apply(null, [async.each].concat(args));
205
+ };
206
+ };
207
+ var doParallelLimit = function(limit, fn) {
208
+ return function () {
209
+ var args = Array.prototype.slice.call(arguments);
210
+ return fn.apply(null, [_eachLimit(limit)].concat(args));
211
+ };
212
+ };
213
+ var doSeries = function (fn) {
214
+ return function () {
215
+ var args = Array.prototype.slice.call(arguments);
216
+ return fn.apply(null, [async.eachSeries].concat(args));
217
+ };
218
+ };
219
+
220
+
221
+ var _asyncMap = function (eachfn, arr, iterator, callback) {
222
+ var results = [];
223
+ arr = _map(arr, function (x, i) {
224
+ return {index: i, value: x};
225
+ });
226
+ eachfn(arr, function (x, callback) {
227
+ iterator(x.value, function (err, v) {
228
+ results[x.index] = v;
229
+ callback(err);
230
+ });
231
+ }, function (err) {
232
+ callback(err, results);
233
+ });
234
+ };
235
+ async.map = doParallel(_asyncMap);
236
+ async.mapSeries = doSeries(_asyncMap);
237
+ async.mapLimit = function (arr, limit, iterator, callback) {
238
+ return _mapLimit(limit)(arr, iterator, callback);
239
+ };
240
+
241
+ var _mapLimit = function(limit) {
242
+ return doParallelLimit(limit, _asyncMap);
243
+ };
244
+
245
+ // reduce only has a series version, as doing reduce in parallel won't
246
+ // work in many situations.
247
+ async.reduce = function (arr, memo, iterator, callback) {
248
+ async.eachSeries(arr, function (x, callback) {
249
+ iterator(memo, x, function (err, v) {
250
+ memo = v;
251
+ callback(err);
252
+ });
253
+ }, function (err) {
254
+ callback(err, memo);
255
+ });
256
+ };
257
+ // inject alias
258
+ async.inject = async.reduce;
259
+ // foldl alias
260
+ async.foldl = async.reduce;
261
+
262
+ async.reduceRight = function (arr, memo, iterator, callback) {
263
+ var reversed = _map(arr, function (x) {
264
+ return x;
265
+ }).reverse();
266
+ async.reduce(reversed, memo, iterator, callback);
267
+ };
268
+ // foldr alias
269
+ async.foldr = async.reduceRight;
270
+
271
+ var _filter = function (eachfn, arr, iterator, callback) {
272
+ var results = [];
273
+ arr = _map(arr, function (x, i) {
274
+ return {index: i, value: x};
275
+ });
276
+ eachfn(arr, function (x, callback) {
277
+ iterator(x.value, function (v) {
278
+ if (v) {
279
+ results.push(x);
280
+ }
281
+ callback();
282
+ });
283
+ }, function (err) {
284
+ callback(_map(results.sort(function (a, b) {
285
+ return a.index - b.index;
286
+ }), function (x) {
287
+ return x.value;
288
+ }));
289
+ });
290
+ };
291
+ async.filter = doParallel(_filter);
292
+ async.filterSeries = doSeries(_filter);
293
+ // select alias
294
+ async.select = async.filter;
295
+ async.selectSeries = async.filterSeries;
296
+
297
+ var _reject = function (eachfn, arr, iterator, callback) {
298
+ var results = [];
299
+ arr = _map(arr, function (x, i) {
300
+ return {index: i, value: x};
301
+ });
302
+ eachfn(arr, function (x, callback) {
303
+ iterator(x.value, function (v) {
304
+ if (!v) {
305
+ results.push(x);
306
+ }
307
+ callback();
308
+ });
309
+ }, function (err) {
310
+ callback(_map(results.sort(function (a, b) {
311
+ return a.index - b.index;
312
+ }), function (x) {
313
+ return x.value;
314
+ }));
315
+ });
316
+ };
317
+ async.reject = doParallel(_reject);
318
+ async.rejectSeries = doSeries(_reject);
319
+
320
+ var _detect = function (eachfn, arr, iterator, main_callback) {
321
+ eachfn(arr, function (x, callback) {
322
+ iterator(x, function (result) {
323
+ if (result) {
324
+ main_callback(x);
325
+ main_callback = function () {};
326
+ }
327
+ else {
328
+ callback();
329
+ }
330
+ });
331
+ }, function (err) {
332
+ main_callback();
333
+ });
334
+ };
335
+ async.detect = doParallel(_detect);
336
+ async.detectSeries = doSeries(_detect);
337
+
338
+ async.some = function (arr, iterator, main_callback) {
339
+ async.each(arr, function (x, callback) {
340
+ iterator(x, function (v) {
341
+ if (v) {
342
+ main_callback(true);
343
+ main_callback = function () {};
344
+ }
345
+ callback();
346
+ });
347
+ }, function (err) {
348
+ main_callback(false);
349
+ });
350
+ };
351
+ // any alias
352
+ async.any = async.some;
353
+
354
+ async.every = function (arr, iterator, main_callback) {
355
+ async.each(arr, function (x, callback) {
356
+ iterator(x, function (v) {
357
+ if (!v) {
358
+ main_callback(false);
359
+ main_callback = function () {};
360
+ }
361
+ callback();
362
+ });
363
+ }, function (err) {
364
+ main_callback(true);
365
+ });
366
+ };
367
+ // all alias
368
+ async.all = async.every;
369
+
370
+ async.sortBy = function (arr, iterator, callback) {
371
+ async.map(arr, function (x, callback) {
372
+ iterator(x, function (err, criteria) {
373
+ if (err) {
374
+ callback(err);
375
+ }
376
+ else {
377
+ callback(null, {value: x, criteria: criteria});
378
+ }
379
+ });
380
+ }, function (err, results) {
381
+ if (err) {
382
+ return callback(err);
383
+ }
384
+ else {
385
+ var fn = function (left, right) {
386
+ var a = left.criteria, b = right.criteria;
387
+ return a < b ? -1 : a > b ? 1 : 0;
388
+ };
389
+ callback(null, _map(results.sort(fn), function (x) {
390
+ return x.value;
391
+ }));
392
+ }
393
+ });
394
+ };
395
+
396
+ async.auto = function (tasks, callback) {
397
+ callback = callback || function () {};
398
+ var keys = _keys(tasks);
399
+ if (!keys.length) {
400
+ return callback(null);
401
+ }
402
+
403
+ var results = {};
404
+
405
+ var listeners = [];
406
+ var addListener = function (fn) {
407
+ listeners.unshift(fn);
408
+ };
409
+ var removeListener = function (fn) {
410
+ for (var i = 0; i < listeners.length; i += 1) {
411
+ if (listeners[i] === fn) {
412
+ listeners.splice(i, 1);
413
+ return;
414
+ }
415
+ }
416
+ };
417
+ var taskComplete = function () {
418
+ _each(listeners.slice(0), function (fn) {
419
+ fn();
420
+ });
421
+ };
422
+
423
+ addListener(function () {
424
+ if (_keys(results).length === keys.length) {
425
+ callback(null, results);
426
+ callback = function () {};
427
+ }
428
+ });
429
+
430
+ _each(keys, function (k) {
431
+ var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
432
+ var taskCallback = function (err) {
433
+ var args = Array.prototype.slice.call(arguments, 1);
434
+ if (args.length <= 1) {
435
+ args = args[0];
436
+ }
437
+ if (err) {
438
+ var safeResults = {};
439
+ _each(_keys(results), function(rkey) {
440
+ safeResults[rkey] = results[rkey];
441
+ });
442
+ safeResults[k] = args;
443
+ callback(err, safeResults);
444
+ // stop subsequent errors hitting callback multiple times
445
+ callback = function () {};
446
+ }
447
+ else {
448
+ results[k] = args;
449
+ async.setImmediate(taskComplete);
450
+ }
451
+ };
452
+ var requires = task.slice(0, Math.abs(task.length - 1)) || [];
453
+ var ready = function () {
454
+ return _reduce(requires, function (a, x) {
455
+ return (a && results.hasOwnProperty(x));
456
+ }, true) && !results.hasOwnProperty(k);
457
+ };
458
+ if (ready()) {
459
+ task[task.length - 1](taskCallback, results);
460
+ }
461
+ else {
462
+ var listener = function () {
463
+ if (ready()) {
464
+ removeListener(listener);
465
+ task[task.length - 1](taskCallback, results);
466
+ }
467
+ };
468
+ addListener(listener);
469
+ }
470
+ });
471
+ };
472
+
473
+ async.waterfall = function (tasks, callback) {
474
+ callback = callback || function () {};
475
+ if (tasks.constructor !== Array) {
476
+ var err = new Error('First argument to waterfall must be an array of functions');
477
+ return callback(err);
478
+ }
479
+ if (!tasks.length) {
480
+ return callback();
481
+ }
482
+ var wrapIterator = function (iterator) {
483
+ return function (err) {
484
+ if (err) {
485
+ callback.apply(null, arguments);
486
+ callback = function () {};
487
+ }
488
+ else {
489
+ var args = Array.prototype.slice.call(arguments, 1);
490
+ var next = iterator.next();
491
+ if (next) {
492
+ args.push(wrapIterator(next));
493
+ }
494
+ else {
495
+ args.push(callback);
496
+ }
497
+ async.setImmediate(function () {
498
+ iterator.apply(null, args);
499
+ });
500
+ }
501
+ };
502
+ };
503
+ wrapIterator(async.iterator(tasks))();
504
+ };
505
+
506
+ var _parallel = function(eachfn, tasks, callback) {
507
+ callback = callback || function () {};
508
+ if (tasks.constructor === Array) {
509
+ eachfn.map(tasks, function (fn, callback) {
510
+ if (fn) {
511
+ fn(function (err) {
512
+ var args = Array.prototype.slice.call(arguments, 1);
513
+ if (args.length <= 1) {
514
+ args = args[0];
515
+ }
516
+ callback.call(null, err, args);
517
+ });
518
+ }
519
+ }, callback);
520
+ }
521
+ else {
522
+ var results = {};
523
+ eachfn.each(_keys(tasks), function (k, callback) {
524
+ tasks[k](function (err) {
525
+ var args = Array.prototype.slice.call(arguments, 1);
526
+ if (args.length <= 1) {
527
+ args = args[0];
528
+ }
529
+ results[k] = args;
530
+ callback(err);
531
+ });
532
+ }, function (err) {
533
+ callback(err, results);
534
+ });
535
+ }
536
+ };
537
+
538
+ async.parallel = function (tasks, callback) {
539
+ _parallel({ map: async.map, each: async.each }, tasks, callback);
540
+ };
541
+
542
+ async.parallelLimit = function(tasks, limit, callback) {
543
+ _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
544
+ };
545
+
546
+ async.series = function (tasks, callback) {
547
+ callback = callback || function () {};
548
+ if (tasks.constructor === Array) {
549
+ async.mapSeries(tasks, function (fn, callback) {
550
+ if (fn) {
551
+ fn(function (err) {
552
+ var args = Array.prototype.slice.call(arguments, 1);
553
+ if (args.length <= 1) {
554
+ args = args[0];
555
+ }
556
+ callback.call(null, err, args);
557
+ });
558
+ }
559
+ }, callback);
560
+ }
561
+ else {
562
+ var results = {};
563
+ async.eachSeries(_keys(tasks), function (k, callback) {
564
+ tasks[k](function (err) {
565
+ var args = Array.prototype.slice.call(arguments, 1);
566
+ if (args.length <= 1) {
567
+ args = args[0];
568
+ }
569
+ results[k] = args;
570
+ callback(err);
571
+ });
572
+ }, function (err) {
573
+ callback(err, results);
574
+ });
575
+ }
576
+ };
577
+
578
+ async.iterator = function (tasks) {
579
+ var makeCallback = function (index) {
580
+ var fn = function () {
581
+ if (tasks.length) {
582
+ tasks[index].apply(null, arguments);
583
+ }
584
+ return fn.next();
585
+ };
586
+ fn.next = function () {
587
+ return (index < tasks.length - 1) ? makeCallback(index + 1): null;
588
+ };
589
+ return fn;
590
+ };
591
+ return makeCallback(0);
592
+ };
593
+
594
+ async.apply = function (fn) {
595
+ var args = Array.prototype.slice.call(arguments, 1);
596
+ return function () {
597
+ return fn.apply(
598
+ null, args.concat(Array.prototype.slice.call(arguments))
599
+ );
600
+ };
601
+ };
602
+
603
+ var _concat = function (eachfn, arr, fn, callback) {
604
+ var r = [];
605
+ eachfn(arr, function (x, cb) {
606
+ fn(x, function (err, y) {
607
+ r = r.concat(y || []);
608
+ cb(err);
609
+ });
610
+ }, function (err) {
611
+ callback(err, r);
612
+ });
613
+ };
614
+ async.concat = doParallel(_concat);
615
+ async.concatSeries = doSeries(_concat);
616
+
617
+ async.whilst = function (test, iterator, callback) {
618
+ if (test()) {
619
+ iterator(function (err) {
620
+ if (err) {
621
+ return callback(err);
622
+ }
623
+ async.whilst(test, iterator, callback);
624
+ });
625
+ }
626
+ else {
627
+ callback();
628
+ }
629
+ };
630
+
631
+ async.doWhilst = function (iterator, test, callback) {
632
+ iterator(function (err) {
633
+ if (err) {
634
+ return callback(err);
635
+ }
636
+ if (test()) {
637
+ async.doWhilst(iterator, test, callback);
638
+ }
639
+ else {
640
+ callback();
641
+ }
642
+ });
643
+ };
644
+
645
+ async.until = function (test, iterator, callback) {
646
+ if (!test()) {
647
+ iterator(function (err) {
648
+ if (err) {
649
+ return callback(err);
650
+ }
651
+ async.until(test, iterator, callback);
652
+ });
653
+ }
654
+ else {
655
+ callback();
656
+ }
657
+ };
658
+
659
+ async.doUntil = function (iterator, test, callback) {
660
+ iterator(function (err) {
661
+ if (err) {
662
+ return callback(err);
663
+ }
664
+ if (!test()) {
665
+ async.doUntil(iterator, test, callback);
666
+ }
667
+ else {
668
+ callback();
669
+ }
670
+ });
671
+ };
672
+
673
+ async.queue = function (worker, concurrency) {
674
+ if (concurrency === undefined) {
675
+ concurrency = 1;
676
+ }
677
+ function _insert(q, data, pos, callback) {
678
+ if(data.constructor !== Array) {
679
+ data = [data];
680
+ }
681
+ _each(data, function(task) {
682
+ var item = {
683
+ data: task,
684
+ callback: typeof callback === 'function' ? callback : null
685
+ };
686
+
687
+ if (pos) {
688
+ q.tasks.unshift(item);
689
+ } else {
690
+ q.tasks.push(item);
691
+ }
692
+
693
+ if (q.saturated && q.tasks.length === concurrency) {
694
+ q.saturated();
695
+ }
696
+ async.setImmediate(q.process);
697
+ });
698
+ }
699
+
700
+ var workers = 0;
701
+ var q = {
702
+ tasks: [],
703
+ concurrency: concurrency,
704
+ saturated: null,
705
+ empty: null,
706
+ drain: null,
707
+ push: function (data, callback) {
708
+ _insert(q, data, false, callback);
709
+ },
710
+ unshift: function (data, callback) {
711
+ _insert(q, data, true, callback);
712
+ },
713
+ process: function () {
714
+ if (workers < q.concurrency && q.tasks.length) {
715
+ var task = q.tasks.shift();
716
+ if (q.empty && q.tasks.length === 0) {
717
+ q.empty();
718
+ }
719
+ workers += 1;
720
+ var next = function () {
721
+ workers -= 1;
722
+ if (task.callback) {
723
+ task.callback.apply(task, arguments);
724
+ }
725
+ if (q.drain && q.tasks.length + workers === 0) {
726
+ q.drain();
727
+ }
728
+ q.process();
729
+ };
730
+ var cb = only_once(next);
731
+ worker(task.data, cb);
732
+ }
733
+ },
734
+ length: function () {
735
+ return q.tasks.length;
736
+ },
737
+ running: function () {
738
+ return workers;
739
+ }
740
+ };
741
+ return q;
742
+ };
743
+
744
+ async.cargo = function (worker, payload) {
745
+ var working = false,
746
+ tasks = [];
747
+
748
+ var cargo = {
749
+ tasks: tasks,
750
+ payload: payload,
751
+ saturated: null,
752
+ empty: null,
753
+ drain: null,
754
+ push: function (data, callback) {
755
+ if(data.constructor !== Array) {
756
+ data = [data];
757
+ }
758
+ _each(data, function(task) {
759
+ tasks.push({
760
+ data: task,
761
+ callback: typeof callback === 'function' ? callback : null
762
+ });
763
+ if (cargo.saturated && tasks.length === payload) {
764
+ cargo.saturated();
765
+ }
766
+ });
767
+ async.setImmediate(cargo.process);
768
+ },
769
+ process: function process() {
770
+ if (working) return;
771
+ if (tasks.length === 0) {
772
+ if(cargo.drain) cargo.drain();
773
+ return;
774
+ }
775
+
776
+ var ts = typeof payload === 'number'
777
+ ? tasks.splice(0, payload)
778
+ : tasks.splice(0);
779
+
780
+ var ds = _map(ts, function (task) {
781
+ return task.data;
782
+ });
783
+
784
+ if(cargo.empty) cargo.empty();
785
+ working = true;
786
+ worker(ds, function () {
787
+ working = false;
788
+
789
+ var args = arguments;
790
+ _each(ts, function (data) {
791
+ if (data.callback) {
792
+ data.callback.apply(null, args);
793
+ }
794
+ });
795
+
796
+ process();
797
+ });
798
+ },
799
+ length: function () {
800
+ return tasks.length;
801
+ },
802
+ running: function () {
803
+ return working;
804
+ }
805
+ };
806
+ return cargo;
807
+ };
808
+
809
+ var _console_fn = function (name) {
810
+ return function (fn) {
811
+ var args = Array.prototype.slice.call(arguments, 1);
812
+ fn.apply(null, args.concat([function (err) {
813
+ var args = Array.prototype.slice.call(arguments, 1);
814
+ if (typeof console !== 'undefined') {
815
+ if (err) {
816
+ if (console.error) {
817
+ console.error(err);
818
+ }
819
+ }
820
+ else if (console[name]) {
821
+ _each(args, function (x) {
822
+ console[name](x);
823
+ });
824
+ }
825
+ }
826
+ }]));
827
+ };
828
+ };
829
+ async.log = _console_fn('log');
830
+ async.dir = _console_fn('dir');
831
+ /*async.info = _console_fn('info');
832
+ async.warn = _console_fn('warn');
833
+ async.error = _console_fn('error');*/
834
+
835
+ async.memoize = function (fn, hasher) {
836
+ var memo = {};
837
+ var queues = {};
838
+ hasher = hasher || function (x) {
839
+ return x;
840
+ };
841
+ var memoized = function () {
842
+ var args = Array.prototype.slice.call(arguments);
843
+ var callback = args.pop();
844
+ var key = hasher.apply(null, args);
845
+ if (key in memo) {
846
+ callback.apply(null, memo[key]);
847
+ }
848
+ else if (key in queues) {
849
+ queues[key].push(callback);
850
+ }
851
+ else {
852
+ queues[key] = [callback];
853
+ fn.apply(null, args.concat([function () {
854
+ memo[key] = arguments;
855
+ var q = queues[key];
856
+ delete queues[key];
857
+ for (var i = 0, l = q.length; i < l; i++) {
858
+ q[i].apply(null, arguments);
859
+ }
860
+ }]));
861
+ }
862
+ };
863
+ memoized.memo = memo;
864
+ memoized.unmemoized = fn;
865
+ return memoized;
866
+ };
867
+
868
+ async.unmemoize = function (fn) {
869
+ return function () {
870
+ return (fn.unmemoized || fn).apply(null, arguments);
871
+ };
872
+ };
873
+
874
+ async.times = function (count, iterator, callback) {
875
+ var counter = [];
876
+ for (var i = 0; i < count; i++) {
877
+ counter.push(i);
878
+ }
879
+ return async.map(counter, iterator, callback);
880
+ };
881
+
882
+ async.timesSeries = function (count, iterator, callback) {
883
+ var counter = [];
884
+ for (var i = 0; i < count; i++) {
885
+ counter.push(i);
886
+ }
887
+ return async.mapSeries(counter, iterator, callback);
888
+ };
889
+
890
+ async.compose = function (/* functions... */) {
891
+ var fns = Array.prototype.reverse.call(arguments);
892
+ return function () {
893
+ var that = this;
894
+ var args = Array.prototype.slice.call(arguments);
895
+ var callback = args.pop();
896
+ async.reduce(fns, args, function (newargs, fn, cb) {
897
+ fn.apply(that, newargs.concat([function () {
898
+ var err = arguments[0];
899
+ var nextargs = Array.prototype.slice.call(arguments, 1);
900
+ cb(err, nextargs);
901
+ }]))
902
+ },
903
+ function (err, results) {
904
+ callback.apply(that, [err].concat(results));
905
+ });
906
+ };
907
+ };
908
+
909
+ var _applyEach = function (eachfn, fns /*args...*/) {
910
+ var go = function () {
911
+ var that = this;
912
+ var args = Array.prototype.slice.call(arguments);
913
+ var callback = args.pop();
914
+ return eachfn(fns, function (fn, cb) {
915
+ fn.apply(that, args.concat([cb]));
916
+ },
917
+ callback);
918
+ };
919
+ if (arguments.length > 2) {
920
+ var args = Array.prototype.slice.call(arguments, 2);
921
+ return go.apply(this, args);
922
+ }
923
+ else {
924
+ return go;
925
+ }
926
+ };
927
+ async.applyEach = doParallel(_applyEach);
928
+ async.applyEachSeries = doSeries(_applyEach);
929
+
930
+ async.forever = function (fn, callback) {
931
+ function next(err) {
932
+ if (err) {
933
+ if (callback) {
934
+ return callback(err);
935
+ }
936
+ throw err;
937
+ }
938
+ fn(next);
939
+ }
940
+ next();
941
+ };
942
+
943
+ // AMD / RequireJS
944
+ if (typeof define !== 'undefined' && define.amd) {
945
+ define([], function () {
946
+ return async;
947
+ });
948
+ }
949
+ // Node.js
950
+ else if (typeof module !== 'undefined' && module.exports) {
951
+ module.exports = async;
952
+ }
953
+ // included directly via <script> tag
954
+ else {
955
+ root.async = async;
956
+ }
957
+
958
+ }());
js/jquery.modal.min.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*
2
+ A simple jQuery modal (http://github.com/kylefox/jquery-modal)
3
+ Version 0.5.5
4
+ */
5
+ !function(a){var b=null;a.modal=function(c,d){a.modal.close();var e,f;if(this.$body=a("body"),this.options=a.extend({},a.modal.defaults,d),this.options.doFade=!isNaN(parseInt(this.options.fadeDuration,10)),c.is("a"))if(f=c.attr("href"),/^#/.test(f)){if(this.$elm=a(f),1!==this.$elm.length)return null;this.open()}else this.$elm=a("<div>"),this.$body.append(this.$elm),e=function(a,b){b.elm.remove()},this.showSpinner(),c.trigger(a.modal.AJAX_SEND),a.get(f).done(function(d){b&&(c.trigger(a.modal.AJAX_SUCCESS),b.$elm.empty().append(d).on(a.modal.CLOSE,e),b.hideSpinner(),b.open(),c.trigger(a.modal.AJAX_COMPLETE))}).fail(function(){c.trigger(a.modal.AJAX_FAIL),b.hideSpinner(),c.trigger(a.modal.AJAX_COMPLETE)});else this.$elm=c,this.open()},a.modal.prototype={constructor:a.modal,open:function(){var b=this;this.options.doFade?(this.block(),setTimeout(function(){b.show()},this.options.fadeDuration*this.options.fadeDelay)):(this.block(),this.show()),this.options.escapeClose&&a(document).on("keydown.modal",function(b){27==b.which&&a.modal.close()}),this.options.clickClose&&this.blocker.click(a.modal.close)},close:function(){this.unblock(),this.hide(),a(document).off("keydown.modal")},block:function(){var b=this.options.doFade?0:this.options.opacity;this.$elm.trigger(a.modal.BEFORE_BLOCK,[this._ctx()]),this.blocker=a('<div class="jquery-modal blocker"></div>').css({top:0,right:0,bottom:0,left:0,width:"100%",height:"100%",position:"fixed",zIndex:this.options.zIndex,background:this.options.overlay,opacity:b}),this.$body.append(this.blocker),this.options.doFade&&this.blocker.animate({opacity:this.options.opacity},this.options.fadeDuration),this.$elm.trigger(a.modal.BLOCK,[this._ctx()])},unblock:function(){this.options.doFade?this.blocker.fadeOut(this.options.fadeDuration,function(){a(this).remove()}):this.blocker.remove()},show:function(){this.$elm.trigger(a.modal.BEFORE_OPEN,[this._ctx()]),this.options.showClose&&(this.closeButton=a('<a href="#close-modal" rel="modal:close" class="close-modal '+this.options.closeClass+'">'+this.options.closeText+"</a>"),this.$elm.append(this.closeButton)),this.$elm.addClass(this.options.modalClass+" current"),this.center(),this.options.doFade?this.$elm.fadeIn(this.options.fadeDuration):this.$elm.show(),this.$elm.trigger(a.modal.OPEN,[this._ctx()])},hide:function(){this.$elm.trigger(a.modal.BEFORE_CLOSE,[this._ctx()]),this.closeButton&&this.closeButton.remove(),this.$elm.removeClass("current"),this.options.doFade?this.$elm.fadeOut(this.options.fadeDuration):this.$elm.hide(),this.$elm.trigger(a.modal.CLOSE,[this._ctx()])},showSpinner:function(){this.options.showSpinner&&(this.spinner=this.spinner||a('<div class="'+this.options.modalClass+'-spinner"></div>').append(this.options.spinnerHtml),this.$body.append(this.spinner),this.spinner.show())},hideSpinner:function(){this.spinner&&this.spinner.remove()},center:function(){this.$elm.css({position:"fixed",top:"50%",left:"50%",marginTop:-(this.$elm.outerHeight()/2),marginLeft:-(this.$elm.outerWidth()/2),zIndex:this.options.zIndex+1})},_ctx:function(){return{elm:this.$elm,blocker:this.blocker,options:this.options}}},a.modal.prototype.resize=a.modal.prototype.center,a.modal.close=function(a){if(b){a&&a.preventDefault(),b.close();var c=b.$elm;return b=null,c}},a.modal.resize=function(){b&&b.resize()},a.modal.isActive=function(){return b?!0:!1},a.modal.defaults={overlay:"#000",opacity:.75,zIndex:1,escapeClose:!0,clickClose:!0,closeText:"Close",closeClass:"",modalClass:"modal",spinnerHtml:null,showSpinner:!0,showClose:!0,fadeDuration:null,fadeDelay:1},a.modal.BEFORE_BLOCK="modal:before-block",a.modal.BLOCK="modal:block",a.modal.BEFORE_OPEN="modal:before-open",a.modal.OPEN="modal:open",a.modal.BEFORE_CLOSE="modal:before-close",a.modal.CLOSE="modal:close",a.modal.AJAX_SEND="modal:ajax:send",a.modal.AJAX_SUCCESS="modal:ajax:success",a.modal.AJAX_FAIL="modal:ajax:fail",a.modal.AJAX_COMPLETE="modal:ajax:complete",a.fn.modal=function(c){return 1===this.length&&(b=new a.modal(this,c)),this},a(document).on("click.modal",'a[rel="modal:close"]',a.modal.close),a(document).on("click.modal",'a[rel="modal:open"]',function(b){b.preventDefault(),a(this).modal()})}(jQuery);
kraken.php CHANGED
@@ -21,8 +21,8 @@
21
  * Plugin URI: http://wordpress.org/plugins/kraken-image-optimizer/
22
  * Description: Optimize Wordpress image uploads through Kraken.io's Image Optimization API
23
  * Author: Karim Salman
24
- * Version: 1.0.2.1
25
- * Stable Tag: 1.0.2.1
26
  * Author URI: https://kraken.io
27
  * License GPL2
28
  */
@@ -101,10 +101,14 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
101
  function my_enqueue( $hook ) {
102
  wp_enqueue_script( 'jquery' );
103
  wp_enqueue_script( 'tipsy-js', plugins_url( '/js/jquery.tipsy.js', __FILE__ ), array( 'jquery' ) );
 
104
  wp_enqueue_script( 'ajax-script', plugins_url( '/js/ajax.js', __FILE__ ), array( 'jquery' ) );
105
  wp_enqueue_style( 'kraken_admin_style', plugins_url( 'css/admin.css', __FILE__ ) );
106
- wp_enqueue_style( 'tipsy_style', plugins_url( 'css/tipsy.css', __FILE__ ) );
 
107
  wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
 
 
108
  }
109
 
110
  function get_api_status( $api_key, $api_secret ) {
@@ -130,8 +134,13 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
130
  * Handles optimizing already-uploaded images in the Media Library
131
  */
132
  function kraken_media_library_ajax_callback() {
133
-
134
  $image_id = (int) $_POST['id'];
 
 
 
 
 
135
  $this->id = $image_id;
136
 
137
  if ( wp_attachment_is_image( $image_id ) ) {
@@ -158,7 +167,7 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
158
  die();
159
  }
160
 
161
- $result = $this->optimize_image( $imageUrl );
162
  $kv = array();
163
 
164
  if ( $result['success'] == true && !isset( $result['error'] ) ) {
@@ -177,7 +186,7 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
177
 
178
  // get metadata for thumbnails
179
  $image_data = wp_get_attachment_metadata( $image_id );
180
- $this->optimize_thumbnails( $image_data );
181
 
182
  // store kraked info to DB
183
  update_post_meta( $image_id, '_kraken_size', $kv );
@@ -189,6 +198,9 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
189
  }
190
 
191
  echo json_encode( $kv );
 
 
 
192
  }
193
  } else {
194
 
@@ -219,14 +231,15 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
219
  * Handles optimizing images uploaded through any of the media uploaders.
220
  */
221
  function kraken_media_uploader_callback( $image_id ) {
222
-
223
  $this->id = $image_id;
224
 
225
  if ( wp_attachment_is_image( $image_id ) ) {
226
 
 
 
227
  $imageUrl = wp_get_attachment_url( $image_id );
228
  $image_path = get_attached_file( $image_id );
229
- $result = $this->optimize_image( $imageUrl );
230
 
231
  if ( $result['success'] == true && !isset( $result['error'] ) ) {
232
 
@@ -268,6 +281,7 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
268
  }
269
  }
270
 
 
271
  function show_credentials_validity() {
272
 
273
  $settings = $this->kraken_settings;
@@ -361,7 +375,7 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
361
 
362
  function add_media_columns( $columns ) {
363
  $columns['original_size'] = 'Original Size';
364
- $columns['kraken_size'] = 'Kraked Size';
365
  return $columns;
366
  }
367
 
@@ -370,6 +384,10 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
370
  $original_size = filesize( get_attached_file( $id ) );
371
  $original_size = self::pretty_kb( $original_size );
372
 
 
 
 
 
373
  if ( strcmp( $column_name, 'original_size' ) === 0 ) {
374
  if ( wp_attachment_is_image( $id ) ) {
375
 
@@ -383,7 +401,7 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
383
  } else {
384
  echo $original_size;
385
  }
386
- } else {
387
 
388
  if ( wp_attachment_is_image( $id ) ) {
389
 
@@ -405,8 +423,9 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
405
 
406
  // Were there no savings, or was there an error?
407
  } else {
408
- echo '<div class="buttonWrap"><button type="button" class="kraken_req" data-id="' . $id . '" id="krakenid-' . $id .'">Optimize This Image</button><span class="krakenSpinner"></span></div>';
409
-
 
410
  if ( !empty( $meta['no_savings'] ) ) {
411
  echo '<div class="noSavings"><strong>No savings found</strong><br /><small>Type:&nbsp;' . $meta['type'] . '</small></div>';
412
  } else if ( isset( $meta['error'] ) ) {
@@ -429,13 +448,16 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
429
  return $rv !== false;
430
  }
431
 
432
- function optimize_image( $url ) {
433
 
434
  $settings = $this->kraken_settings;
435
- $kraken = new Kraken($settings['api_key'], $settings['api_secret']);
436
-
437
- $lossy = $settings['api_lossy'] === "lossy";
438
 
 
 
 
 
 
439
  $params = array(
440
  "url" => $url,
441
  "wait" => true,
@@ -443,17 +465,36 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
443
  );
444
 
445
  $data = $kraken->url( $params );
446
- $data['type'] = $settings['api_lossy'];
447
 
448
  return $data;
449
  }
450
 
451
- function optimize_thumbnails( $image_data ) {
 
 
 
 
 
 
 
452
 
453
  $image_id = $this->id;
454
- $upload_dir = wp_upload_dir();
455
- $upload_path = $upload_dir['path'];
456
- $upload_url = $upload_dir['url'];
 
 
 
 
 
 
 
 
 
 
 
 
457
  $sizes = array();
458
 
459
  if ( isset( $image_data['sizes'] ) ) {
@@ -470,11 +511,11 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
470
 
471
  foreach ( $sizes as $key => $size ) {
472
 
473
- $thumb_path = $upload_path . '/' . $size['file'];
474
  $thumb_url = $upload_url . '/' . $size['file'];
475
 
476
  if ( file_exists( $thumb_path ) !== false ) {
477
- $result = $this->optimize_image( $thumb_url );
478
 
479
  if ( !empty($result) && isset($result['success']) && isset( $result['kraked_url'] ) ) {
480
  $kraked_url = $result["kraked_url"];
@@ -486,7 +527,9 @@ if ( !class_exists( 'Wp_Kraken' ) ) {
486
  }
487
  }
488
  }
489
- update_post_meta( $image_id, '_kraked_thumbs', $thumbs_optimized_store, false );
 
 
490
  return $image_data;
491
  }
492
 
21
  * Plugin URI: http://wordpress.org/plugins/kraken-image-optimizer/
22
  * Description: Optimize Wordpress image uploads through Kraken.io's Image Optimization API
23
  * Author: Karim Salman
24
+ * Version: 1.0.3
25
+ * Stable Tag: 1.0.3
26
  * Author URI: https://kraken.io
27
  * License GPL2
28
  */
101
  function my_enqueue( $hook ) {
102
  wp_enqueue_script( 'jquery' );
103
  wp_enqueue_script( 'tipsy-js', plugins_url( '/js/jquery.tipsy.js', __FILE__ ), array( 'jquery' ) );
104
+ wp_enqueue_script( 'async-js', plugins_url( '/js/async.js', __FILE__ ) );
105
  wp_enqueue_script( 'ajax-script', plugins_url( '/js/ajax.js', __FILE__ ), array( 'jquery' ) );
106
  wp_enqueue_style( 'kraken_admin_style', plugins_url( 'css/admin.css', __FILE__ ) );
107
+ wp_enqueue_style( 'tipsy-style', plugins_url( 'css/tipsy.css', __FILE__ ) );
108
+ wp_enqueue_style( 'modal-style', plugins_url( 'css/jquery.modal.css', __FILE__ ) );
109
  wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
110
+ wp_enqueue_script( 'modal-js', plugins_url( '/js/jquery.modal.min.js', __FILE__ ), array( 'jquery' ) );
111
+
112
  }
113
 
114
  function get_api_status( $api_key, $api_secret ) {
134
  * Handles optimizing already-uploaded images in the Media Library
135
  */
136
  function kraken_media_library_ajax_callback() {
137
+
138
  $image_id = (int) $_POST['id'];
139
+ $type = false;
140
+ if ( isset( $_POST['type'] ) ) {
141
+ $type = $_POST['type'];
142
+ }
143
+
144
  $this->id = $image_id;
145
 
146
  if ( wp_attachment_is_image( $image_id ) ) {
167
  die();
168
  }
169
 
170
+ $result = $this->optimize_image( $imageUrl, $type );
171
  $kv = array();
172
 
173
  if ( $result['success'] == true && !isset( $result['error'] ) ) {
186
 
187
  // get metadata for thumbnails
188
  $image_data = wp_get_attachment_metadata( $image_id );
189
+ $this->optimize_thumbnails( $image_data, $type );
190
 
191
  // store kraked info to DB
192
  update_post_meta( $image_id, '_kraken_size', $kv );
198
  }
199
 
200
  echo json_encode( $kv );
201
+ } else {
202
+ echo json_encode( array( 'error' => 'Could not overwrite original file. Please ensure that your files are writable by plugins.' ) );
203
+ exit;
204
  }
205
  } else {
206
 
231
  * Handles optimizing images uploaded through any of the media uploaders.
232
  */
233
  function kraken_media_uploader_callback( $image_id ) {
 
234
  $this->id = $image_id;
235
 
236
  if ( wp_attachment_is_image( $image_id ) ) {
237
 
238
+ $settings = $this->kraken_settings;
239
+ $type = $settings['api_lossy'];
240
  $imageUrl = wp_get_attachment_url( $image_id );
241
  $image_path = get_attached_file( $image_id );
242
+ $result = $this->optimize_image( $imageUrl, $type );
243
 
244
  if ( $result['success'] == true && !isset( $result['error'] ) ) {
245
 
281
  }
282
  }
283
 
284
+
285
  function show_credentials_validity() {
286
 
287
  $settings = $this->kraken_settings;
375
 
376
  function add_media_columns( $columns ) {
377
  $columns['original_size'] = 'Original Size';
378
+ $columns['kraked_size'] = 'Kraked Size';
379
  return $columns;
380
  }
381
 
384
  $original_size = filesize( get_attached_file( $id ) );
385
  $original_size = self::pretty_kb( $original_size );
386
 
387
+ $options = get_option( '_kraken_options' );
388
+ $type = isset( $options['api_lossy'] ) ? $options['api_lossy'] : 'lossy';
389
+
390
+
391
  if ( strcmp( $column_name, 'original_size' ) === 0 ) {
392
  if ( wp_attachment_is_image( $id ) ) {
393
 
401
  } else {
402
  echo $original_size;
403
  }
404
+ } else if ( strcmp( $column_name, 'kraked_size' ) === 0 ) {
405
 
406
  if ( wp_attachment_is_image( $id ) ) {
407
 
423
 
424
  // Were there no savings, or was there an error?
425
  } else {
426
+ $image_url = wp_get_attachment_url( $id );
427
+ $filename = basename( $image_url );
428
+ echo '<div class="buttonWrap"><button data-setting="' . $type . '" type="button" class="kraken_req" data-id="' . $id . '" id="krakenid-' . $id .'" data-filename="' . $filename . '" data-url="' . $image_url . '">Optimize This Image</button><span class="krakenSpinner"></span></div>';
429
  if ( !empty( $meta['no_savings'] ) ) {
430
  echo '<div class="noSavings"><strong>No savings found</strong><br /><small>Type:&nbsp;' . $meta['type'] . '</small></div>';
431
  } else if ( isset( $meta['error'] ) ) {
448
  return $rv !== false;
449
  }
450
 
451
+ function optimize_image( $url, $type ) {
452
 
453
  $settings = $this->kraken_settings;
454
+ $kraken = new Kraken( $settings['api_key'], $settings['api_secret'] );
 
 
455
 
456
+ if ( !empty( $type ) ) {
457
+ $lossy = $type === 'lossy';
458
+ } else {
459
+ $lossy = $settings['api_lossy'] === "lossy";
460
+ }
461
  $params = array(
462
  "url" => $url,
463
  "wait" => true,
465
  );
466
 
467
  $data = $kraken->url( $params );
468
+ $data['type'] = !empty( $type ) ? $type : $settings['api_lossy'];
469
 
470
  return $data;
471
  }
472
 
473
+ function optimize_thumbnails( $image_data, $type = 'lossy' ) {
474
+
475
+ if ( empty( $type ) ) {
476
+ $settings = $this->kraken_settings;
477
+ $optimizationType = $settings['api_lossy'];
478
+ } else {
479
+ $optimizationType = $type;
480
+ }
481
 
482
  $image_id = $this->id;
483
+ $path_parts = pathinfo( $image_data['file'] );
484
+
485
+ // e.g. 04/02, for use in getting correct path or URL
486
+ $upload_subdir = $path_parts['dirname'];
487
+
488
+ $upload_dir = wp_upload_dir();
489
+
490
+ // all the way up to /uploads
491
+ $upload_base_path = $upload_dir['basedir'];
492
+ $upload_full_path = $upload_base_path . '/' . $upload_subdir;
493
+
494
+ // all the way up to /uploads
495
+ $upload_base_url = $upload_dir['baseurl'];
496
+ $upload_url = $upload_base_url . '/' . $upload_subdir;
497
+
498
  $sizes = array();
499
 
500
  if ( isset( $image_data['sizes'] ) ) {
511
 
512
  foreach ( $sizes as $key => $size ) {
513
 
514
+ $thumb_path = $upload_full_path . '/' . $size['file'];
515
  $thumb_url = $upload_url . '/' . $size['file'];
516
 
517
  if ( file_exists( $thumb_path ) !== false ) {
518
+ $result = $this->optimize_image( $thumb_url, $optimizationType );
519
 
520
  if ( !empty($result) && isset($result['success']) && isset( $result['kraked_url'] ) ) {
521
  $kraked_url = $result["kraked_url"];
527
  }
528
  }
529
  }
530
+ if ( !empty( $thumbs_optimized_store ) ) {
531
+ update_post_meta( $image_id, '_kraked_thumbs', $thumbs_optimized_store, false );
532
+ }
533
  return $image_data;
534
  }
535
 
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: Image Optimizer, Optimize, Images, Media, Performance, SEO, smushit, compr
4
  Requires at least: 3.0.1
5
  Tested up to: 3.8.1
6
  Donate link: https://kraken.io
7
- Stable tag: 1.0.2.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10
 
@@ -19,7 +19,7 @@ This plugin allows you to optimize new and existing Wordpress image uploads thro
19
 
20
 
21
  * All images uploaded throught the media uploader are optimized on-the-fly. All generated thumbnails are optimized too.
22
- * All images already present in the media library can be optimized individually.
23
  * This plugin does not require any root or command-line access. No compilation and installation of any binaries is necessary.
24
  * All optimization is carried out by sending images to Kraken.io's infrastructure, and pulling the optimized files to your Wordpress installation.
25
  * To use this plugin, you must obtain a full API key and secret from [https://kraken.io/plans](https://kraken.io/plans "Kraken Image Optimizer"). The Developer API key/secret will **not** work with this plugin.
@@ -27,6 +27,12 @@ This plugin allows you to optimize new and existing Wordpress image uploads thro
27
 
28
  Once you have obtained your credentials, from your Wordpress admin, go to the Settings->Media page. The Kraken Wordpress plugin adds a Kraken.io Settings section to the bottom of the page, from where you can enter your API credentials, and select your optimization preferences. Once you have done this, click **Save**. If everything is in order, it will simply say "settings saved" and give you a reassuring green tick in the **Kraken.io settings** section. You can now start optimizing images from within Media Library. Any image you upload from now on, through any of the media upload screens will be optimized on-the-fly by Kraken.
29
 
 
 
 
 
 
 
30
  Please send bug reports, problems, feature requests and so on to support (at) Kraken dot io, or directly to the author of this plugin.
31
 
32
  = Connect with Kraken.io =
@@ -84,4 +90,10 @@ From our plans page, right [here](https://kraken.io/plans "Kraken.io plans and p
84
  * Fixed bug which led to kraked file not being retrieved in rare cases.
85
  * Increase ajax timeout for media library inline kraking to be kinder to slower WordPress blogs.
86
 
 
 
 
 
 
 
87
 
4
  Requires at least: 3.0.1
5
  Tested up to: 3.8.1
6
  Donate link: https://kraken.io
7
+ Stable tag: 1.0.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10
 
19
 
20
 
21
  * All images uploaded throught the media uploader are optimized on-the-fly. All generated thumbnails are optimized too.
22
+ * All images already present in the media library can be optimized individually, or using the Bulk Action menu "Krak 'em all" feature.
23
  * This plugin does not require any root or command-line access. No compilation and installation of any binaries is necessary.
24
  * All optimization is carried out by sending images to Kraken.io's infrastructure, and pulling the optimized files to your Wordpress installation.
25
  * To use this plugin, you must obtain a full API key and secret from [https://kraken.io/plans](https://kraken.io/plans "Kraken Image Optimizer"). The Developer API key/secret will **not** work with this plugin.
27
 
28
  Once you have obtained your credentials, from your Wordpress admin, go to the Settings->Media page. The Kraken Wordpress plugin adds a Kraken.io Settings section to the bottom of the page, from where you can enter your API credentials, and select your optimization preferences. Once you have done this, click **Save**. If everything is in order, it will simply say "settings saved" and give you a reassuring green tick in the **Kraken.io settings** section. You can now start optimizing images from within Media Library. Any image you upload from now on, through any of the media upload screens will be optimized on-the-fly by Kraken.
29
 
30
+ Features on the way:
31
+ * Optimize images directly to Amazon S3.
32
+ * Optimize entire media library in one click.
33
+ * Optimize your currently active theme.
34
+ * Kraken NextGen Gallery extension.
35
+
36
  Please send bug reports, problems, feature requests and so on to support (at) Kraken dot io, or directly to the author of this plugin.
37
 
38
  = Connect with Kraken.io =
90
  * Fixed bug which led to kraked file not being retrieved in rare cases.
91
  * Increase ajax timeout for media library inline kraking to be kinder to slower WordPress blogs.
92
 
93
+ = 1.0.3 =
94
+ * Bulk Actions menu in Media Library is now extended with "Krak 'em all", our Bulk Optimization feature.
95
+ * Fixed a bug which caused old images' thumbnails to not be optimized.
96
+ * Fixed a failure condition which occured only on WPEngine-hosted systems.
97
+
98
+
99