Kraken.io Image Optimizer - Version 1.0.1

Version Description

  • Minor cleanup release.
Download this release

Release Info

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

Version 1.0.1

Files changed (7) hide show
  1. css/admin.css +98 -0
  2. css/tipsy.css +25 -0
  3. js/ajax.js +155 -0
  4. js/jquery.tipsy.js +258 -0
  5. kraken.php +478 -0
  6. lib/Kraken.php +100 -0
  7. readme.txt +65 -0
css/admin.css ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .head_cell{
2
+ float:left;
3
+ width:10em;
4
+ height: 20px;
5
+ background-color:#F0F0F0;
6
+ padding-top: 5px;
7
+ }
8
+ .head_file_name{
9
+ float:left;
10
+ width:30em;
11
+ height: 20px;
12
+ background-color:#F0F0F0;
13
+ padding-top: 5px;
14
+ padding-left: 2px;
15
+ }
16
+ .item_cell{
17
+ float:left;
18
+ width:10em;
19
+ height: 20px;
20
+ background-color:#F8F8F8 ;
21
+ padding-top: 5px;
22
+ margin-top: 5px;
23
+ }
24
+ .item_file_name{
25
+ float:left;
26
+ width:30em;
27
+ height: 20px;
28
+ background-color:#F8F8F8 ;
29
+ padding-top: 5px;
30
+ padding-left: 2px;
31
+ margin-top: 5px;
32
+ }
33
+ #kraken_loading{
34
+ float: left;
35
+ clear: left;
36
+ }
37
+ .kraken_req {
38
+ border: 1px solid #efab00;
39
+ color: #222;
40
+ padding: 3px 8px 4px;
41
+ font-size: 11px;
42
+ text-shadow: 0 1px 0 rgba(255,255,255,0.5);
43
+ background: #ffc942;
44
+ background-image: -webkit-gradient(linear,left top,left bottom,from(#ffc942),to(#efab00));
45
+ background-image: -webkit-linear-gradient(top,#ffc942,#efab00);
46
+ background-image: -moz-linear-gradient(top,#ffc942,#efab00);
47
+ background-image: -o-linear-gradient(top,#ffc942,#efab00);
48
+ background-image: linear-gradient(to bottom,#ffc942,#efab00);
49
+ -webkit-border-radius: 2px;
50
+ -moz-border-radius: 2px;
51
+ border-radius: 2px;
52
+ cursor: pointer;
53
+ font-weight: bold;
54
+ }
55
+ .buttonWrap {
56
+ position: relative;
57
+ }
58
+ .apiValid {
59
+ left: 0;
60
+ display: inline-block;
61
+ width: 16px;
62
+ height: 16px;
63
+ top: 3px;
64
+ position: absolute;
65
+ }
66
+ .apiInvalid {
67
+ left: 0;
68
+ display: inline-block;
69
+ width: 16px;
70
+ height: 16px;
71
+ top: 2px;
72
+ position: absolute;
73
+ }
74
+ p.apiStatus {
75
+ padding-left: 26px;
76
+ position: relative;
77
+ width: 400px;
78
+ }
79
+ .krakenSpinner {
80
+ display: none;
81
+ position: absolute;
82
+ width: 16px;
83
+ height: 16px;
84
+ top: 5px;
85
+ background: url('https://kraken.r.worldssl.net/assets/images/spinner.gif') no-repeat 0 0;
86
+ }
87
+ .krakenErrorWrap {
88
+ margin-top: 10px;
89
+ margin-bottom: 4px;
90
+ }
91
+ .noSavings {
92
+ margin-left: 2px;
93
+ }
94
+ .krakenError {
95
+ margin-left: 2px;
96
+ font-weight: bold;
97
+ color: #dd3d36;
98
+ }
css/tipsy.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .tipsy { font-size: 10px; position: absolute; padding: 5px; z-index: 100000; }
2
+ .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; }
3
+
4
+ /* Rounded corners */
5
+ .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
6
+
7
+ /* Uncomment for shadow */
8
+ /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
9
+
10
+ .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
11
+
12
+ /* Rules to colour arrows */
13
+ .tipsy-arrow-n { border-bottom-color: #000; }
14
+ .tipsy-arrow-s { border-top-color: #000; }
15
+ .tipsy-arrow-e { border-left-color: #000; }
16
+ .tipsy-arrow-w { border-right-color: #000; }
17
+
18
+ .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
19
+ .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
20
+ .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
21
+ .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
22
+ .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
23
+ .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
24
+ .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
25
+ .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
js/ajax.js ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function ($) {
2
+
3
+
4
+ var errors = [
5
+ {
6
+ code: 401,
7
+ msg: 'Unnknown API Key. Please check your API key and try again'
8
+ },
9
+ {
10
+ code: 403,
11
+ msg: 'Your account has been temporarily suspended'
12
+ },
13
+ {
14
+ code: 413,
15
+ msg: 'File size too large. The maximum file size for your plan is 1048576 bytes'
16
+ },
17
+ {
18
+ code: 415,
19
+ msg: 'File type not supported'
20
+ },
21
+ {
22
+ code: 415,
23
+ msg: 'WebP compression is non available for SVG images'
24
+ },
25
+ {
26
+ code: 422,
27
+ msg: 'You need to specify either callback_url or wait flag'
28
+ },
29
+ {
30
+ code: 422,
31
+ msg: 'This image can not be optimized any further'
32
+ },
33
+ {
34
+ code: 500,
35
+ msg: 'Kraken has encountered an unexpected error and cannot fulfill your request'
36
+ },
37
+ {
38
+ code: 502,
39
+ msg: 'Couldn\'t get this file'
40
+ }
41
+ ];
42
+
43
+
44
+ $('a.krakenError').tipsy({
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");
63
+
64
+ if (data.success && typeof data.error === 'undefined') {
65
+
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
73
+ , $originalSizeColumn = $(this).parent().prev("td.original_size");
74
+
75
+ $parent.fadeOut("fast", function () {
76
+ $cell.find(".noSavings, .krakenErrorWrap").remove();
77
+ $(this).replaceWith('<strong>' + krakedSize + '</strong><br /><small>Type:&nbsp;' + type + '</small><br /><small>Savings: ' + savingsPercent + '</small>');
78
+ $originalSizeColumn.html(originalSize);
79
+ $parent.remove();
80
+ });
81
+
82
+ } else if (data.error) {
83
+
84
+ var $error = $(errorTpl).attr("title", data.error);
85
+
86
+ $parent
87
+ .closest("td")
88
+ .find(".krakenErrorWrap")
89
+ .remove();
90
+
91
+
92
+ $parent.after($error);
93
+ $error.tipsy({
94
+ fade: true,
95
+ gravity: 'e'
96
+ });
97
+
98
+ $button
99
+ .text("Retry request")
100
+ .removeAttr("disabled")
101
+ .css({
102
+ opacity: 1
103
+ });
104
+ }
105
+ },
106
+
107
+ requestFail = function (jqXHR, textStatus, errorThrown) {
108
+ $(this).removeAttr("disabled");
109
+ },
110
+
111
+ requestComplete = function (jqXHR, textStatus, errorThrown) {
112
+ $(this).removeAttr("disabled");
113
+ $(this)
114
+ .parent()
115
+ .find(".krakenSpinner")
116
+ .css("display", "none");
117
+ };
118
+
119
+
120
+ $(".kraken_req").click(function (e) {
121
+ e.preventDefault();
122
+ var $button = $(this)
123
+ , $parent = $(this).parent();
124
+
125
+ data.id = $(this).data("id");
126
+
127
+ $button
128
+ .text("Optimizing image...")
129
+ .attr("disabled", true)
130
+ .css({
131
+ opacity: "0.5"
132
+ });
133
+
134
+
135
+ $parent
136
+ .find(".krakenSpinner")
137
+ .css("display", "inline");
138
+
139
+
140
+ var jqxhr = $.ajax({
141
+ url: ajax_object.ajax_url,
142
+ data: data,
143
+ type: "post",
144
+ dataType: "json",
145
+ context: $button
146
+ })
147
+
148
+ .done(requestSuccess)
149
+
150
+ .fail(requestFail)
151
+
152
+ .always(requestComplete);
153
+
154
+ });
155
+ });
js/jquery.tipsy.js ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // tipsy, facebook style tooltips for jquery
2
+ // version 1.0.0a
3
+ // (c) 2008-2010 jason frame [jason@onehackoranother.com]
4
+ // released under the MIT license
5
+
6
+ (function($) {
7
+
8
+ function maybeCall(thing, ctx) {
9
+ return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
10
+ };
11
+
12
+ function isElementInDOM(ele) {
13
+ while (ele = ele.parentNode) {
14
+ if (ele == document) return true;
15
+ }
16
+ return false;
17
+ };
18
+
19
+ function Tipsy(element, options) {
20
+ this.$element = $(element);
21
+ this.options = options;
22
+ this.enabled = true;
23
+ this.fixTitle();
24
+ };
25
+
26
+ Tipsy.prototype = {
27
+ show: function() {
28
+ var title = this.getTitle();
29
+ if (title && this.enabled) {
30
+ var $tip = this.tip();
31
+
32
+ $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
33
+ $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
34
+ $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
35
+
36
+ var pos = $.extend({}, this.$element.offset(), {
37
+ width: this.$element[0].offsetWidth,
38
+ height: this.$element[0].offsetHeight
39
+ });
40
+
41
+ var actualWidth = $tip[0].offsetWidth,
42
+ actualHeight = $tip[0].offsetHeight,
43
+ gravity = maybeCall(this.options.gravity, this.$element[0]);
44
+
45
+ var tp;
46
+ switch (gravity.charAt(0)) {
47
+ case 'n':
48
+ tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
49
+ break;
50
+ case 's':
51
+ tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
52
+ break;
53
+ case 'e':
54
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
55
+ break;
56
+ case 'w':
57
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
58
+ break;
59
+ }
60
+
61
+ if (gravity.length == 2) {
62
+ if (gravity.charAt(1) == 'w') {
63
+ tp.left = pos.left + pos.width / 2 - 15;
64
+ } else {
65
+ tp.left = pos.left + pos.width / 2 - actualWidth + 15;
66
+ }
67
+ }
68
+
69
+ $tip.css(tp).addClass('tipsy-' + gravity);
70
+ $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
71
+ if (this.options.className) {
72
+ $tip.addClass(maybeCall(this.options.className, this.$element[0]));
73
+ }
74
+
75
+ if (this.options.fade) {
76
+ $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
77
+ } else {
78
+ $tip.css({visibility: 'visible', opacity: this.options.opacity});
79
+ }
80
+ }
81
+ },
82
+
83
+ hide: function() {
84
+ if (this.options.fade) {
85
+ this.tip().stop().fadeOut(function() { $(this).remove(); });
86
+ } else {
87
+ this.tip().remove();
88
+ }
89
+ },
90
+
91
+ fixTitle: function() {
92
+ var $e = this.$element;
93
+ if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
94
+ $e.attr('original-title', $e.attr('title') || '').removeAttr('title');
95
+ }
96
+ },
97
+
98
+ getTitle: function() {
99
+ var title, $e = this.$element, o = this.options;
100
+ this.fixTitle();
101
+ var title, o = this.options;
102
+ if (typeof o.title == 'string') {
103
+ title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
104
+ } else if (typeof o.title == 'function') {
105
+ title = o.title.call($e[0]);
106
+ }
107
+ title = ('' + title).replace(/(^\s*|\s*$)/, "");
108
+ return title || o.fallback;
109
+ },
110
+
111
+ tip: function() {
112
+ if (!this.$tip) {
113
+ this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
114
+ this.$tip.data('tipsy-pointee', this.$element[0]);
115
+ }
116
+ return this.$tip;
117
+ },
118
+
119
+ validate: function() {
120
+ if (!this.$element[0].parentNode) {
121
+ this.hide();
122
+ this.$element = null;
123
+ this.options = null;
124
+ }
125
+ },
126
+
127
+ enable: function() { this.enabled = true; },
128
+ disable: function() { this.enabled = false; },
129
+ toggleEnabled: function() { this.enabled = !this.enabled; }
130
+ };
131
+
132
+ $.fn.tipsy = function(options) {
133
+
134
+ if (options === true) {
135
+ return this.data('tipsy');
136
+ } else if (typeof options == 'string') {
137
+ var tipsy = this.data('tipsy');
138
+ if (tipsy) tipsy[options]();
139
+ return this;
140
+ }
141
+
142
+ options = $.extend({}, $.fn.tipsy.defaults, options);
143
+
144
+ function get(ele) {
145
+ var tipsy = $.data(ele, 'tipsy');
146
+ if (!tipsy) {
147
+ tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
148
+ $.data(ele, 'tipsy', tipsy);
149
+ }
150
+ return tipsy;
151
+ }
152
+
153
+ function enter() {
154
+ var tipsy = get(this);
155
+ tipsy.hoverState = 'in';
156
+ if (options.delayIn == 0) {
157
+ tipsy.show();
158
+ } else {
159
+ tipsy.fixTitle();
160
+ setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
161
+ }
162
+ };
163
+
164
+ function leave() {
165
+ var tipsy = get(this);
166
+ tipsy.hoverState = 'out';
167
+ if (options.delayOut == 0) {
168
+ tipsy.hide();
169
+ } else {
170
+ setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
171
+ }
172
+ };
173
+
174
+ if (!options.live) this.each(function() { get(this); });
175
+
176
+ if (options.trigger != 'manual') {
177
+ var binder = options.live ? 'live' : 'bind',
178
+ eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
179
+ eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
180
+ this[binder](eventIn, enter)[binder](eventOut, leave);
181
+ }
182
+
183
+ return this;
184
+
185
+ };
186
+
187
+ $.fn.tipsy.defaults = {
188
+ className: null,
189
+ delayIn: 0,
190
+ delayOut: 0,
191
+ fade: false,
192
+ fallback: '',
193
+ gravity: 'n',
194
+ html: false,
195
+ live: false,
196
+ offset: 0,
197
+ opacity: 0.8,
198
+ title: 'title',
199
+ trigger: 'hover'
200
+ };
201
+
202
+ $.fn.tipsy.revalidate = function() {
203
+ $('.tipsy').each(function() {
204
+ var pointee = $.data(this, 'tipsy-pointee');
205
+ if (!pointee || !isElementInDOM(pointee)) {
206
+ $(this).remove();
207
+ }
208
+ });
209
+ };
210
+
211
+ // Overwrite this method to provide options on a per-element basis.
212
+ // For example, you could store the gravity in a 'tipsy-gravity' attribute:
213
+ // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
214
+ // (remember - do not modify 'options' in place!)
215
+ $.fn.tipsy.elementOptions = function(ele, options) {
216
+ return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
217
+ };
218
+
219
+ $.fn.tipsy.autoNS = function() {
220
+ return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
221
+ };
222
+
223
+ $.fn.tipsy.autoWE = function() {
224
+ return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
225
+ };
226
+
227
+ /**
228
+ * yields a closure of the supplied parameters, producing a function that takes
229
+ * no arguments and is suitable for use as an autogravity function like so:
230
+ *
231
+ * @param margin (int) - distance from the viewable region edge that an
232
+ * element should be before setting its tooltip's gravity to be away
233
+ * from that edge.
234
+ * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
235
+ * if there are no viewable region edges effecting the tooltip's
236
+ * gravity. It will try to vary from this minimally, for example,
237
+ * if 'sw' is preferred and an element is near the right viewable
238
+ * region edge, but not the top edge, it will set the gravity for
239
+ * that element's tooltip to be 'se', preserving the southern
240
+ * component.
241
+ */
242
+ $.fn.tipsy.autoBounds = function(margin, prefer) {
243
+ return function() {
244
+ var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
245
+ boundTop = $(document).scrollTop() + margin,
246
+ boundLeft = $(document).scrollLeft() + margin,
247
+ $this = $(this);
248
+
249
+ if ($this.offset().top < boundTop) dir.ns = 'n';
250
+ if ($this.offset().left < boundLeft) dir.ew = 'w';
251
+ if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
252
+ if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
253
+
254
+ return dir.ns + (dir.ew ? dir.ew : '');
255
+ }
256
+ };
257
+
258
+ })(jQuery);
kraken.php ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Copyright 2014 Karim Salman (email : ksalman@kraken.io)
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License, version 2, as
7
+ published by the Free Software Foundation.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program; if not, write to the Free Software
16
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
+ */
18
+
19
+ /*
20
+ * Plugin Name: Kraken Image Optimizer
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.1
25
+ * Stable Tag: 1.0.1
26
+ * Author URI: https://kraken.io
27
+ * License GPL2
28
+ */
29
+
30
+
31
+ if ( !class_exists( 'Wp_Kraken' ) ) {
32
+
33
+ class Wp_Kraken {
34
+
35
+ private $id;
36
+
37
+ private $kraken_settings = array();
38
+
39
+ function __construct() {
40
+ $plugin_dir_path = dirname( __FILE__ );
41
+ require_once( $plugin_dir_path . '/lib/Kraken.php' );
42
+ $this->kraken_settings = get_option( '_kraken_options' );
43
+ add_action( 'admin_init', array( &$this, 'admin_init' ) );
44
+ add_action( 'admin_enqueue_scripts', array( &$this, 'my_enqueue' ) );
45
+ add_action( 'wp_ajax_kraken_request', array( &$this, 'kraken_media_library_ajax_callback' ) );
46
+ add_action( 'manage_media_custom_column', array( &$this, 'fill_media_columns' ), 10, 2 );
47
+ add_filter( 'manage_media_columns', array( &$this, 'add_media_columns') );
48
+ add_filter( 'wp_generate_attachment_metadata', array( &$this, 'optimize_thumbnails' ) );
49
+ add_action( 'add_attachment', array( &$this, 'kraken_media_uploader_callback' ) );
50
+ }
51
+
52
+ /*
53
+ * Adds kraken fields and settings to Settings->Media settings page
54
+ */
55
+ function admin_init() {
56
+
57
+ add_settings_section( 'kraken_image_optimizer', 'Kraken Image Optimizer', array( &$this, 'show_kraken_image_optimizer' ), 'media' );
58
+
59
+ register_setting(
60
+ 'media',
61
+ '_kraken_options',
62
+ array( &$this, 'validate_options' )
63
+ );
64
+
65
+ add_settings_field(
66
+ 'kraken_api_key',
67
+ 'API Key:',
68
+ array( &$this, 'show_api_key' ),
69
+ 'media',
70
+ 'kraken_image_optimizer'
71
+ );
72
+
73
+ add_settings_field(
74
+ 'kraken_api_secret',
75
+ 'API Secret:',
76
+ array( &$this, 'show_api_secret' ),
77
+ 'media',
78
+ 'kraken_image_optimizer'
79
+ );
80
+
81
+ add_settings_field(
82
+ 'kraken_lossy',
83
+ 'Optimization Type:',
84
+ array( &$this, 'show_lossy' ),
85
+ 'media',
86
+ 'kraken_image_optimizer'
87
+ );
88
+
89
+ add_settings_field(
90
+ 'credentials_valid',
91
+ 'API status:',
92
+ array( &$this, 'show_credentials_validity' ),
93
+ 'media',
94
+ 'kraken_image_optimizer'
95
+ );
96
+
97
+ }
98
+
99
+ function my_enqueue( $hook ) {
100
+ wp_enqueue_script( 'jquery' );
101
+ wp_enqueue_script( 'tipsy-js', plugins_url( '/js/jquery.tipsy.js', __FILE__ ), array( 'jquery' ) );
102
+ wp_enqueue_script( 'ajax-script', plugins_url( '/js/ajax.js', __FILE__ ), array( 'jquery' ) );
103
+ wp_enqueue_style( 'kraken_admin_style', plugins_url( 'css/admin.css', __FILE__ ) );
104
+ wp_enqueue_style( 'tipsy_style', plugins_url( 'css/tipsy.css', __FILE__ ) );
105
+ wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
106
+ }
107
+
108
+ function get_api_status( $api_key, $api_secret ) {
109
+
110
+ /* Possible API Status Errors:
111
+ *
112
+ * 'Incoming request body does not contain a valid JSON object'
113
+ * 'Incoming request body does not contain a valid auth.api_key or auth.api_secret'
114
+ * 'Kraken has encountered an unexpected error and cannot fulfill your request'
115
+ * 'User not found'
116
+ * 'API Key and API Secret mismatch'
117
+ */
118
+
119
+ if ( !empty( $api_key ) && !empty( $api_secret ) ) {
120
+ $kraken = new Kraken( $api_key, $api_secret );
121
+ $status = $kraken->status();
122
+ return $status;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ /**
128
+ * Handles optimizing already-uploaded images in the Media Library
129
+ */
130
+ function kraken_media_library_ajax_callback() {
131
+
132
+ $image_id = (int) $_POST['id'];
133
+
134
+ if ( wp_attachment_is_image( $image_id ) ) {
135
+
136
+ $imageUrl = wp_get_attachment_url( $image_id );
137
+ $image_path = get_attached_file( $image_id );
138
+
139
+ $settings = $this->kraken_settings;
140
+
141
+ $api_key = isset( $settings['api_key'] ) ? $settings['api_key'] : '';
142
+ $api_secret = isset( $settings['api_secret'] ) ? $settings['api_secret'] : '';
143
+
144
+ $status = $this->get_api_status( $api_key, $api_secret );
145
+
146
+
147
+ if ( $status === false ) {
148
+
149
+ // TODO: Update older error messages stored in WP Post Meta
150
+ $kv['error'] = 'There is a problem with your credentials. Please check them in the Kraken.io settings section of Media Settings, and try again.';
151
+ update_post_meta( $image_id, '_kraken_size', $kv );
152
+ echo json_encode( array( 'error' => $kv['error'] ) );
153
+ exit;
154
+ }
155
+
156
+ if ( isset($status['active']) && $status['active'] === true ) {
157
+
158
+ } else {
159
+ echo json_encode( array( 'error' => 'Your API is inactive. Please visit your account settings' ) );
160
+ die();
161
+ }
162
+
163
+ $result = $this->optimize_image( $imageUrl );
164
+ $kv = array();
165
+
166
+ if ( $result['success'] == true && !isset( $result['error'] ) ) {
167
+
168
+ $kraked_url = $result['kraked_url'];
169
+ $savings_percentage = (int) $result['saved_bytes'] / (int) $result['original_size'] * 100;
170
+ $kv['original_size'] = self::pretty_kb( $result['original_size'] );
171
+ $kv['kraked_size'] = self::pretty_kb( $result['kraked_size'] );
172
+ $kv['saved_bytes'] = self::pretty_kb( $result['saved_bytes'] );
173
+ $kv['savings_percent'] = round( $savings_percentage, 2 ) . '%';
174
+ $kv['type'] = $result['type'];
175
+ $kv['success'] = true;
176
+ $kv['meta'] = wp_get_attachment_metadata( $image_id );
177
+
178
+ if ( $this->replace_image( $image_path, $kraked_url ) ) {
179
+ update_post_meta( $image_id, '_kraken_size', $kv );
180
+ echo json_encode( $kv );
181
+ }
182
+ } else {
183
+
184
+ // error or no optimization
185
+ if ( file_exists( $image_path ) ) {
186
+
187
+ $kv['original_size'] = self::pretty_kb( filesize( $image_path ) );
188
+ $kv['error'] = $result['error'];
189
+ $kv['type'] = $result['type'];
190
+
191
+ if ( $kv['error'] == 'This image can not be optimized any further' ) {
192
+ $kv['kraked_size'] = 'No savings found';
193
+ $kv['no_savings'] = true;
194
+ }
195
+
196
+ update_post_meta( $image_id, '_kraken_size', $kv );
197
+
198
+ } else {
199
+ // file not found
200
+ }
201
+ echo json_encode($result);
202
+ }
203
+ }
204
+ die();
205
+ }
206
+
207
+ /**
208
+ * Handles optimizing images uploaded through any of the media uploaders.
209
+ */
210
+ function kraken_media_uploader_callback( $image_id ) {
211
+
212
+ $this->id = $image_id;
213
+
214
+ if ( wp_attachment_is_image( $image_id ) ) {
215
+
216
+ $imageUrl = wp_get_attachment_url( $image_id );
217
+ $image_path = get_attached_file( $image_id );
218
+ $result = $this->optimize_image( $imageUrl );
219
+
220
+ if ( $result['success'] == true && !isset( $result['error'] ) ) {
221
+
222
+ $kraked_url = $result['kraked_url'];
223
+ $savings_percentage = (int) $result['saved_bytes'] / (int) $result['original_size'] * 100;
224
+ $kv['original_size'] = self::pretty_kb( $result['original_size'] );
225
+ $kv['kraked_size'] = self::pretty_kb( $result['kraked_size'] );
226
+ $kv['saved_bytes'] = self::pretty_kb( $result['saved_bytes'] );
227
+ $kv['savings_percent'] = round( $savings_percentage, 2 ) . '%';
228
+ $kv['type'] = $result['type'];
229
+ $kv['success'] = true;
230
+ $kv['meta'] = wp_get_attachment_metadata( $image_id );
231
+
232
+ if ( $this->replace_image( $image_path, $kraked_url ) ) {
233
+ update_post_meta( $image_id, '_kraken_size', $kv );
234
+ } else {
235
+ // writing image failed
236
+ }
237
+ } else {
238
+
239
+ // error or no optimization
240
+ if ( file_exists( $image_path ) ) {
241
+
242
+ $kv['original_size'] = self::pretty_kb( filesize( $image_path ) );
243
+ $kv['error'] = $result['error'];
244
+ $kv['type'] = $result['type'];
245
+
246
+ if ( $kv['error'] == 'This image can not be optimized any further' ) {
247
+ $kv['kraked_size'] = 'No savings found';
248
+ $kv['no_savings'] = true;
249
+ }
250
+
251
+ update_post_meta( $image_id, '_kraken_size', $kv );
252
+
253
+ } else {
254
+ // file not found
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ function show_credentials_validity() {
261
+
262
+ $settings = $this->kraken_settings;
263
+
264
+ $api_key = isset( $settings['api_key'] ) ? $settings['api_key'] : '';
265
+ $api_secret = isset( $settings['api_secret'] ) ? $settings['api_secret'] : '';
266
+
267
+ $status = $this->get_api_status( $api_key, $api_secret );
268
+ $url = admin_url() . 'images/';
269
+
270
+ if ( $status !== false && isset( $status['active'] ) && $status['active'] === true ) {
271
+ $url .= 'yes.png';
272
+ echo '<p class="apiStatus">Your credentials are valid <span class="apiValid" style="background:url(' . "'$url') no-repeat 0 0" . '"></span></p>';
273
+ } else {
274
+ $url .= 'no.png';
275
+ echo '<p class="apiStatus">There is a problem with your credentials <span class="apiInvalid" style="background:url(' . "'$url') no-repeat 0 0" . '"></span></p>';
276
+ }
277
+
278
+ }
279
+
280
+ function show_kraken_image_optimizer() {
281
+ echo '<a href="http://kraken.io" title="Visit Kraken.io Homepage">Kraken.io</a> API settings';
282
+ }
283
+
284
+ function validate_options( $input ) {
285
+ $valid = array();
286
+ $error = '';
287
+ $valid['api_lossy'] = $input['api_lossy'];
288
+
289
+ $status = $this->get_api_status( $input['api_key'], $input['api_secret'] );
290
+
291
+ if ( $status !== false ) {
292
+
293
+ if ( isset($status['active']) && $status['active'] === true ) {
294
+ if ( $status['plan_name'] === 'Developers' ) {
295
+ $error = 'Developer API credentials cannot be used with this plugin.';
296
+ } else {
297
+ $valid['api_key'] = $input['api_key'];
298
+ $valid['api_secret'] = $input['api_secret'];
299
+ }
300
+ } else {
301
+ $error = 'There is a problem with your credentials. Please check them from your Kraken.io account.';
302
+ }
303
+
304
+ } else {
305
+ $error = 'Please enter a valid Kraken.io API key and secret';
306
+ }
307
+
308
+ if ( !empty( $error) ) {
309
+ add_settings_error(
310
+ 'media',
311
+ 'api_key_error',
312
+ $error,
313
+ 'error'
314
+ );
315
+ }
316
+
317
+ return $valid;
318
+ }
319
+
320
+ function show_api_key() {
321
+ $settings = $this->kraken_settings;
322
+ $value = isset( $settings['api_key'] ) ? $settings['api_key'] : '';
323
+ ?>
324
+ <input id='kraken_api_key' name='_kraken_options[api_key]'
325
+ type='text' value='<?php echo esc_attr( $value ); ?>' size="50"/>
326
+ <?php
327
+ }
328
+
329
+ function show_api_secret() {
330
+ $settings = $this->kraken_settings;
331
+ $value = isset( $settings['api_secret'] ) ? $settings['api_secret'] : '';
332
+ ?>
333
+ <input id='kraken_api_secret' name='_kraken_options[api_secret]'
334
+ type='text' value='<?php echo esc_attr( $value ); ?>' size="50"/>
335
+ <?php
336
+ }
337
+
338
+ function show_lossy() {
339
+ $options = get_option( '_kraken_options' );
340
+ $value = isset( $options['api_lossy'] ) ? $options['api_lossy'] : 'lossy';
341
+
342
+ $html = '<input type="radio" id="kraken_lossy" name="_kraken_options[api_lossy]" value="lossy"' . checked( 'lossy', $value, false ) . '/>';
343
+ $html .= '<label for="kraken_lossy">Lossy</label>';
344
+
345
+ $html .= '<input style="margin-left:10px;" type="radio" id="kraken_lossless" name="_kraken_options[api_lossy]" value="lossless"' . checked( 'lossless', $value, false ) . '/>';
346
+ $html .= '<label for="kraken_lossless">Lossless</label>';
347
+
348
+ echo $html;
349
+ }
350
+
351
+ function fill_media_columns( $column_name, $id ) {
352
+
353
+ $original_size = filesize( get_attached_file( $id ) );
354
+ $original_size = self::pretty_kb( $original_size );
355
+ switch ( $column_name ) {
356
+ case 'original_size' :
357
+ $meta = get_post_meta($id, '_kraken_size', true);
358
+
359
+ if ( isset( $meta['original_size'] ) ) {
360
+ echo $meta['original_size'];
361
+ } else {
362
+ echo $original_size;
363
+ }
364
+
365
+ break;
366
+
367
+ case 'kraken_size' :
368
+ $meta = get_post_meta($id, '_kraken_size', true);
369
+
370
+ // Is it optimized? Show some stats
371
+ if ( isset( $meta['kraked_size'] ) && empty( $meta['no_savings'] ) ) {
372
+ $kraked_size = $meta['kraked_size'];
373
+ $type = $meta['type'];
374
+ $savings_percentage = $meta['savings_percent'];
375
+ echo '<strong>' . $kraked_size .'</strong><br /><small>Type:&nbsp;' . $type . '</small><br /><small>Savings:&nbsp;' . $savings_percentage . '</small>';
376
+
377
+ // Were there no savings, or was there an error?
378
+ } else {
379
+ 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>';
380
+
381
+ if ( isset( $meta['error'] ) ) {
382
+ $error = $meta['error'];
383
+ echo '<div class="krakenErrorWrap"><a class="krakenError" title="' . $error . '">Failed! Hover here</a></div>';
384
+ }
385
+
386
+ if ( !empty( $meta['no_savings'] ) ) {
387
+ echo '<div class="noSavings"><strong>No savings found</strong><br /><small>Type:&nbsp;' . $meta['type'] . '</small></div>';
388
+ }
389
+
390
+ }
391
+ break;
392
+ }
393
+ }
394
+
395
+ function add_media_columns( $columns ) {
396
+ $columns['original_size'] = 'Original Size';
397
+ $columns['kraken_size'] = 'Kraked Size';
398
+ return $columns;
399
+ }
400
+
401
+ function replace_image($image_path, $kraked_url) {
402
+
403
+ $rv = false;
404
+ if( ini_get( 'allow_url_fopen' ) ) {
405
+
406
+ $rv = file_put_contents( $image_path, file_get_contents($kraked_url) );
407
+
408
+ } else if ( function_exists('curl_version') ) {
409
+
410
+ $ch = curl_init( $kraked_url );
411
+ curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
412
+ $result = curl_exec($ch);
413
+ }
414
+ return $rv !== false;
415
+ }
416
+
417
+ function optimize_image($url) {
418
+
419
+ $settings = $this->kraken_settings;
420
+ $kraken = new Kraken($settings['api_key'], $settings['api_secret']);
421
+
422
+ $lossy = $settings['api_lossy'] === "lossy";
423
+
424
+ $params = array(
425
+ "url" => $url,
426
+ "wait" => true,
427
+ "lossy" => $lossy
428
+ );
429
+
430
+ $data = $kraken->url( $params );
431
+ $data['type'] = $settings['api_lossy'];
432
+
433
+ return $data;
434
+ }
435
+
436
+ function optimize_thumbnails($image_data) {
437
+
438
+ $image_id = $this->id;
439
+ $upload_dir = wp_upload_dir();
440
+ $upload_path = $upload_dir['path'];
441
+ $upload_url = $upload_dir['url'];
442
+
443
+ if ( isset( $image_data['sizes'] ) ) {
444
+ $sizes = $image_data['sizes'];
445
+ }
446
+
447
+ if ( !empty( $sizes ) ) {
448
+
449
+ $thumb_url = '';
450
+ $thumb_path = '';
451
+
452
+ foreach ( $sizes as $size ) {
453
+
454
+ $thumb_path = $upload_path . '/' . $size['file'];
455
+ $thumb_url = $upload_url . '/' . $size['file'];
456
+
457
+ if ( file_exists( $thumb_path ) !== false ) {
458
+ $result = $this->optimize_image( $thumb_url );
459
+
460
+ if ( !empty($result) && isset($result['success']) && isset( $result['kraked_url'] ) ) {
461
+ $kraked_url = $result["kraked_url"];
462
+ if ( $this->replace_image( $thumb_path, $kraked_url ) ) {
463
+ // file written successfully
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ return $image_data;
470
+ }
471
+
472
+ static function pretty_kb( $bytes ) {
473
+ return round( ( $bytes / 1024 ), 2 ) . ' kB';
474
+ }
475
+ }
476
+ }
477
+
478
+ new Wp_Kraken();
lib/Kraken.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Kraken
4
+ {
5
+
6
+ protected $auth = array();
7
+
8
+ public function __construct($key = '', $secret = '')
9
+ {
10
+ $this->auth = array(
11
+ "auth" => array(
12
+ "api_key" => $key,
13
+ "api_secret" => $secret
14
+ )
15
+ );
16
+ }
17
+
18
+ public function url($opts = array())
19
+ {
20
+ $data = json_encode(array_merge($this->auth, $opts));
21
+ $response = self::request($data, "https://api.kraken.io/v1/url");
22
+
23
+ return $response;
24
+ }
25
+
26
+ public function upload($opts = array())
27
+ {
28
+ if (!isset($opts['file']))
29
+ {
30
+ return array(
31
+ "success" => false,
32
+ "error" => "File parameter was not provided"
33
+ );
34
+ }
35
+
36
+ if (preg_match("/\/\//i", $opts['file']))
37
+ {
38
+ $opts['url'] = $opts['file'];
39
+ unset($opts['file']);
40
+ return $this->url($opts);
41
+ }
42
+
43
+ if (!file_exists($opts['file']))
44
+ {
45
+ return array(
46
+ "success" => false,
47
+ "error" => "File `" . $opts['file'] . "` does not exist"
48
+ );
49
+ }
50
+
51
+ $file = '@' . $opts['file'];
52
+
53
+ unset($opts['file']);
54
+
55
+ $data = array_merge(array(
56
+ "file" => $file,
57
+ "data" => json_encode(array_merge(
58
+ $this->auth, $opts
59
+ ))
60
+ ));
61
+
62
+ $response = self::request($data, "https://api.kraken.io/v1/upload");
63
+
64
+ return $response;
65
+ }
66
+
67
+ public function status()
68
+ {
69
+ $data = array('auth' => array(
70
+ 'api_key' => $this->auth['auth']['api_key'],
71
+ 'api_secret' => $this->auth['auth']['api_secret']
72
+ ));
73
+ $response = self::request(json_encode($data), "https://api.kraken.io/user_status");
74
+
75
+ return $response;
76
+ }
77
+
78
+ private function request($data, $url)
79
+ {
80
+ $curl = curl_init();
81
+
82
+ curl_setopt($curl, CURLOPT_URL, $url);
83
+ curl_setopt($curl, CURLOPT_POST, 1);
84
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
85
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
86
+ curl_setopt($curl, CURLOPT_FAILONERROR, 1);
87
+
88
+ $response = json_decode(curl_exec($curl), true);
89
+ $error = curl_errno($curl);
90
+
91
+ curl_close($curl);
92
+
93
+ if ($error > 0) {
94
+ throw new RuntimeException(sprintf('cURL returned with the following error code: "%s"', $error));
95
+ }
96
+
97
+ return $response;
98
+ }
99
+
100
+ }
readme.txt ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Kraken Image Optimizer ===
2
+ Contributors: karim79
3
+ Tags: Image Optimizer, Optimize, Images, Media, Performance, SEO, smushit, compress, kraken-image-optimizer
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.1
8
+ License: GPLv2 or later
9
+ License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10
+
11
+
12
+ This plugin allows you to optimize all your Wordpress images through the Kraken API
13
+
14
+
15
+ == Description ==
16
+
17
+ This plugin allows you to optimize new and existing Wordpress image uploads through [Kraken Image Optimizer's](https://kraken.io "Kraken Image Optimizer") API. Both lossless and lossy optimization modes are supported. Supported filetypes are JPEG, PNG and GIF. Maximum filesize limit is 8MB. For more details, including detailed documentation and plans and pricing, please visit [Kraken.io](https://kraken.io "Kraken Image Optimizer"). **Note: You will need to obtain a paid Kraken API key and secret to use this plugin**.
18
+
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 [Kraken.io](https://kraken.io "Kraken Image Optimizer"). The Developer API key/secret will **not** work with this plugin.
26
+
27
+
28
+ Once you have obtained your credentials, from your Wordpress admin, go to Settings->Media. 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
+
33
+ == Installation ==
34
+
35
+ To install the Kraken Wordpress Plugin:
36
+
37
+ 1. Upload `kraken.php` to the `/wp-content/plugins/` directory
38
+ 2. Activate the plugin through the 'Plugins' menu in WordPress
39
+ 3. Enter your Kraken API key and secret into the new **Kraken.io Settings** section of Settings->Media.
40
+ 4. Any images you upload from now on using Wordpress's Media Upload will be optimized according to your settings. Auto-generated thumbnails will also be optimized.
41
+ 5. Images already present can be optimized from within the Media Library.
42
+
43
+ == Screenshots ==
44
+
45
+ 1. This screenshot shows the new section which this plugin to Settings->Media. You must enter your credentials, and select your optimization mode from there, then hit **save**.
46
+ 2. This screenshot shows the two columns added by Kraken Image Optimizer: **original image** and **Kraked size**, as well as the new **Optimize This Image** button which is present for images which already exist in your media library. Stats and optimization type are shown for optimized images.
47
+
48
+ == Frequently Asked Questions ==
49
+
50
+ = Can I test the plugin before I purchase an account from Kraken.io? =
51
+
52
+ No, you can't. You can, however, directly contact Kraken.io support for a private demonstration, if you like.
53
+ To test the performance and results of Kraken Image Optimizer, you can try the free Web Interface at https://kraken.io/web-interface
54
+
55
+
56
+ == Changelog ==
57
+
58
+ = 1.0 =
59
+ * First version. Supports lossy and lossless optimization of JPG, PNG and GIF (including aniGIF) image formats
60
+ * Hooks to Media Uploader to optimize all uploaded images, including generated thumbnails.
61
+ * Allows optimization of existing images in Wordpress Media Library.
62
+
63
+ = 1.0.1 =
64
+ * Minor cleanup release.
65
+