Version Description
- Minor cleanup release.
Download this release
Release Info
Developer | karim79 |
Plugin | Kraken.io Image Optimizer |
Version | 1.0.1 |
Comparing to | |
See all releases |
Version 1.0.1
- css/admin.css +98 -0
- css/tipsy.css +25 -0
- js/ajax.js +155 -0
- js/jquery.tipsy.js +258 -0
- kraken.php +478 -0
- lib/Kraken.php +100 -0
- 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: ' + 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: ' . $type . '</small><br /><small>Savings: ' . $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: ' . $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 |
+
|