Version Description
Install new version over the old one
Download this release
Release Info
Developer | JoomUnited |
Plugin | WP Meta SEO |
Version | v1.0.0 |
Comparing to | |
See all releases |
Version v1.0.0
- css/chart.css +102 -0
- css/google-chart-dashboard.css +23 -0
- css/metaseo_admin.css +346 -0
- css/style.css +274 -0
- css/tooltip-metaimage.css +20 -0
- css/tooltip.css +20 -0
- img/ajax-loader.gif +0 -0
- img/arrow-down.png +0 -0
- img/arrow-up.png +0 -0
- img/bubble-top-v2.png +0 -0
- img/gplus-loader.gif +0 -0
- img/hz-loading.gif +0 -0
- img/icon.png +0 -0
- img/img-arrow.png +0 -0
- img/info.png +0 -0
- img/no-data-alert.png +0 -0
- img/update_loader.gif +0 -0
- img/update_loading.gif +0 -0
- img/view.gif +0 -0
- img/view.png +0 -0
- img/warning-20x20.png +0 -0
- img/warning-25x25.png +0 -0
- img/warning.png +0 -0
- inc/class.image-helper.php +513 -0
- inc/class.metaseo-admin.php +218 -0
- inc/class.metaseo-content-list-table.php +621 -0
- inc/class.metaseo-dashboard.php +218 -0
- inc/class.metaseo-image-list-table.php +1236 -0
- inc/class.wp-metaseo.php +79 -0
- inc/pages/content-meta.php +59 -0
- inc/pages/dashboard.php +131 -0
- inc/pages/image-meta.php +38 -0
- js/Chart.js +3379 -0
- js/dashboard-chart.js +87 -0
- js/metaseo_admin.js +784 -0
- js/pop-up.js +34 -0
- readme.txt +90 -0
- wp-meta-seo.php +203 -0
css/chart.css
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*-------------------------------------------------------
|
2 |
+
CSS FOR DASHBOARD
|
3 |
+
*/
|
4 |
+
.dashboard{
|
5 |
+
float: left;
|
6 |
+
border: #CCC 1px solid;
|
7 |
+
width: 99%;
|
8 |
+
background: #FFF;
|
9 |
+
}
|
10 |
+
|
11 |
+
.dashboard .left{width: 50%; float: left;}
|
12 |
+
.dashboard .right{width: 50%; float: right; }
|
13 |
+
.avera{width: 100%; height: 40px; font-size: 24px ;position: absolute; top: 50%; left: 0; margin-top: -20px; line-height:19px; text-align: center; z-index: 999999999999999}
|
14 |
+
|
15 |
+
.title-seo {
|
16 |
+
font-weight: bold;
|
17 |
+
}
|
18 |
+
|
19 |
+
.load {
|
20 |
+
height: 20px;
|
21 |
+
background: #4a8ace;
|
22 |
+
-moz-border-radius: 2px;
|
23 |
+
border-radius: 2px;
|
24 |
+
}
|
25 |
+
|
26 |
+
.noload {
|
27 |
+
float: right;
|
28 |
+
width: 60%;
|
29 |
+
background: #cecfce;
|
30 |
+
-moz-border-radius: 2px;
|
31 |
+
border-radius: 2px;
|
32 |
+
border: 1px solid #eee;
|
33 |
+
position: relative;
|
34 |
+
margin-top: -22px;
|
35 |
+
}
|
36 |
+
|
37 |
+
.loadtext {
|
38 |
+
font-family: Consolas;
|
39 |
+
font-size: 14px;
|
40 |
+
color: #FFF;
|
41 |
+
padding-left: 3px;
|
42 |
+
position: absolute;
|
43 |
+
bottom: 1px;
|
44 |
+
left: 40%;
|
45 |
+
}
|
46 |
+
|
47 |
+
.dashboard h1 {
|
48 |
+
padding: 10px 0px 10px 20px
|
49 |
+
}
|
50 |
+
|
51 |
+
.dashboard-left {
|
52 |
+
margin: 10px;
|
53 |
+
min-height:580px;
|
54 |
+
background: #eff3f7;
|
55 |
+
padding-bottom: 10px;
|
56 |
+
}
|
57 |
+
|
58 |
+
#canvas-holder{
|
59 |
+
margin:50px 0 0 0;
|
60 |
+
width:100%;
|
61 |
+
}
|
62 |
+
#chart-container {
|
63 |
+
margin: 10px 10px 0 10px;
|
64 |
+
padding: 10px;
|
65 |
+
background-color: #fff;
|
66 |
+
}
|
67 |
+
|
68 |
+
#canvas-holder h2 {
|
69 |
+
text-align: center;
|
70 |
+
}
|
71 |
+
|
72 |
+
.dashboard-right {
|
73 |
+
padding-bottom: 10px;
|
74 |
+
margin-top: 10px;
|
75 |
+
position: relative;
|
76 |
+
}
|
77 |
+
#wpmetaseo-update-version {
|
78 |
+
bottom: 0;
|
79 |
+
width: 90% ;
|
80 |
+
position: absolute;
|
81 |
+
}
|
82 |
+
#alexa-ranking{
|
83 |
+
float:left;
|
84 |
+
margin-left:30px;
|
85 |
+
margin-top: 30px;
|
86 |
+
}
|
87 |
+
#wpmetaseo-update-version {
|
88 |
+
background: none repeat scroll 0 0 #eff3f7;
|
89 |
+
padding:20px;
|
90 |
+
}
|
91 |
+
#wpmetaseo-update-version h4 {
|
92 |
+
margin:0;
|
93 |
+
}
|
94 |
+
#wpmetaseo-update-version ul {
|
95 |
+
margin-bottom:0;
|
96 |
+
}
|
97 |
+
|
98 |
+
header{
|
99 |
+
width: 90%;
|
100 |
+
margin: 0px auto;
|
101 |
+
padding: 30px 10px 10px 20px;
|
102 |
+
}
|
css/google-chart-dashboard.css
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.form-left{
|
2 |
+
width: 450px;
|
3 |
+
border-radius: 10px;
|
4 |
+
border: #CCC 1px solid;
|
5 |
+
margin: 20px 0px 0px 20px;
|
6 |
+
float: left;
|
7 |
+
padding: 20px 35px 20px 35px;
|
8 |
+
position: relative;
|
9 |
+
}
|
10 |
+
|
11 |
+
.form-right{
|
12 |
+
width: 450px;
|
13 |
+
border-radius: 10px;
|
14 |
+
border: #CCC 1px solid;
|
15 |
+
margin: 20px 20px 0px 0px;
|
16 |
+
float: right;
|
17 |
+
padding: 20px 35px 20px 35px;
|
18 |
+
position: relative;
|
19 |
+
}
|
20 |
+
.form-left .report{width: 100%; height: 300px}
|
21 |
+
.form-right .report{width: 100%; height: 300px}
|
22 |
+
.form-left .title{position: absolute; left: 30px; top:-10px; font-weight: bold; background:#eff3ef; padding: 0px 20px 0px 20px }
|
23 |
+
.form-right .title{position: absolute; left: 30px; top:-10px; font-weight: bold; background:#eff3ef; padding: 0px 20px 0px 20px }
|
css/metaseo_admin.css
ADDED
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
To change this license header, choose License Headers in Project Properties.
|
3 |
+
To change this template file, choose Tools | Templates
|
4 |
+
and open the template in the editor.
|
5 |
+
*/
|
6 |
+
/*
|
7 |
+
Created on : Sep 26, 2014, 11:41:40 AM
|
8 |
+
Author : Vien Nguyen
|
9 |
+
*/
|
10 |
+
|
11 |
+
.search-box{padding: 0px 0px 20px 0px; float: left}
|
12 |
+
.widefat .column-col_id {
|
13 |
+
width: 2.2em;
|
14 |
+
vertical-align: top;
|
15 |
+
}
|
16 |
+
.column-col_meta_title {
|
17 |
+
width: 20%;
|
18 |
+
}
|
19 |
+
.column-col_meta_desc {
|
20 |
+
width: 30%;
|
21 |
+
}
|
22 |
+
|
23 |
+
.title-len, .desc-len, .word-exceed {
|
24 |
+
color: #FFF;
|
25 |
+
border-radius: 2px;
|
26 |
+
min-width: 15px;
|
27 |
+
text-align: center;
|
28 |
+
float: left;
|
29 |
+
padding: 0px 10px;
|
30 |
+
position: relative;
|
31 |
+
bottom: 12px;
|
32 |
+
left: 15px;
|
33 |
+
}
|
34 |
+
|
35 |
+
.title-len, .desc-len {
|
36 |
+
background: none repeat scroll 0% 0% #7ad03a;
|
37 |
+
|
38 |
+
}
|
39 |
+
.word-exceed {
|
40 |
+
background: none repeat scroll 0% 0% #FFCC00;
|
41 |
+
}
|
42 |
+
|
43 |
+
.savedInfo {
|
44 |
+
float: right;
|
45 |
+
color: #999;
|
46 |
+
margin-right: 30px;
|
47 |
+
}
|
48 |
+
|
49 |
+
/* Snippet */
|
50 |
+
.snippet {
|
51 |
+
border:none;
|
52 |
+
padding: 4px;
|
53 |
+
margin-top: 2px;
|
54 |
+
}
|
55 |
+
|
56 |
+
.snippet_metatitle{
|
57 |
+
display: block;
|
58 |
+
color: #1a0dab;
|
59 |
+
font-size: 16px!important;
|
60 |
+
line-height: 1.2;
|
61 |
+
text-overflow: ellipsis;
|
62 |
+
}
|
63 |
+
.snippet_metalink {
|
64 |
+
color: #006621;
|
65 |
+
font-size: 13px;
|
66 |
+
line-height: 16px;
|
67 |
+
}
|
68 |
+
|
69 |
+
.widefat .snippet_metades {
|
70 |
+
color: #545454;
|
71 |
+
}
|
72 |
+
|
73 |
+
a.snippet_metatitle:hover {
|
74 |
+
text-decoration: underline;
|
75 |
+
color: #1E0FBE;
|
76 |
+
}
|
77 |
+
|
78 |
+
a.info-content {
|
79 |
+
outline:none;
|
80 |
+
padding-right: 10px;
|
81 |
+
}
|
82 |
+
|
83 |
+
a.info-content:hover {
|
84 |
+
text-decoration:none;
|
85 |
+
}
|
86 |
+
|
87 |
+
a.info-content .tooltip-metacontent {
|
88 |
+
z-index:10;
|
89 |
+
display:none;
|
90 |
+
padding:10px 15px;
|
91 |
+
margin-top:3px;
|
92 |
+
margin-left:10px;
|
93 |
+
width:250px;
|
94 |
+
}
|
95 |
+
|
96 |
+
a.info-content img {
|
97 |
+
height: 13px;
|
98 |
+
}
|
99 |
+
|
100 |
+
a.info-content .tooltip-metacontent .tooltip-metatitle {
|
101 |
+
overflow: hidden;
|
102 |
+
width: 512px;
|
103 |
+
color: #1e0fbe;
|
104 |
+
font-size: 18px!important;
|
105 |
+
line-height: 1.2;
|
106 |
+
white-space: nowrap;
|
107 |
+
text-overflow: ellipsis;
|
108 |
+
}
|
109 |
+
|
110 |
+
a.info-content .tooltip-metacontent .tooltip-metalink{color: #006621;
|
111 |
+
font-size: 13px;
|
112 |
+
line-height: 16px;
|
113 |
+
}
|
114 |
+
|
115 |
+
a.info-content .tooltip-metacontent .tooltip-metades {
|
116 |
+
color: #777
|
117 |
+
}
|
118 |
+
|
119 |
+
a.info-content:hover .tooltip-metacontent {
|
120 |
+
display:inline;
|
121 |
+
position:absolute;
|
122 |
+
color:#777;
|
123 |
+
border:1px solid #CCC;
|
124 |
+
border-radius: 2px;
|
125 |
+
background:#ffffff;
|
126 |
+
}
|
127 |
+
|
128 |
+
a.info-content .tooltip-metacontent
|
129 |
+
{
|
130 |
+
border-radius:2px;
|
131 |
+
box-shadow: 5px 5px 8px #eee;
|
132 |
+
|
133 |
+
}
|
134 |
+
|
135 |
+
.metaseo-metatitle, .metaseo-metadesc{
|
136 |
+
padding:4px 6px;
|
137 |
+
}
|
138 |
+
.action-wrapper{
|
139 |
+
width:38%;
|
140 |
+
float:left;
|
141 |
+
}
|
142 |
+
.snippet-wrapper{
|
143 |
+
width:60%;
|
144 |
+
float:left;
|
145 |
+
margin-left:2%
|
146 |
+
}
|
147 |
+
|
148 |
+
@media all and (max-width:768px){
|
149 |
+
.action-wrapper, .snippet-wrapper{
|
150 |
+
width: auto; float:none;
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
/* Alexa CSS */
|
155 |
+
.rank-row, .metrics-row {
|
156 |
+
margin-bottom: 25px;
|
157 |
+
margin-top: 0px !important;
|
158 |
+
}
|
159 |
+
a.tt {
|
160 |
+
cursor: help;
|
161 |
+
}
|
162 |
+
.ptt {
|
163 |
+
left: 57px;
|
164 |
+
position: absolute;
|
165 |
+
top: 210px;
|
166 |
+
}
|
167 |
+
.tt {
|
168 |
+
color: #333;
|
169 |
+
font-size: 12px !important;
|
170 |
+
font-weight: normal !important;
|
171 |
+
text-align: left;
|
172 |
+
text-decoration: none !important;
|
173 |
+
}
|
174 |
+
.tt span {
|
175 |
+
display: none;
|
176 |
+
}
|
177 |
+
.tt .fakeLink {
|
178 |
+
text-decoration: underline;
|
179 |
+
}
|
180 |
+
.tt img {
|
181 |
+
vertical-align: baseline;
|
182 |
+
}
|
183 |
+
.ptt-hover, .tt:hover {
|
184 |
+
background: none repeat scroll 0 0 transparent;
|
185 |
+
color: #aaaaff;
|
186 |
+
position: relative;
|
187 |
+
text-decoration: none;
|
188 |
+
z-index: 10000;
|
189 |
+
}
|
190 |
+
.ptt-hover span.tttooltip, .tt:hover span.tttooltip {
|
191 |
+
color: #111;
|
192 |
+
display: block;
|
193 |
+
left: -60px;
|
194 |
+
padding: 15px 0 0;
|
195 |
+
position: absolute;
|
196 |
+
text-align: left;
|
197 |
+
text-decoration: none;
|
198 |
+
top: -10px;
|
199 |
+
white-space: normal !important;
|
200 |
+
width: 200px;
|
201 |
+
z-index: 20000;
|
202 |
+
}
|
203 |
+
.ptt-hover.rightEnd span.tttooltip, .tt.rightEnd:hover span.tttooltip {
|
204 |
+
left: auto;
|
205 |
+
right: -20px;
|
206 |
+
}
|
207 |
+
.ptt-hover span.top, .tt:hover span.top {
|
208 |
+
background: url("../img/bubble-top-v2.png") no-repeat scroll 80px 1px rgba(0, 0, 0, 0);
|
209 |
+
display: block;
|
210 |
+
padding: 15px 8px 0;
|
211 |
+
}
|
212 |
+
.ptt-hover.rightEnd span.top, .tt.rightEnd:hover span.top {
|
213 |
+
background: url("/images/help/bubble-right.png") no-repeat scroll center top rgba(0, 0, 0, 0);
|
214 |
+
}
|
215 |
+
.ptt-hover span.middle, .tt:hover span.middle {
|
216 |
+
background-color: #f2f2f2;
|
217 |
+
border: 1px solid #c5c5c5;
|
218 |
+
border-radius: 3px;
|
219 |
+
display: block;
|
220 |
+
line-height: normal;
|
221 |
+
padding: 8px;
|
222 |
+
}
|
223 |
+
.ptt-hover span.bottom, .tt:hover span.bottom {
|
224 |
+
color: #548912;
|
225 |
+
display: block;
|
226 |
+
padding: 0 8px;
|
227 |
+
}
|
228 |
+
.popover {
|
229 |
+
border: 1px solid #3b81de;
|
230 |
+
box-shadow: none;
|
231 |
+
}
|
232 |
+
.popover, .popover-title, .popover a, .popover a:visited, .popover a:hover, .popover a:active {
|
233 |
+
color: #fff;
|
234 |
+
}
|
235 |
+
.siteInfoPage h3.popover-title {
|
236 |
+
color: #fff;
|
237 |
+
}
|
238 |
+
.popover-title {
|
239 |
+
border-bottom: 0 none;
|
240 |
+
font-size: 12px;
|
241 |
+
font-weight: bold;
|
242 |
+
}
|
243 |
+
.popover, .popover-title {
|
244 |
+
background-color: #3b81de;
|
245 |
+
}
|
246 |
+
.popover-content {
|
247 |
+
padding-top: 0;
|
248 |
+
}
|
249 |
+
.popover.top {
|
250 |
+
margin-top: -10px;
|
251 |
+
}
|
252 |
+
.popover.bottom {
|
253 |
+
margin-top: 10px;
|
254 |
+
}
|
255 |
+
.popover.left {
|
256 |
+
margin-left: -20px;
|
257 |
+
}
|
258 |
+
.popover.right {
|
259 |
+
margin-left: 20px;
|
260 |
+
}
|
261 |
+
.popover.right .arrow, .popover.right .arrow:after {
|
262 |
+
border-right-color: #3b81de;
|
263 |
+
}
|
264 |
+
.popover.left .arrow, .popover.left .arrow:after {
|
265 |
+
border-left-color: #3b81de;
|
266 |
+
}
|
267 |
+
.popover.top .arrow, .popover.top .arrow:after {
|
268 |
+
border-top-color: #3b81de;
|
269 |
+
}
|
270 |
+
.popover.bottom .arrow, .popover.bottom .arrow:after {
|
271 |
+
border-bottom-color: #3b81de;
|
272 |
+
}
|
273 |
+
|
274 |
+
.spark-bars {
|
275 |
+
display: none;
|
276 |
+
}
|
277 |
+
.panel-wrapper.no-padding .traffic-rankpromo-content {
|
278 |
+
padding: 12px 20px;
|
279 |
+
}
|
280 |
+
|
281 |
+
.img-inline {
|
282 |
+
padding-right: 5px;
|
283 |
+
vertical-align: middle;
|
284 |
+
}
|
285 |
+
|
286 |
+
.align-vmiddle {
|
287 |
+
vertical-align: middle;
|
288 |
+
}
|
289 |
+
.metrics-data, .metrics-pending {
|
290 |
+
color: #333;
|
291 |
+
font-family: "Open Sans",Helvetica,Arial,sans-serif;
|
292 |
+
font-size: 24px;
|
293 |
+
}
|
294 |
+
strong {
|
295 |
+
font-weight: bold;
|
296 |
+
}
|
297 |
+
.change-wrapper {
|
298 |
+
border-radius: 3px;
|
299 |
+
color: #fff;
|
300 |
+
font-family: "Open Sans",Helvetica,Arial,sans-serif;
|
301 |
+
font-size: 12px;
|
302 |
+
margin: 0;
|
303 |
+
padding: 3px 5px 3px 15px;
|
304 |
+
}
|
305 |
+
.change-up.change-r {
|
306 |
+
background: url("../img/arrow-up.png") no-repeat scroll 5px 50% #c32f16;
|
307 |
+
color: #fff;
|
308 |
+
}
|
309 |
+
.align-vmiddle {
|
310 |
+
vertical-align: middle;
|
311 |
+
}
|
312 |
+
.change-up {
|
313 |
+
background: url("../img/arrow-up.png") no-repeat scroll 5px 50% #68b325;
|
314 |
+
color: #fff;
|
315 |
+
}
|
316 |
+
|
317 |
+
.change-down {
|
318 |
+
background: #68b325 url("../img/arrow-down.png") no-repeat 5px 50%;
|
319 |
+
color: #fff;
|
320 |
+
}
|
321 |
+
|
322 |
+
.no-wrap {
|
323 |
+
white-space: nowrap;
|
324 |
+
}
|
325 |
+
|
326 |
+
.metrics-title {
|
327 |
+
color: #666;
|
328 |
+
font-weight: normal;
|
329 |
+
margin: 0 0 10px;
|
330 |
+
}
|
331 |
+
.note-no-data{
|
332 |
+
padding:12px 12px 12px 0;
|
333 |
+
}
|
334 |
+
.yellow-box {
|
335 |
+
height: auto;
|
336 |
+
padding: 5px 10px;
|
337 |
+
}
|
338 |
+
.yellow-box {
|
339 |
+
background-color: #fdf9e0;
|
340 |
+
border: 1px solid #feed78;
|
341 |
+
}
|
342 |
+
.note-no-data .col-pad {
|
343 |
+
background: url("../img/no-data-alert.png") no-repeat scroll 0 50% transparent;
|
344 |
+
display: block;
|
345 |
+
padding-left: 45px;
|
346 |
+
}
|
css/style.css
ADDED
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.search-box{padding: 0px 0px 20px 0px; float: left}
|
2 |
+
.img_advan{border: #CCC 1px solid;border-radius: 0px 30px 30px 0px}
|
3 |
+
.listpre img{float: left; padding: 5px}
|
4 |
+
.listpre:hover>.show-popup{display: block;}
|
5 |
+
|
6 |
+
a.show-popup {
|
7 |
+
display: none;
|
8 |
+
margin:10px 0 0 0;
|
9 |
+
float:left;
|
10 |
+
|
11 |
+
}
|
12 |
+
div.popup-bg {
|
13 |
+
position:absolute;
|
14 |
+
top:0;
|
15 |
+
bottom:0;
|
16 |
+
left:-20px;
|
17 |
+
right:0;
|
18 |
+
z-index:99;
|
19 |
+
background:#999;
|
20 |
+
display:none;
|
21 |
+
}
|
22 |
+
div.popup {
|
23 |
+
border-radius:2px;
|
24 |
+
z-index:999;
|
25 |
+
display:none;
|
26 |
+
background:#FFF;
|
27 |
+
overflow: auto;
|
28 |
+
}
|
29 |
+
div.popup-header {
|
30 |
+
position:relative;
|
31 |
+
float:left;
|
32 |
+
width:1000px;
|
33 |
+
line-height:30px;
|
34 |
+
background:#FF9933;
|
35 |
+
}
|
36 |
+
|
37 |
+
.popup-header .pop-title{
|
38 |
+
background: #FFF;
|
39 |
+
padding: 6px 0px 0px 15px;
|
40 |
+
color:#555!important;
|
41 |
+
margin:0;
|
42 |
+
font-size:16px;
|
43 |
+
}
|
44 |
+
span.popup-close {
|
45 |
+
cursor:pointer;
|
46 |
+
color: #555;
|
47 |
+
font-weight: bold;
|
48 |
+
font-size:16px;
|
49 |
+
position:absolute;
|
50 |
+
right:10px;
|
51 |
+
z-index:9999;
|
52 |
+
}
|
53 |
+
div.popup-content .content-header{
|
54 |
+
color:#666;margin:0;padding: 10px 10px 12px 14px;background: #fffeee
|
55 |
+
}
|
56 |
+
div.popup-content .content-box{
|
57 |
+
/*width:980px;*/
|
58 |
+
min-height:250px;
|
59 |
+
overflow: auto;
|
60 |
+
padding: 0 10px 10px;
|
61 |
+
position: relative;
|
62 |
+
}
|
63 |
+
|
64 |
+
span.spinner-light{
|
65 |
+
background: url("../../../../wp-admin/images/wpspin_light.gif") no-repeat scroll 0 0 / 16px 16px rgba(0, 0, 0, 0);
|
66 |
+
background-size: 16px 16px;
|
67 |
+
display: none;
|
68 |
+
float: right;
|
69 |
+
opacity: 0.7;
|
70 |
+
filter: alpha(opacity=70);
|
71 |
+
width: 16px;
|
72 |
+
height: 16px;
|
73 |
+
margin: 5px 5px 0;
|
74 |
+
position: absolute; right:-8px;top:-19px; z-index: 999;
|
75 |
+
}
|
76 |
+
span.metaseo-checked{
|
77 |
+
background: url("../../../../wp-admin/images/yes.png") no-repeat scroll 0 0 / 20px 20px rgba(0, 0, 0, 0);
|
78 |
+
display: inline-block;
|
79 |
+
float: right;
|
80 |
+
height: 20px;
|
81 |
+
margin: 2px 5px 0;
|
82 |
+
opacity: 0.7;
|
83 |
+
width: 20px;
|
84 |
+
}
|
85 |
+
p.metaseo-msg{
|
86 |
+
background: none repeat scroll 0 0 #fff;
|
87 |
+
border-left: 4px solid #7ad03a;
|
88 |
+
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
|
89 |
+
bottom: 0;
|
90 |
+
clear: both;
|
91 |
+
color: #444;
|
92 |
+
display: none;
|
93 |
+
margin: 5px !important;
|
94 |
+
padding: 3px 5px;
|
95 |
+
right: 2px;
|
96 |
+
text-align: center;
|
97 |
+
}
|
98 |
+
.metaseo-border-top td{
|
99 |
+
border-top:1px solid #ddd;
|
100 |
+
}
|
101 |
+
|
102 |
+
.metaseo-border-bottom td{
|
103 |
+
border-bottom:1px solid #dedede;
|
104 |
+
}
|
105 |
+
td.metaseo-action{
|
106 |
+
text-align:center;
|
107 |
+
}
|
108 |
+
.metaseo-img-wrapper{
|
109 |
+
clear:left;
|
110 |
+
padding:5px;
|
111 |
+
}
|
112 |
+
.metaseo-img-wrapper div.dimension{
|
113 |
+
display:inline-block;padding:5px;
|
114 |
+
font-size:12px;color:#555;
|
115 |
+
background:none;
|
116 |
+
opacity: 0.9;
|
117 |
+
filter: alpha(opacity=90);
|
118 |
+
clear:left;
|
119 |
+
}
|
120 |
+
.metaseo-img-wrapper div.dimension span{
|
121 |
+
font-weight:bold;
|
122 |
+
}
|
123 |
+
.metaseo-img-wrapper img, .metaseo-img img{
|
124 |
+
max-width:100%;
|
125 |
+
}
|
126 |
+
.metaseo-img-wrapper .metaseo-img{
|
127 |
+
position:relative;
|
128 |
+
}
|
129 |
+
|
130 |
+
.metaseo-img .img-choosen{
|
131 |
+
position: absolute;
|
132 |
+
top:0px;
|
133 |
+
left: 0px;
|
134 |
+
float:left;
|
135 |
+
}
|
136 |
+
|
137 |
+
div.img-name-wrapper{
|
138 |
+
font-size:0.98em;
|
139 |
+
}
|
140 |
+
|
141 |
+
div.img-name-wrapper p{
|
142 |
+
margin:3px;padding-left:2px;
|
143 |
+
}
|
144 |
+
|
145 |
+
table.metaseo_images td input{
|
146 |
+
font-size:0.99em!important;
|
147 |
+
color:#686868;
|
148 |
+
max-width:100%;
|
149 |
+
}
|
150 |
+
|
151 |
+
a.button.meta-default{
|
152 |
+
float: left;
|
153 |
+
border: #CCC 1px solid;
|
154 |
+
color: #018601;
|
155 |
+
width: 70px;
|
156 |
+
font-weight: bold;
|
157 |
+
text-align: center;
|
158 |
+
padding-top: 4px;
|
159 |
+
line-height: 16px;
|
160 |
+
}
|
161 |
+
|
162 |
+
a.button.meta-default img {
|
163 |
+
width: 14px;
|
164 |
+
height: 8px;
|
165 |
+
}
|
166 |
+
|
167 |
+
.img_seo_type {
|
168 |
+
padding: 5px;
|
169 |
+
float: left
|
170 |
+
}
|
171 |
+
|
172 |
+
input.metaseo-fix-meta {
|
173 |
+
float:left;
|
174 |
+
margin-right:6px;
|
175 |
+
}
|
176 |
+
|
177 |
+
/* Tooltip */
|
178 |
+
a.info {outline:none; padding-right: 10px}
|
179 |
+
|
180 |
+
a.info:hover {text-decoration:none;}
|
181 |
+
|
182 |
+
a.info .tooltip{
|
183 |
+
z-index:10;display:none; padding:14px 20px;
|
184 |
+
margin-top:-60px; margin-left:10px;
|
185 |
+
width:250px; line-height:15px;
|
186 |
+
}
|
187 |
+
|
188 |
+
a.info:hover .tooltip {
|
189 |
+
display:inline;
|
190 |
+
position:absolute;
|
191 |
+
color:#111;
|
192 |
+
border:1px solid #DCA;
|
193 |
+
border-radius: 10px;
|
194 |
+
background:rgba(51,51,51,0.8);
|
195 |
+
color:#ffffff;
|
196 |
+
font-style: italic;
|
197 |
+
}
|
198 |
+
|
199 |
+
a.info .tooltip
|
200 |
+
{
|
201 |
+
border-radius:4px;
|
202 |
+
box-shadow: 5px 5px 8px #CCC;
|
203 |
+
}
|
204 |
+
|
205 |
+
.opt-info-warning{
|
206 |
+
background:url('../img/warning-25x25.png') no-repeat scroll 1px 5px;
|
207 |
+
min-height:30px;
|
208 |
+
}
|
209 |
+
|
210 |
+
.opt-info{
|
211 |
+
padding-left:30px;
|
212 |
+
}
|
213 |
+
|
214 |
+
span.saved-info {
|
215 |
+
display: block;
|
216 |
+
margin: 0;
|
217 |
+
min-height: 18px;
|
218 |
+
padding-left: 5px;
|
219 |
+
clear:left;
|
220 |
+
}
|
221 |
+
|
222 |
+
.metaseo-msg-success {
|
223 |
+
color:#7ad03a!important;
|
224 |
+
}
|
225 |
+
|
226 |
+
.metaseo-msg-warning{
|
227 |
+
color:#f51212!important;
|
228 |
+
}
|
229 |
+
|
230 |
+
.opt-info > p, .opt-info-warning > p {
|
231 |
+
font-size:0.95em!important;
|
232 |
+
color:#2EA2CC;
|
233 |
+
margin-bottom:1px!important;
|
234 |
+
}
|
235 |
+
|
236 |
+
a.img-resize, a.fix-metas{
|
237 |
+
font-size:11px!important;
|
238 |
+
margin-top:8px!important;
|
239 |
+
position: relative;
|
240 |
+
}
|
241 |
+
a.fix-metas, a.img-resize{
|
242 |
+
float:right;
|
243 |
+
margin-left:8px!important;
|
244 |
+
}
|
245 |
+
|
246 |
+
td.meta-info-row{
|
247 |
+
position:relative;
|
248 |
+
}
|
249 |
+
th.column-col_image_info{
|
250 |
+
padding-left:35px;
|
251 |
+
}
|
252 |
+
|
253 |
+
span.metaseo-loading{
|
254 |
+
display:block;width:32px;height:32px;border-radius:32px;
|
255 |
+
position:absolute;left:45%;top:40%;
|
256 |
+
background:url('../img/gplus-loader.gif') no-repeat scroll 0 0 transparent;
|
257 |
+
}
|
258 |
+
span.meta-update{
|
259 |
+
float:left;
|
260 |
+
display:inline-block;
|
261 |
+
width:30px;
|
262 |
+
height:30px;
|
263 |
+
}
|
264 |
+
span.update-loader{
|
265 |
+
background: url('../img/update_loader.gif') no-repeat scroll 0 0 transparent;
|
266 |
+
}
|
267 |
+
|
268 |
+
span.hz-loading{
|
269 |
+
background: url('../img/hz-loading.gif') no-repeat scroll 8px 6px transparent;
|
270 |
+
display:inline-block;
|
271 |
+
min-width:100px;
|
272 |
+
min-height:14px;
|
273 |
+
}
|
274 |
+
|
css/tooltip-metaimage.css
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
a.info {outline:none; padding-right: 10px}
|
2 |
+
a.info:hover {text-decoration:none;}
|
3 |
+
a.info .tooltip{
|
4 |
+
z-index:10;display:none; padding:14px 20px;
|
5 |
+
margin-top:-60px; margin-left:10px;
|
6 |
+
width:250px; line-height:15px;
|
7 |
+
}
|
8 |
+
a.info:hover .tooltip {
|
9 |
+
display:inline; position:absolute; color:#111;
|
10 |
+
border:1px solid #DCA;
|
11 |
+
border-radius: 10px;
|
12 |
+
background:rgba(51,51,51,0.8);
|
13 |
+
color:#ffffff;
|
14 |
+
font-style: italic;
|
15 |
+
}
|
16 |
+
a.info .tooltip
|
17 |
+
{
|
18 |
+
border-radius:4px;
|
19 |
+
box-shadow: 5px 5px 8px #CCC;
|
20 |
+
}
|
css/tooltip.css
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
a.info {outline:none; padding-right: 10px}
|
2 |
+
a.info:hover {text-decoration:none;}
|
3 |
+
a.info .tooltip {
|
4 |
+
z-index:10;display:none; padding:14px 20px;
|
5 |
+
margin-top:-65px; margin-right:28px;
|
6 |
+
width:250px; line-height:16px;
|
7 |
+
}
|
8 |
+
a.info:hover .tooltip{
|
9 |
+
display:inline; position:absolute; color:#111;
|
10 |
+
border:1px solid #DCA;
|
11 |
+
border-radius: 10px;
|
12 |
+
background:rgba(51,51,51,0.8);
|
13 |
+
color:#ffffff;
|
14 |
+
font-style: italic;
|
15 |
+
}
|
16 |
+
a.info .tooltip
|
17 |
+
{
|
18 |
+
border-radius:4px;
|
19 |
+
box-shadow: 5px 5px 8px #CCC;
|
20 |
+
}
|
img/ajax-loader.gif
ADDED
Binary file
|
img/arrow-down.png
ADDED
Binary file
|
img/arrow-up.png
ADDED
Binary file
|
img/bubble-top-v2.png
ADDED
Binary file
|
img/gplus-loader.gif
ADDED
Binary file
|
img/hz-loading.gif
ADDED
Binary file
|
img/icon.png
ADDED
Binary file
|
img/img-arrow.png
ADDED
Binary file
|
img/info.png
ADDED
Binary file
|
img/no-data-alert.png
ADDED
Binary file
|
img/update_loader.gif
ADDED
Binary file
|
img/update_loading.gif
ADDED
Binary file
|
img/view.gif
ADDED
Binary file
|
img/view.png
ADDED
Binary file
|
img/warning-20x20.png
ADDED
Binary file
|
img/warning-25x25.png
ADDED
Binary file
|
img/warning.png
ADDED
Binary file
|
inc/class.image-helper.php
ADDED
@@ -0,0 +1,513 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ImageHelper{
|
4 |
+
/**
|
5 |
+
*
|
6 |
+
*/
|
7 |
+
public static $src = '';
|
8 |
+
|
9 |
+
public function __construct(array $params = array()){
|
10 |
+
$c = new ReflectionClass(__CLASS__);
|
11 |
+
$statics = $c->getStaticProperties();
|
12 |
+
|
13 |
+
foreach($params as $key => $value){
|
14 |
+
if(!in_array($key, array_keys($statics))){
|
15 |
+
$this->{$key} = $value;
|
16 |
+
}
|
17 |
+
else{
|
18 |
+
self::$$key = $value;
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
*
|
25 |
+
*/
|
26 |
+
public static function IGetPart($src = ''){
|
27 |
+
if($src == '') { $src = self::$src; }
|
28 |
+
$ipart = new ImageHelper(array(
|
29 |
+
'src' => $src,
|
30 |
+
'base_path' => '',
|
31 |
+
'name' => '',
|
32 |
+
'ext' => '',
|
33 |
+
'dimension' => '',
|
34 |
+
'name_prefix' => '',
|
35 |
+
'name_suffix' => ''));
|
36 |
+
|
37 |
+
if(($dot = strrpos($src, '.')) === false){
|
38 |
+
return $ipart;
|
39 |
+
}
|
40 |
+
|
41 |
+
if(($bslash = strrpos($src, '/')) === false){
|
42 |
+
$bslash = 0;
|
43 |
+
}
|
44 |
+
else{ $bslash++; }
|
45 |
+
|
46 |
+
$ipart->_src = $src;
|
47 |
+
$ipart->base_path = substr($src, 0, $bslash);
|
48 |
+
$ipart->name = substr($src, $bslash, $dot - $bslash);
|
49 |
+
$ipart->ext = substr($src, $dot);
|
50 |
+
$ipart->dimension = self::IGetDimension($ipart->name);
|
51 |
+
if(!empty($ipart->dimension)){
|
52 |
+
$ipart->name_prefix = substr($ipart->name,0,strpos( $ipart->name,$ipart->dimension ) - 1 );
|
53 |
+
$ipart->name_suffix = str_replace($ipart->name_prefix.'-'.$ipart->dimension, '', $ipart->name);
|
54 |
+
}else {
|
55 |
+
$ipart->name_prefix = $ipart->name_suffix = '';
|
56 |
+
}
|
57 |
+
|
58 |
+
return $ipart;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
*
|
63 |
+
*/
|
64 |
+
public static function IGetDimension($src){
|
65 |
+
if(preg_match_all('/(\d+)x(\d+)/i', $src, $match)){
|
66 |
+
return count($match) > 0 ? end($match[0]) : '';
|
67 |
+
}
|
68 |
+
|
69 |
+
return '';
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
*
|
74 |
+
*/
|
75 |
+
public static function get($part, $src = ''){
|
76 |
+
if($src == ''){ $src = self::$src; }
|
77 |
+
$ipart = self::getIPart($src);
|
78 |
+
return isset($ipart->{$part}) ? $ipart->{$part} : '';
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
*
|
83 |
+
*/
|
84 |
+
public static function IReplace($newName, $src = ''){
|
85 |
+
if($src == ''){ $src = self::$src; }
|
86 |
+
if($newName === ''){
|
87 |
+
return $src;
|
88 |
+
}
|
89 |
+
|
90 |
+
$ipart = self::IgetPart($src);
|
91 |
+
$src = $ipart->base_path.$newName.'-'.$ipart->dimension.$ipart->name_suffix.$ipart->ext;
|
92 |
+
return $src;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
*
|
97 |
+
*/
|
98 |
+
public static function IHasClone($img, $obj){
|
99 |
+
if(!is_array($img)){ $img = array($img); }
|
100 |
+
$obj_part = self::IGetPart($obj);
|
101 |
+
|
102 |
+
foreach($img as $_img){
|
103 |
+
$img_part = self::IGetPart($_img);
|
104 |
+
if($img_part->ext !== $obj_part->ext){
|
105 |
+
continue;
|
106 |
+
}
|
107 |
+
|
108 |
+
if($obj_part->dimension !== ''){
|
109 |
+
$cmp = $obj_part->name_prefix;
|
110 |
+
}else{ $cmp = $obj_part->name; }
|
111 |
+
|
112 |
+
if( strcmp( $img_part->name, $cmp ) === 0 ){
|
113 |
+
return $_img;
|
114 |
+
}
|
115 |
+
}
|
116 |
+
|
117 |
+
return false;
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
*
|
122 |
+
*/
|
123 |
+
public static function IScan($imgs = array(), $content){
|
124 |
+
$ifound = array();
|
125 |
+
$doc = new DOMDocument();
|
126 |
+
@$doc->loadHtml($content);
|
127 |
+
$tags = $doc->getElementsByTagName('img');
|
128 |
+
|
129 |
+
//For standard images names, convert spaces to -
|
130 |
+
$_imgs = array();
|
131 |
+
foreach($imgs as $iname => $iid){
|
132 |
+
$iname = preg_replace('/(\s{1,})/', '-', $iname);
|
133 |
+
$_imgs[$iname] = $iid;
|
134 |
+
}
|
135 |
+
|
136 |
+
if($tags->length > 0){
|
137 |
+
foreach($tags as $order => $tag){
|
138 |
+
if(($obj = $tag->getAttribute('src')) == '') { continue; }
|
139 |
+
if($img_name = self::IHasClone(array_keys($_imgs), $obj)){
|
140 |
+
if(!empty($_imgs[$img_name])){
|
141 |
+
$ifound[$order]['id'] = $_imgs[$img_name];
|
142 |
+
$ifound[$order]['src'] = $obj;
|
143 |
+
$ifound[$order]['width'] = $tag->getAttribute('width');
|
144 |
+
$ifound[$order]['height'] = $tag->getAttribute('height');
|
145 |
+
$ifound[$order]['alt'] = trim($tag->getAttribute('alt'));
|
146 |
+
$ifound[$order]['title'] = trim($tag->getAttribute('title'));
|
147 |
+
}
|
148 |
+
else{ continue; }
|
149 |
+
}
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
return $ifound;
|
154 |
+
}
|
155 |
+
|
156 |
+
public static function _get_post_list($iID, $opt_type){
|
157 |
+
global $wpdb;
|
158 |
+
//Get image info from wp_postmeta with key equals to _metaseo_img_meta_not_good
|
159 |
+
$meta_key = '_metaseo_' . strtolower(trim($opt_type));
|
160 |
+
$posts = get_post_meta($iID, $meta_key, true);
|
161 |
+
|
162 |
+
if(is_array($posts) && !empty($posts)){
|
163 |
+
$posts = metaseo_utf8($posts, 'decode');
|
164 |
+
return $posts;
|
165 |
+
}
|
166 |
+
|
167 |
+
return array();
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
*
|
172 |
+
*/
|
173 |
+
public static function IPrepare($imgs, $posts, $meta_checkout = array(), $forceScan = false){
|
174 |
+
$iNotGood = array();
|
175 |
+
$iNotGoodTotal = array();
|
176 |
+
$metaNotGood = array();
|
177 |
+
$metaNotGoodTotal = array();
|
178 |
+
$upload_dir = wp_upload_dir();
|
179 |
+
|
180 |
+
foreach($posts as $post){
|
181 |
+
if(empty($post->post_content)) { continue; }
|
182 |
+
$ifound = self::IScan($imgs, $post->post_content);
|
183 |
+
if(count($ifound) < 1){ continue; }
|
184 |
+
|
185 |
+
foreach($ifound as $order => $img){
|
186 |
+
$iID = $img['id'];
|
187 |
+
//Get image that its size is not good
|
188 |
+
if(!list($width_origin, $height_origin) = @getimagesize($img['src'])){ continue; }
|
189 |
+
$ratio_origin = $width_origin/$height_origin;
|
190 |
+
$width = $img['width'];
|
191 |
+
$height = $img['height'];
|
192 |
+
//Check if img tag is missing with/height attribute value or not
|
193 |
+
if(!$width && !$height){
|
194 |
+
$width = $width_origin;
|
195 |
+
$height = $height_origin;
|
196 |
+
}
|
197 |
+
elseif($width && !$height){
|
198 |
+
$height = $width*(1/$ratio_origin);
|
199 |
+
}
|
200 |
+
elseif($height && !$width){
|
201 |
+
$width = $height*($ratio_origin);
|
202 |
+
}
|
203 |
+
|
204 |
+
if($width_origin > $width || $height_origin > $height){
|
205 |
+
$img_before = str_replace(array($upload_dir['baseurl']),'',$img['src']);
|
206 |
+
$ibpart = ImageHelper::IGetPart($img_before);
|
207 |
+
|
208 |
+
$img_after = $ibpart->base_path
|
209 |
+
.(!empty($ibpart->name_prefix) ? $ibpart->name_prefix : $ibpart->name)
|
210 |
+
. '-' . $width . 'x' . $height
|
211 |
+
. $ibpart->name_suffix
|
212 |
+
. $ibpart->ext;
|
213 |
+
|
214 |
+
$destination = $upload_dir['basedir'] . '/' . $img_after;
|
215 |
+
|
216 |
+
$srcs = array(
|
217 |
+
'img_before_dir' => $upload_dir['basedir'] . $img_before,
|
218 |
+
'img_before_url' => $upload_dir['baseurl'] . $img_before,
|
219 |
+
'img_after_dir' => $upload_dir['basedir'] . $img_after,
|
220 |
+
'img_after_url' => $upload_dir['baseurl'] . $img_after
|
221 |
+
);
|
222 |
+
$size = (filesize($srcs['img_before_dir'])/1024);
|
223 |
+
if($size>1024){
|
224 |
+
$size=$size/1024;
|
225 |
+
$sizes = 'MB';
|
226 |
+
}else{
|
227 |
+
$sizes = 'KB';
|
228 |
+
}
|
229 |
+
$size=@round($size,1);
|
230 |
+
|
231 |
+
$iNotGood[$iID][$post->ID]['ID'] = $post->ID;
|
232 |
+
$iNotGood[$iID][$post->ID]['title'] = $post->post_title;
|
233 |
+
$iNotGood[$iID][$post->ID]['post_type'] = $post->post_type;
|
234 |
+
$iNotGood[$iID][$post->ID]['img_before_optm'][$order] = array(
|
235 |
+
'size' => $size,
|
236 |
+
'sizes' => $sizes,
|
237 |
+
'src' => $srcs['img_before_url'],
|
238 |
+
'width' => $width,
|
239 |
+
'height' => $height,
|
240 |
+
'dimension' => ImageHelper::IGetDimension($img_before)
|
241 |
+
);
|
242 |
+
|
243 |
+
$iNotGood[$iID][$post->ID]['img_after_optm'][$order] = array(
|
244 |
+
'size' => 0,
|
245 |
+
'path' => $img_after,
|
246 |
+
'src' => $srcs['img_after_url'],
|
247 |
+
'src_origin' => $srcs['img_before_url'],
|
248 |
+
'width' => $width,
|
249 |
+
'height' => $height,
|
250 |
+
'dimension' => ImageHelper::IGetDimension($img_after)
|
251 |
+
);
|
252 |
+
|
253 |
+
//Get the number of images which their size are not good
|
254 |
+
if(!isset($iNotGoodTotal[$iID])){
|
255 |
+
$iNotGoodTotal[$iID] = 0;
|
256 |
+
}
|
257 |
+
$iNotGoodTotal[$iID]++;
|
258 |
+
|
259 |
+
}else{
|
260 |
+
if(!isset($iNotGood[$iID])){ $iNotGood[$iID] = array(); }
|
261 |
+
if(!isset($iNotGoodTotal[$iID])){ $iNotGoodTotal[$iID] = 0; }
|
262 |
+
}
|
263 |
+
|
264 |
+
|
265 |
+
//Get image that its meta/metas is/are not good
|
266 |
+
if(count($meta_checkout) > 0){
|
267 |
+
foreach($meta_checkout as $key => $meta){
|
268 |
+
$meta_value = $img[$meta];
|
269 |
+
$imNotGood[$iID][$post->ID]['ID'] = $post->ID;
|
270 |
+
$imNotGood[$iID][$post->ID]['title'] = $post->post_title;
|
271 |
+
$imNotGood[$iID][$post->ID]['post_type'] = $post->post_type;
|
272 |
+
$imNotGood[$iID][$post->ID]['meta'][$order]['img_src'] = $img['src'];
|
273 |
+
$imNotGood[$iID][$post->ID]['meta'][$order]['type'][$meta] = $meta_value;
|
274 |
+
|
275 |
+
#if($forceScan !== TRUE && $meta_value != ''){
|
276 |
+
#unset($_posts['meta_not_good'][$img_post_id][$post->ID]['meta'][$order]['type'][$meta]);
|
277 |
+
#}
|
278 |
+
|
279 |
+
if($meta_value == ''){
|
280 |
+
if(!isset($imNotGoodTotal[$iID][$meta])){
|
281 |
+
$imNotGoodTotal[$iID][$meta] = 0;
|
282 |
+
}
|
283 |
+
$imNotGoodTotal[$iID][$meta]++;
|
284 |
+
}
|
285 |
+
|
286 |
+
}
|
287 |
+
}
|
288 |
+
}
|
289 |
+
|
290 |
+
}
|
291 |
+
|
292 |
+
foreach($imgs as $name => $iID){
|
293 |
+
if(!isset($iNotGoodTotal[$iID])){ $iNotGoodTotal[$iID] = -1; }
|
294 |
+
if(!isset($iNotGood[$iID])){ $iNotGood[$iID] = array(); }
|
295 |
+
if(!isset($imNotGood[$iID])){ $imNotGood[$iID] = array(); }
|
296 |
+
if(!isset($imNotGoodTotal[$iID])){
|
297 |
+
foreach($meta_checkout as $mkey){
|
298 |
+
$imNotGoodTotal[$iID][$mkey] = 0;
|
299 |
+
}
|
300 |
+
}
|
301 |
+
}
|
302 |
+
|
303 |
+
foreach($imNotGoodTotal as &$mStatis){
|
304 |
+
foreach($meta_checkout as $mkey){
|
305 |
+
if(!isset($mStatis[$mkey])){ $mStatis[$mkey] = 0; }
|
306 |
+
}
|
307 |
+
}
|
308 |
+
|
309 |
+
unset($posts, $imgs);
|
310 |
+
|
311 |
+
$ret = array('iNotGood' => $iNotGood, 'iNotGoodTotal' => $iNotGoodTotal, 'imNotGood' => $imNotGood, 'imNotGoodTotal' => $imNotGoodTotal);
|
312 |
+
|
313 |
+
return $ret;
|
314 |
+
}
|
315 |
+
|
316 |
+
/*
|
317 |
+
* Scan image that has not good size or not good meta(s), then update info to postmeta table
|
318 |
+
* @return: array of message that will be responsed to ajax call
|
319 |
+
*/
|
320 |
+
public static function IScanPosts($imgs, $meta_checking = false, $forceScan = false){
|
321 |
+
global $wpdb;
|
322 |
+
$msg = array();
|
323 |
+
$meta_keys = array('alt', 'title');
|
324 |
+
$_imgs = array_flip($imgs);
|
325 |
+
|
326 |
+
$post_types = MetaSeo_Content_List_Table::get_post_types();
|
327 |
+
$query = "SELECT `ID`, `post_title`, `post_content`, `post_type`, `post_date`
|
328 |
+
FROM $wpdb->posts
|
329 |
+
WHERE `post_type` IN ($post_types)
|
330 |
+
AND `post_content` <> ''
|
331 |
+
AND `post_content` LIKE '%<img%>%'
|
332 |
+
ORDER BY ID";
|
333 |
+
|
334 |
+
$posts = $wpdb->get_results($query);
|
335 |
+
$results = self::IPrepare($imgs, $posts, $meta_keys, $forceScan);
|
336 |
+
|
337 |
+
//Update some value into fields in wp_postmeta
|
338 |
+
if(count($results['iNotGood']) > 0){
|
339 |
+
foreach($results['iNotGood'] as $iID => $post_group){
|
340 |
+
if($results['iNotGoodTotal'][$iID] > 0){
|
341 |
+
//This has a litle bit value
|
342 |
+
if($results['iNotGoodTotal'][$iID] > 1){
|
343 |
+
$im = ' images ';
|
344 |
+
}
|
345 |
+
else{
|
346 |
+
$im = ' image ';
|
347 |
+
}
|
348 |
+
|
349 |
+
$msg[$iID]['iNotGood']['msg'] = __($results['iNotGoodTotal'][$iID] .$im. 'with wrong size', 'wp-meta-seo');
|
350 |
+
$msg[$iID]['iNotGood']['warning'] = true;
|
351 |
+
|
352 |
+
$msg[$iID]['iNotGood']['button'] = '<a href="javascript:void(0);" class=" img-resize button button-primary" data-img-name="'.$_imgs[$iID].'" data-post-id="'. $iID.'" data-opt-key="resize_image" onclick="showPostsList(this)">'.__('Resize image', 'wp-meta-seo').'<span class="spinner-light"></span></a>';
|
353 |
+
|
354 |
+
update_post_meta($iID, '_metaseo_resize_image_counter', $results['iNotGoodTotal'][$iID]);
|
355 |
+
update_post_meta($iID, '_metaseo_resize_image', $post_group);
|
356 |
+
}else{
|
357 |
+
$msg[$iID]['iNotGood']['msg'] = __('Image sizes are good!', 'wp-meta-seo');
|
358 |
+
$msg[$iID]['iNotGood']['warning'] = false;
|
359 |
+
|
360 |
+
delete_post_meta($iID, '_metaseo_resize_image_counter');
|
361 |
+
delete_post_meta($iID, '_metaseo_resize_image');
|
362 |
+
}
|
363 |
+
}
|
364 |
+
}
|
365 |
+
|
366 |
+
if(count($results['imNotGood']) > 0){
|
367 |
+
foreach($results['imNotGood'] as $iID => $post_group){
|
368 |
+
//This has a litle bit value
|
369 |
+
foreach($meta_keys as $mkey){
|
370 |
+
$text = '';
|
371 |
+
if($mkey == 'alt'){ $text = ' text'; }
|
372 |
+
|
373 |
+
if($results['imNotGoodTotal'][$iID][$mkey] > 1){
|
374 |
+
$msg[$iID]['imNotGood']['msg'][$mkey] = __($results['imNotGoodTotal'][$iID][$mkey] . ' ' . $mkey . $text . 's are missing', 'wp-meta-seo');
|
375 |
+
}
|
376 |
+
elseif($results['imNotGoodTotal'][$iID][$mkey] == 1){
|
377 |
+
$msg[$iID]['imNotGood']['msg'][$mkey] = __($results['imNotGoodTotal'][$iID][$mkey] . ' ' . $mkey . $text . ' is missing', 'wp-meta-seo');
|
378 |
+
}
|
379 |
+
|
380 |
+
}
|
381 |
+
|
382 |
+
$msg[$iID]['imNotGood']['warning'] = true;
|
383 |
+
$msg[$iID]['imNotGood']['button'] = '<a href="javascript:void(0);" class=" fix-metas button button-primary" data-img-name="'.$_imgs[$iID].'" data-post-id="'. $iID.'" data-opt-key="fix_metas" onclick="showPostsList(this)">'.__('Fix meta', 'wp-meta-seo').'<span class="spinner-light"></span></a>';
|
384 |
+
|
385 |
+
|
386 |
+
update_post_meta($iID, '_metaseo_fix_metas_counter', count($post_group));
|
387 |
+
update_post_meta($iID, '_metaseo_fix_metas', $post_group);
|
388 |
+
|
389 |
+
|
390 |
+
if( $results['imNotGoodTotal'][$iID]['alt'] == 0
|
391 |
+
&& $results['imNotGoodTotal'][$iID]['title'] == 0 ){
|
392 |
+
if($results['iNotGoodTotal'][$iID] != -1){
|
393 |
+
$msg[$iID]['imNotGood']['button'] = '<a href="javascript:void(0);" class=" fix-metas button" data-img-name="'.$_imgs[$iID].'" data-post-id="'. $iID.'" data-opt-key="fix_metas" onclick="showPostsList(this)">'.__('Edit meta', 'wp-meta-seo').'<span class="spinner-light"></span></a>';
|
394 |
+
}
|
395 |
+
else{
|
396 |
+
$msg[$iID]['imNotGood']['button'] = '';
|
397 |
+
}
|
398 |
+
$msg[$iID]['imNotGood']['warning'] = false;
|
399 |
+
$msg[$iID]['imNotGood']['msg'] = '';
|
400 |
+
}
|
401 |
+
|
402 |
+
}
|
403 |
+
}
|
404 |
+
|
405 |
+
unset($results, $imgs);
|
406 |
+
|
407 |
+
return $msg;
|
408 |
+
}
|
409 |
+
|
410 |
+
/**
|
411 |
+
*
|
412 |
+
*/
|
413 |
+
public static function _optimizeImages($post_id, $img_post_id, $img_exclude){
|
414 |
+
global $wpdb;
|
415 |
+
$ret = array('success' => false, 'msg' => '');
|
416 |
+
$query = "SELECT `post_content` FROM $wpdb->posts WHERE `ID`=" . $post_id;
|
417 |
+
|
418 |
+
if(!($post_content = @$wpdb->get_row($query)->post_content)){
|
419 |
+
$ret['msg'] = __('This post is not existed or deleted, please choose one another!','wp-meta-seo');
|
420 |
+
|
421 |
+
return $ret;
|
422 |
+
}
|
423 |
+
|
424 |
+
$imgs_to_resize = get_post_meta($img_post_id, '_metaseo_resize_image', true);
|
425 |
+
|
426 |
+
if(preg_match_all('/<img [^<>]+ \/>/i', $post_content, $matches)){
|
427 |
+
$img_tags = $matches[0];
|
428 |
+
$replacement = array();
|
429 |
+
foreach($matches[0] as $order => $tag){
|
430 |
+
$replacement[$order] = $tag;
|
431 |
+
//This block of code maybe changed later
|
432 |
+
if(preg_match('/(width|height)="([^\"]+)"/i', $tag, $dimension)){
|
433 |
+
if(!isset($imgs_to_resize[$post_id])) { continue; }
|
434 |
+
foreach($imgs_to_resize[$post_id]['img_after_optm'] as $key => $img){
|
435 |
+
if(!in_array($order, $img_exclude)){
|
436 |
+
if(stripos($tag, $img['src_origin']) !== false &&
|
437 |
+
(('width' == $dimension[1] && $dimension[2] == $img['width'])
|
438 |
+
|| ('height' == $dimension[1] && $dimension[2] == $img['height']))){
|
439 |
+
|
440 |
+
$replacement[$order] = str_replace($img['src_origin'], $img['src'], $tag);
|
441 |
+
|
442 |
+
}
|
443 |
+
}
|
444 |
+
}
|
445 |
+
}
|
446 |
+
#}
|
447 |
+
}
|
448 |
+
|
449 |
+
//Replace all imgs sources that have new value
|
450 |
+
$post_content = str_replace($matches[0], $replacement, $post_content);
|
451 |
+
}
|
452 |
+
|
453 |
+
//Update post content with all imgs has been just optimized
|
454 |
+
$id = wp_update_post(array(
|
455 |
+
'ID' => $post_id,
|
456 |
+
'post_content' => $post_content
|
457 |
+
));
|
458 |
+
|
459 |
+
if($id){
|
460 |
+
$ret['success'] = true;
|
461 |
+
$ret['msg'] = __('Well! This image is so good now.', 'wp-meta-seo');
|
462 |
+
}
|
463 |
+
else{
|
464 |
+
$ret['msg'] = __('Opps! An error occured when updating the post, please try again', 'wp-meta-seo');
|
465 |
+
}
|
466 |
+
|
467 |
+
return $ret;
|
468 |
+
}
|
469 |
+
|
470 |
+
/**
|
471 |
+
*
|
472 |
+
*/
|
473 |
+
public static function IResize($src, $width, $height, $destination){
|
474 |
+
if(!list($w_origin, $h_origin) = getimagesize($src)) return "Unsupported picture type!";
|
475 |
+
|
476 |
+
if(is_readable($destination)){
|
477 |
+
return true;
|
478 |
+
}
|
479 |
+
|
480 |
+
$type = strtolower(substr(strrchr($src,"."),1));
|
481 |
+
if($type === 'jpeg') $type = 'jpg';
|
482 |
+
switch($type){
|
483 |
+
case 'bmp': $img = imagecreatefromwbmp($src); break;
|
484 |
+
case 'gif': $img = imagecreatefromgif($src); break;
|
485 |
+
case 'jpg': $img = imagecreatefromjpeg($src); break;
|
486 |
+
case 'png': $img = imagecreatefrompng($src); break;
|
487 |
+
default : return "Unsupported picture type!";
|
488 |
+
}
|
489 |
+
|
490 |
+
$new = imagecreatetruecolor($width, $height);
|
491 |
+
|
492 |
+
// preserve transparency
|
493 |
+
if($type === "gif" or $type === "png"){
|
494 |
+
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
495 |
+
imagealphablending($new, false);
|
496 |
+
imagesavealpha($new, true);
|
497 |
+
}
|
498 |
+
|
499 |
+
imagecopyresampled($new, $img, 0, 0, 0, 0, $width, $height, $w_origin, $h_origin);
|
500 |
+
|
501 |
+
//Output
|
502 |
+
switch($type){
|
503 |
+
case 'bmp': imagewbmp($new, $destination); break;
|
504 |
+
case 'gif': imagegif($new, $destination); break;
|
505 |
+
case 'jpg': imagejpeg($new, $destination); break;
|
506 |
+
case 'png': imagepng($new, $destination); break;
|
507 |
+
}
|
508 |
+
|
509 |
+
return true;
|
510 |
+
}
|
511 |
+
|
512 |
+
}
|
513 |
+
|
inc/class.metaseo-admin.php
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
//Main plugin functions here
|
4 |
+
class MetaSeo_Admin {
|
5 |
+
|
6 |
+
function __construct() {
|
7 |
+
|
8 |
+
add_action('admin_menu', array($this, 'register_menu_page'));
|
9 |
+
|
10 |
+
/** Load admin js * */
|
11 |
+
add_action('admin_enqueue_scripts', array($this, 'loadAdminScripts'));
|
12 |
+
|
13 |
+
/** Load admin css * */
|
14 |
+
add_action('admin_init', array($this, 'addAdminStylesheets'));
|
15 |
+
|
16 |
+
$this->ajaxHandle();
|
17 |
+
|
18 |
+
//register ajax update meta handler...
|
19 |
+
add_action( 'wp_ajax_updateContentMeta', array($this, 'updateContentMeta_callback') );
|
20 |
+
add_action( 'admin_init', array($this, 'stop_heartbeat') , 1 );
|
21 |
+
|
22 |
+
|
23 |
+
}
|
24 |
+
|
25 |
+
function stop_heartbeat() {
|
26 |
+
global $pagenow;
|
27 |
+
if ( 'post.php' != $pagenow && 'post-new.php' != $pagenow )
|
28 |
+
wp_deregister_script('heartbeat');
|
29 |
+
}
|
30 |
+
|
31 |
+
function updateContentMeta_callback() {
|
32 |
+
global $wpdb;
|
33 |
+
$_POST = stripslashes_deep( $_POST );
|
34 |
+
$response = new stdClass();
|
35 |
+
|
36 |
+
if( !empty( $_POST['metakey'] ) && !empty( $_POST['postid'] ) && !empty( $_POST['value'] ) );
|
37 |
+
$metakey = strtolower(trim($_POST['metakey']));
|
38 |
+
$postID = intval($_POST['postid']);
|
39 |
+
$value = trim($_POST['value']);
|
40 |
+
|
41 |
+
// if(preg_match('/[<>\/\'\"]+/', $value)){
|
42 |
+
// $response->updated = false;
|
43 |
+
// $response->msg = 'Meta content should not contains html tag or special char';
|
44 |
+
//
|
45 |
+
// echo json_encode($response);
|
46 |
+
// wp_die();
|
47 |
+
// }
|
48 |
+
|
49 |
+
$response->msg = __('Modification was saved', 'wp-meta-seo') ;
|
50 |
+
if($metakey == 'metatitle') {
|
51 |
+
if(!update_post_meta($postID, '_metaseo_metatitle', $value)) {
|
52 |
+
$response->updated = false;
|
53 |
+
$response->msg = __('Meta title was not saved', 'wp-meta-seo') ;
|
54 |
+
}
|
55 |
+
else{
|
56 |
+
$response->updated = true;
|
57 |
+
$response->msg = __('Meta title was saved', 'wp-meta-seo') ;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
if($metakey =='metadesc') {
|
62 |
+
if(!update_post_meta($postID, '_metaseo_metadesc', $value)) {
|
63 |
+
$response->updated = false;
|
64 |
+
$response->msg = __('Meta description was not saved', 'wp-meta-seo') ;
|
65 |
+
}
|
66 |
+
else{
|
67 |
+
$response->updated = true;
|
68 |
+
$response->msg = __('Meta description was saved', 'wp-meta-seo') ;
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
echo json_encode($response);
|
73 |
+
wp_die();
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Loads js/ajax scripts
|
78 |
+
*
|
79 |
+
*/
|
80 |
+
public function loadAdminScripts($hook) {
|
81 |
+
|
82 |
+
wp_enqueue_script('jquery');
|
83 |
+
|
84 |
+
wp_enqueue_script(
|
85 |
+
'wpmetaseoAdmin', plugins_url('js/metaseo_admin.js', dirname(__FILE__)), array('jquery'), '0.1', true
|
86 |
+
);
|
87 |
+
|
88 |
+
wp_enqueue_script('Chart', plugins_url('js/Chart.js', dirname(__FILE__)), array('jquery'), '0.1', true);
|
89 |
+
wp_enqueue_script('dashboard-chart', plugins_url('js/dashboard-chart.js', dirname(__FILE__)), array('jquery'), '0.1', true);
|
90 |
+
|
91 |
+
// in JavaScript, object properties are accessed as ajax_object.ajax_url, ajax_object.we_value
|
92 |
+
wp_localize_script( 'wpmetaseoAdmin', 'myAjax',
|
93 |
+
array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Load additional admin stylesheets
|
98 |
+
* of jquery-ui
|
99 |
+
*
|
100 |
+
*/
|
101 |
+
function addAdminStylesheets() {
|
102 |
+
|
103 |
+
wp_enqueue_style('wpmetaseoAdmin', plugins_url('css/metaseo_admin.css', dirname(__FILE__)));
|
104 |
+
wp_enqueue_style('tooltip-metaimage', plugins_url('/css/tooltip-metaimage.css',dirname(__FILE__)));
|
105 |
+
wp_enqueue_style('style', plugins_url('/css/style.css', dirname(__FILE__) ) );
|
106 |
+
wp_enqueue_style('chart', plugins_url('/css/chart.css', dirname(__FILE__) ) );
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
function register_menu_page() {
|
111 |
+
|
112 |
+
// Add main page
|
113 |
+
$admin_page = add_menu_page(__('WP Meta SEO:', 'wp-meta-seo') . ' ' . __('Dashboard', 'wp-meta-seo'), __('WP Meta SEO', 'wp-meta-seo'), 'manage_options', 'metaseo_dashboard', array(
|
114 |
+
$this,
|
115 |
+
'load_page',
|
116 |
+
), plugins_url('/img/icon.png', dirname(__FILE__)) );
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Filter: 'metaseo_manage_options_capability' - Allow changing the capability users need to view the settings pages
|
120 |
+
*
|
121 |
+
* @api string unsigned The capability
|
122 |
+
*/
|
123 |
+
$manage_options_cap = apply_filters('metaseo_manage_options_capability', 'manage_options');
|
124 |
+
|
125 |
+
// Sub menu pages
|
126 |
+
$submenu_pages = array(
|
127 |
+
array(
|
128 |
+
'metaseo_dashboard',
|
129 |
+
'',
|
130 |
+
__('Content meta', 'wp-meta-seo'),
|
131 |
+
$manage_options_cap,
|
132 |
+
'metaseo_content_meta',
|
133 |
+
array($this, 'load_page'),
|
134 |
+
null,
|
135 |
+
),
|
136 |
+
array(
|
137 |
+
'metaseo_dashboard',
|
138 |
+
'',
|
139 |
+
__('Image meta', 'wp-meta-seo'),
|
140 |
+
$manage_options_cap,
|
141 |
+
'metaseo_image_meta',
|
142 |
+
array($this, 'load_page'),
|
143 |
+
null,
|
144 |
+
),
|
145 |
+
);
|
146 |
+
|
147 |
+
|
148 |
+
|
149 |
+
// Allow submenu pages manipulation
|
150 |
+
$submenu_pages = apply_filters('metaseo_submenu_pages', $submenu_pages);
|
151 |
+
|
152 |
+
// Loop through submenu pages and add them
|
153 |
+
if (count($submenu_pages)) {
|
154 |
+
foreach ($submenu_pages as $submenu_page) {
|
155 |
+
|
156 |
+
// Add submenu page
|
157 |
+
$admin_page = add_submenu_page($submenu_page[0], $submenu_page[2] . ' - ' . __('WP Meta SEO:', 'wp-meta-seo'), $submenu_page[2], $submenu_page[3], $submenu_page[4], $submenu_page[5]);
|
158 |
+
|
159 |
+
// Check if we need to hook
|
160 |
+
if (isset($submenu_page[6]) && null != $submenu_page[6] && is_array($submenu_page[6]) && count($submenu_page[6]) > 0) {
|
161 |
+
foreach ($submenu_page[6] as $submenu_page_action) {
|
162 |
+
add_action('load-' . $admin_page, $submenu_page_action);
|
163 |
+
}
|
164 |
+
}
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
global $submenu;
|
169 |
+
if (isset($submenu['metaseo_dashboard']) && current_user_can($manage_options_cap)) {
|
170 |
+
$submenu['metaseo_dashboard'][0][0] = __('Dashboard', 'wp-meta-seo');
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Load the form for a WPSEO admin page
|
176 |
+
*/
|
177 |
+
function load_page() {
|
178 |
+
if (isset($_GET['page'])) {
|
179 |
+
switch ($_GET['page']) {
|
180 |
+
case 'metaseo_content_meta':
|
181 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/pages/content-meta.php' );
|
182 |
+
break;
|
183 |
+
|
184 |
+
|
185 |
+
case 'metaseo_image_meta':
|
186 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/pages/image-meta.php' );
|
187 |
+
break;
|
188 |
+
|
189 |
+
case 'metaseo_image_optimize':
|
190 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/pages/image-optimize.php' );
|
191 |
+
break;
|
192 |
+
|
193 |
+
case 'metaseo_dashboard':
|
194 |
+
default:
|
195 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/pages/dashboard.php' );
|
196 |
+
break;
|
197 |
+
}
|
198 |
+
}
|
199 |
+
}
|
200 |
+
|
201 |
+
private function ajaxHandle(){
|
202 |
+
//
|
203 |
+
add_action( 'wp_ajax_scanPosts', array('MetaSeo_Image_List_Table', 'scan_posts_callback') );
|
204 |
+
add_action( 'wp_ajax_load_posts', array('MetaSeo_Image_List_Table', 'load_posts_callback') );
|
205 |
+
add_action( 'wp_ajax_optimize_imgs', array('MetaSeo_Image_List_Table', 'optimizeImages') );
|
206 |
+
add_action( 'wp_ajax_updateMeta', array('MetaSeo_Image_List_Table', 'updateMeta_callback') );
|
207 |
+
add_action( 'wp_ajax_opt_checking', array('MetaSeo_Dashboard', 'optimizationChecking') );
|
208 |
+
//
|
209 |
+
add_action( 'wp_ajax_import_meta_data', array('MetaSeo_Content_List_Table', 'importMetaData') );
|
210 |
+
add_action( 'wp_ajax_dismiss_import_meta', array('MetaSeo_Content_List_Table', 'dismissImport') );
|
211 |
+
//
|
212 |
+
add_action( 'added_post_meta' , array( 'MetaSeo_Content_List_Table', 'updateMetaSync' ), 99, 4);
|
213 |
+
add_action( 'updated_post_meta', array( 'MetaSeo_Content_List_Table', 'updateMetaSync' ), 99, 4);
|
214 |
+
add_action( 'deleted_post_meta', array( 'MetaSeo_Content_List_Table', 'deleteMetaSync' ), 99, 4);
|
215 |
+
|
216 |
+
}
|
217 |
+
|
218 |
+
}
|
inc/class.metaseo-content-list-table.php
ADDED
@@ -0,0 +1,621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
* Comments to come later
|
4 |
+
*
|
5 |
+
*
|
6 |
+
*/
|
7 |
+
|
8 |
+
if (!class_exists('WP_List_Table')) {
|
9 |
+
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
|
10 |
+
}
|
11 |
+
|
12 |
+
class MetaSeo_Content_List_Table extends WP_List_Table {
|
13 |
+
function __construct() {
|
14 |
+
parent::__construct(array(
|
15 |
+
'singular' => 'metaseo_content',
|
16 |
+
'plural' => 'metaseo_contents',
|
17 |
+
'ajax' => true
|
18 |
+
));
|
19 |
+
|
20 |
+
}
|
21 |
+
|
22 |
+
function display_tablenav($which) {
|
23 |
+
?>
|
24 |
+
<div class="tablenav <?php echo esc_attr($which); ?>">
|
25 |
+
|
26 |
+
<?php #if($which=='top'): ?>
|
27 |
+
<input type="hidden" name="page" value="metaseo_content_meta" />
|
28 |
+
<?php #endif ?>
|
29 |
+
|
30 |
+
<input type="hidden" name="page" value="metaseo_content_meta" />
|
31 |
+
<?php if (!empty($_REQUEST['post_status'])): ?>
|
32 |
+
<input type="hidden" name="post_status" value="<?php echo esc_attr($_REQUEST['post_status']); ?>" />
|
33 |
+
<?php endif ?>
|
34 |
+
|
35 |
+
<?php $this->extra_tablenav($which); ?>
|
36 |
+
|
37 |
+
<div style="float:right;margin-left:8px;">
|
38 |
+
<input type="number" required min="1" value="<?php echo $this->_pagination_args['per_page'] ?>" maxlength="3" name="metaseo_posts_per_page" class="metaseo_imgs_per_page screen-per-page" max="999" min="1" step="1">
|
39 |
+
<input type="submit" name="btn_perpage" class="button_perpage button" id="button_perpage" value="Apply" >
|
40 |
+
</div>
|
41 |
+
|
42 |
+
<?php $this->pagination($which); ?>
|
43 |
+
<br class="clear" />
|
44 |
+
</div>
|
45 |
+
|
46 |
+
<?php
|
47 |
+
}
|
48 |
+
|
49 |
+
function get_views() {
|
50 |
+
global $wpdb;
|
51 |
+
|
52 |
+
|
53 |
+
$status_links = array();
|
54 |
+
|
55 |
+
$post_types = get_post_types(array('public' => true, 'exclude_from_search' => false));
|
56 |
+
$post_types = "'" . implode("', '", $post_types) . "'";
|
57 |
+
|
58 |
+
$states = get_post_stati(array('show_in_admin_all_list' => true));
|
59 |
+
$states['trash'] = 'trash';
|
60 |
+
$all_states = "'" . implode("', '", $states) . "'";
|
61 |
+
|
62 |
+
$total_posts = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ($all_states) AND post_type IN ($post_types)");
|
63 |
+
|
64 |
+
$class = empty($_REQUEST['post_status']) ? ' class="current"' : '';
|
65 |
+
$status_links['all'] = "<a href='admin.php?page=metaseo_content_meta'$class>" . sprintf(_nx('All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts'), number_format_i18n($total_posts)) . '</a>';
|
66 |
+
|
67 |
+
foreach (get_post_stati(array('show_in_admin_all_list' => true), 'objects') as $status) {
|
68 |
+
|
69 |
+
$status_name = $status->name;
|
70 |
+
|
71 |
+
$total = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ('$status_name') AND post_type IN ($post_types)");
|
72 |
+
|
73 |
+
if ($total == 0) {
|
74 |
+
continue;
|
75 |
+
}
|
76 |
+
|
77 |
+
if (isset($_REQUEST['post_status']) && $status_name == $_REQUEST['post_status']) {
|
78 |
+
$class = ' class="current"';
|
79 |
+
} else {
|
80 |
+
$class = '';
|
81 |
+
}
|
82 |
+
|
83 |
+
$status_links[$status_name] = "<a href='admin.php?page=metaseo_content_meta&post_status=$status_name'$class>" . sprintf(translate_nooped_plural($status->label_count, $total), number_format_i18n($total)) . '</a>';
|
84 |
+
}
|
85 |
+
$trashed_posts = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ('trash') AND post_type IN ($post_types)");
|
86 |
+
$class = ( isset($_REQUEST['post_status']) && 'trash' == $_REQUEST['post_status'] ) ? 'class="current"' : '';
|
87 |
+
$status_links['trash'] = "<a href='admin.php?page=metaseo_content_meta&post_status=trash'$class>" . sprintf(_nx('Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>', $trashed_posts, 'posts'), number_format_i18n($trashed_posts)) . '</a>';
|
88 |
+
|
89 |
+
return $status_links;
|
90 |
+
}
|
91 |
+
|
92 |
+
function extra_tablenav($which) {
|
93 |
+
|
94 |
+
#if ('top' == $which) {
|
95 |
+
echo '<div class="alignleft actions">';
|
96 |
+
global $wpdb;
|
97 |
+
|
98 |
+
$post_types = get_post_types(array('public' => true, 'exclude_from_search' => false));
|
99 |
+
$post_types = "'" . implode("', '", $post_types) . "'";
|
100 |
+
|
101 |
+
$states = get_post_stati(array('show_in_admin_all_list' => true));
|
102 |
+
$states['trash'] = 'trash';
|
103 |
+
$all_states = "'" . implode("', '", $states) . "'";
|
104 |
+
|
105 |
+
$query = "SELECT DISTINCT post_type FROM $wpdb->posts WHERE post_status IN ($all_states) AND post_type IN ($post_types) ORDER BY 'post_type' ASC";
|
106 |
+
$post_types = $wpdb->get_results($query);
|
107 |
+
|
108 |
+
$selected = !empty($_REQUEST['post_type_filter']) ? $_REQUEST['post_type_filter'] : -1;
|
109 |
+
|
110 |
+
$options = '<option value="-1">Show All Post Types</option>';
|
111 |
+
|
112 |
+
foreach ($post_types as $post_type) {
|
113 |
+
$obj = get_post_type_object($post_type->post_type);
|
114 |
+
$options .= sprintf('<option value="%2$s" %3$s>%1$s</option>', $obj->labels->name, $post_type->post_type, selected($selected, $post_type->post_type, false));
|
115 |
+
}
|
116 |
+
|
117 |
+
if($which=='top') {
|
118 |
+
echo sprintf('<select name="post_type_filter" class="metaseo-filter">%1$s</select>', $options);
|
119 |
+
submit_button(__('Filter'), 'button', 'do_filter', false, array('id' => 'post-query-submit'));
|
120 |
+
}else{
|
121 |
+
echo sprintf('<select name="post_type_filter" class="metaseo-filter">%1$s</select>', $options);
|
122 |
+
submit_button(__('Filter'), 'button', 'do_filter', false, array('id' => 'post-query-submit'));
|
123 |
+
}
|
124 |
+
|
125 |
+
echo "</div>";
|
126 |
+
#echo "</form>";
|
127 |
+
#}
|
128 |
+
}
|
129 |
+
|
130 |
+
function get_columns() {
|
131 |
+
return $columns = array(
|
132 |
+
'col_id' => __('', 'wp-meta-seo'),
|
133 |
+
'col_title' => __('Title', 'wp-meta-seo'),
|
134 |
+
'col_meta_title' => __('Meta Title', 'wp-meta-seo'),
|
135 |
+
'col_meta_desc' => __('Meta Description', 'wp-meta-seo')
|
136 |
+
);
|
137 |
+
}
|
138 |
+
|
139 |
+
function get_sortable_columns() {
|
140 |
+
return $sortable = array(
|
141 |
+
'col_title' => array('post_title', true),
|
142 |
+
);
|
143 |
+
}
|
144 |
+
|
145 |
+
function prepare_items() {
|
146 |
+
global $wpdb, $_wp_column_headers;
|
147 |
+
//$GLOBALS['wp_filter']["manage_{$GLOBALS['screen']->id}_screen_columns"];
|
148 |
+
|
149 |
+
$screen = get_current_screen();
|
150 |
+
|
151 |
+
$where = array();
|
152 |
+
$post_type = isset($_REQUEST['post_type_filter'])? $_REQUEST['post_type_filter'] : "";
|
153 |
+
if($post_type=="-1") {
|
154 |
+
$post_type="";
|
155 |
+
}
|
156 |
+
|
157 |
+
$post_types = get_post_types( array('public' => true, 'exclude_from_search' => false) ) ;
|
158 |
+
if ( !empty( $post_type ) && !in_array( $post_type,$post_types ) )
|
159 |
+
$post_type = '\'post\'';
|
160 |
+
else if(empty ($post_type)) {
|
161 |
+
$post_type = "'" . implode("', '", $post_types) . "'";
|
162 |
+
}else {
|
163 |
+
$post_type = "'" . $post_type . "'";
|
164 |
+
}
|
165 |
+
$where[] = "post_type IN ($post_type)";
|
166 |
+
|
167 |
+
$states = get_post_stati(array('show_in_admin_all_list' => true));
|
168 |
+
$states['trash'] = 'trash';
|
169 |
+
$all_states = "'" . implode("', '", $states) . "'";
|
170 |
+
|
171 |
+
if (empty($_REQUEST['post_status'])) {
|
172 |
+
$where[] = "post_status IN ($all_states)";
|
173 |
+
} else {
|
174 |
+
$requested_state = $_REQUEST['post_status'];
|
175 |
+
if (in_array($requested_state, $states)) {
|
176 |
+
$where[] = "post_status IN ('$requested_state')";
|
177 |
+
} else {
|
178 |
+
$where[] = "post_status IN ($all_states)";
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
//Order By block
|
183 |
+
$orderby = !empty($_GET["orderby"]) ? ($_GET["orderby"]) : 'post_title';
|
184 |
+
$order = !empty($_GET["order"]) ? ($_GET["order"]) : 'asc';
|
185 |
+
|
186 |
+
$sortable = $this->get_sortable_columns();
|
187 |
+
if(in_array($orderby, $sortable)) {
|
188 |
+
$orderStr = $orderby;
|
189 |
+
}else {
|
190 |
+
$orderStr = 'post_title';
|
191 |
+
}
|
192 |
+
|
193 |
+
if($order=="asc") {
|
194 |
+
$orderStr .= " ASC";
|
195 |
+
}else {
|
196 |
+
$orderStr .= " DESC";
|
197 |
+
}
|
198 |
+
|
199 |
+
if (!empty($orderby) & !empty($order)) {
|
200 |
+
$orderStr =' ORDER BY ' . $orderStr;
|
201 |
+
}
|
202 |
+
|
203 |
+
$query = "SELECT ID, post_title, post_name, post_type, post_status , mt.meta_value AS metatitle, md.meta_value AS metadesc "
|
204 |
+
. " FROM $wpdb->posts "
|
205 |
+
. " LEFT JOIN (SELECT * FROM $wpdb->postmeta WHERE meta_key = '_metaseo_metatitle') mt ON mt.post_id = $wpdb->posts.ID "
|
206 |
+
. " LEFT JOIN (SELECT * FROM $wpdb->postmeta WHERE meta_key = '_metaseo_metadesc') md ON md.post_id = $wpdb->posts.ID "
|
207 |
+
. " WHERE ". implode(' AND ', $where) . $orderStr;
|
208 |
+
|
209 |
+
$total_items = $wpdb->query($query);
|
210 |
+
|
211 |
+
if(!empty($_REQUEST['metaseo_posts_per_page'])){
|
212 |
+
$_per_page = intval($_REQUEST['metaseo_posts_per_page']);
|
213 |
+
}
|
214 |
+
else {
|
215 |
+
$_per_page = 0;
|
216 |
+
}
|
217 |
+
$per_page = get_user_option('metaseo_posts_per_page');
|
218 |
+
if( $per_page !== false) {
|
219 |
+
if($_per_page && $_per_page !== $per_page ){
|
220 |
+
$per_page = $_per_page;
|
221 |
+
update_user_option(get_current_user_id(), 'metaseo_posts_per_page', $per_page);
|
222 |
+
}
|
223 |
+
}
|
224 |
+
else{
|
225 |
+
if($_per_page > 0) {
|
226 |
+
$per_page = $_per_page;
|
227 |
+
}
|
228 |
+
else { $per_page = 10; }
|
229 |
+
add_user_meta(get_current_user_id(), 'metaseo_posts_per_page', $per_page);
|
230 |
+
}
|
231 |
+
|
232 |
+
$paged = !empty($_GET["paged"]) ? $_GET["paged"] : '';
|
233 |
+
if (empty($paged) || !is_numeric($paged) || $paged <= 0) {
|
234 |
+
$paged = 1;
|
235 |
+
}
|
236 |
+
|
237 |
+
$total_pages = ceil($total_items / $per_page);
|
238 |
+
|
239 |
+
if (!empty($paged) && !empty($per_page)) {
|
240 |
+
$offset = ($paged - 1) * $per_page;
|
241 |
+
$query .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
|
242 |
+
}
|
243 |
+
|
244 |
+
$this->set_pagination_args(array(
|
245 |
+
'total_items' => $total_items,
|
246 |
+
'total_pages' => $total_pages,
|
247 |
+
'per_page' => $per_page
|
248 |
+
));
|
249 |
+
|
250 |
+
$columns = $this->get_columns();
|
251 |
+
$hidden = array();
|
252 |
+
$sortable = $this->get_sortable_columns();
|
253 |
+
$this->_column_headers = array($columns, $hidden, $sortable);
|
254 |
+
|
255 |
+
$this->items = $wpdb->get_results($query);
|
256 |
+
}
|
257 |
+
|
258 |
+
function display_rows() {
|
259 |
+
|
260 |
+
$records = $this->items;
|
261 |
+
$i = 0;
|
262 |
+
$alternate = "";
|
263 |
+
$url = preg_replace('/(http|https):\/\/[w]*[.]?/', '', network_site_url('/'));
|
264 |
+
|
265 |
+
list( $columns, $hidden ) = $this->get_column_info();
|
266 |
+
|
267 |
+
if (!empty($records)) {
|
268 |
+
foreach ($records as $rec) {
|
269 |
+
$alternate = 'alternate' == $alternate ? '' : 'alternate';
|
270 |
+
$i++;
|
271 |
+
$classes = $alternate;
|
272 |
+
$rec->link = $url;
|
273 |
+
|
274 |
+
echo '<tr id="record_' . $rec->ID . '" class="' . $classes . '" >';
|
275 |
+
|
276 |
+
foreach ($columns as $column_name => $column_display_name) {
|
277 |
+
|
278 |
+
$class = sprintf('class="%1$s column-%1$s"', $column_name);
|
279 |
+
$style = "";
|
280 |
+
|
281 |
+
if (in_array($column_name, $hidden)) {
|
282 |
+
$style = ' style="display:none;"';
|
283 |
+
}
|
284 |
+
|
285 |
+
$attributes = $class . $style;
|
286 |
+
|
287 |
+
switch ($column_name) {
|
288 |
+
case 'col_id':
|
289 |
+
echo '<td class="col_id" >';
|
290 |
+
echo $i;
|
291 |
+
echo '</td>';
|
292 |
+
|
293 |
+
break;
|
294 |
+
|
295 |
+
case 'col_title':
|
296 |
+
echo sprintf('<td %2$s><div class="action-wrapper"><strong id="post-title-'.$rec->ID.'">%1$s</strong>', stripslashes($rec->post_title), $attributes);
|
297 |
+
|
298 |
+
$post_type_object = get_post_type_object($rec->post_type);
|
299 |
+
$can_edit_post = current_user_can($post_type_object->cap->edit_post, $rec->ID);
|
300 |
+
|
301 |
+
$actions = array();
|
302 |
+
|
303 |
+
if ($can_edit_post && 'trash' != $rec->post_status) {
|
304 |
+
$actions['edit'] = '<a href="' . get_edit_post_link($rec->ID, true) . '" title="' . esc_attr(__('Edit this item')) . '">' . __('Edit') . '</a>';
|
305 |
+
}
|
306 |
+
|
307 |
+
if ($post_type_object->public) {
|
308 |
+
if (in_array($rec->post_status, array('pending', 'draft', 'future'))) {
|
309 |
+
if ($can_edit_post)
|
310 |
+
$actions['view'] = '<a href="' . esc_url(add_query_arg('preview', 'true', get_permalink($rec->ID))) . '" title="' . esc_attr(sprintf(__('Preview “%s”'), $rec->post_title)) . '" rel="permalink">' . __('Preview') . '</a>';
|
311 |
+
} elseif ('trash' != $rec->post_status) {
|
312 |
+
$actions['view'] = '<a target="_blank" href="' . get_permalink($rec->ID) . '" title="' . esc_attr(sprintf(__('View “%s”'), $rec->post_title)) . '" rel="permalink">' . __('View') . '</a>';
|
313 |
+
}
|
314 |
+
}
|
315 |
+
|
316 |
+
echo $this->row_actions($actions);
|
317 |
+
echo '</div><div class="snippet-wrapper">';
|
318 |
+
$preview = __(" This is a rendering of what this post might look like in Google's search results.", 'wp-meta-seo');
|
319 |
+
$info = sprintf('<a class="info-content"><img src=' . WPMETASEO_PLUGIN_URL . 'img/info.png href="#">'
|
320 |
+
. '<p class="tooltip-metacontent">'
|
321 |
+
.$preview
|
322 |
+
.'</p></a>');
|
323 |
+
|
324 |
+
echo '<div><strong>' . __('Snippet Preview', 'wp-meta-seo') . '</strong> ' . $info. '</div>';
|
325 |
+
|
326 |
+
echo '<div class="snippet">
|
327 |
+
<a id="snippet_title'.$rec->ID . '" class="snippet_metatitle">'.(!empty($rec->metatitle) ? $rec->metatitle : $rec->post_title).'</a>';
|
328 |
+
|
329 |
+
echo '
|
330 |
+
<span class="snippet_metalink" id="snippet_metalink_'.$rec->ID.'">'.$rec->link.'</span>';
|
331 |
+
|
332 |
+
echo '
|
333 |
+
<p id="snippet_desc'.$rec->ID . '" class="snippet_metades">'.$rec->metadesc.'</p>
|
334 |
+
</div>';
|
335 |
+
echo '
|
336 |
+
<span id="savedInfo'.$rec->ID.'" style="position: relative; display: block;float:right" class="saved-info metaseo-msg-success"><span style="position:absolute; float:right" class="meta-update"></span></span>';
|
337 |
+
//echo '<span id="savedInfo'.$rec->ID.'" class="savedInfo" style="display:none;"></span>';
|
338 |
+
echo '</div>';
|
339 |
+
// echo sprintf('<div %2$s>%1$s</div>', $info, $attributes);
|
340 |
+
|
341 |
+
|
342 |
+
|
343 |
+
break;
|
344 |
+
case 'col_page_slug':
|
345 |
+
$permalink = get_permalink($rec->ID);
|
346 |
+
$display_slug = str_replace(get_bloginfo('url'), '', $permalink);
|
347 |
+
echo sprintf('<td %2$s><a href="%3$s" target="_blank">%1$s</a></td>', stripslashes($display_slug), $attributes, $permalink);
|
348 |
+
break;
|
349 |
+
|
350 |
+
case 'col_meta_title':
|
351 |
+
$input = sprintf('</br><textarea class="large-text metaseo-metatitle" rows="3" id="%1$s" name="%2$s" autocomplete="off">%3$s</textarea>', 'metaseo-metatitle-' . $rec->ID, 'metatitle['. $rec->ID.']', ( ($rec->metatitle ) ? $rec->metatitle : ''));
|
352 |
+
$input .= sprintf('<div class="title-len" id="%1$s"></div>', 'metaseo-metatitle-len' . $rec->ID);
|
353 |
+
echo sprintf('<td %2$s>%1$s</td>', $input, $attributes);
|
354 |
+
break;
|
355 |
+
|
356 |
+
case 'col_meta_desc':
|
357 |
+
$input = sprintf('</br><textarea class="large-text metaseo-metadesc" rows="3" id="%1$s" name="%2$s" autocomplete="off">%3$s</textarea>', 'metaseo-metadesc-' . $rec->ID, ' metades['. $rec->ID.']', (($rec->metadesc ) ? $rec->metadesc : ''));
|
358 |
+
$input .= sprintf('<div class="desc-len" id="%1$s"></div>', 'metaseo-metadesc-len' . $rec->ID);
|
359 |
+
echo sprintf('<td %2$s>%1$s</td>', $input, $attributes);
|
360 |
+
break;
|
361 |
+
}
|
362 |
+
}
|
363 |
+
|
364 |
+
echo '</tr>';
|
365 |
+
}
|
366 |
+
}
|
367 |
+
}
|
368 |
+
|
369 |
+
protected function get_bulk_actions() {
|
370 |
+
$actions = array();
|
371 |
+
$actions = array(
|
372 |
+
'update' => 'Update',
|
373 |
+
);
|
374 |
+
|
375 |
+
return $actions;
|
376 |
+
}
|
377 |
+
|
378 |
+
function process_action() {
|
379 |
+
$current_url = set_url_scheme('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
380 |
+
$redirect = false;
|
381 |
+
|
382 |
+
if (isset($_POST['do_filter']) and $_POST['do_filter'] === 'Filter') {
|
383 |
+
$current_url = add_query_arg(array( "post_type_filter" => $_POST['post_type_filter']), $current_url);
|
384 |
+
$redirect = true;
|
385 |
+
}
|
386 |
+
|
387 |
+
if(!empty($_POST['paged'])){
|
388 |
+
$current_url = add_query_arg(array( "paged" => intval($_POST['paged'])), $current_url);
|
389 |
+
$redirect = true;
|
390 |
+
}
|
391 |
+
|
392 |
+
if(!empty($_POST['metaseo_posts_per_page'])){
|
393 |
+
$current_url = add_query_arg(array( "metaseo_posts_per_page" => intval($_POST['metaseo_posts_per_page'])), $current_url);
|
394 |
+
$redirect = true;
|
395 |
+
}
|
396 |
+
|
397 |
+
if($redirect === true){
|
398 |
+
wp_redirect($current_url);
|
399 |
+
ob_end_flush();
|
400 |
+
exit();
|
401 |
+
}
|
402 |
+
}
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Get all posts that is public and contain images with a string seperated by comma
|
406 |
+
*/
|
407 |
+
public static function get_post_types(){
|
408 |
+
global $wpdb;
|
409 |
+
$post_types = get_post_types( array('public' => true, 'exclude_from_search' => false) ) ;
|
410 |
+
if(!empty($post_types)){
|
411 |
+
$post_types = "'" . implode("', '", $post_types) . "'";
|
412 |
+
}
|
413 |
+
|
414 |
+
return $post_types;
|
415 |
+
}
|
416 |
+
|
417 |
+
public static function importMetaData(){
|
418 |
+
global $wpdb;
|
419 |
+
$meta_metaseo_keys = array('_metaseo_metatitle', '_metaseo_metadesc');
|
420 |
+
$meta_other_keys = array(
|
421 |
+
'_aio_' => array('_aioseop_title', '_aioseop_description'),
|
422 |
+
'_yoast_' => array('_yoast_wpseo_title', '_yoast_wpseo_metadesc')
|
423 |
+
);
|
424 |
+
|
425 |
+
if(!empty($_POST['plugin']) and in_array(strtolower(trim($_POST['plugin'])), array('_aio_', '_yoast_'))){
|
426 |
+
$plugin = strtolower(trim($_POST['plugin']));
|
427 |
+
$metakeys = '';
|
428 |
+
foreach($meta_metaseo_keys as $k => $mkey){
|
429 |
+
$metakeys .= ' OR `meta_key` = \''. $mkey . '\' OR `meta_key` = \'' . $meta_other_keys[$plugin][$k] . '\'';
|
430 |
+
}
|
431 |
+
|
432 |
+
$metakeys = ltrim($metakeys, ' OR ');
|
433 |
+
$query = "SELECT `post_id` as pID, `meta_key`, `meta_value`
|
434 |
+
FROM $wpdb->postmeta
|
435 |
+
WHERE $metakeys
|
436 |
+
ORDER BY `meta_key`";
|
437 |
+
$posts_metas = $wpdb->get_results($query);
|
438 |
+
|
439 |
+
if(is_array($posts_metas) && count($posts_metas) > 0){
|
440 |
+
foreach($posts_metas as $postmeta){
|
441 |
+
$_posts_metas[$postmeta->pID][$postmeta->meta_key] = $postmeta->meta_value;
|
442 |
+
}
|
443 |
+
unset($posts_metas);
|
444 |
+
foreach($_posts_metas as $pID => $pmeta){
|
445 |
+
foreach($meta_metaseo_keys as $k => $mkey){
|
446 |
+
$mvalue = $pmeta[$mkey];
|
447 |
+
$msynckey = $meta_other_keys[$plugin][$k];
|
448 |
+
$msyncvalue = $pmeta[$msynckey];
|
449 |
+
|
450 |
+
if( is_null($mvalue ) || ( $mvalue == '' && $msynckey != '' ) ){
|
451 |
+
update_post_meta( $pID, $mkey, $msyncvalue );
|
452 |
+
}
|
453 |
+
elseif( is_null($msyncvalue ) || ( $msyncvalue == '' && $mvalue != '' ) ){
|
454 |
+
update_post_meta( $pID, $msynckey, $mvalue );
|
455 |
+
}
|
456 |
+
elseif($mvalue != '' && $msyncvalue != ''){
|
457 |
+
update_post_meta( $pID, $mkey, $msyncvalue );
|
458 |
+
}
|
459 |
+
}
|
460 |
+
|
461 |
+
}
|
462 |
+
|
463 |
+
unset($posts_metas);
|
464 |
+
}
|
465 |
+
|
466 |
+
|
467 |
+
$ret['success'] = true;
|
468 |
+
|
469 |
+
update_option('_aio_import_notice_flag', 1);
|
470 |
+
update_option('_yoast_import_notice_flag', 1);
|
471 |
+
update_option('plugin_to_sync_with', $plugin);
|
472 |
+
}else{
|
473 |
+
$ret['success'] = false;
|
474 |
+
}
|
475 |
+
|
476 |
+
echo json_encode($ret);
|
477 |
+
wp_die();
|
478 |
+
}
|
479 |
+
|
480 |
+
public static function dismissImport(){
|
481 |
+
if(!empty($_POST['plugin']) and in_array(strtolower(trim($_POST['plugin'])), array('_aio_', '_yoast_'))){
|
482 |
+
$plugin = strtolower(trim($_POST['plugin']));
|
483 |
+
|
484 |
+
update_option($plugin.'import_notice_flag', 1);
|
485 |
+
$ret['success'] = true;
|
486 |
+
}else{
|
487 |
+
$ret['success'] = false;
|
488 |
+
}
|
489 |
+
|
490 |
+
echo json_encode($ret);
|
491 |
+
wp_die();
|
492 |
+
}
|
493 |
+
|
494 |
+
/**
|
495 |
+
*
|
496 |
+
*/
|
497 |
+
public static function updateMetaSync($meta_id, $object_id, $meta_key, $meta_value){
|
498 |
+
if(!self::is_updateSync($meta_key)){
|
499 |
+
return null;
|
500 |
+
}
|
501 |
+
|
502 |
+
if(self::_updateMetaSync('update', $object_id, $meta_key, $meta_value)){
|
503 |
+
return true;
|
504 |
+
}
|
505 |
+
|
506 |
+
return null;
|
507 |
+
}
|
508 |
+
|
509 |
+
/**
|
510 |
+
*
|
511 |
+
*/
|
512 |
+
public static function deleteMetaSync($meta_ids, $object_id, $meta_key, $meta_value){
|
513 |
+
|
514 |
+
if(!self::is_updateSync($meta_key)){
|
515 |
+
return null;
|
516 |
+
}
|
517 |
+
|
518 |
+
if(self::_updateMetaSync('delete', $object_id, $meta_key, $meta_value)){
|
519 |
+
return true;
|
520 |
+
}
|
521 |
+
|
522 |
+
return null;
|
523 |
+
}
|
524 |
+
|
525 |
+
/**
|
526 |
+
*
|
527 |
+
*/
|
528 |
+
private static function _updateMetaSync($type = '', $object_id, $meta_key, $meta_value){
|
529 |
+
if( ! ( $sync = get_option('plugin_to_sync_with') ) or !in_array( $sync, array('_aio_', '_yoast_' ) ) ){
|
530 |
+
return false;
|
531 |
+
}
|
532 |
+
|
533 |
+
$metakeys = array(
|
534 |
+
'_metaseo_' => array('_metaseo_metatitle' , '_metaseo_metadesc'),
|
535 |
+
'_aio_' => array('_aioseop_title', '_aioseop_description'),
|
536 |
+
'_yoast_' => array('_yoast_wpseo_title', '_yoast_wpseo_metadesc')
|
537 |
+
);
|
538 |
+
|
539 |
+
$_metakeys = array();
|
540 |
+
$_metakeys['_metaseo_'] = $metakeys['_metaseo_'];
|
541 |
+
$_metakeys[$sync] = $metakeys[$sync];
|
542 |
+
unset($metakeys);
|
543 |
+
|
544 |
+
foreach($_metakeys as $identify => $mkeys){
|
545 |
+
foreach($mkeys as $k => $mkey){
|
546 |
+
if($meta_key === $mkey){
|
547 |
+
if($identify === '_metaseo_' ){
|
548 |
+
$mkeysync = $_metakeys[$sync][$k];
|
549 |
+
}
|
550 |
+
else{
|
551 |
+
$mkeysync = $_metakeys['_metaseo_'][$k];
|
552 |
+
}
|
553 |
+
|
554 |
+
if($type == 'update'){
|
555 |
+
update_post_meta($object_id, $mkeysync, $meta_value);
|
556 |
+
return true;
|
557 |
+
}
|
558 |
+
|
559 |
+
if($type == 'delete'){
|
560 |
+
delete_post_meta($object_id, $mkeysync);
|
561 |
+
return true;
|
562 |
+
}
|
563 |
+
|
564 |
+
}
|
565 |
+
}
|
566 |
+
|
567 |
+
}
|
568 |
+
|
569 |
+
return false;
|
570 |
+
}
|
571 |
+
|
572 |
+
/**
|
573 |
+
*
|
574 |
+
*/
|
575 |
+
public static function updateMetaSyncAll($meta_id, $object_id, $meta_key, $meta_value){
|
576 |
+
if(!self::is_updateSync($meta_key)){
|
577 |
+
return null;
|
578 |
+
}
|
579 |
+
//These info may be got from database in later version
|
580 |
+
$mseo = 'wp-meta-seo/wp-meta-seo.php';
|
581 |
+
$yoast = 'wordpress-seo/wp-seo.php';
|
582 |
+
$aio = 'all-in-one-seo-pack/all_in_one_seo_pack.php';
|
583 |
+
|
584 |
+
$metakeys = array(
|
585 |
+
'mtitle' => array(
|
586 |
+
$mseo => '_metaseo_metatitle',
|
587 |
+
$yoast => '_yoast_wpseo_title',
|
588 |
+
$aio => '_aioseop_title' ),
|
589 |
+
'mdesciption' => array(
|
590 |
+
$mseo => '_metaseo_metadesc',
|
591 |
+
$yoast => '_yoast_wpseo_metadesc',
|
592 |
+
$aio => '_aioseop_description' )
|
593 |
+
);
|
594 |
+
|
595 |
+
//Update post meta
|
596 |
+
foreach($metakeys as $metakey){
|
597 |
+
if(in_array($meta_key, $metakey)){
|
598 |
+
foreach($metakey as $plg => $mkey){
|
599 |
+
if($mkey !== $meta_key){
|
600 |
+
if($plg === $mseo || is_plugin_active($plg)){
|
601 |
+
update_post_meta($object_id, $mkey, $meta_value);
|
602 |
+
}
|
603 |
+
}
|
604 |
+
}
|
605 |
+
}
|
606 |
+
}
|
607 |
+
|
608 |
+
return null;
|
609 |
+
}
|
610 |
+
|
611 |
+
public static function is_updateSync($meta_key){
|
612 |
+
$mkey_prefix = array('_metaseo_', '_yoast_', '_aio');
|
613 |
+
foreach($mkey_prefix as $prefix){
|
614 |
+
if(strpos($meta_key, $prefix) === 0){
|
615 |
+
return true;
|
616 |
+
}
|
617 |
+
}
|
618 |
+
|
619 |
+
return false;
|
620 |
+
}
|
621 |
+
}
|
inc/class.metaseo-dashboard.php
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* Comments to come later
|
5 |
+
*
|
6 |
+
*
|
7 |
+
*/
|
8 |
+
|
9 |
+
class MetaSeo_Dashboard {
|
10 |
+
|
11 |
+
public static function optimizationChecking() {
|
12 |
+
global $wpdb;
|
13 |
+
$imgs = 0;
|
14 |
+
#$imgs_metas = 0;
|
15 |
+
$imgs_metas = array('alt' => 0, 'title' => 0);
|
16 |
+
$imgs_are_good = 0;
|
17 |
+
$imgs_metas_are_good = array();
|
18 |
+
$meta_keys = array('alt', 'title');
|
19 |
+
$response = array(
|
20 |
+
'imgs_statis' => array(0, 0),
|
21 |
+
'imgs_metas_statis' => array(0, 0),
|
22 |
+
'meta_title_statis' => array(0, 0),
|
23 |
+
'meta_desc_statis' => array(0, 0),
|
24 |
+
'metacontent' => array(0, 0),
|
25 |
+
);
|
26 |
+
foreach ($meta_keys as $meta_key) {
|
27 |
+
$imgs_metas_are_good[$meta_key] = 0;
|
28 |
+
$imgs_metas_are_not_good[$meta_key] = 0;
|
29 |
+
}
|
30 |
+
|
31 |
+
$post_types = MetaSeo_Content_List_Table::get_post_types();
|
32 |
+
$query = "SELECT `ID`, `post_title`, `post_content`, `post_type`, `post_date`
|
33 |
+
FROM $wpdb->posts
|
34 |
+
WHERE `post_type` IN ($post_types)
|
35 |
+
AND `post_content` <> ''
|
36 |
+
AND `post_content` LIKE '%<img%>%'
|
37 |
+
ORDER BY ID";
|
38 |
+
|
39 |
+
$posts = $wpdb->get_results($query);
|
40 |
+
if (count($posts) > 0) {
|
41 |
+
$doc = new DOMDocument();
|
42 |
+
$upload_dir = wp_upload_dir();
|
43 |
+
|
44 |
+
foreach ($posts as $post) {
|
45 |
+
$dom = $doc->loadHTML($post->post_content);
|
46 |
+
$tags = $doc->getElementsByTagName('img');
|
47 |
+
foreach ($tags as $tag) {
|
48 |
+
$img_src = $tag->getAttribute('src');
|
49 |
+
|
50 |
+
if(!preg_match('/\.(jpg|png|gif)$/i', $img_src, $matches)){
|
51 |
+
continue;
|
52 |
+
}
|
53 |
+
|
54 |
+
$img_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $img_src);
|
55 |
+
if(!file_exists($img_path)){
|
56 |
+
continue;
|
57 |
+
}
|
58 |
+
|
59 |
+
$width = $tag->getAttribute('width');
|
60 |
+
$height = $tag->getAttribute('height');
|
61 |
+
if (list($real_width, $real_height) = @getimagesize($img_path)) {
|
62 |
+
$ratio_origin = $real_width / $real_height;
|
63 |
+
//Check if img tag is missing with/height attribute value or not
|
64 |
+
if (!$width && !$height) {
|
65 |
+
$width = $real_width;
|
66 |
+
$height = $real_height;
|
67 |
+
} elseif ($width && !$height) {
|
68 |
+
$height = $width * (1 / $ratio_origin);
|
69 |
+
} elseif ($height && !$width) {
|
70 |
+
$width = $height * ($ratio_origin);
|
71 |
+
}
|
72 |
+
|
73 |
+
if ($real_width <= $width && $real_height <= $height) {
|
74 |
+
$imgs_are_good++;
|
75 |
+
}
|
76 |
+
|
77 |
+
foreach ($meta_keys as $meta_key) {
|
78 |
+
|
79 |
+
if (trim($tag->getAttribute($meta_key))) {
|
80 |
+
$imgs_metas_are_good[$meta_key] ++;
|
81 |
+
}
|
82 |
+
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
$imgs++;
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
//Report analytic of images optimization
|
91 |
+
$response['imgs_statis'][0] = $imgs_are_good;
|
92 |
+
$response['imgs_statis'][1] = $imgs;
|
93 |
+
$response['imgs_metas_statis'][0] = ceil(($imgs_metas_are_good['alt'] + $imgs_metas_are_good['title'])/2 ) ;
|
94 |
+
$response['imgs_metas_statis'][1] = $imgs;
|
95 |
+
}
|
96 |
+
|
97 |
+
//Get number of post/page and number of images inserted into them
|
98 |
+
$posts_counter = wp_count_posts('post');
|
99 |
+
$pages_counter = wp_count_posts('page');
|
100 |
+
$posts_pages_total = $posts_counter->publish + $pages_counter->publish;
|
101 |
+
$response['meta_title_statis'][1] = $posts_pages_total;
|
102 |
+
$response['meta_desc_statis'][1] = $posts_pages_total;
|
103 |
+
|
104 |
+
$query = "SELECT `meta_key`, count( `meta_value` ) as total
|
105 |
+
FROM $wpdb->postmeta
|
106 |
+
WHERE `meta_key` = '_metaseo_metatitle' AND `meta_value` <> ''
|
107 |
+
UNION (
|
108 |
+
SELECT `meta_key`, count( `meta_value` ) as total
|
109 |
+
FROM $wpdb->postmeta
|
110 |
+
WHERE `meta_key` = '_metaseo_metadesc' AND `meta_value` <> ''
|
111 |
+
)";
|
112 |
+
|
113 |
+
$alias_names = array('_metaseo_metatitle' => 'meta_title_statis', '_metaseo_metadesc' => 'meta_desc_statis');
|
114 |
+
|
115 |
+
$results = $wpdb->get_results($query);
|
116 |
+
if (count($results) > 0) {
|
117 |
+
$count_tt_desc = 0;
|
118 |
+
foreach ($results as $result) {
|
119 |
+
if ($result->meta_key === NULL) {
|
120 |
+
continue;
|
121 |
+
}
|
122 |
+
//Report analytic of content meta
|
123 |
+
$count_tt_desc +=(int) $result->total;
|
124 |
+
$response[$alias_names[$result->meta_key]][0] = (int) $result->total;
|
125 |
+
$response[$alias_names[$result->meta_key]][1] = $posts_pages_total;
|
126 |
+
}
|
127 |
+
$response['metacontent'][0] = ceil($count_tt_desc/2);
|
128 |
+
$response['metacontent'][1] = $posts_pages_total ;
|
129 |
+
}
|
130 |
+
|
131 |
+
echo json_encode($response);
|
132 |
+
wp_die();
|
133 |
+
}
|
134 |
+
|
135 |
+
public function displayRank($url){
|
136 |
+
$rank = $this->getRank($url);
|
137 |
+
if($rank !== ''){
|
138 |
+
echo $rank;
|
139 |
+
}
|
140 |
+
else{
|
141 |
+
echo __('We can\'t get rank of this site from Alexa.com!', 'wp-meta-seo');
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
public function getRank($url){
|
146 |
+
if(!function_exists('curl_version')){
|
147 |
+
if(!$content = @file_get_contents($url)){
|
148 |
+
return '';
|
149 |
+
}
|
150 |
+
}
|
151 |
+
else{
|
152 |
+
if(!is_array($url)){ $url = array($url); }
|
153 |
+
$contents = $this->get_contents($url);
|
154 |
+
$content = $contents[0];
|
155 |
+
}
|
156 |
+
|
157 |
+
$doc = new DOMDocument();
|
158 |
+
@$doc->loadHTML($content);
|
159 |
+
$doc->preserveWhiteSpace = false;
|
160 |
+
|
161 |
+
$finder = new DOMXPath($doc);
|
162 |
+
$classname = 'note-no-data';
|
163 |
+
$nodes = $finder->query("//section[contains(@class, '$classname')]");
|
164 |
+
if($nodes->length < 1) {
|
165 |
+
$classname = 'rank-row';
|
166 |
+
$nodes = $finder->query("//div[contains(@class, '$classname')]");
|
167 |
+
}
|
168 |
+
|
169 |
+
$tmp_dom = new DOMDocument();
|
170 |
+
foreach($nodes as $key => $node){
|
171 |
+
$tmp_dom->appendChild($tmp_dom->importNode($node,true));
|
172 |
+
}
|
173 |
+
|
174 |
+
$html = trim($tmp_dom->saveHTML());
|
175 |
+
$html = str_replace('We don\'t have', 'Alexa doesn\'t has', $html);
|
176 |
+
$html = str_replace('Get Certified', '', $html);
|
177 |
+
$html = str_replace('"/topsites/countries', '"http://www.alexa.com/topsites/countries', $html);
|
178 |
+
return $html;
|
179 |
+
}
|
180 |
+
|
181 |
+
public function get_contents($urls){
|
182 |
+
$mh = curl_multi_init();
|
183 |
+
$curl_array = array();
|
184 |
+
$useragent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36';
|
185 |
+
foreach($urls as $i => $url)
|
186 |
+
{
|
187 |
+
$curl_array[$i] = curl_init($url);
|
188 |
+
curl_setopt($curl_array[$i], CURLOPT_URL, $url);
|
189 |
+
curl_setopt($curl_array[$i], CURLOPT_USERAGENT, $useragent); // set user agent
|
190 |
+
curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, TRUE);
|
191 |
+
curl_setopt($curl_array[$i], CURLOPT_FOLLOWLOCATION, TRUE);
|
192 |
+
curl_setopt($curl_array[$i], CURLOPT_CONNECTTIMEOUT, 5);
|
193 |
+
curl_setopt($curl_array[$i], CURLOPT_ENCODING ,"UTF-8");
|
194 |
+
curl_multi_add_handle($mh, $curl_array[$i]);
|
195 |
+
|
196 |
+
}
|
197 |
+
|
198 |
+
$running = NULL;
|
199 |
+
do {
|
200 |
+
usleep(10000);
|
201 |
+
curl_multi_exec($mh,$running);
|
202 |
+
} while($running > 0);
|
203 |
+
|
204 |
+
$contents = array();
|
205 |
+
foreach($urls as $i => $url)
|
206 |
+
{
|
207 |
+
$content = curl_multi_getcontent($curl_array[$i]);
|
208 |
+
$contents[$i] = $content;
|
209 |
+
}
|
210 |
+
|
211 |
+
foreach($urls as $i => $url){
|
212 |
+
curl_multi_remove_handle($mh, $curl_array[$i]);
|
213 |
+
}
|
214 |
+
curl_multi_close($mh);
|
215 |
+
return $contents;
|
216 |
+
}
|
217 |
+
|
218 |
+
}
|
inc/class.metaseo-image-list-table.php
ADDED
@@ -0,0 +1,1236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
* Comments to come later
|
4 |
+
*
|
5 |
+
*
|
6 |
+
*/
|
7 |
+
|
8 |
+
if (!class_exists('WP_List_Table')) {
|
9 |
+
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
|
10 |
+
}
|
11 |
+
|
12 |
+
if ( !class_exists( 'ImageHelper' ) ) {
|
13 |
+
require_once( 'class.image-helper.php' );
|
14 |
+
}
|
15 |
+
|
16 |
+
class MetaSeo_Image_List_Table extends WP_List_Table {
|
17 |
+
|
18 |
+
function __construct() {
|
19 |
+
parent::__construct(array(
|
20 |
+
'singular' => 'metaseo_image',
|
21 |
+
'plural' => 'metaseo_images',
|
22 |
+
'ajax' => true
|
23 |
+
));
|
24 |
+
}
|
25 |
+
|
26 |
+
function display_tablenav($which) {
|
27 |
+
?>
|
28 |
+
<div class="tablenav <?php echo esc_attr($which); ?>">
|
29 |
+
|
30 |
+
<?php if($which=='top'): ?>
|
31 |
+
<input type="hidden" name="page" value="metaseo_image_meta" />
|
32 |
+
<div class="alignleft actions bulkactions">
|
33 |
+
<?php $this->months_fillter('attachment','sldate','filter_date_action'); ?>
|
34 |
+
</div>
|
35 |
+
<?php elseif($which=='bottom'):?>
|
36 |
+
<input type="hidden" name="page" value="metaseo_image_meta" />
|
37 |
+
<div class="alignleft actions bulkactions">
|
38 |
+
<?php $this->months_fillter('attachment','sldate1','filter_date_action'); ?>
|
39 |
+
</div>
|
40 |
+
<?php endif ?>
|
41 |
+
|
42 |
+
<input type="hidden" name="page" value="metaseo_image_meta" />
|
43 |
+
<?php if (!empty($_REQUEST['post_status'])): ?>
|
44 |
+
<input type="hidden" name="post_status" value="<?php echo esc_attr($_REQUEST['post_status']); ?>" />
|
45 |
+
<?php endif ?>
|
46 |
+
|
47 |
+
<?php //$this->extra_tablenav($which); ?>
|
48 |
+
|
49 |
+
<div style="float:right;margin-left:8px;">
|
50 |
+
<input type="number" required min="1" value="<?php echo $this->_pagination_args['per_page'] ?>" maxlength="3" name="metaseo_imgs_per_page" class="metaseo_imgs_per_page screen-per-page" max="999" min="1" step="1">
|
51 |
+
<input type="submit" name="btn_perpage" class="button_perpage button" id="button_perpage" value="Apply" >
|
52 |
+
</div>
|
53 |
+
|
54 |
+
<?php $this->pagination($which); ?>
|
55 |
+
<br class="clear" />
|
56 |
+
</div>
|
57 |
+
|
58 |
+
<?php
|
59 |
+
}
|
60 |
+
|
61 |
+
function get_views() {
|
62 |
+
global $wpdb;
|
63 |
+
|
64 |
+
|
65 |
+
$status_links = array();
|
66 |
+
|
67 |
+
$post_types = get_post_types(array('public' => true, 'exclude_from_search' => false));
|
68 |
+
$post_types = "'" . implode("', '", $post_types) . "'";
|
69 |
+
|
70 |
+
$states = get_post_stati(array('show_in_admin_all_list' => true));
|
71 |
+
$states['trash'] = 'trash';
|
72 |
+
$all_states = "'" . implode("', '", $states) . "'";
|
73 |
+
|
74 |
+
$total_posts = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ($all_states) AND post_type IN ($post_types)");
|
75 |
+
|
76 |
+
$class = empty($_REQUEST['post_status']) ? ' class="current"' : '';
|
77 |
+
$status_links['all'] = "<a href='admin.php?page=metaseo_image_meta'$class>" . sprintf(_nx('All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts'), number_format_i18n($total_posts)) . '</a>';
|
78 |
+
|
79 |
+
foreach (get_post_stati(array('show_in_admin_all_list' => true), 'objects') as $status) {
|
80 |
+
|
81 |
+
$status_name = $status->name;
|
82 |
+
|
83 |
+
$total = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ('$status_name') AND post_type IN ($post_types)");
|
84 |
+
|
85 |
+
if ($total == 0) {
|
86 |
+
continue;
|
87 |
+
}
|
88 |
+
|
89 |
+
if (isset($_REQUEST['post_status']) && $status_name == $_REQUEST['post_status']) {
|
90 |
+
$class = ' class="current"';
|
91 |
+
} else {
|
92 |
+
$class = '';
|
93 |
+
}
|
94 |
+
|
95 |
+
$status_links[$status_name] = "<a href='admin.php?page=metaseo_image_meta&post_status=$status_name'$class>" . sprintf(translate_nooped_plural($status->label_count, $total), number_format_i18n($total)) . '</a>';
|
96 |
+
}
|
97 |
+
$trashed_posts = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status IN ('trash') AND post_type IN ($post_types)");
|
98 |
+
$class = ( isset($_REQUEST['post_status']) && 'trash' == $_REQUEST['post_status'] ) ? 'class="current"' : '';
|
99 |
+
$status_links['trash'] = "<a href='admin.php?page=metaseo_image_meta&post_status=trash'$class>" . sprintf(_nx('Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>', $trashed_posts, 'posts'), number_format_i18n($trashed_posts)) . '</a>';
|
100 |
+
|
101 |
+
return $status_links;
|
102 |
+
}
|
103 |
+
|
104 |
+
function extra_tablenav($which) {
|
105 |
+
|
106 |
+
#if ('top' == $which) {
|
107 |
+
echo '<div class="alignleft actions">';
|
108 |
+
global $wpdb;
|
109 |
+
|
110 |
+
$post_types = get_post_types(array('public' => true, 'exclude_from_search' => false));
|
111 |
+
$post_types = "'" . implode("', '", $post_types) . "'";
|
112 |
+
|
113 |
+
$states = get_post_stati(array('show_in_admin_all_list' => true));
|
114 |
+
$states['trash'] = 'trash';
|
115 |
+
$all_states = "'" . implode("', '", $states) . "'";
|
116 |
+
|
117 |
+
$query = "SELECT DISTINCT post_type FROM $wpdb->posts WHERE post_status IN ($all_states) AND post_type IN ($post_types) ORDER BY 'post_type' ASC";
|
118 |
+
$post_types = $wpdb->get_results($query);
|
119 |
+
|
120 |
+
$selected = !empty($_REQUEST['post_type_filter']) ? $_REQUEST['post_type_filter'] : -1;
|
121 |
+
|
122 |
+
$options = '<option value="-1">Show All Post Types</option>';
|
123 |
+
|
124 |
+
foreach ($post_types as $post_type) {
|
125 |
+
$obj = get_post_type_object($post_type->post_type);
|
126 |
+
$options .= sprintf('<option value="%2$s" %3$s>%1$s</option>', $obj->labels->name, $post_type->post_type, selected($selected, $post_type->post_type, false));
|
127 |
+
}
|
128 |
+
|
129 |
+
echo "</div>";
|
130 |
+
#echo "</form>";
|
131 |
+
#}
|
132 |
+
}
|
133 |
+
|
134 |
+
function get_columns() {
|
135 |
+
return $columns = array(
|
136 |
+
'col_id' => __('ID', 'wp-meta-seo'),
|
137 |
+
'col_image' => __('Image', 'wp-meta-seo'),
|
138 |
+
'col_image_name' => __('Name', 'wp-meta-seo'),
|
139 |
+
'col_image_info' => __('Optimization Info', 'wp-meta-seo'),
|
140 |
+
'col_image_alternative' => __('Alternative text', 'wp-meta-seo'),
|
141 |
+
'col_image_title' => __('Title', 'wp-meta-seo'),
|
142 |
+
'col_image_legend' => __('Legend', 'wp-meta-seo'),
|
143 |
+
'col_image_desc' => __('Description', 'wp-meta-seo'),
|
144 |
+
);
|
145 |
+
}
|
146 |
+
|
147 |
+
function get_sortable_columns() {
|
148 |
+
return $sortable = array(
|
149 |
+
'col_image_name' => array('post_name', true),
|
150 |
+
'col_image_title' => array('post_title', true),
|
151 |
+
);
|
152 |
+
}
|
153 |
+
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Print column headers, accounting for hidden and sortable columns.
|
157 |
+
*
|
158 |
+
* @since 3.1.0
|
159 |
+
* @access public
|
160 |
+
*
|
161 |
+
* @param bool $with_id Whether to set the id attribute or not
|
162 |
+
*/
|
163 |
+
public function print_column_headers( $with_id = true ) {
|
164 |
+
list( $columns, $hidden, $sortable ) = $this->get_column_info();
|
165 |
+
|
166 |
+
$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
|
167 |
+
$current_url = remove_query_arg( 'paged', $current_url );
|
168 |
+
|
169 |
+
if ( isset( $_GET['orderby'] ) )
|
170 |
+
$current_orderby = $_GET['orderby'];
|
171 |
+
else
|
172 |
+
$current_orderby = '';
|
173 |
+
|
174 |
+
if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
|
175 |
+
$current_order = 'desc';
|
176 |
+
else
|
177 |
+
$current_order = 'asc';
|
178 |
+
|
179 |
+
if ( ! empty( $columns['cb'] ) ) {
|
180 |
+
static $cb_counter = 1;
|
181 |
+
$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
|
182 |
+
. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
|
183 |
+
$cb_counter++;
|
184 |
+
}
|
185 |
+
|
186 |
+
foreach ( $columns as $column_key => $column_display_name ) {
|
187 |
+
$class = array( 'manage-column', "column-$column_key" );
|
188 |
+
|
189 |
+
$style = '';
|
190 |
+
if ( in_array( $column_key, $hidden ) )
|
191 |
+
$style = 'display:none;';
|
192 |
+
|
193 |
+
$style = ' style="' . $style . '"';
|
194 |
+
|
195 |
+
if ( 'cb' == $column_key )
|
196 |
+
$class[] = 'check-column';
|
197 |
+
elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
|
198 |
+
$class[] = 'num';
|
199 |
+
|
200 |
+
if ( isset( $sortable[$column_key] ) ) {
|
201 |
+
list( $orderby, $desc_first ) = $sortable[$column_key];
|
202 |
+
|
203 |
+
if ( $current_orderby == $orderby ) {
|
204 |
+
$order = 'asc' == $current_order ? 'desc' : 'asc';
|
205 |
+
$class[] = 'sorted';
|
206 |
+
$class[] = $current_order;
|
207 |
+
} else {
|
208 |
+
$order = $desc_first ? 'desc' : 'asc';
|
209 |
+
$class[] = 'sortable';
|
210 |
+
$class[] = $desc_first ? 'asc' : 'desc';
|
211 |
+
}
|
212 |
+
|
213 |
+
$column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
|
214 |
+
}
|
215 |
+
|
216 |
+
$id = $with_id ? "id='$column_key'" : '';
|
217 |
+
|
218 |
+
if ( !empty( $class ) )
|
219 |
+
$class = "class='" . join( ' ', $class ) . "'";
|
220 |
+
|
221 |
+
if($column_key === 'col_id'){
|
222 |
+
echo "<th scope='col' $id $class $style colspan=\"1\">$column_display_name</th>";
|
223 |
+
}
|
224 |
+
elseif($column_key === 'col_image_name'){
|
225 |
+
echo "<th scope='col' $id $class $style colspan=\"4\">$column_display_name</th>";
|
226 |
+
}
|
227 |
+
elseif($column_key === 'col_image_info'){
|
228 |
+
echo "<th scope='col' $id $class $style colspan=\"5\">$column_display_name</th>";
|
229 |
+
}
|
230 |
+
else{
|
231 |
+
echo "<th scope='col' $id $class $style colspan=\"3\">$column_display_name</th>";
|
232 |
+
}
|
233 |
+
}
|
234 |
+
}
|
235 |
+
|
236 |
+
function prepare_items() {
|
237 |
+
global $wpdb, $_wp_column_headers;
|
238 |
+
$screen = get_current_screen();
|
239 |
+
|
240 |
+
$where=array();
|
241 |
+
$post_type='attachment';
|
242 |
+
$where[] = " post_type='$post_type' ";
|
243 |
+
$where[] = " ((post_mime_type='image/jpeg') OR (post_mime_type='image/jpg') OR (post_mime_type='image/png') OR (post_mime_type='image/gif')) ";
|
244 |
+
|
245 |
+
if (!empty($_REQUEST["search"])) {
|
246 |
+
if (!empty($_REQUEST["txtkeyword"])){
|
247 |
+
$_REQUEST["txtkeyword"] = stripslashes($_REQUEST["txtkeyword"]);
|
248 |
+
$_REQUEST["txtkeyword"] = $wpdb->esc_like( $_REQUEST["txtkeyword"] );
|
249 |
+
$where[] = $wpdb->prepare(" (post_title Like %s or post_name Like %s)", "%" . $_REQUEST["txtkeyword"] . "%", "%" . $_REQUEST["txtkeyword"] . "%" );
|
250 |
+
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
if(!empty($_REQUEST['sldate'])){
|
255 |
+
$where[] =$wpdb->prepare(" post_date Like %s","%" .$_REQUEST['sldate']. "%");
|
256 |
+
}
|
257 |
+
|
258 |
+
$orderby = !empty($_GET["orderby"]) ? ($_GET["orderby"]) : 'post_name';
|
259 |
+
$order = !empty($_GET["order"]) ? ($_GET["order"]) : 'ASC';
|
260 |
+
if (!empty($orderby) & !empty($order)) {
|
261 |
+
$orderStr =$wpdb->prepare(' ORDER BY %s %s',$orderby,$order);
|
262 |
+
$orderStr = str_replace("'","",$orderStr);
|
263 |
+
}
|
264 |
+
|
265 |
+
|
266 |
+
|
267 |
+
$query = "SELECT ID, post_title as title, post_name as name, post_content as des, post_excerpt as legend, guid, post_type , post_mime_type, post_status, mt.meta_value AS alt
|
268 |
+
FROM $wpdb->posts as posts
|
269 |
+
LEFT JOIN (SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = '_wp_attachment_image_alt') mt ON mt.post_id = posts.ID
|
270 |
+
WHERE ". implode(" and ", $where) .$orderStr;
|
271 |
+
|
272 |
+
$total_items = $wpdb->query($query);
|
273 |
+
|
274 |
+
if(!empty($_REQUEST['metaseo_imgs_per_page'])){
|
275 |
+
$_per_page = intval($_REQUEST['metaseo_imgs_per_page']);
|
276 |
+
}
|
277 |
+
else {
|
278 |
+
$_per_page = 0;
|
279 |
+
}
|
280 |
+
$per_page = get_user_option('metaseo_imgs_per_page');
|
281 |
+
if( $per_page !== false) {
|
282 |
+
if($_per_page && $_per_page !== $per_page ){
|
283 |
+
$per_page = $_per_page;
|
284 |
+
update_user_option(get_current_user_id(), 'metaseo_imgs_per_page', $per_page);
|
285 |
+
}
|
286 |
+
}
|
287 |
+
else{
|
288 |
+
if($_per_page > 0) {
|
289 |
+
$per_page = $_per_page;
|
290 |
+
}
|
291 |
+
else { $per_page = 10; }
|
292 |
+
add_user_meta(get_current_user_id(), 'metaseo_imgs_per_page', $per_page);
|
293 |
+
}
|
294 |
+
|
295 |
+
$paged = !empty($_GET["paged"]) ? ($_GET["paged"]) : '';
|
296 |
+
|
297 |
+
if (empty($paged) || !is_numeric($paged) || $paged <= 0) {
|
298 |
+
$paged = 1;
|
299 |
+
}
|
300 |
+
|
301 |
+
$total_pages = ceil($total_items / $per_page);
|
302 |
+
|
303 |
+
if (!empty($paged) && !empty($per_page)) {
|
304 |
+
$offset = ($paged - 1) * $per_page;
|
305 |
+
$query .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
|
306 |
+
}
|
307 |
+
|
308 |
+
$this->set_pagination_args(array(
|
309 |
+
'total_items' => $total_items,
|
310 |
+
'total_pages' => $total_pages,
|
311 |
+
'per_page' => $per_page
|
312 |
+
));
|
313 |
+
|
314 |
+
$columns = $this->get_columns();
|
315 |
+
$hidden = array();
|
316 |
+
$sortable = $this->get_sortable_columns();
|
317 |
+
$this->_column_headers = array($columns, $hidden, $sortable);
|
318 |
+
$this->items = $wpdb->get_results($query);
|
319 |
+
|
320 |
+
}
|
321 |
+
|
322 |
+
function search_box1( ) {
|
323 |
+
if (empty($_REQUEST['txtkeyword']) && !$this->has_items())
|
324 |
+
return;
|
325 |
+
$txtkeyword = (!empty($_REQUEST['txtkeyword'])) ? urldecode(stripslashes($_REQUEST['txtkeyword']) ) : "";
|
326 |
+
if (!empty($_REQUEST['orderby']))
|
327 |
+
echo '<input type="hidden" name="orderby" value="' . esc_attr($_REQUEST['orderby']) . '" />';
|
328 |
+
if (!empty($_REQUEST['order']))
|
329 |
+
echo '<input type="hidden" name="order" value="' . esc_attr($_REQUEST['order']) . '" />';
|
330 |
+
if (!empty($_REQUEST['post_mime_type']))
|
331 |
+
echo '<input type="hidden" name="post_mime_type" value="' . esc_attr($_REQUEST['post_mime_type']) . '" />';
|
332 |
+
if (!empty($_REQUEST['detached']))
|
333 |
+
echo '<input type="hidden" name="detached" value="' . esc_attr($_REQUEST['detached']) . '" />';
|
334 |
+
?>
|
335 |
+
<p class="search-box">
|
336 |
+
|
337 |
+
<input type="search" id="image-search-input" name="txtkeyword" value="<?php echo esc_attr(stripslashes($txtkeyword)); ?>" />
|
338 |
+
<?php submit_button('Search', 'button', 'search', false, array('id' => 'search-submit')); ?>
|
339 |
+
</p>
|
340 |
+
<?php
|
341 |
+
}
|
342 |
+
|
343 |
+
|
344 |
+
function months_fillter($post_type, $name,$namebutton) {
|
345 |
+
global $wpdb, $wp_locale;
|
346 |
+
|
347 |
+
$where = " AND ((post_mime_type='image/jpeg') OR (post_mime_type='image/jpg') OR (post_mime_type='image/png') OR (post_mime_type='image/gif')) ";
|
348 |
+
$months = $wpdb->get_results($wpdb->prepare("
|
349 |
+
SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
|
350 |
+
FROM $wpdb->posts
|
351 |
+
WHERE post_type = %s".$where."
|
352 |
+
ORDER BY post_date DESC
|
353 |
+
", $post_type));
|
354 |
+
|
355 |
+
$months = apply_filters('months_dropdown_results', $months, $post_type);
|
356 |
+
|
357 |
+
$month_count = count($months);
|
358 |
+
|
359 |
+
if (!$month_count || ( 1 == $month_count && 0 == $months[0]->month ))
|
360 |
+
return;
|
361 |
+
|
362 |
+
$m = isset($_REQUEST['sldate']) ? $_REQUEST['sldate'] : 0;
|
363 |
+
?>
|
364 |
+
<label for="filter-by-date" class="screen-reader-text"><?php _e('Filter by date'); ?></label>
|
365 |
+
<select name="<?php echo $name ?>" id="filter-by-date" class="metaseo-filter">
|
366 |
+
<option<?php selected($m, 0); ?> value="0"><?php _e('All dates'); ?></option>
|
367 |
+
<?php
|
368 |
+
foreach ($months as $arc_row) {
|
369 |
+
|
370 |
+
if (0 == $arc_row->year) continue;
|
371 |
+
$month = zeroise($arc_row->month, 2);
|
372 |
+
$year = $arc_row->year;
|
373 |
+
printf("<option %s value='%s' >%s</option>\n",
|
374 |
+
selected($m, "$year-$month", false),
|
375 |
+
esc_attr("$arc_row->year-$month"),
|
376 |
+
sprintf(__('%1$s %2$d'), $wp_locale->get_month($month), $year)
|
377 |
+
);
|
378 |
+
}
|
379 |
+
?>
|
380 |
+
</select>
|
381 |
+
|
382 |
+
<?php
|
383 |
+
submit_button(__('Filter'), 'button', $namebutton, false, array('id' => 'image-submit'));
|
384 |
+
}
|
385 |
+
|
386 |
+
function display_rows() {
|
387 |
+
$url = URL;
|
388 |
+
$url = preg_replace('/(^(http|https):\/\/[w]*\.*)/', '', $url);
|
389 |
+
$records = $this->items;
|
390 |
+
$i = 0;
|
391 |
+
$alternate = "";
|
392 |
+
|
393 |
+
list( $columns, $hidden ) = $this->get_column_info();
|
394 |
+
|
395 |
+
if (!empty($records)) {
|
396 |
+
foreach ($records as $rec) {
|
397 |
+
$alternate = 'alternate' == $alternate ? '' : 'alternate';
|
398 |
+
$i++;
|
399 |
+
$classes = $alternate;
|
400 |
+
$img_meta = get_post_meta($rec->ID, '_wp_attachment_metadata', TRUE);
|
401 |
+
$thumb = wp_get_attachment_image_src($rec->ID, 'thumbnail' );
|
402 |
+
if(!$thumb) {
|
403 |
+
$thumb_url = $rec->guid;
|
404 |
+
}else {
|
405 |
+
$thumb_url = $thumb['0'];
|
406 |
+
}
|
407 |
+
|
408 |
+
if (strrpos($img_meta['file'], '/') !== false) {
|
409 |
+
$img_name = substr($img_meta['file'], strrpos($img_meta['file'], '/') + 1);
|
410 |
+
} else {
|
411 |
+
$img_name = $img_meta['file'];
|
412 |
+
}
|
413 |
+
$type = substr($img_meta['file'], strrpos($img_meta['file'], '.'));
|
414 |
+
$img_name = str_replace($type, '', $img_name);
|
415 |
+
|
416 |
+
$upload_dir = wp_upload_dir();
|
417 |
+
$img_path = $upload_dir['basedir'] . '/' . $img_meta['file'];
|
418 |
+
if (is_readable($img_path)) {
|
419 |
+
//Get image attributes including width and height
|
420 |
+
list($img_width, $img_height,$img_type) = getimagesize($img_path);
|
421 |
+
//Get image size
|
422 |
+
if (($size = filesize($img_path) / 1024) > 1024) {
|
423 |
+
$img_size = ($size / 1024);
|
424 |
+
$img_sizes = ' MB';
|
425 |
+
} else {
|
426 |
+
$img_size = ($size);
|
427 |
+
$img_sizes = ' KB';
|
428 |
+
}
|
429 |
+
$img_size=round($img_size,1);
|
430 |
+
//Get the date that image was uploaded
|
431 |
+
$img_date = get_the_date("", $rec->ID);
|
432 |
+
}
|
433 |
+
|
434 |
+
echo '<tr id="record_' . $rec->ID . '" class="' . $classes . '" >';
|
435 |
+
|
436 |
+
foreach ($columns as $column_name => $column_display_name) {
|
437 |
+
|
438 |
+
$class = sprintf('class="%1$s column-%1$s"', $column_name);
|
439 |
+
$style = "";
|
440 |
+
|
441 |
+
if (in_array($column_name, $hidden)) {
|
442 |
+
$style = ' style="display:none;"';
|
443 |
+
}
|
444 |
+
|
445 |
+
$attributes = $class . $style;
|
446 |
+
|
447 |
+
switch ($column_name) {
|
448 |
+
case 'col_id':
|
449 |
+
echo '<td class="col_id" colspan="1">';
|
450 |
+
echo $i;
|
451 |
+
echo '</td>';
|
452 |
+
break;
|
453 |
+
|
454 |
+
case 'col_image':
|
455 |
+
$img = sprintf("<img src='$thumb_url' width='100px' height='100px' class=\"metaseo-image\" data-name=\"$img_name$type\" data-img-post-id=\"$rec->ID\" />");
|
456 |
+
|
457 |
+
echo sprintf('<td %2$s colspan="3">%1$s</td>', $img, $attributes);
|
458 |
+
break;
|
459 |
+
|
460 |
+
case 'col_image_name':
|
461 |
+
$info = '<div class="img-name-wrapper">';
|
462 |
+
$info .= '<input type="text" name="name_image['.$rec->ID.']" class="metaseo-img-meta metaseo-img-name" data-meta-type="change_image_name" id="img-name-'.$rec->ID.'" data-post-id="'.$rec->ID.'" size="12" value="'.$img_name.'" data-extension="'.$type.'" /><span class="img_type">'.$type.'</span>';
|
463 |
+
#$info .= '<p class="savedInfo" style="display:none;"></p>';
|
464 |
+
$info .= '<p>size: ' . $img_size . $img_sizes. '</p>';
|
465 |
+
$info .= '<p>' . $img_width . 'x' . $img_height . '</p>';
|
466 |
+
$info .= '<p>' . $img_date . '</p>';
|
467 |
+
$info .= '<span class="saved-info" style="position:relative">
|
468 |
+
<span class="meta-update" style="position:absolute"></span>
|
469 |
+
</span>';
|
470 |
+
$info .= '</div>';
|
471 |
+
echo sprintf('<td %2$s colspan="4">%1$s</td>', $info, $attributes);
|
472 |
+
break;
|
473 |
+
|
474 |
+
case 'col_image_info':
|
475 |
+
$icon = '<img src= "' . WPMETASEO_PLUGIN_URL . 'img/view.png" />';
|
476 |
+
$icon = '';
|
477 |
+
$info = "<div class=\"opt-info\" id=\"opt-info-$rec->ID\"></div>";
|
478 |
+
$info .= '<span class="metaseo-loading"></span>';
|
479 |
+
$info .= '
|
480 |
+
<div class="popup-bg"></div>
|
481 |
+
<div class="popup post-list">
|
482 |
+
<span class="popup-close" title="Close">x</span>
|
483 |
+
<div class="popup-content"></div>
|
484 |
+
</div>
|
485 |
+
';
|
486 |
+
|
487 |
+
echo sprintf('<td %2$s colspan="5" style="position:relative">%1$s</td>', $info, $attributes);
|
488 |
+
break;
|
489 |
+
|
490 |
+
case 'col_image_alternative':
|
491 |
+
$input = ("<input name='img_alternative[$rec->ID]' class='metaseo-img-meta' data-meta-type='alt_text' id='img-alt-$rec->ID' data-post-id='$rec->ID' size='13' value='". esc_attr($rec->alt)."' type='text'>");
|
492 |
+
$input .= ('<span class="saved-info" style="position:relative">
|
493 |
+
<span class="meta-update" style="position:absolute"></span>
|
494 |
+
</span>');
|
495 |
+
echo sprintf('<td %2$s colspan="3">%1$s</td>', $input, $attributes);
|
496 |
+
break;
|
497 |
+
|
498 |
+
case 'col_image_title':
|
499 |
+
$input = ("<input name='img_title[$rec->ID]' class='metaseo-img-meta' data-meta-type='image_title' id='img-title-$rec->ID' data-post-id='$rec->ID' size='13' value='".esc_attr($rec->title)."' type='text'>");
|
500 |
+
$input .= ('<span class="saved-info" style="position:relative">
|
501 |
+
<span class="meta-update" style="position:absolute"></span>
|
502 |
+
</span>');
|
503 |
+
echo sprintf('<td %2$s colspan="3">%1$s</td>', $input, $attributes);
|
504 |
+
break;
|
505 |
+
|
506 |
+
case 'col_image_legend':
|
507 |
+
$input = ("<input name='img_legend[$rec->ID]' class='metaseo-img-meta' data-meta-type='image_caption' id='img-legend-$rec->ID' data-post-id='$rec->ID' value='".esc_attr($rec->legend)."' size='13' type='text'>");
|
508 |
+
$input .= ('<span class="saved-info" style="position:relative">
|
509 |
+
<span class="meta-update" style="position:absolute"></span>
|
510 |
+
</span>');
|
511 |
+
echo sprintf('<td %2$s colspan="3">%1$s</td>', $input, $attributes);
|
512 |
+
break;
|
513 |
+
|
514 |
+
case 'col_image_desc':
|
515 |
+
$input = ("<input name='img_desc[$rec->ID]' class='metaseo-img-meta' data-meta-type='image_description' id='img-desc-$rec->ID' data-post-id='$rec->ID' size='30' value='".esc_attr($rec->des)."' type='text'>");
|
516 |
+
$input .= ('<span class="saved-info" style="position:relative">
|
517 |
+
<span class="meta-update" style="position:absolute"></span>
|
518 |
+
</span>');
|
519 |
+
echo sprintf('<td %2$s colspan="3">%1$s</td>', $input, $attributes);
|
520 |
+
break;
|
521 |
+
}
|
522 |
+
|
523 |
+
}
|
524 |
+
|
525 |
+
echo '</tr>';
|
526 |
+
}
|
527 |
+
}
|
528 |
+
}
|
529 |
+
|
530 |
+
public static function add_more_attachment_sizes_js($response, $attachment){
|
531 |
+
$metaseo_imgs_sizes = get_post_meta($attachment->ID, '_metaseo_sizes_optional', true);
|
532 |
+
|
533 |
+
if(!empty($metaseo_imgs_sizes)){
|
534 |
+
foreach($metaseo_imgs_sizes as $key => $size){
|
535 |
+
$response['sizes'][$key] = $size;
|
536 |
+
}
|
537 |
+
}
|
538 |
+
|
539 |
+
return $response;
|
540 |
+
}
|
541 |
+
|
542 |
+
public static function add_more_attachment_sizes_choose($sizes){
|
543 |
+
global $wpdb;
|
544 |
+
$query = "SELECT `meta_value` FROM $wpdb->postmeta WHERE `meta_key` = '_metaseo_sizes_optional' AND `meta_value` <> ''";
|
545 |
+
|
546 |
+
$metaseo_imgs_sizes = $wpdb->get_results($query);
|
547 |
+
if(!empty($metaseo_imgs_sizes)){
|
548 |
+
$_sizes = array();
|
549 |
+
foreach($metaseo_imgs_sizes as $metaseo_img_sizes){
|
550 |
+
$metaseo_img_sizes = @unserialize($metaseo_img_sizes->meta_value);
|
551 |
+
foreach($metaseo_img_sizes as $key => $size){
|
552 |
+
add_image_size($key, $size['width'], $size['height'], false);
|
553 |
+
}
|
554 |
+
}
|
555 |
+
|
556 |
+
}
|
557 |
+
|
558 |
+
$new_sizes = array();
|
559 |
+
|
560 |
+
$added_sizes = get_intermediate_image_sizes();
|
561 |
+
|
562 |
+
// $added_sizes is an indexed array, therefore need to convert it
|
563 |
+
// to associative array, using $value for $key and $value
|
564 |
+
foreach( $added_sizes as $value) {
|
565 |
+
if(strpos($value, '-metaseo') !== false){
|
566 |
+
$_value = substr($value, 0, strrpos($value, '-metaseo'));
|
567 |
+
}else{
|
568 |
+
$_value = $value;
|
569 |
+
}
|
570 |
+
$new_sizes[$value] = ucwords(str_replace(array('-','_'), ' – ', $_value));
|
571 |
+
}
|
572 |
+
|
573 |
+
// This preserves the labels in $sizes, and merges the two arrays
|
574 |
+
$new_sizes = array_merge( $new_sizes, $sizes );
|
575 |
+
|
576 |
+
return $new_sizes;
|
577 |
+
}
|
578 |
+
|
579 |
+
private static function display_fix_metas_list($img_post_id, $posts, $meta_counter, $p, $im){
|
580 |
+
if($meta_counter){
|
581 |
+
$header = __('We found ' . $meta_counter . $im . $p . 'which needed to add or change meta information', 'wp-meta-seo');
|
582 |
+
}else{
|
583 |
+
$header = __('We found 0 image which needed to add or change meta information', 'wp-meta-seo');
|
584 |
+
}
|
585 |
+
|
586 |
+
//Get default meta information of the image
|
587 |
+
$img_post = get_post($img_post_id);
|
588 |
+
$alt = get_post_meta($img_post_id, '_wp_attachment_image_alt', true);
|
589 |
+
$title = $img_post->post_title;
|
590 |
+
?>
|
591 |
+
<h3 class="content-header"><?php echo $header ?></h3>
|
592 |
+
<div class="content-box">
|
593 |
+
<table class="wp-list-table widefat fixed posts">
|
594 |
+
<thead></thead>
|
595 |
+
<tbody>
|
596 |
+
<?php $alternate = '';?>
|
597 |
+
<?php if(count($posts) < 1): ?>
|
598 |
+
<tr><td colspan="10" style="height:95%"><?php echo __('This image has still not been inserted in any post!', 'wp-meta-seo') ?></td></tr>
|
599 |
+
<?php else: ?>
|
600 |
+
<tr class="metaseo-border-bottom">
|
601 |
+
<td colspan="1">ID</td>
|
602 |
+
<td colspan="2">Title</td>
|
603 |
+
<td colspan="2">Image</td>
|
604 |
+
<td colspan="5">Image Meta</td>
|
605 |
+
</tr>
|
606 |
+
<?php foreach($posts as $post): ?>
|
607 |
+
<?php foreach($post['meta'] as $k => $meta): ?>
|
608 |
+
<?php $alternate = 'alternate' == $alternate ? '' : 'alternate';
|
609 |
+
$file_name = substr($meta['img_src'], strrpos($meta['img_src'], '/')+1);
|
610 |
+
?>
|
611 |
+
<tr class="<?php echo $alternate ?>">
|
612 |
+
<td colspan="1"><?php echo $post['ID'] ?></td>
|
613 |
+
<td colspan="2">
|
614 |
+
<p><?php echo $post['title'] ?></p>
|
615 |
+
</td>
|
616 |
+
<td colspan="2">
|
617 |
+
<div class="metaseo-img-wrapper">
|
618 |
+
<img src="<?php echo $meta['img_src'] ?>" />
|
619 |
+
</div>
|
620 |
+
</td>
|
621 |
+
<td colspan="5">
|
622 |
+
<?php foreach($meta['type'] as $type => $value): ?>
|
623 |
+
<div class="metaseo-img-wrapper">
|
624 |
+
<?php
|
625 |
+
$specialChr = array('"', '\'');
|
626 |
+
foreach($specialChr as $chr){
|
627 |
+
$value = str_replace($chr, htmlentities2($chr), $value);
|
628 |
+
}
|
629 |
+
?>
|
630 |
+
<input type="text" value="<?php echo ($value != '' ? $value : ''); ?>" id="metaseo-img-<?php echo $type. '-' .$post['ID'] ?>" class="metaseo-fix-meta metaseo-img-<?php echo $type ?>" data-meta-key="_metaseo_fix_metas" data-post-id="<?php echo $post['ID'] ?>" data-img-post-id="<?php echo $img_post_id ?>" data-meta-type="<?php echo $type ?>" data-meta-order="<?php echo $k ?>" data-file-name="<?php echo $file_name; ?>" placeholder="<?php echo ($value == '' ? __(ucfirst($type) . ' is empty', 'wp-meta-seo') : '') ?>" onfocus="metaseo_fix_meta(this);" onblur="updateInputBlur(this)" onkeydown="return checkeyCode(event,this)" />
|
631 |
+
<span class="meta-update"></span>
|
632 |
+
<?php if(trim($$type) != '' && trim($$type) != $value): ?>
|
633 |
+
<a class="button meta-default" href="#" data-default-value="<?php echo esc_attr($$type) ?>" title="Add to input box" onclick="add_meta_default(this)"> <?php echo '<img src= "' . WPMETASEO_PLUGIN_URL . 'img/img-arrow.png" />' ?> Copy </a>
|
634 |
+
<span class="img_seo_type"><?php echo $$type; ?></span>
|
635 |
+
<?php endif ?>
|
636 |
+
</div>
|
637 |
+
<?php endforeach ?>
|
638 |
+
<span class="saved-info"></span>
|
639 |
+
</td>
|
640 |
+
</tr>
|
641 |
+
<?php endforeach ?>
|
642 |
+
<?php endforeach ?>
|
643 |
+
<?php endif ?>
|
644 |
+
</tbody>
|
645 |
+
<tfoot></tfoot>
|
646 |
+
</table>
|
647 |
+
</div>
|
648 |
+
<div style="padding:5px"></div>
|
649 |
+
<?php
|
650 |
+
|
651 |
+
}
|
652 |
+
|
653 |
+
private static function display_resize_image_list($img_post_id,$posts, $img_counter, $p, $im){
|
654 |
+
|
655 |
+
$header = __('We found ' . $img_counter . $im . $p . ' which needed to optimize', 'wp-meta-seo');
|
656 |
+
?>
|
657 |
+
<h3 class="content-header"><?php echo $header ?></h3>
|
658 |
+
<div class="content-box">
|
659 |
+
<table class="wp-list-table widefat fixed posts">
|
660 |
+
<thead></thead>
|
661 |
+
<tbody>
|
662 |
+
<tr class="metaseo-border-bottom">
|
663 |
+
<td colspan="1">ID</td>
|
664 |
+
<td colspan="3">Title</td>
|
665 |
+
<td colspan="4">Current Images</td>
|
666 |
+
<td colspan="2" class="metaseo-action">Action</td>
|
667 |
+
<td colspan="4">After Replacing</td>
|
668 |
+
</tr>
|
669 |
+
<?php $alternate ="";
|
670 |
+
foreach($posts as $post): ?>
|
671 |
+
<?php $alternate = 'alternate' == $alternate ? '' : 'alternate'; ?>
|
672 |
+
<tr class="<?php echo $alternate ?>">
|
673 |
+
<td colspan="1"><?php echo $post['ID'] ?></td>
|
674 |
+
<td colspan="3">
|
675 |
+
<p><?php echo $post['title'] ?></p>
|
676 |
+
</td>
|
677 |
+
<td colspan="4" style="overflow: hidden;">
|
678 |
+
<?php foreach($post['img_before_optm'] as $key => $src):?>
|
679 |
+
<div class="metaseo-img-wrapper">
|
680 |
+
<div class="metaseo-img">
|
681 |
+
<img width="<?php echo @$src['width'] ;?>" height="<?php #echo @$src['height'] ;?>" src="<?php echo $src['src'] ?>" />
|
682 |
+
<div class="img-choosen">
|
683 |
+
<input type="checkbox" checked="true" class="metaseo-checkin checkin-<?php echo $post['ID'] ?>" value="<?php echo $key ?>" id="checkin-<?php echo $post['ID'].'-'.$key ?>" onclick="uncheck(this)" />
|
684 |
+
</div>
|
685 |
+
<p class="metaseo-msg"></p>
|
686 |
+
</div>
|
687 |
+
<div class="dimension">
|
688 |
+
Orig. </br>
|
689 |
+
<span>Dimensions</span>: <?php echo $src['dimension'] ?></br>
|
690 |
+
<span>File size</span>: <?php echo $src['size'].' '.$src['sizes'] ?>
|
691 |
+
</div>
|
692 |
+
</div>
|
693 |
+
<?php endforeach ?>
|
694 |
+
</td>
|
695 |
+
<td colspan="2" class="metaseo-action">
|
696 |
+
<a href="javascript:void(0);" class="metaseo-optimize button" data-img-post-id="<?php echo $img_post_id ?>" data-post-id="<?php echo $post['ID'] ?>" onclick="optimize_imgs(this)"><?php echo __('Replace?', 'wp-meta-seo') ?></a>
|
697 |
+
<span class="optimizing spinner"></span>
|
698 |
+
</td>
|
699 |
+
<td colspan="4">
|
700 |
+
<?php foreach($post['img_after_optm'] as $src):?>
|
701 |
+
<div class="metaseo-img-wrapper">
|
702 |
+
<div class="metaseo-img">
|
703 |
+
<img src="<?php echo $src['src'] ?>" />
|
704 |
+
</div>
|
705 |
+
<div class="dimension">
|
706 |
+
OPT </br>
|
707 |
+
<span>Dimensions</span>: <?php echo $src['dimension'] ?></br>
|
708 |
+
<span>File size</span>: <?php echo $src['size'].' '.$src['sizes'] ?>
|
709 |
+
</div>
|
710 |
+
</div>
|
711 |
+
<?php endforeach ?>
|
712 |
+
</td>
|
713 |
+
</tr>
|
714 |
+
|
715 |
+
<?php endforeach ?>
|
716 |
+
<tr class="metaseo-border-top">
|
717 |
+
<td colspan="8"></td>
|
718 |
+
<td colspan="2">
|
719 |
+
<a href="javascript:void(0);" id="metaseo-replace-all" class="button button-primary" onclick="optimize_imgs_group(this)">
|
720 |
+
<?php echo __('Replace All') ?>
|
721 |
+
</a>
|
722 |
+
<span class="optimizing spinner"></span>
|
723 |
+
</td>
|
724 |
+
<td colspan="4"></td>
|
725 |
+
</tr>
|
726 |
+
</tbody>
|
727 |
+
<tfoot></tfoot>
|
728 |
+
</table>
|
729 |
+
</div>
|
730 |
+
<div style="padding:5px"></div>
|
731 |
+
<?php
|
732 |
+
}
|
733 |
+
|
734 |
+
public static function optimizeImages(){
|
735 |
+
if(!empty($_POST['post_id']) && !empty($_POST['img_post_id'])){
|
736 |
+
$post_id = intval($_POST['post_id']);
|
737 |
+
$img_post_id = intval($_POST['img_post_id']);
|
738 |
+
if(!empty($_POST['img_exclude'])){
|
739 |
+
$img_exclude = $_POST['img_exclude'];
|
740 |
+
}
|
741 |
+
else{
|
742 |
+
$img_exclude = array();
|
743 |
+
}
|
744 |
+
|
745 |
+
$ret = ImageHelper::_optimizeImages($post_id, $img_post_id, $img_exclude);
|
746 |
+
}
|
747 |
+
else{
|
748 |
+
$ret = array(
|
749 |
+
'success' => false,
|
750 |
+
'msg' => __('The post is not existed, please choose one another!', 'wp-meta-seo')
|
751 |
+
);
|
752 |
+
}
|
753 |
+
|
754 |
+
echo json_encode($ret);
|
755 |
+
wp_die();
|
756 |
+
|
757 |
+
}
|
758 |
+
|
759 |
+
public function process_action() {
|
760 |
+
global $wpdb;
|
761 |
+
$current_url = set_url_scheme('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
762 |
+
$redirect = false;
|
763 |
+
|
764 |
+
if (isset($_POST['search']) and $_POST['search'] === 'Search') {
|
765 |
+
$current_url = add_query_arg(array("search" => "Search", "txtkeyword" => urlencode(stripslashes($_POST["txtkeyword"])) ) , $current_url);
|
766 |
+
$redirect = true;
|
767 |
+
}
|
768 |
+
|
769 |
+
if (isset($_POST['filter_date_action']) and $_POST['filter_date_action'] === 'Filter') {
|
770 |
+
$current_url = add_query_arg(array("sldate" => $_POST["sldate"]), $current_url);
|
771 |
+
$redirect = true;
|
772 |
+
}
|
773 |
+
|
774 |
+
if(!empty($_POST['paged'])){
|
775 |
+
$current_url = add_query_arg(array( "paged" => intval($_POST['paged'])), $current_url);
|
776 |
+
$redirect = true;
|
777 |
+
}
|
778 |
+
|
779 |
+
if(!empty($_POST['metaseo_imgs_per_page'])){
|
780 |
+
$current_url = add_query_arg(array( "metaseo_imgs_per_page" => intval($_POST['metaseo_imgs_per_page'])), $current_url);
|
781 |
+
$redirect = true;
|
782 |
+
}
|
783 |
+
|
784 |
+
if($redirect === true){
|
785 |
+
wp_redirect($current_url);
|
786 |
+
ob_end_flush();
|
787 |
+
exit();
|
788 |
+
}
|
789 |
+
}
|
790 |
+
|
791 |
+
public static function load_posts_callback(){
|
792 |
+
global $wpdb;
|
793 |
+
$_POST = stripslashes_deep( $_POST );
|
794 |
+
$post_id = intval($_POST['post_id']);
|
795 |
+
$img = trim($_POST['img_name']);
|
796 |
+
$opt_key = strtolower(trim($_POST['opt_key']));
|
797 |
+
$btn = preg_replace('/[_]+/i', ' ',$opt_key);
|
798 |
+
$btn = ucwords($btn);
|
799 |
+
$gotIt = false;
|
800 |
+
|
801 |
+
if($post_id && !empty($img) && !empty($opt_key)){
|
802 |
+
$fn = "display_{$opt_key}_list";
|
803 |
+
if(method_exists('MetaSeo_Image_List_Table', $fn)){
|
804 |
+
//Get list of posts contain this image and its clones
|
805 |
+
$posts = ImageHelper::_get_post_list($post_id, $opt_key);
|
806 |
+
|
807 |
+
if(count($posts) > 0){
|
808 |
+
$img_counter = 0;
|
809 |
+
//Now the time to resize the images
|
810 |
+
if($opt_key === 'resize_image'){
|
811 |
+
$upload_dir = wp_upload_dir();
|
812 |
+
$metaseo_sizes_optional = get_post_meta($post_id, '_metaseo_sizes_optional', TRUE);
|
813 |
+
if(!is_array($metaseo_sizes_optional)){ $metaseo_sizes_optional = array(); }
|
814 |
+
$attachment_meta_data = wp_get_attachment_metadata($post_id);
|
815 |
+
|
816 |
+
foreach($posts as &$post){
|
817 |
+
foreach($post['img_after_optm'] as &$img){
|
818 |
+
$img_counter++;
|
819 |
+
$destination = $upload_dir['basedir'] . '/' . $img['path'];
|
820 |
+
if(@ImageHelper::IResize($img['src_origin'], $img['width'], $img['height'], $destination)){
|
821 |
+
|
822 |
+
$size = (filesize($destination)/1024);
|
823 |
+
if($size>1024){
|
824 |
+
$size=$size/1024;
|
825 |
+
$sizes = 'MB';
|
826 |
+
}else{
|
827 |
+
$sizes = 'KB';
|
828 |
+
}
|
829 |
+
$size=@round($size,1);
|
830 |
+
$img['size'] = $size;
|
831 |
+
$img['sizes'] = $sizes;
|
832 |
+
}
|
833 |
+
|
834 |
+
$kpart = ImageHelper::IGetPart($img['path']);
|
835 |
+
$key = preg_replace('/\-(\d+)x(\d+)$/i', '-metaseo${1}${2}', $kpart->name);
|
836 |
+
$key = strtolower($key);
|
837 |
+
$file = substr($img['path'], strrpos($img['path'], '/') + 1);
|
838 |
+
if(!in_array($key, array_keys($metaseo_sizes_optional))){
|
839 |
+
$metaseo_sizes_optional[$key] = array(
|
840 |
+
'url' => $img['src'],
|
841 |
+
'width' => $img['width'],
|
842 |
+
'height' => $img['height'],
|
843 |
+
'orientation' => 'landscape',
|
844 |
+
);
|
845 |
+
}
|
846 |
+
|
847 |
+
if(!isset($attachment_meta_data['sizes'][$key])){
|
848 |
+
$attachment_meta_data['sizes'][$key] = array(
|
849 |
+
'file' => $file,
|
850 |
+
'width' => $img['width'],
|
851 |
+
'height' => $img['height'],
|
852 |
+
'mime-type' => 'image/jpeg'
|
853 |
+
);
|
854 |
+
}
|
855 |
+
|
856 |
+
}
|
857 |
+
}
|
858 |
+
|
859 |
+
wp_update_attachment_metadata($post_id, $attachment_meta_data);
|
860 |
+
update_post_meta($post_id, '_metaseo_sizes_optional', $metaseo_sizes_optional);
|
861 |
+
}
|
862 |
+
elseif($opt_key === 'fix_metas'){
|
863 |
+
$toEdit = false;
|
864 |
+
$pIDs = array();
|
865 |
+
foreach($posts as $ID => &$post){
|
866 |
+
$img_counter += count($post['meta']);
|
867 |
+
foreach($post['meta'] as $order => $meta){
|
868 |
+
if($meta['type']['alt'] == '' || $meta['type']['title'] == ''){
|
869 |
+
$toEdit = true;
|
870 |
+
}
|
871 |
+
|
872 |
+
if($meta['type']['alt'] != '' && $meta['type']['title'] != ''){
|
873 |
+
$pIDs[$ID][] = $order;
|
874 |
+
}
|
875 |
+
}
|
876 |
+
}
|
877 |
+
|
878 |
+
if($toEdit === true){
|
879 |
+
foreach($pIDs as $ID => $orders){
|
880 |
+
foreach($orders as $order){
|
881 |
+
unset($posts[$ID]['meta'][$order]);
|
882 |
+
if($img_counter > 0){ $img_counter--; }
|
883 |
+
}
|
884 |
+
|
885 |
+
if(empty($posts[$ID]['meta'])){ unset($posts[$ID]); }
|
886 |
+
}
|
887 |
+
}
|
888 |
+
}
|
889 |
+
//-----------------------------
|
890 |
+
}
|
891 |
+
|
892 |
+
}
|
893 |
+
}
|
894 |
+
|
895 |
+
//This is a bit crazy but could give more exact information
|
896 |
+
|
897 |
+
if(count($posts) > 1){
|
898 |
+
$p = ' in ' . count($posts) . ' posts ';
|
899 |
+
}
|
900 |
+
else{
|
901 |
+
$p = '';
|
902 |
+
}
|
903 |
+
|
904 |
+
if(isset($img_counter) and $img_counter > 1){
|
905 |
+
$im = ' images ';
|
906 |
+
}
|
907 |
+
else{
|
908 |
+
if(!isset($img_counter)) { $img_counter = 0; }
|
909 |
+
$im = ' image ';
|
910 |
+
}
|
911 |
+
|
912 |
+
self::$fn($post_id, $posts, $img_counter, $p, $im);
|
913 |
+
wp_die();
|
914 |
+
|
915 |
+
}
|
916 |
+
|
917 |
+
public static function scan_posts_callback(){
|
918 |
+
$_POST = stripslashes_deep( $_POST );
|
919 |
+
$imgs = $_POST['imgs'];
|
920 |
+
if(!empty($imgs)){
|
921 |
+
if(!is_array($imgs)){
|
922 |
+
$ret['success'] = false;
|
923 |
+
$ret['msg'] = 'No images are available, please check again!';
|
924 |
+
return $ret;
|
925 |
+
}
|
926 |
+
|
927 |
+
$_imgs = array();
|
928 |
+
foreach($imgs as $key => $img){
|
929 |
+
if(empty($img['img_post_id']) || empty($img['name'])){
|
930 |
+
continue;
|
931 |
+
}
|
932 |
+
|
933 |
+
$_imgs[trim($img['name'])] = $img['img_post_id'];
|
934 |
+
}
|
935 |
+
unset($imgs);
|
936 |
+
|
937 |
+
if(!count($_imgs)){
|
938 |
+
$ret['success'] = false;
|
939 |
+
$ret['msg'] = 'No images are available, please check again!';
|
940 |
+
return $ret;
|
941 |
+
}
|
942 |
+
|
943 |
+
$msg = ImageHelper::IScanPosts($_imgs, true);
|
944 |
+
$ret['msg'] = $msg;
|
945 |
+
$ret['success'] = true;
|
946 |
+
}
|
947 |
+
|
948 |
+
else{
|
949 |
+
$ret['success'] = false;
|
950 |
+
$ret['msg'] = 'No images are available, please check again!';
|
951 |
+
}
|
952 |
+
|
953 |
+
echo json_encode($ret);
|
954 |
+
wp_die();
|
955 |
+
}
|
956 |
+
|
957 |
+
public static function updateMeta_callback(){
|
958 |
+
global $wpdb;
|
959 |
+
$response = new stdClass();
|
960 |
+
$response->updated = false;
|
961 |
+
|
962 |
+
if(!empty($_POST['addition']['meta_key'])){
|
963 |
+
self::updateImgMeta_call_back($_POST['addition']);
|
964 |
+
}
|
965 |
+
|
966 |
+
if(!empty($_POST['meta_type']) and $_POST['meta_type'] == 'change_image_name'){
|
967 |
+
self::updateImageName_callback($_POST);
|
968 |
+
}
|
969 |
+
|
970 |
+
if(!empty($_POST['meta_type']) && !empty($_POST['post_id'])){
|
971 |
+
$meta_type = strtolower(trim($_POST['meta_type']));
|
972 |
+
$post_id = intval($_POST['post_id']);
|
973 |
+
|
974 |
+
if(!isset($_POST['meta_value'])){
|
975 |
+
$meta_value = '';
|
976 |
+
}
|
977 |
+
else{
|
978 |
+
$meta_value = trim($_POST['meta_value']);
|
979 |
+
if(preg_match('/[<>\/\'\"]+/', $meta_value)){
|
980 |
+
$response->updated = false;
|
981 |
+
$response->message = 'Should not html tag or special char';
|
982 |
+
|
983 |
+
echo json_encode($response);
|
984 |
+
wp_die();
|
985 |
+
}
|
986 |
+
}
|
987 |
+
|
988 |
+
$label = str_replace('_', ' ', $meta_type);
|
989 |
+
$label = ucfirst($label);
|
990 |
+
|
991 |
+
$aliases = array('image_title' => 'post_title', 'image_caption' => 'post_excerpt', 'image_description' => 'post_content', 'alt_text' => '_wp_attachment_image_alt');
|
992 |
+
|
993 |
+
if($meta_type != 'alt_text'){
|
994 |
+
$data = array('ID' => $post_id, $aliases[$meta_type] => $meta_value);
|
995 |
+
|
996 |
+
if(wp_update_post($data)){
|
997 |
+
$response->updated = true;
|
998 |
+
$response->msg = __($label . ' was saved', 'wp-meta-seo');
|
999 |
+
}
|
1000 |
+
}
|
1001 |
+
else{
|
1002 |
+
update_post_meta($post_id, $aliases[$meta_type], $meta_value);
|
1003 |
+
$response->updated = true;
|
1004 |
+
$response->msg = __($label . ' was saved', 'wp-meta-seo');
|
1005 |
+
}
|
1006 |
+
|
1007 |
+
}
|
1008 |
+
else{
|
1009 |
+
$response->msg = __('There is a problem when update image meta!', 'wp-meta-seo');
|
1010 |
+
}
|
1011 |
+
|
1012 |
+
echo json_encode($response);
|
1013 |
+
wp_die();
|
1014 |
+
}
|
1015 |
+
|
1016 |
+
public static function updateImageName_callback() {
|
1017 |
+
|
1018 |
+
global $wpdb;
|
1019 |
+
$postID = (int) $_POST['post_id'];
|
1020 |
+
$name = trim($_POST['meta_value']);
|
1021 |
+
$iname = preg_replace('/(\s{1,})/', '-', $name);
|
1022 |
+
$img_meta = get_post_meta($postID, '_wp_attachment_metadata', TRUE);
|
1023 |
+
$linkold = $img_meta['file'];
|
1024 |
+
$response = new stdClass();
|
1025 |
+
$response->updated = FALSE;
|
1026 |
+
$response->msg = __('There is a problem when update image name', 'wp-meta-seo');
|
1027 |
+
|
1028 |
+
$upload_dirs = wp_upload_dir();
|
1029 |
+
$upload_dir = $upload_dirs['basedir'];
|
1030 |
+
$oldpart = ImageHelper::IGetPart($linkold);
|
1031 |
+
$old_name = $oldpart->name;
|
1032 |
+
|
1033 |
+
if ($name !== "") {
|
1034 |
+
if (file_exists($upload_dir . "/" . $linkold)) {
|
1035 |
+
$newFileName = $oldpart->base_path . $iname . $oldpart->ext;
|
1036 |
+
#if ((!file_exists($upload_dir . "/" . $newFileName)) && $check==0) {
|
1037 |
+
if(!file_exists($upload_dir . "/" . $newFileName)){
|
1038 |
+
if (rename($upload_dir . "/" . $linkold, $upload_dir . "/" . $newFileName)) {
|
1039 |
+
$post_title = get_the_title($postID);
|
1040 |
+
$data_post = array('ID' => $postID, 'post_name' => $name);
|
1041 |
+
//if (wp_update_post($data_post)) {
|
1042 |
+
$where = array('ID' => $postID);
|
1043 |
+
$guid = $upload_dirs['baseurl'] . "/" . $newFileName;
|
1044 |
+
if(!$post_title){
|
1045 |
+
$id = $wpdb->update($wpdb->posts, array('guid' => $guid, 'post_title' => $name, 'post_name' => strtolower($iname)), $where);
|
1046 |
+
}
|
1047 |
+
else{
|
1048 |
+
$id = $wpdb->update($wpdb->posts, array('guid' => $guid), $where);
|
1049 |
+
}
|
1050 |
+
|
1051 |
+
if($id){
|
1052 |
+
$attached_metadata = get_post_meta($postID, "_wp_attachment_metadata", true);
|
1053 |
+
$attached_metadata["file"] = $newFileName;
|
1054 |
+
|
1055 |
+
$images_to_rename = array($oldpart->name . $oldpart->ext => $iname . $oldpart->ext);
|
1056 |
+
$old_path = $upload_dir . "/" . $linkold;
|
1057 |
+
|
1058 |
+
foreach($attached_metadata['sizes'] as &$clone){
|
1059 |
+
$clone_file_new = ImageHelper::IReplace($iname, $clone['file']);
|
1060 |
+
$clone_path = $upload_dir . '/' . $oldpart->base_path . $clone['file'];
|
1061 |
+
$clone_path_new = $upload_dir . '/' . $oldpart->base_path . $clone_file_new;
|
1062 |
+
|
1063 |
+
if( @rename($clone_path, $clone_path_new) ){
|
1064 |
+
$images_to_rename[$clone['file']] = $clone_file_new;
|
1065 |
+
$clone['file'] = $clone_file_new;
|
1066 |
+
}
|
1067 |
+
|
1068 |
+
}
|
1069 |
+
|
1070 |
+
/** Update source of this image or its clones in post contains them **/
|
1071 |
+
$query = "SELECT `ID`,`post_title`,`post_content`,`post_type`,`post_date`
|
1072 |
+
FROM $wpdb->posts
|
1073 |
+
WHERE (`post_type` = 'post' or `post_type` = 'page')
|
1074 |
+
AND `post_content` <> ''
|
1075 |
+
AND `post_content` LIKE '%<img%>%'
|
1076 |
+
ORDER BY ID";
|
1077 |
+
|
1078 |
+
$posts = $wpdb->get_results($query);
|
1079 |
+
$imgs = array($old_name . $oldpart->ext => $postID);
|
1080 |
+
$posts_contain_img = array();
|
1081 |
+
foreach($posts as $post){
|
1082 |
+
$ifound = ImageHelper::IScan($imgs, $post->post_content);
|
1083 |
+
if(count($ifound) > 0){
|
1084 |
+
$posts_contain_img[] = $post->ID;
|
1085 |
+
}
|
1086 |
+
}
|
1087 |
+
|
1088 |
+
foreach($posts_contain_img as $id){
|
1089 |
+
if($post = get_post($id)){
|
1090 |
+
foreach($images_to_rename as $src_before => $src_after){
|
1091 |
+
$src_before = '/' . $src_before;
|
1092 |
+
$src_after = '/' . $src_after;
|
1093 |
+
$post->post_content = str_replace($src_before, $src_after, $post->post_content);
|
1094 |
+
}
|
1095 |
+
|
1096 |
+
wp_update_post(array(
|
1097 |
+
'ID' => $post->ID,
|
1098 |
+
'post_content' => $post->post_content)
|
1099 |
+
);
|
1100 |
+
unset($post, $posts_contain_img);
|
1101 |
+
//---------------------------------
|
1102 |
+
|
1103 |
+
}
|
1104 |
+
}
|
1105 |
+
/*****************************************************/
|
1106 |
+
/** Update Image registered to Attachment sizes on Add media page**/
|
1107 |
+
$sizeOptional = get_post_meta($postID, '_metaseo_sizes_optional', true);
|
1108 |
+
$newOptional = array();
|
1109 |
+
if(!empty($sizeOptional) && is_array($sizeOptional)){
|
1110 |
+
foreach($sizeOptional as $key => $detail){
|
1111 |
+
$pattern = '/^'.strtolower($old_name).'(-metaseo\d+)$/';
|
1112 |
+
$key = preg_replace($pattern, strtolower($iname).'${1}', $key);
|
1113 |
+
$detail['url'] = ImageHelper::IReplace($iname, $detail['url']);
|
1114 |
+
$newOptional[$key] = $detail;
|
1115 |
+
}
|
1116 |
+
|
1117 |
+
update_post_meta($postID, '_metaseo_sizes_optional', $newOptional);
|
1118 |
+
unset($sizeOptional, $newOptional);
|
1119 |
+
}
|
1120 |
+
/****************************************************/
|
1121 |
+
//Need to update optimization info of this image
|
1122 |
+
ImageHelper::IScanPosts(array($iname.$oldpart->ext => $postID),true);
|
1123 |
+
|
1124 |
+
update_post_meta($postID, '_wp_attached_file', $newFileName);
|
1125 |
+
update_post_meta($postID, '_wp_attachment_metadata', $attached_metadata);
|
1126 |
+
|
1127 |
+
$response->updated = true;
|
1128 |
+
$response->msg = __('Image name was changed', 'wp-meta-seo');
|
1129 |
+
} else {
|
1130 |
+
$response->iname = $old_name;
|
1131 |
+
$response->msg = __('There is a problem when update image name', 'wp-meta-seo');
|
1132 |
+
}
|
1133 |
+
}
|
1134 |
+
} else {
|
1135 |
+
|
1136 |
+
$response->msg = __('Name is existing', 'wp-meta-seo');
|
1137 |
+
$response->iname = $old_name;
|
1138 |
+
}
|
1139 |
+
} else {
|
1140 |
+
$response->iname = $old_name;
|
1141 |
+
$response->msg = __('File is not existed', 'wp-meta-seo');
|
1142 |
+
}
|
1143 |
+
} else {
|
1144 |
+
$response->iname = $old_name;
|
1145 |
+
$response->msg = __('Should not be empty', 'wp-meta-seo');
|
1146 |
+
}
|
1147 |
+
echo json_encode($response);
|
1148 |
+
wp_die();
|
1149 |
+
}
|
1150 |
+
|
1151 |
+
public static function updateImgMeta_call_back($_post){
|
1152 |
+
global $wpdb;
|
1153 |
+
$response = new stdClass();
|
1154 |
+
$response->updated = false;
|
1155 |
+
foreach($_post as $k => $v){
|
1156 |
+
if(!$v && !in_array($k, array('meta_value', 'meta_order'))){
|
1157 |
+
$response->msg = __('There is a problem when update image meta!', 'wp-meta-seo') ;
|
1158 |
+
|
1159 |
+
echo json_encode($response);
|
1160 |
+
wp_die();
|
1161 |
+
}
|
1162 |
+
}
|
1163 |
+
|
1164 |
+
$meta_key = strtolower(trim($_post['meta_key']));
|
1165 |
+
$meta_type = strtolower(trim($_post['meta_type']));
|
1166 |
+
$meta_value = htmlspecialchars(trim($_post['meta_value']));
|
1167 |
+
$meta_order = intval($_post['meta_order']);
|
1168 |
+
$img_post_id = intval($_post['img_post_id']);
|
1169 |
+
$post_id = intval($_post['post_id']);
|
1170 |
+
|
1171 |
+
$meta = get_post_meta($img_post_id, $meta_key, true);
|
1172 |
+
//Update new value for meta info of this image in wp_postmeta
|
1173 |
+
$meta[$post_id]['meta'][$meta_order]['type'][$meta_type] = metaseo_utf8($meta_value);
|
1174 |
+
update_post_meta($img_post_id, $meta_key, $meta);
|
1175 |
+
$meta = get_post_meta($img_post_id, $meta_key, true);
|
1176 |
+
|
1177 |
+
//Then we must update this meta info in the appropriate post content
|
1178 |
+
if(!$post = get_post($post_id))
|
1179 |
+
{
|
1180 |
+
$response->msg = __('The post has been deleted before, please check again!', 'wp-meta-seo');
|
1181 |
+
}
|
1182 |
+
else{
|
1183 |
+
if($post->post_content !== ''){
|
1184 |
+
//Split content part that do not contain img tag
|
1185 |
+
$post_content_split = preg_split('/<img [^<>]+ \/>/i', $post->post_content);
|
1186 |
+
//Get all img tag from the content
|
1187 |
+
preg_match_all('/<img [^<>]+ \/>/i', $post->post_content, $matches);
|
1188 |
+
$img_tags = $matches[0];
|
1189 |
+
|
1190 |
+
if(isset($img_tags[$meta_order])){
|
1191 |
+
//&& strpos($img_tags[$meta_order], $img_src)){
|
1192 |
+
$pattern = '/'. $meta_type .'\s*?\=?\"[^\"]*\"/i';
|
1193 |
+
$replacement = $meta_type .'="'. $meta_value .'"';
|
1194 |
+
if(!preg_match($pattern, $img_tags[$meta_order], $match)){
|
1195 |
+
$pattern = '/\/>/i';
|
1196 |
+
$replacement = $meta_type .'="'. $meta_value .'" />';
|
1197 |
+
}
|
1198 |
+
|
1199 |
+
$img_tags[$meta_order] = preg_replace($pattern, $replacement, $img_tags[$meta_order]);
|
1200 |
+
|
1201 |
+
$post_content = '';
|
1202 |
+
foreach($post_content_split as $key => $split){
|
1203 |
+
if(isset($img_tags[$key])){
|
1204 |
+
$img_tag = $img_tags[$key];
|
1205 |
+
}
|
1206 |
+
else{
|
1207 |
+
$img_tag = '';
|
1208 |
+
}
|
1209 |
+
|
1210 |
+
$post_content .= $split . $img_tag;
|
1211 |
+
}
|
1212 |
+
|
1213 |
+
//Update content of this post.
|
1214 |
+
if(!wp_update_post(array('ID' => $post->ID, 'post_content' => $post_content))){
|
1215 |
+
$response->msg = __('The post haven\'t been updated, please check again!', 'wp-meta-seo');
|
1216 |
+
}
|
1217 |
+
else{
|
1218 |
+
$response->updated = true;
|
1219 |
+
$response->msg = __(ucfirst($meta_type) . ' was saved','wp-meta-seo') ;
|
1220 |
+
}
|
1221 |
+
}
|
1222 |
+
else{
|
1223 |
+
$response->msg = __('This image has been removed from the post, please check again!', 'wp-meta-seo');
|
1224 |
+
}
|
1225 |
+
}
|
1226 |
+
else{
|
1227 |
+
$response->msg = __('Content of the post is empty, please check again', 'wp-meta-seo!');
|
1228 |
+
}
|
1229 |
+
}
|
1230 |
+
|
1231 |
+
|
1232 |
+
echo json_encode($response);
|
1233 |
+
wp_die();
|
1234 |
+
}
|
1235 |
+
|
1236 |
+
}
|
inc/class.wp-metaseo.php
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
//Main plugin functions here
|
3 |
+
class WpMetaSeo {
|
4 |
+
|
5 |
+
private static $initiated = false;
|
6 |
+
|
7 |
+
public function __construct(){
|
8 |
+
add_action('admin_notices', array($this, 'plugin_activation_notices'));
|
9 |
+
}
|
10 |
+
|
11 |
+
public static function init() {
|
12 |
+
ob_start();
|
13 |
+
if ( ! self::$initiated ) {
|
14 |
+
self::init_hooks();
|
15 |
+
}
|
16 |
+
}
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Initializes WordPress hooks
|
20 |
+
*/
|
21 |
+
private static function init_hooks() {
|
22 |
+
self::$initiated = true;
|
23 |
+
}
|
24 |
+
|
25 |
+
public static function new_title($title){
|
26 |
+
global $wp_query;
|
27 |
+
return $title;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
|
32 |
+
* @static
|
33 |
+
*/
|
34 |
+
public static function plugin_activation() {
|
35 |
+
if (version_compare($GLOBALS['wp_version'], WPMETASEO_MINIMUM_WP_VERSION, '<')) {
|
36 |
+
deactivate_plugins(basename(__FILE__));
|
37 |
+
wp_die('<p>The <strong>WP Meta SEO</strong> plugin requires WordPress ' . WPMETASEO_MINIMUM_WP_VERSION . ' or higher.</p>', 'Plugin Activation Error', array('response' => 200, 'back_link' => TRUE));
|
38 |
+
}
|
39 |
+
|
40 |
+
//Set two param as flags that determine whether show import meta data from other SEO plugin button or not to 0
|
41 |
+
update_option('_aio_import_notice_flag', 0);
|
42 |
+
update_option('_yoast_import_notice_flag', 0);
|
43 |
+
update_option('plugin_to_sync_with', 0);
|
44 |
+
|
45 |
+
self::install_db();
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Removes all connection options
|
50 |
+
* @static
|
51 |
+
*/
|
52 |
+
public static function plugin_deactivation() {
|
53 |
+
//tidy up
|
54 |
+
}
|
55 |
+
|
56 |
+
|
57 |
+
public static function install_db(){
|
58 |
+
global $wpdb;
|
59 |
+
$table_name = $wpdb->prefix . 'metaseo_images';
|
60 |
+
|
61 |
+
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
|
62 |
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
63 |
+
`post_id` int(11) NOT NULL,
|
64 |
+
`posts_optimized_id` text COLLATE utf8_unicode_ci NOT NULL,
|
65 |
+
`posts_need_to_optimize_id` varchar(1000) COLLATE utf8_unicode_ci NOT NULL,
|
66 |
+
`posts_prepare_to_optimize` text COLLATE utf8_unicode_ci NOT NULL,
|
67 |
+
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
68 |
+
`alt_text` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
69 |
+
`legend` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
70 |
+
`description` varchar(500) COLLATE utf8_unicode_ci NOT NULL,
|
71 |
+
`link` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
|
72 |
+
PRIMARY KEY (`id`)
|
73 |
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;";
|
74 |
+
|
75 |
+
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
76 |
+
|
77 |
+
dbDelta( $sql );
|
78 |
+
}
|
79 |
+
}
|
inc/pages/content-meta.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Meta tags content
|
4 |
+
*/
|
5 |
+
|
6 |
+
if (!class_exists('MetaSeo_Content_List_Table')) {
|
7 |
+
require_once( WPMETASEO_PLUGIN_DIR . '/inc/class.metaseo-content-list-table.php' );
|
8 |
+
}
|
9 |
+
|
10 |
+
$metaseo_list_table = new MetaSeo_Content_List_Table();
|
11 |
+
$metaseo_list_table->process_action();
|
12 |
+
$metaseo_list_table->prepare_items();
|
13 |
+
|
14 |
+
if (!empty($_REQUEST['_wp_http_referer'])) {
|
15 |
+
wp_redirect(remove_query_arg(array('_wp_http_referer', '_wpnonce'), stripslashes($_SERVER['REQUEST_URI'])));
|
16 |
+
exit;
|
17 |
+
}
|
18 |
+
|
19 |
+
?>
|
20 |
+
|
21 |
+
<div class="wrap seo_extended_table_page">
|
22 |
+
<div id="icon-edit-pages" class="icon32 icon32-posts-page"></div>
|
23 |
+
|
24 |
+
<?php echo '<h2>' . __('Content Meta', 'wp-meta-seo') . '</h2>'; ?>
|
25 |
+
|
26 |
+
<form id="wp-seo-meta-form" action="" method="post">
|
27 |
+
<?php $metaseo_list_table->display(); ?>
|
28 |
+
</form>
|
29 |
+
|
30 |
+
</div>
|
31 |
+
<script type="text/javascript">
|
32 |
+
jQuery(document).ready(function($){
|
33 |
+
$('.metaseo-metatitle').each(function() {
|
34 |
+
metaseo_updateTitle(this.id, false, false);
|
35 |
+
});
|
36 |
+
$('.metaseo-metadesc').each(function() {
|
37 |
+
metaseo_updateDesc(this.id,false);
|
38 |
+
});
|
39 |
+
|
40 |
+
$('.metaseo-metatitle').bind('input propertychange', function() {
|
41 |
+
metaseo_updateTitle(this.id, true);
|
42 |
+
});
|
43 |
+
|
44 |
+
$('.metaseo-metadesc').bind('input propertychange', function() {
|
45 |
+
metaseo_updateDesc(this.id, true);
|
46 |
+
});
|
47 |
+
|
48 |
+
$('.metaseo-metadesc, .metaseo-metatitle').bind('input propertychange', function(){
|
49 |
+
var idNumber = this.id.substr(this.id.lastIndexOf('-')+1);
|
50 |
+
if(this.id == 'metaseo-metatitle-'+idNumber){
|
51 |
+
if(!$(this).val()){
|
52 |
+
var post_title = $('#post-title-'+idNumber).text();
|
53 |
+
$('#snippet_title'+idNumber).text(post_title);
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
});
|
58 |
+
});
|
59 |
+
</script>
|
inc/pages/dashboard.php
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if (!class_exists('MetaSeo_Dashboard')) {
|
3 |
+
require_once( WPMETASEO_PLUGIN_DIR . '/inc/class.metaseo-dashboard.php' );
|
4 |
+
}
|
5 |
+
|
6 |
+
$site_name = preg_replace('/(^(http|https):\/\/[w]*\.*)/', '', get_site_url());
|
7 |
+
//$site_name = 'testdev-united.com';
|
8 |
+
$url = 'http://www.alexa.com/siteinfo/' . $site_name;
|
9 |
+
$dashboard = new MetaSeo_Dashboard();
|
10 |
+
|
11 |
+
?>
|
12 |
+
<h1><?php echo __('DASH BOARD', 'wp-meta-seo') ?></h1>
|
13 |
+
<div class="dashboard">
|
14 |
+
<div class="left">
|
15 |
+
<div class="dashboard-left" id='dashboard-left'>
|
16 |
+
<header>
|
17 |
+
<p>
|
18 |
+
<div class="title-seo"><?php echo __('Image Meta :', 'wp-meta-seo'); ?> <span id="imgs_metas_statis"></span></div>
|
19 |
+
<div class="noload" id="imgs_metas_statis_noload">
|
20 |
+
<span class="loadtext" id="imgs_metas_statis_value"></span>
|
21 |
+
<div class="load" id="imgs_metas_statis_load">
|
22 |
+
</div>
|
23 |
+
</div>
|
24 |
+
</p>
|
25 |
+
<p>
|
26 |
+
<div class="title-seo"><?php echo __('Content Meta :', 'wp-meta-seo'); ?> <span id="metacontent"></span></div>
|
27 |
+
<div class="noload" id="imgs_metas_statis_noload">
|
28 |
+
<span class="loadtext" id="metacontent_value"></span>
|
29 |
+
<div class="load" id="metacontent_load">
|
30 |
+
</div>
|
31 |
+
</div>
|
32 |
+
</p>
|
33 |
+
<p>
|
34 |
+
<div class="title-seo"><?php echo __('Image optimization :', 'wp-meta-seo'); ?> <span id="imgs_statis"></span></div>
|
35 |
+
<div class="noload" id="imgs_metas_statis_noload">
|
36 |
+
<span class="loadtext" id="imgs_statis_value"></span>
|
37 |
+
<div class="load" id="imgs_statis_load">
|
38 |
+
</div>
|
39 |
+
</div>
|
40 |
+
</p>
|
41 |
+
</header>
|
42 |
+
<div id="canvas-holder">
|
43 |
+
<div id="chart-container">
|
44 |
+
<h2><?php echo __('Total', 'wp-meta-seo'); ?></h2>
|
45 |
+
<div style="width: 300px; margin: 0px auto; position: relative;">
|
46 |
+
<div id="avera">
|
47 |
+
</div>
|
48 |
+
<canvas id="chart-area" width="500" height="500"/>
|
49 |
+
</div>
|
50 |
+
</div>
|
51 |
+
</div>
|
52 |
+
</div>
|
53 |
+
</div>
|
54 |
+
|
55 |
+
<div class="right">
|
56 |
+
<div class="dashboard-right">
|
57 |
+
<div id="alexa-ranking">
|
58 |
+
<?php $dashboard->displayRank($url) ?>
|
59 |
+
</div>
|
60 |
+
|
61 |
+
<div style="clear:left"></div>
|
62 |
+
<div id="wpmetaseo-update-version">
|
63 |
+
<h4><?php echo __('Latest WP Meta SEO News', 'wp-meta-seo') ?></h4>
|
64 |
+
<ul>
|
65 |
+
<li>– <?php echo __('Version 1.0') ?></li>
|
66 |
+
<li>– <?php echo __('Version Beta') ?></li>
|
67 |
+
</ul>
|
68 |
+
</div>
|
69 |
+
</div>
|
70 |
+
</div>
|
71 |
+
|
72 |
+
<script type="text/javascript">
|
73 |
+
jQuery(document).ready(function() {
|
74 |
+
optimizationChecking(updateChart);
|
75 |
+
var height_left = jQuery("#dashboard-left").height();
|
76 |
+
jQuery(".dashboard-right").css('height',height_left);
|
77 |
+
});
|
78 |
+
|
79 |
+
|
80 |
+
function updateChart(response) {
|
81 |
+
// console.log(response);
|
82 |
+
|
83 |
+
var columns = {
|
84 |
+
meta_desc_statis: 0,
|
85 |
+
meta_title_statis: 0,
|
86 |
+
imgs_metas_statis: 0,
|
87 |
+
imgs_statis: 0,
|
88 |
+
metacontent: 0
|
89 |
+
};
|
90 |
+
|
91 |
+
var background_color = {
|
92 |
+
imgs_metas_statis: "#FFC870",
|
93 |
+
imgs_statis: "#5AD3D1",
|
94 |
+
metacontent: "#7eb5e8"
|
95 |
+
};
|
96 |
+
for (key in response) {
|
97 |
+
// console.log(response[key]);
|
98 |
+
if(response[key][0]>0 && response[key][1]>0){
|
99 |
+
columns[key] = (response[key][0] / response[key][1]) * 100;
|
100 |
+
}else{
|
101 |
+
columns[key] = 0;
|
102 |
+
}
|
103 |
+
if(columns[key]>5){
|
104 |
+
if(columns[key] %1 !=0){
|
105 |
+
jQuery('#' + key + '_value').text(columns[key].toFixed(2) + '%').css("left", (columns[key] - 5) / 2 + "%");
|
106 |
+
}else{
|
107 |
+
jQuery('#' + key + '_value').text(columns[key] + '%').css("left", (columns[key] - 5) / 2 + "%");
|
108 |
+
}
|
109 |
+
}else{
|
110 |
+
jQuery('#' + key + '_value').text(columns[key] + '%').css("left", 0 + "%");
|
111 |
+
}
|
112 |
+
jQuery('#' + key + '_load').css({"width":columns[key] + '%','background':background_color[key]});
|
113 |
+
|
114 |
+
|
115 |
+
}
|
116 |
+
var count = columns.metacontent + columns.imgs_metas_statis + columns.imgs_statis;
|
117 |
+
var metacontent= (columns.metacontent/3);
|
118 |
+
var imgs_metas_statis= (columns.imgs_metas_statis/3);
|
119 |
+
var imgs_statis= (columns.imgs_statis/3);
|
120 |
+
|
121 |
+
var notmetacontent= 100/3 - metacontent;
|
122 |
+
var notimgs_metas_statis= 100/3 - imgs_metas_statis;
|
123 |
+
var notimgs_statis= 100/3 - imgs_statis;
|
124 |
+
|
125 |
+
var avera = ((count)/3);
|
126 |
+
jQuery('#avera').text(avera.toFixed(2) + '%').addClass('avera');
|
127 |
+
|
128 |
+
drawChart(metacontent,notmetacontent, imgs_metas_statis,notimgs_metas_statis, imgs_statis,notimgs_statis);
|
129 |
+
|
130 |
+
}
|
131 |
+
</script>
|
inc/pages/image-meta.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Meta image
|
4 |
+
*/
|
5 |
+
|
6 |
+
if (!class_exists('MetaSeo_Image_List_Table')) {
|
7 |
+
require_once( WPMETASEO_PLUGIN_DIR . '/inc/class.metaseo-image-list-table.php' );
|
8 |
+
}
|
9 |
+
|
10 |
+
$metaseo_list_table = new MetaSeo_Image_List_Table();
|
11 |
+
$metaseo_list_table->process_action();
|
12 |
+
$metaseo_list_table->prepare_items();
|
13 |
+
|
14 |
+
if (!empty($_REQUEST['_wp_http_referer'])) {
|
15 |
+
wp_redirect(remove_query_arg(array('_wp_http_referer', '_wpnonce'), stripslashes($_SERVER['REQUEST_URI'])));
|
16 |
+
exit;
|
17 |
+
}
|
18 |
+
?>
|
19 |
+
|
20 |
+
<div class="wrap seo_extended_table_page">
|
21 |
+
<div id="icon-edit-pages" class="icon32 icon32-posts-page"></div>
|
22 |
+
|
23 |
+
<?php echo '<h2>' . __('Image Meta', 'wp-meta-seo') . '</h2>'; ?>
|
24 |
+
|
25 |
+
<form id="wp-seo-meta-form" action="" method="post">
|
26 |
+
|
27 |
+
<?php $metaseo_list_table->search_box1(); ?>
|
28 |
+
|
29 |
+
<?php $metaseo_list_table->display(); ?>
|
30 |
+
</form>
|
31 |
+
|
32 |
+
</div>
|
33 |
+
<script type="text/javascript">
|
34 |
+
jQuery(document).ready(function(){
|
35 |
+
//Scan all posts to find a group of images in their content
|
36 |
+
metaSeoScanImages();
|
37 |
+
});
|
38 |
+
</script>
|
js/Chart.js
ADDED
@@ -0,0 +1,3379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*!
|
2 |
+
* Chart.js
|
3 |
+
* http://chartjs.org/
|
4 |
+
* Version: 1.0.1-beta.4
|
5 |
+
*
|
6 |
+
* Copyright 2014 Nick Downie
|
7 |
+
* Released under the MIT license
|
8 |
+
* https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
|
9 |
+
*/
|
10 |
+
|
11 |
+
|
12 |
+
(function(){
|
13 |
+
|
14 |
+
"use strict";
|
15 |
+
|
16 |
+
//Declare root variable - window in the browser, global on the server
|
17 |
+
var root = this,
|
18 |
+
previous = root.Chart;
|
19 |
+
|
20 |
+
//Occupy the global variable of Chart, and create a simple base class
|
21 |
+
var Chart = function(context){
|
22 |
+
var chart = this;
|
23 |
+
this.canvas = context.canvas;
|
24 |
+
|
25 |
+
this.ctx = context;
|
26 |
+
|
27 |
+
//Variables global to the chart
|
28 |
+
var width = this.width = context.canvas.width;
|
29 |
+
var height = this.height = context.canvas.height;
|
30 |
+
this.aspectRatio = this.width / this.height;
|
31 |
+
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
|
32 |
+
helpers.retinaScale(this);
|
33 |
+
|
34 |
+
return this;
|
35 |
+
};
|
36 |
+
//Globally expose the defaults to allow for user updating/changing
|
37 |
+
Chart.defaults = {
|
38 |
+
global: {
|
39 |
+
// Boolean - Whether to animate the chart
|
40 |
+
animation: true,
|
41 |
+
|
42 |
+
// Number - Number of animation steps
|
43 |
+
animationSteps: 60,
|
44 |
+
|
45 |
+
// String - Animation easing effect
|
46 |
+
animationEasing: "easeOutQuart",
|
47 |
+
|
48 |
+
// Boolean - If we should show the scale at all
|
49 |
+
showScale: true,
|
50 |
+
|
51 |
+
// Boolean - If we want to override with a hard coded scale
|
52 |
+
scaleOverride: false,
|
53 |
+
|
54 |
+
// ** Required if scaleOverride is true **
|
55 |
+
// Number - The number of steps in a hard coded scale
|
56 |
+
scaleSteps: null,
|
57 |
+
// Number - The value jump in the hard coded scale
|
58 |
+
scaleStepWidth: null,
|
59 |
+
// Number - The scale starting value
|
60 |
+
scaleStartValue: null,
|
61 |
+
|
62 |
+
// String - Colour of the scale line
|
63 |
+
scaleLineColor: "rgba(0,0,0,.1)",
|
64 |
+
|
65 |
+
// Number - Pixel width of the scale line
|
66 |
+
scaleLineWidth: 1,
|
67 |
+
|
68 |
+
// Boolean - Whether to show labels on the scale
|
69 |
+
scaleShowLabels: true,
|
70 |
+
|
71 |
+
// Interpolated JS string - can access value
|
72 |
+
scaleLabel: "<%=value%>",
|
73 |
+
|
74 |
+
// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
|
75 |
+
scaleIntegersOnly: true,
|
76 |
+
|
77 |
+
// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
78 |
+
scaleBeginAtZero: false,
|
79 |
+
|
80 |
+
// String - Scale label font declaration for the scale label
|
81 |
+
scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
82 |
+
|
83 |
+
// Number - Scale label font size in pixels
|
84 |
+
scaleFontSize: 12,
|
85 |
+
|
86 |
+
// String - Scale label font weight style
|
87 |
+
scaleFontStyle: "normal",
|
88 |
+
|
89 |
+
// String - Scale label font colour
|
90 |
+
scaleFontColor: "#666",
|
91 |
+
|
92 |
+
// Boolean - whether or not the chart should be responsive and resize when the browser does.
|
93 |
+
responsive: false,
|
94 |
+
|
95 |
+
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
|
96 |
+
maintainAspectRatio: true,
|
97 |
+
|
98 |
+
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
|
99 |
+
showTooltips: true,
|
100 |
+
|
101 |
+
// Array - Array of string names to attach tooltip events
|
102 |
+
tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
|
103 |
+
|
104 |
+
// String - Tooltip background colour
|
105 |
+
tooltipFillColor: "rgba(0,0,0,0.8)",
|
106 |
+
|
107 |
+
// String - Tooltip label font declaration for the scale label
|
108 |
+
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
109 |
+
|
110 |
+
// Number - Tooltip label font size in pixels
|
111 |
+
tooltipFontSize: 14,
|
112 |
+
|
113 |
+
// String - Tooltip font weight style
|
114 |
+
tooltipFontStyle: "normal",
|
115 |
+
|
116 |
+
// String - Tooltip label font colour
|
117 |
+
tooltipFontColor: "#fff",
|
118 |
+
|
119 |
+
// String - Tooltip title font declaration for the scale label
|
120 |
+
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
121 |
+
|
122 |
+
// Number - Tooltip title font size in pixels
|
123 |
+
tooltipTitleFontSize: 14,
|
124 |
+
|
125 |
+
// String - Tooltip title font weight style
|
126 |
+
tooltipTitleFontStyle: "bold",
|
127 |
+
|
128 |
+
// String - Tooltip title font colour
|
129 |
+
tooltipTitleFontColor: "#fff",
|
130 |
+
|
131 |
+
// Number - pixel width of padding around tooltip text
|
132 |
+
tooltipYPadding: 6,
|
133 |
+
|
134 |
+
// Number - pixel width of padding around tooltip text
|
135 |
+
tooltipXPadding: 6,
|
136 |
+
|
137 |
+
// Number - Size of the caret on the tooltip
|
138 |
+
tooltipCaretSize: 8,
|
139 |
+
|
140 |
+
// Number - Pixel radius of the tooltip border
|
141 |
+
tooltipCornerRadius: 6,
|
142 |
+
|
143 |
+
// Number - Pixel offset from point x to tooltip edge
|
144 |
+
tooltipXOffset: 10,
|
145 |
+
|
146 |
+
// String - Template string for single tooltips
|
147 |
+
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
|
148 |
+
|
149 |
+
// String - Template string for single tooltips
|
150 |
+
multiTooltipTemplate: "<%= value %>",
|
151 |
+
|
152 |
+
// String - Colour behind the legend colour block
|
153 |
+
multiTooltipKeyBackground: '#fff',
|
154 |
+
|
155 |
+
// Function - Will fire on animation progression.
|
156 |
+
onAnimationProgress: function(){},
|
157 |
+
|
158 |
+
// Function - Will fire on animation completion.
|
159 |
+
onAnimationComplete: function(){}
|
160 |
+
|
161 |
+
}
|
162 |
+
};
|
163 |
+
|
164 |
+
//Create a dictionary of chart types, to allow for extension of existing types
|
165 |
+
Chart.types = {};
|
166 |
+
|
167 |
+
//Global Chart helpers object for utility methods and classes
|
168 |
+
var helpers = Chart.helpers = {};
|
169 |
+
|
170 |
+
//-- Basic js utility methods
|
171 |
+
var each = helpers.each = function(loopable,callback,self){
|
172 |
+
var additionalArgs = Array.prototype.slice.call(arguments, 3);
|
173 |
+
// Check to see if null or undefined firstly.
|
174 |
+
if (loopable){
|
175 |
+
if (loopable.length === +loopable.length){
|
176 |
+
var i;
|
177 |
+
for (i=0; i<loopable.length; i++){
|
178 |
+
callback.apply(self,[loopable[i], i].concat(additionalArgs));
|
179 |
+
}
|
180 |
+
}
|
181 |
+
else{
|
182 |
+
for (var item in loopable){
|
183 |
+
callback.apply(self,[loopable[item],item].concat(additionalArgs));
|
184 |
+
}
|
185 |
+
}
|
186 |
+
}
|
187 |
+
},
|
188 |
+
clone = helpers.clone = function(obj){
|
189 |
+
var objClone = {};
|
190 |
+
each(obj,function(value,key){
|
191 |
+
if (obj.hasOwnProperty(key)) objClone[key] = value;
|
192 |
+
});
|
193 |
+
return objClone;
|
194 |
+
},
|
195 |
+
extend = helpers.extend = function(base){
|
196 |
+
each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
|
197 |
+
each(extensionObject,function(value,key){
|
198 |
+
if (extensionObject.hasOwnProperty(key)) base[key] = value;
|
199 |
+
});
|
200 |
+
});
|
201 |
+
return base;
|
202 |
+
},
|
203 |
+
merge = helpers.merge = function(base,master){
|
204 |
+
//Merge properties in left object over to a shallow clone of object right.
|
205 |
+
var args = Array.prototype.slice.call(arguments,0);
|
206 |
+
args.unshift({});
|
207 |
+
return extend.apply(null, args);
|
208 |
+
},
|
209 |
+
indexOf = helpers.indexOf = function(arrayToSearch, item){
|
210 |
+
if (Array.prototype.indexOf) {
|
211 |
+
return arrayToSearch.indexOf(item);
|
212 |
+
}
|
213 |
+
else{
|
214 |
+
for (var i = 0; i < arrayToSearch.length; i++) {
|
215 |
+
if (arrayToSearch[i] === item) return i;
|
216 |
+
}
|
217 |
+
return -1;
|
218 |
+
}
|
219 |
+
},
|
220 |
+
where = helpers.where = function(collection, filterCallback){
|
221 |
+
var filtered = [];
|
222 |
+
|
223 |
+
helpers.each(collection, function(item){
|
224 |
+
if (filterCallback(item)){
|
225 |
+
filtered.push(item);
|
226 |
+
}
|
227 |
+
});
|
228 |
+
|
229 |
+
return filtered;
|
230 |
+
},
|
231 |
+
findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
|
232 |
+
// Default to start of the array
|
233 |
+
if (!startIndex){
|
234 |
+
startIndex = -1;
|
235 |
+
}
|
236 |
+
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
|
237 |
+
var currentItem = arrayToSearch[i];
|
238 |
+
if (filterCallback(currentItem)){
|
239 |
+
return currentItem;
|
240 |
+
}
|
241 |
+
};
|
242 |
+
},
|
243 |
+
findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
|
244 |
+
// Default to end of the array
|
245 |
+
if (!startIndex){
|
246 |
+
startIndex = arrayToSearch.length;
|
247 |
+
}
|
248 |
+
for (var i = startIndex - 1; i >= 0; i--) {
|
249 |
+
var currentItem = arrayToSearch[i];
|
250 |
+
if (filterCallback(currentItem)){
|
251 |
+
return currentItem;
|
252 |
+
}
|
253 |
+
};
|
254 |
+
},
|
255 |
+
inherits = helpers.inherits = function(extensions){
|
256 |
+
//Basic javascript inheritance based on the model created in Backbone.js
|
257 |
+
var parent = this;
|
258 |
+
var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
|
259 |
+
|
260 |
+
var Surrogate = function(){ this.constructor = ChartElement;};
|
261 |
+
Surrogate.prototype = parent.prototype;
|
262 |
+
ChartElement.prototype = new Surrogate();
|
263 |
+
|
264 |
+
ChartElement.extend = inherits;
|
265 |
+
|
266 |
+
if (extensions) extend(ChartElement.prototype, extensions);
|
267 |
+
|
268 |
+
ChartElement.__super__ = parent.prototype;
|
269 |
+
|
270 |
+
return ChartElement;
|
271 |
+
},
|
272 |
+
noop = helpers.noop = function(){},
|
273 |
+
uid = helpers.uid = (function(){
|
274 |
+
var id=0;
|
275 |
+
return function(){
|
276 |
+
return "chart-" + id++;
|
277 |
+
};
|
278 |
+
})(),
|
279 |
+
warn = helpers.warn = function(str){
|
280 |
+
//Method for warning of errors
|
281 |
+
if (window.console && typeof window.console.warn == "function") console.warn(str);
|
282 |
+
},
|
283 |
+
amd = helpers.amd = (typeof define == 'function' && define.amd),
|
284 |
+
//-- Math methods
|
285 |
+
isNumber = helpers.isNumber = function(n){
|
286 |
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
287 |
+
},
|
288 |
+
max = helpers.max = function(array){
|
289 |
+
return Math.max.apply( Math, array );
|
290 |
+
},
|
291 |
+
min = helpers.min = function(array){
|
292 |
+
return Math.min.apply( Math, array );
|
293 |
+
},
|
294 |
+
cap = helpers.cap = function(valueToCap,maxValue,minValue){
|
295 |
+
if(isNumber(maxValue)) {
|
296 |
+
if( valueToCap > maxValue ) {
|
297 |
+
return maxValue;
|
298 |
+
}
|
299 |
+
}
|
300 |
+
else if(isNumber(minValue)){
|
301 |
+
if ( valueToCap < minValue ){
|
302 |
+
return minValue;
|
303 |
+
}
|
304 |
+
}
|
305 |
+
return valueToCap;
|
306 |
+
},
|
307 |
+
getDecimalPlaces = helpers.getDecimalPlaces = function(num){
|
308 |
+
if (num%1!==0 && isNumber(num)){
|
309 |
+
return num.toString().split(".")[1].length;
|
310 |
+
}
|
311 |
+
else {
|
312 |
+
return 0;
|
313 |
+
}
|
314 |
+
},
|
315 |
+
toRadians = helpers.radians = function(degrees){
|
316 |
+
return degrees * (Math.PI/180);
|
317 |
+
},
|
318 |
+
// Gets the angle from vertical upright to the point about a centre.
|
319 |
+
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
|
320 |
+
var distanceFromXCenter = anglePoint.x - centrePoint.x,
|
321 |
+
distanceFromYCenter = anglePoint.y - centrePoint.y,
|
322 |
+
radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
|
323 |
+
|
324 |
+
|
325 |
+
var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
|
326 |
+
|
327 |
+
//If the segment is in the top left quadrant, we need to add another rotation to the angle
|
328 |
+
if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
|
329 |
+
angle += Math.PI*2;
|
330 |
+
}
|
331 |
+
|
332 |
+
return {
|
333 |
+
angle: angle,
|
334 |
+
distance: radialDistanceFromCenter
|
335 |
+
};
|
336 |
+
},
|
337 |
+
aliasPixel = helpers.aliasPixel = function(pixelWidth){
|
338 |
+
return (pixelWidth % 2 === 0) ? 0 : 0.5;
|
339 |
+
},
|
340 |
+
splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
|
341 |
+
//Props to Rob Spencer at scaled innovation for his post on splining between points
|
342 |
+
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
343 |
+
var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
|
344 |
+
d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
|
345 |
+
fa=t*d01/(d01+d12),// scaling factor for triangle Ta
|
346 |
+
fb=t*d12/(d01+d12);
|
347 |
+
return {
|
348 |
+
inner : {
|
349 |
+
x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
|
350 |
+
y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
|
351 |
+
},
|
352 |
+
outer : {
|
353 |
+
x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
|
354 |
+
y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
|
355 |
+
}
|
356 |
+
};
|
357 |
+
},
|
358 |
+
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
|
359 |
+
return Math.floor(Math.log(val) / Math.LN10);
|
360 |
+
},
|
361 |
+
calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
|
362 |
+
|
363 |
+
//Set a minimum step of two - a point at the top of the graph, and a point at the base
|
364 |
+
var minSteps = 2,
|
365 |
+
maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
|
366 |
+
skipFitting = (minSteps >= maxSteps);
|
367 |
+
|
368 |
+
var maxValue = max(valuesArray),
|
369 |
+
minValue = min(valuesArray);
|
370 |
+
|
371 |
+
// We need some degree of seperation here to calculate the scales if all the values are the same
|
372 |
+
// Adding/minusing 0.5 will give us a range of 1.
|
373 |
+
if (maxValue === minValue){
|
374 |
+
maxValue += 0.5;
|
375 |
+
// So we don't end up with a graph with a negative start value if we've said always start from zero
|
376 |
+
if (minValue >= 0.5 && !startFromZero){
|
377 |
+
minValue -= 0.5;
|
378 |
+
}
|
379 |
+
else{
|
380 |
+
// Make up a whole number above the values
|
381 |
+
maxValue += 0.5;
|
382 |
+
}
|
383 |
+
}
|
384 |
+
|
385 |
+
var valueRange = Math.abs(maxValue - minValue),
|
386 |
+
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
|
387 |
+
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
388 |
+
graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
389 |
+
graphRange = graphMax - graphMin,
|
390 |
+
stepValue = Math.pow(10, rangeOrderOfMagnitude),
|
391 |
+
numberOfSteps = Math.round(graphRange / stepValue);
|
392 |
+
|
393 |
+
//If we have more space on the graph we'll use it to give more definition to the data
|
394 |
+
while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
|
395 |
+
if(numberOfSteps > maxSteps){
|
396 |
+
stepValue *=2;
|
397 |
+
numberOfSteps = Math.round(graphRange/stepValue);
|
398 |
+
// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
|
399 |
+
if (numberOfSteps % 1 !== 0){
|
400 |
+
skipFitting = true;
|
401 |
+
}
|
402 |
+
}
|
403 |
+
//We can fit in double the amount of scale points on the scale
|
404 |
+
else{
|
405 |
+
//If user has declared ints only, and the step value isn't a decimal
|
406 |
+
if (integersOnly && rangeOrderOfMagnitude >= 0){
|
407 |
+
//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
|
408 |
+
if(stepValue/2 % 1 === 0){
|
409 |
+
stepValue /=2;
|
410 |
+
numberOfSteps = Math.round(graphRange/stepValue);
|
411 |
+
}
|
412 |
+
//If it would make it a float break out of the loop
|
413 |
+
else{
|
414 |
+
break;
|
415 |
+
}
|
416 |
+
}
|
417 |
+
//If the scale doesn't have to be an int, make the scale more granular anyway.
|
418 |
+
else{
|
419 |
+
stepValue /=2;
|
420 |
+
numberOfSteps = Math.round(graphRange/stepValue);
|
421 |
+
}
|
422 |
+
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
if (skipFitting){
|
427 |
+
numberOfSteps = minSteps;
|
428 |
+
stepValue = graphRange / numberOfSteps;
|
429 |
+
}
|
430 |
+
|
431 |
+
return {
|
432 |
+
steps : numberOfSteps,
|
433 |
+
stepValue : stepValue,
|
434 |
+
min : graphMin,
|
435 |
+
max : graphMin + (numberOfSteps * stepValue)
|
436 |
+
};
|
437 |
+
|
438 |
+
},
|
439 |
+
/* jshint ignore:start */
|
440 |
+
// Blows up jshint errors based on the new Function constructor
|
441 |
+
//Templating methods
|
442 |
+
//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
|
443 |
+
template = helpers.template = function(templateString, valuesObject){
|
444 |
+
// If templateString is function rather than string-template - call the function for valuesObject
|
445 |
+
if(templateString instanceof Function){
|
446 |
+
return templateString(valuesObject);
|
447 |
+
}
|
448 |
+
|
449 |
+
var cache = {};
|
450 |
+
function tmpl(str, data){
|
451 |
+
// Figure out if we're getting a template, or if we need to
|
452 |
+
// load the template - and be sure to cache the result.
|
453 |
+
var fn = !/\W/.test(str) ?
|
454 |
+
cache[str] = cache[str] :
|
455 |
+
|
456 |
+
// Generate a reusable function that will serve as a template
|
457 |
+
// generator (and which will be cached).
|
458 |
+
new Function("obj",
|
459 |
+
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
460 |
+
|
461 |
+
// Introduce the data as local variables using with(){}
|
462 |
+
"with(obj){p.push('" +
|
463 |
+
|
464 |
+
// Convert the template into pure JavaScript
|
465 |
+
str
|
466 |
+
.replace(/[\r\t\n]/g, " ")
|
467 |
+
.split("<%").join("\t")
|
468 |
+
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
469 |
+
.replace(/\t=(.*?)%>/g, "',$1,'")
|
470 |
+
.split("\t").join("');")
|
471 |
+
.split("%>").join("p.push('")
|
472 |
+
.split("\r").join("\\'") +
|
473 |
+
"');}return p.join('');"
|
474 |
+
);
|
475 |
+
|
476 |
+
// Provide some basic currying to the user
|
477 |
+
return data ? fn( data ) : fn;
|
478 |
+
}
|
479 |
+
return tmpl(templateString,valuesObject);
|
480 |
+
},
|
481 |
+
/* jshint ignore:end */
|
482 |
+
generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
|
483 |
+
var labelsArray = new Array(numberOfSteps);
|
484 |
+
if (labelTemplateString){
|
485 |
+
each(labelsArray,function(val,index){
|
486 |
+
labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
|
487 |
+
});
|
488 |
+
}
|
489 |
+
return labelsArray;
|
490 |
+
},
|
491 |
+
//--Animation methods
|
492 |
+
//Easing functions adapted from Robert Penner's easing equations
|
493 |
+
//http://www.robertpenner.com/easing/
|
494 |
+
easingEffects = helpers.easingEffects = {
|
495 |
+
linear: function (t) {
|
496 |
+
return t;
|
497 |
+
},
|
498 |
+
easeInQuad: function (t) {
|
499 |
+
return t * t;
|
500 |
+
},
|
501 |
+
easeOutQuad: function (t) {
|
502 |
+
return -1 * t * (t - 2);
|
503 |
+
},
|
504 |
+
easeInOutQuad: function (t) {
|
505 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
|
506 |
+
return -1 / 2 * ((--t) * (t - 2) - 1);
|
507 |
+
},
|
508 |
+
easeInCubic: function (t) {
|
509 |
+
return t * t * t;
|
510 |
+
},
|
511 |
+
easeOutCubic: function (t) {
|
512 |
+
return 1 * ((t = t / 1 - 1) * t * t + 1);
|
513 |
+
},
|
514 |
+
easeInOutCubic: function (t) {
|
515 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
|
516 |
+
return 1 / 2 * ((t -= 2) * t * t + 2);
|
517 |
+
},
|
518 |
+
easeInQuart: function (t) {
|
519 |
+
return t * t * t * t;
|
520 |
+
},
|
521 |
+
easeOutQuart: function (t) {
|
522 |
+
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
|
523 |
+
},
|
524 |
+
easeInOutQuart: function (t) {
|
525 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
|
526 |
+
return -1 / 2 * ((t -= 2) * t * t * t - 2);
|
527 |
+
},
|
528 |
+
easeInQuint: function (t) {
|
529 |
+
return 1 * (t /= 1) * t * t * t * t;
|
530 |
+
},
|
531 |
+
easeOutQuint: function (t) {
|
532 |
+
return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
|
533 |
+
},
|
534 |
+
easeInOutQuint: function (t) {
|
535 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
|
536 |
+
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
|
537 |
+
},
|
538 |
+
easeInSine: function (t) {
|
539 |
+
return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
|
540 |
+
},
|
541 |
+
easeOutSine: function (t) {
|
542 |
+
return 1 * Math.sin(t / 1 * (Math.PI / 2));
|
543 |
+
},
|
544 |
+
easeInOutSine: function (t) {
|
545 |
+
return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
|
546 |
+
},
|
547 |
+
easeInExpo: function (t) {
|
548 |
+
return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
|
549 |
+
},
|
550 |
+
easeOutExpo: function (t) {
|
551 |
+
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
|
552 |
+
},
|
553 |
+
easeInOutExpo: function (t) {
|
554 |
+
if (t === 0) return 0;
|
555 |
+
if (t === 1) return 1;
|
556 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
|
557 |
+
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
|
558 |
+
},
|
559 |
+
easeInCirc: function (t) {
|
560 |
+
if (t >= 1) return t;
|
561 |
+
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
|
562 |
+
},
|
563 |
+
easeOutCirc: function (t) {
|
564 |
+
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
|
565 |
+
},
|
566 |
+
easeInOutCirc: function (t) {
|
567 |
+
if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
|
568 |
+
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
|
569 |
+
},
|
570 |
+
easeInElastic: function (t) {
|
571 |
+
var s = 1.70158;
|
572 |
+
var p = 0;
|
573 |
+
var a = 1;
|
574 |
+
if (t === 0) return 0;
|
575 |
+
if ((t /= 1) == 1) return 1;
|
576 |
+
if (!p) p = 1 * 0.3;
|
577 |
+
if (a < Math.abs(1)) {
|
578 |
+
a = 1;
|
579 |
+
s = p / 4;
|
580 |
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
581 |
+
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
582 |
+
},
|
583 |
+
easeOutElastic: function (t) {
|
584 |
+
var s = 1.70158;
|
585 |
+
var p = 0;
|
586 |
+
var a = 1;
|
587 |
+
if (t === 0) return 0;
|
588 |
+
if ((t /= 1) == 1) return 1;
|
589 |
+
if (!p) p = 1 * 0.3;
|
590 |
+
if (a < Math.abs(1)) {
|
591 |
+
a = 1;
|
592 |
+
s = p / 4;
|
593 |
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
594 |
+
return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
|
595 |
+
},
|
596 |
+
easeInOutElastic: function (t) {
|
597 |
+
var s = 1.70158;
|
598 |
+
var p = 0;
|
599 |
+
var a = 1;
|
600 |
+
if (t === 0) return 0;
|
601 |
+
if ((t /= 1 / 2) == 2) return 1;
|
602 |
+
if (!p) p = 1 * (0.3 * 1.5);
|
603 |
+
if (a < Math.abs(1)) {
|
604 |
+
a = 1;
|
605 |
+
s = p / 4;
|
606 |
+
} else s = p / (2 * Math.PI) * Math.asin(1 / a);
|
607 |
+
if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
608 |
+
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
|
609 |
+
},
|
610 |
+
easeInBack: function (t) {
|
611 |
+
var s = 1.70158;
|
612 |
+
return 1 * (t /= 1) * t * ((s + 1) * t - s);
|
613 |
+
},
|
614 |
+
easeOutBack: function (t) {
|
615 |
+
var s = 1.70158;
|
616 |
+
return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
|
617 |
+
},
|
618 |
+
easeInOutBack: function (t) {
|
619 |
+
var s = 1.70158;
|
620 |
+
if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
|
621 |
+
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
|
622 |
+
},
|
623 |
+
easeInBounce: function (t) {
|
624 |
+
return 1 - easingEffects.easeOutBounce(1 - t);
|
625 |
+
},
|
626 |
+
easeOutBounce: function (t) {
|
627 |
+
if ((t /= 1) < (1 / 2.75)) {
|
628 |
+
return 1 * (7.5625 * t * t);
|
629 |
+
} else if (t < (2 / 2.75)) {
|
630 |
+
return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
|
631 |
+
} else if (t < (2.5 / 2.75)) {
|
632 |
+
return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
|
633 |
+
} else {
|
634 |
+
return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
|
635 |
+
}
|
636 |
+
},
|
637 |
+
easeInOutBounce: function (t) {
|
638 |
+
if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
|
639 |
+
return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
|
640 |
+
}
|
641 |
+
},
|
642 |
+
//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
|
643 |
+
requestAnimFrame = helpers.requestAnimFrame = (function(){
|
644 |
+
return window.requestAnimationFrame ||
|
645 |
+
window.webkitRequestAnimationFrame ||
|
646 |
+
window.mozRequestAnimationFrame ||
|
647 |
+
window.oRequestAnimationFrame ||
|
648 |
+
window.msRequestAnimationFrame ||
|
649 |
+
function(callback) {
|
650 |
+
return window.setTimeout(callback, 1000 / 60);
|
651 |
+
};
|
652 |
+
})(),
|
653 |
+
cancelAnimFrame = helpers.cancelAnimFrame = (function(){
|
654 |
+
return window.cancelAnimationFrame ||
|
655 |
+
window.webkitCancelAnimationFrame ||
|
656 |
+
window.mozCancelAnimationFrame ||
|
657 |
+
window.oCancelAnimationFrame ||
|
658 |
+
window.msCancelAnimationFrame ||
|
659 |
+
function(callback) {
|
660 |
+
return window.clearTimeout(callback, 1000 / 60);
|
661 |
+
};
|
662 |
+
})(),
|
663 |
+
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
|
664 |
+
|
665 |
+
var currentStep = 0,
|
666 |
+
easingFunction = easingEffects[easingString] || easingEffects.linear;
|
667 |
+
|
668 |
+
var animationFrame = function(){
|
669 |
+
currentStep++;
|
670 |
+
var stepDecimal = currentStep/totalSteps;
|
671 |
+
var easeDecimal = easingFunction(stepDecimal);
|
672 |
+
|
673 |
+
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
|
674 |
+
onProgress.call(chartInstance,easeDecimal,stepDecimal);
|
675 |
+
if (currentStep < totalSteps){
|
676 |
+
chartInstance.animationFrame = requestAnimFrame(animationFrame);
|
677 |
+
} else{
|
678 |
+
onComplete.apply(chartInstance);
|
679 |
+
}
|
680 |
+
};
|
681 |
+
requestAnimFrame(animationFrame);
|
682 |
+
},
|
683 |
+
//-- DOM methods
|
684 |
+
getRelativePosition = helpers.getRelativePosition = function(evt){
|
685 |
+
var mouseX, mouseY;
|
686 |
+
var e = evt.originalEvent || evt,
|
687 |
+
canvas = evt.currentTarget || evt.srcElement,
|
688 |
+
boundingRect = canvas.getBoundingClientRect();
|
689 |
+
|
690 |
+
if (e.touches){
|
691 |
+
mouseX = e.touches[0].clientX - boundingRect.left;
|
692 |
+
mouseY = e.touches[0].clientY - boundingRect.top;
|
693 |
+
|
694 |
+
}
|
695 |
+
else{
|
696 |
+
mouseX = e.clientX - boundingRect.left;
|
697 |
+
mouseY = e.clientY - boundingRect.top;
|
698 |
+
}
|
699 |
+
|
700 |
+
return {
|
701 |
+
x : mouseX,
|
702 |
+
y : mouseY
|
703 |
+
};
|
704 |
+
|
705 |
+
},
|
706 |
+
addEvent = helpers.addEvent = function(node,eventType,method){
|
707 |
+
if (node.addEventListener){
|
708 |
+
node.addEventListener(eventType,method);
|
709 |
+
} else if (node.attachEvent){
|
710 |
+
node.attachEvent("on"+eventType, method);
|
711 |
+
} else {
|
712 |
+
node["on"+eventType] = method;
|
713 |
+
}
|
714 |
+
},
|
715 |
+
removeEvent = helpers.removeEvent = function(node, eventType, handler){
|
716 |
+
if (node.removeEventListener){
|
717 |
+
node.removeEventListener(eventType, handler, false);
|
718 |
+
} else if (node.detachEvent){
|
719 |
+
node.detachEvent("on"+eventType,handler);
|
720 |
+
} else{
|
721 |
+
node["on" + eventType] = noop;
|
722 |
+
}
|
723 |
+
},
|
724 |
+
bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
|
725 |
+
// Create the events object if it's not already present
|
726 |
+
if (!chartInstance.events) chartInstance.events = {};
|
727 |
+
|
728 |
+
each(arrayOfEvents,function(eventName){
|
729 |
+
chartInstance.events[eventName] = function(){
|
730 |
+
handler.apply(chartInstance, arguments);
|
731 |
+
};
|
732 |
+
addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
|
733 |
+
});
|
734 |
+
},
|
735 |
+
unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
|
736 |
+
each(arrayOfEvents, function(handler,eventName){
|
737 |
+
removeEvent(chartInstance.chart.canvas, eventName, handler);
|
738 |
+
});
|
739 |
+
},
|
740 |
+
getMaximumWidth = helpers.getMaximumWidth = function(domNode){
|
741 |
+
var container = domNode.parentNode;
|
742 |
+
// TODO = check cross browser stuff with this.
|
743 |
+
return container.clientWidth;
|
744 |
+
},
|
745 |
+
getMaximumHeight = helpers.getMaximumHeight = function(domNode){
|
746 |
+
var container = domNode.parentNode;
|
747 |
+
// TODO = check cross browser stuff with this.
|
748 |
+
return container.clientHeight;
|
749 |
+
},
|
750 |
+
getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
|
751 |
+
retinaScale = helpers.retinaScale = function(chart){
|
752 |
+
var ctx = chart.ctx,
|
753 |
+
width = chart.canvas.width,
|
754 |
+
height = chart.canvas.height;
|
755 |
+
|
756 |
+
if (window.devicePixelRatio) {
|
757 |
+
ctx.canvas.style.width = width + "px";
|
758 |
+
ctx.canvas.style.height = height + "px";
|
759 |
+
ctx.canvas.height = height * window.devicePixelRatio;
|
760 |
+
ctx.canvas.width = width * window.devicePixelRatio;
|
761 |
+
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
762 |
+
}
|
763 |
+
},
|
764 |
+
//-- Canvas methods
|
765 |
+
clear = helpers.clear = function(chart){
|
766 |
+
chart.ctx.clearRect(0,0,chart.width,chart.height);
|
767 |
+
},
|
768 |
+
fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
|
769 |
+
return fontStyle + " " + pixelSize+"px " + fontFamily;
|
770 |
+
},
|
771 |
+
longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
|
772 |
+
ctx.font = font;
|
773 |
+
var longest = 0;
|
774 |
+
each(arrayOfStrings,function(string){
|
775 |
+
var textWidth = ctx.measureText(string).width;
|
776 |
+
longest = (textWidth > longest) ? textWidth : longest;
|
777 |
+
});
|
778 |
+
return longest;
|
779 |
+
},
|
780 |
+
drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
|
781 |
+
ctx.beginPath();
|
782 |
+
ctx.moveTo(x + radius, y);
|
783 |
+
ctx.lineTo(x + width - radius, y);
|
784 |
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
785 |
+
ctx.lineTo(x + width, y + height - radius);
|
786 |
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
787 |
+
ctx.lineTo(x + radius, y + height);
|
788 |
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
789 |
+
ctx.lineTo(x, y + radius);
|
790 |
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
791 |
+
ctx.closePath();
|
792 |
+
};
|
793 |
+
|
794 |
+
|
795 |
+
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
|
796 |
+
//Destroy method on the chart will remove the instance of the chart from this reference.
|
797 |
+
Chart.instances = {};
|
798 |
+
|
799 |
+
Chart.Type = function(data,options,chart){
|
800 |
+
this.options = options;
|
801 |
+
this.chart = chart;
|
802 |
+
this.id = uid();
|
803 |
+
//Add the chart instance to the global namespace
|
804 |
+
Chart.instances[this.id] = this;
|
805 |
+
|
806 |
+
// Initialize is always called when a chart type is created
|
807 |
+
// By default it is a no op, but it should be extended
|
808 |
+
if (options.responsive){
|
809 |
+
this.resize();
|
810 |
+
}
|
811 |
+
this.initialize.call(this,data);
|
812 |
+
};
|
813 |
+
|
814 |
+
//Core methods that'll be a part of every chart type
|
815 |
+
extend(Chart.Type.prototype,{
|
816 |
+
initialize : function(){return this;},
|
817 |
+
clear : function(){
|
818 |
+
clear(this.chart);
|
819 |
+
return this;
|
820 |
+
},
|
821 |
+
stop : function(){
|
822 |
+
// Stops any current animation loop occuring
|
823 |
+
helpers.cancelAnimFrame.call(root, this.animationFrame);
|
824 |
+
return this;
|
825 |
+
},
|
826 |
+
resize : function(callback){
|
827 |
+
this.stop();
|
828 |
+
var canvas = this.chart.canvas,
|
829 |
+
newWidth = getMaximumWidth(this.chart.canvas),
|
830 |
+
newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
|
831 |
+
|
832 |
+
canvas.width = this.chart.width = newWidth;
|
833 |
+
canvas.height = this.chart.height = newHeight;
|
834 |
+
|
835 |
+
retinaScale(this.chart);
|
836 |
+
|
837 |
+
if (typeof callback === "function"){
|
838 |
+
callback.apply(this, Array.prototype.slice.call(arguments, 1));
|
839 |
+
}
|
840 |
+
return this;
|
841 |
+
},
|
842 |
+
reflow : noop,
|
843 |
+
render : function(reflow){
|
844 |
+
if (reflow){
|
845 |
+
this.reflow();
|
846 |
+
}
|
847 |
+
if (this.options.animation && !reflow){
|
848 |
+
helpers.animationLoop(
|
849 |
+
this.draw,
|
850 |
+
this.options.animationSteps,
|
851 |
+
this.options.animationEasing,
|
852 |
+
this.options.onAnimationProgress,
|
853 |
+
this.options.onAnimationComplete,
|
854 |
+
this
|
855 |
+
);
|
856 |
+
}
|
857 |
+
else{
|
858 |
+
this.draw();
|
859 |
+
this.options.onAnimationComplete.call(this);
|
860 |
+
}
|
861 |
+
return this;
|
862 |
+
},
|
863 |
+
generateLegend : function(){
|
864 |
+
return template(this.options.legendTemplate,this);
|
865 |
+
},
|
866 |
+
destroy : function(){
|
867 |
+
this.clear();
|
868 |
+
unbindEvents(this, this.events);
|
869 |
+
delete Chart.instances[this.id];
|
870 |
+
},
|
871 |
+
showTooltip : function(ChartElements, forceRedraw){
|
872 |
+
// Only redraw the chart if we've actually changed what we're hovering on.
|
873 |
+
if (typeof this.activeElements === 'undefined') this.activeElements = [];
|
874 |
+
|
875 |
+
var isChanged = (function(Elements){
|
876 |
+
var changed = false;
|
877 |
+
|
878 |
+
if (Elements.length !== this.activeElements.length){
|
879 |
+
changed = true;
|
880 |
+
return changed;
|
881 |
+
}
|
882 |
+
|
883 |
+
each(Elements, function(element, index){
|
884 |
+
if (element !== this.activeElements[index]){
|
885 |
+
changed = true;
|
886 |
+
}
|
887 |
+
}, this);
|
888 |
+
return changed;
|
889 |
+
}).call(this, ChartElements);
|
890 |
+
|
891 |
+
if (!isChanged && !forceRedraw){
|
892 |
+
return;
|
893 |
+
}
|
894 |
+
else{
|
895 |
+
this.activeElements = ChartElements;
|
896 |
+
}
|
897 |
+
this.draw();
|
898 |
+
if (ChartElements.length > 0){
|
899 |
+
// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
|
900 |
+
if (this.datasets && this.datasets.length > 1) {
|
901 |
+
var dataArray,
|
902 |
+
dataIndex;
|
903 |
+
|
904 |
+
for (var i = this.datasets.length - 1; i >= 0; i--) {
|
905 |
+
dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
|
906 |
+
dataIndex = indexOf(dataArray, ChartElements[0]);
|
907 |
+
if (dataIndex !== -1){
|
908 |
+
break;
|
909 |
+
}
|
910 |
+
}
|
911 |
+
var tooltipLabels = [],
|
912 |
+
tooltipColors = [],
|
913 |
+
medianPosition = (function(index) {
|
914 |
+
|
915 |
+
// Get all the points at that particular index
|
916 |
+
var Elements = [],
|
917 |
+
dataCollection,
|
918 |
+
xPositions = [],
|
919 |
+
yPositions = [],
|
920 |
+
xMax,
|
921 |
+
yMax,
|
922 |
+
xMin,
|
923 |
+
yMin;
|
924 |
+
helpers.each(this.datasets, function(dataset){
|
925 |
+
dataCollection = dataset.points || dataset.bars || dataset.segments;
|
926 |
+
if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
|
927 |
+
Elements.push(dataCollection[dataIndex]);
|
928 |
+
}
|
929 |
+
});
|
930 |
+
|
931 |
+
helpers.each(Elements, function(element) {
|
932 |
+
xPositions.push(element.x);
|
933 |
+
yPositions.push(element.y);
|
934 |
+
|
935 |
+
|
936 |
+
//Include any colour information about the element
|
937 |
+
tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
|
938 |
+
tooltipColors.push({
|
939 |
+
fill: element._saved.fillColor || element.fillColor,
|
940 |
+
stroke: element._saved.strokeColor || element.strokeColor
|
941 |
+
});
|
942 |
+
|
943 |
+
}, this);
|
944 |
+
|
945 |
+
yMin = min(yPositions);
|
946 |
+
yMax = max(yPositions);
|
947 |
+
|
948 |
+
xMin = min(xPositions);
|
949 |
+
xMax = max(xPositions);
|
950 |
+
|
951 |
+
return {
|
952 |
+
x: (xMin > this.chart.width/2) ? xMin : xMax,
|
953 |
+
y: (yMin + yMax)/2
|
954 |
+
};
|
955 |
+
}).call(this, dataIndex);
|
956 |
+
|
957 |
+
new Chart.MultiTooltip({
|
958 |
+
x: medianPosition.x,
|
959 |
+
y: medianPosition.y,
|
960 |
+
xPadding: this.options.tooltipXPadding,
|
961 |
+
yPadding: this.options.tooltipYPadding,
|
962 |
+
xOffset: this.options.tooltipXOffset,
|
963 |
+
fillColor: this.options.tooltipFillColor,
|
964 |
+
textColor: this.options.tooltipFontColor,
|
965 |
+
fontFamily: this.options.tooltipFontFamily,
|
966 |
+
fontStyle: this.options.tooltipFontStyle,
|
967 |
+
fontSize: this.options.tooltipFontSize,
|
968 |
+
titleTextColor: this.options.tooltipTitleFontColor,
|
969 |
+
titleFontFamily: this.options.tooltipTitleFontFamily,
|
970 |
+
titleFontStyle: this.options.tooltipTitleFontStyle,
|
971 |
+
titleFontSize: this.options.tooltipTitleFontSize,
|
972 |
+
cornerRadius: this.options.tooltipCornerRadius,
|
973 |
+
labels: tooltipLabels,
|
974 |
+
legendColors: tooltipColors,
|
975 |
+
legendColorBackground : this.options.multiTooltipKeyBackground,
|
976 |
+
title: ChartElements[0].label,
|
977 |
+
chart: this.chart,
|
978 |
+
ctx: this.chart.ctx
|
979 |
+
}).draw();
|
980 |
+
|
981 |
+
} else {
|
982 |
+
each(ChartElements, function(Element) {
|
983 |
+
var tooltipPosition = Element.tooltipPosition();
|
984 |
+
new Chart.Tooltip({
|
985 |
+
x: Math.round(tooltipPosition.x),
|
986 |
+
y: Math.round(tooltipPosition.y),
|
987 |
+
xPadding: this.options.tooltipXPadding,
|
988 |
+
yPadding: this.options.tooltipYPadding,
|
989 |
+
fillColor: this.options.tooltipFillColor,
|
990 |
+
textColor: this.options.tooltipFontColor,
|
991 |
+
fontFamily: this.options.tooltipFontFamily,
|
992 |
+
fontStyle: this.options.tooltipFontStyle,
|
993 |
+
fontSize: this.options.tooltipFontSize,
|
994 |
+
caretHeight: this.options.tooltipCaretSize,
|
995 |
+
cornerRadius: this.options.tooltipCornerRadius,
|
996 |
+
text: template(this.options.tooltipTemplate, Element),
|
997 |
+
chart: this.chart
|
998 |
+
}).draw();
|
999 |
+
}, this);
|
1000 |
+
}
|
1001 |
+
}
|
1002 |
+
return this;
|
1003 |
+
},
|
1004 |
+
toBase64Image : function(){
|
1005 |
+
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
|
1006 |
+
}
|
1007 |
+
});
|
1008 |
+
|
1009 |
+
Chart.Type.extend = function(extensions){
|
1010 |
+
|
1011 |
+
var parent = this;
|
1012 |
+
|
1013 |
+
var ChartType = function(){
|
1014 |
+
return parent.apply(this,arguments);
|
1015 |
+
};
|
1016 |
+
|
1017 |
+
//Copy the prototype object of the this class
|
1018 |
+
ChartType.prototype = clone(parent.prototype);
|
1019 |
+
//Now overwrite some of the properties in the base class with the new extensions
|
1020 |
+
extend(ChartType.prototype, extensions);
|
1021 |
+
|
1022 |
+
ChartType.extend = Chart.Type.extend;
|
1023 |
+
|
1024 |
+
if (extensions.name || parent.prototype.name){
|
1025 |
+
|
1026 |
+
var chartName = extensions.name || parent.prototype.name;
|
1027 |
+
//Assign any potential default values of the new chart type
|
1028 |
+
|
1029 |
+
//If none are defined, we'll use a clone of the chart type this is being extended from.
|
1030 |
+
//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
|
1031 |
+
//doesn't define some defaults of their own.
|
1032 |
+
|
1033 |
+
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
|
1034 |
+
|
1035 |
+
Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
|
1036 |
+
|
1037 |
+
Chart.types[chartName] = ChartType;
|
1038 |
+
|
1039 |
+
//Register this new chart type in the Chart prototype
|
1040 |
+
Chart.prototype[chartName] = function(data,options){
|
1041 |
+
var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
|
1042 |
+
return new ChartType(data,config,this);
|
1043 |
+
};
|
1044 |
+
} else{
|
1045 |
+
warn("Name not provided for this chart, so it hasn't been registered");
|
1046 |
+
}
|
1047 |
+
return parent;
|
1048 |
+
};
|
1049 |
+
|
1050 |
+
Chart.Element = function(configuration){
|
1051 |
+
extend(this,configuration);
|
1052 |
+
this.initialize.apply(this,arguments);
|
1053 |
+
this.save();
|
1054 |
+
};
|
1055 |
+
extend(Chart.Element.prototype,{
|
1056 |
+
initialize : function(){},
|
1057 |
+
restore : function(props){
|
1058 |
+
if (!props){
|
1059 |
+
extend(this,this._saved);
|
1060 |
+
} else {
|
1061 |
+
each(props,function(key){
|
1062 |
+
this[key] = this._saved[key];
|
1063 |
+
},this);
|
1064 |
+
}
|
1065 |
+
return this;
|
1066 |
+
},
|
1067 |
+
save : function(){
|
1068 |
+
this._saved = clone(this);
|
1069 |
+
delete this._saved._saved;
|
1070 |
+
return this;
|
1071 |
+
},
|
1072 |
+
update : function(newProps){
|
1073 |
+
each(newProps,function(value,key){
|
1074 |
+
this._saved[key] = this[key];
|
1075 |
+
this[key] = value;
|
1076 |
+
},this);
|
1077 |
+
return this;
|
1078 |
+
},
|
1079 |
+
transition : function(props,ease){
|
1080 |
+
each(props,function(value,key){
|
1081 |
+
this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
|
1082 |
+
},this);
|
1083 |
+
return this;
|
1084 |
+
},
|
1085 |
+
tooltipPosition : function(){
|
1086 |
+
return {
|
1087 |
+
x : this.x,
|
1088 |
+
y : this.y
|
1089 |
+
};
|
1090 |
+
},
|
1091 |
+
hasValue: function(){
|
1092 |
+
return isNumber(this.value);
|
1093 |
+
}
|
1094 |
+
});
|
1095 |
+
|
1096 |
+
Chart.Element.extend = inherits;
|
1097 |
+
|
1098 |
+
|
1099 |
+
Chart.Point = Chart.Element.extend({
|
1100 |
+
display: true,
|
1101 |
+
inRange: function(chartX,chartY){
|
1102 |
+
var hitDetectionRange = this.hitDetectionRadius + this.radius;
|
1103 |
+
return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
|
1104 |
+
},
|
1105 |
+
draw : function(){
|
1106 |
+
if (this.display){
|
1107 |
+
var ctx = this.ctx;
|
1108 |
+
ctx.beginPath();
|
1109 |
+
|
1110 |
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
|
1111 |
+
ctx.closePath();
|
1112 |
+
|
1113 |
+
ctx.strokeStyle = this.strokeColor;
|
1114 |
+
ctx.lineWidth = this.strokeWidth;
|
1115 |
+
|
1116 |
+
ctx.fillStyle = this.fillColor;
|
1117 |
+
|
1118 |
+
ctx.fill();
|
1119 |
+
ctx.stroke();
|
1120 |
+
}
|
1121 |
+
|
1122 |
+
|
1123 |
+
//Quick debug for bezier curve splining
|
1124 |
+
//Highlights control points and the line between them.
|
1125 |
+
//Handy for dev - stripped in the min version.
|
1126 |
+
|
1127 |
+
// ctx.save();
|
1128 |
+
// ctx.fillStyle = "black";
|
1129 |
+
// ctx.strokeStyle = "black"
|
1130 |
+
// ctx.beginPath();
|
1131 |
+
// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
|
1132 |
+
// ctx.fill();
|
1133 |
+
|
1134 |
+
// ctx.beginPath();
|
1135 |
+
// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
|
1136 |
+
// ctx.fill();
|
1137 |
+
|
1138 |
+
// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
|
1139 |
+
// ctx.lineTo(this.x, this.y);
|
1140 |
+
// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
|
1141 |
+
// ctx.stroke();
|
1142 |
+
|
1143 |
+
// ctx.restore();
|
1144 |
+
|
1145 |
+
|
1146 |
+
|
1147 |
+
}
|
1148 |
+
});
|
1149 |
+
|
1150 |
+
Chart.Arc = Chart.Element.extend({
|
1151 |
+
inRange : function(chartX,chartY){
|
1152 |
+
|
1153 |
+
var pointRelativePosition = helpers.getAngleFromPoint(this, {
|
1154 |
+
x: chartX,
|
1155 |
+
y: chartY
|
1156 |
+
});
|
1157 |
+
|
1158 |
+
//Check if within the range of the open/close angle
|
1159 |
+
var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
|
1160 |
+
withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
|
1161 |
+
|
1162 |
+
return (betweenAngles && withinRadius);
|
1163 |
+
//Ensure within the outside of the arc centre, but inside arc outer
|
1164 |
+
},
|
1165 |
+
tooltipPosition : function(){
|
1166 |
+
var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
|
1167 |
+
rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
|
1168 |
+
return {
|
1169 |
+
x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
|
1170 |
+
y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
|
1171 |
+
};
|
1172 |
+
},
|
1173 |
+
draw : function(animationPercent){
|
1174 |
+
|
1175 |
+
var easingDecimal = animationPercent || 1;
|
1176 |
+
|
1177 |
+
var ctx = this.ctx;
|
1178 |
+
|
1179 |
+
ctx.beginPath();
|
1180 |
+
|
1181 |
+
ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
|
1182 |
+
|
1183 |
+
ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
|
1184 |
+
|
1185 |
+
ctx.closePath();
|
1186 |
+
ctx.strokeStyle = this.strokeColor;
|
1187 |
+
ctx.lineWidth = this.strokeWidth;
|
1188 |
+
|
1189 |
+
ctx.fillStyle = this.fillColor;
|
1190 |
+
|
1191 |
+
ctx.fill();
|
1192 |
+
ctx.lineJoin = 'bevel';
|
1193 |
+
|
1194 |
+
if (this.showStroke){
|
1195 |
+
ctx.stroke();
|
1196 |
+
}
|
1197 |
+
}
|
1198 |
+
});
|
1199 |
+
|
1200 |
+
Chart.Rectangle = Chart.Element.extend({
|
1201 |
+
draw : function(){
|
1202 |
+
var ctx = this.ctx,
|
1203 |
+
halfWidth = this.width/2,
|
1204 |
+
leftX = this.x - halfWidth,
|
1205 |
+
rightX = this.x + halfWidth,
|
1206 |
+
top = this.base - (this.base - this.y),
|
1207 |
+
halfStroke = this.strokeWidth / 2;
|
1208 |
+
|
1209 |
+
// Canvas doesn't allow us to stroke inside the width so we can
|
1210 |
+
// adjust the sizes to fit if we're setting a stroke on the line
|
1211 |
+
if (this.showStroke){
|
1212 |
+
leftX += halfStroke;
|
1213 |
+
rightX -= halfStroke;
|
1214 |
+
top += halfStroke;
|
1215 |
+
}
|
1216 |
+
|
1217 |
+
ctx.beginPath();
|
1218 |
+
|
1219 |
+
ctx.fillStyle = this.fillColor;
|
1220 |
+
ctx.strokeStyle = this.strokeColor;
|
1221 |
+
ctx.lineWidth = this.strokeWidth;
|
1222 |
+
|
1223 |
+
// It'd be nice to keep this class totally generic to any rectangle
|
1224 |
+
// and simply specify which border to miss out.
|
1225 |
+
ctx.moveTo(leftX, this.base);
|
1226 |
+
ctx.lineTo(leftX, top);
|
1227 |
+
ctx.lineTo(rightX, top);
|
1228 |
+
ctx.lineTo(rightX, this.base);
|
1229 |
+
ctx.fill();
|
1230 |
+
if (this.showStroke){
|
1231 |
+
ctx.stroke();
|
1232 |
+
}
|
1233 |
+
},
|
1234 |
+
height : function(){
|
1235 |
+
return this.base - this.y;
|
1236 |
+
},
|
1237 |
+
inRange : function(chartX,chartY){
|
1238 |
+
return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
|
1239 |
+
}
|
1240 |
+
});
|
1241 |
+
|
1242 |
+
Chart.Tooltip = Chart.Element.extend({
|
1243 |
+
draw : function(){
|
1244 |
+
|
1245 |
+
var ctx = this.chart.ctx;
|
1246 |
+
|
1247 |
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1248 |
+
|
1249 |
+
this.xAlign = "center";
|
1250 |
+
this.yAlign = "above";
|
1251 |
+
|
1252 |
+
//Distance between the actual element.y position and the start of the tooltip caret
|
1253 |
+
var caretPadding = 2;
|
1254 |
+
|
1255 |
+
var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
|
1256 |
+
tooltipRectHeight = this.fontSize + 2*this.yPadding,
|
1257 |
+
tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
|
1258 |
+
|
1259 |
+
if (this.x + tooltipWidth/2 >this.chart.width){
|
1260 |
+
this.xAlign = "left";
|
1261 |
+
} else if (this.x - tooltipWidth/2 < 0){
|
1262 |
+
this.xAlign = "right";
|
1263 |
+
}
|
1264 |
+
|
1265 |
+
if (this.y - tooltipHeight < 0){
|
1266 |
+
this.yAlign = "below";
|
1267 |
+
}
|
1268 |
+
|
1269 |
+
|
1270 |
+
var tooltipX = this.x - tooltipWidth/2,
|
1271 |
+
tooltipY = this.y - tooltipHeight;
|
1272 |
+
|
1273 |
+
ctx.fillStyle = this.fillColor;
|
1274 |
+
|
1275 |
+
switch(this.yAlign)
|
1276 |
+
{
|
1277 |
+
case "above":
|
1278 |
+
//Draw a caret above the x/y
|
1279 |
+
ctx.beginPath();
|
1280 |
+
ctx.moveTo(this.x,this.y - caretPadding);
|
1281 |
+
ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
1282 |
+
ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
|
1283 |
+
ctx.closePath();
|
1284 |
+
ctx.fill();
|
1285 |
+
break;
|
1286 |
+
case "below":
|
1287 |
+
tooltipY = this.y + caretPadding + this.caretHeight;
|
1288 |
+
//Draw a caret below the x/y
|
1289 |
+
ctx.beginPath();
|
1290 |
+
ctx.moveTo(this.x, this.y + caretPadding);
|
1291 |
+
ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
|
1292 |
+
ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
|
1293 |
+
ctx.closePath();
|
1294 |
+
ctx.fill();
|
1295 |
+
break;
|
1296 |
+
}
|
1297 |
+
|
1298 |
+
switch(this.xAlign)
|
1299 |
+
{
|
1300 |
+
case "left":
|
1301 |
+
tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
|
1302 |
+
break;
|
1303 |
+
case "right":
|
1304 |
+
tooltipX = this.x - (this.cornerRadius + this.caretHeight);
|
1305 |
+
break;
|
1306 |
+
}
|
1307 |
+
|
1308 |
+
drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
|
1309 |
+
|
1310 |
+
ctx.fill();
|
1311 |
+
|
1312 |
+
ctx.fillStyle = this.textColor;
|
1313 |
+
ctx.textAlign = "center";
|
1314 |
+
ctx.textBaseline = "middle";
|
1315 |
+
ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
|
1316 |
+
}
|
1317 |
+
});
|
1318 |
+
|
1319 |
+
Chart.MultiTooltip = Chart.Element.extend({
|
1320 |
+
initialize : function(){
|
1321 |
+
this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1322 |
+
|
1323 |
+
this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
|
1324 |
+
|
1325 |
+
this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
|
1326 |
+
|
1327 |
+
this.ctx.font = this.titleFont;
|
1328 |
+
|
1329 |
+
var titleWidth = this.ctx.measureText(this.title).width,
|
1330 |
+
//Label has a legend square as well so account for this.
|
1331 |
+
labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
|
1332 |
+
longestTextWidth = max([labelWidth,titleWidth]);
|
1333 |
+
|
1334 |
+
this.width = longestTextWidth + (this.xPadding*2);
|
1335 |
+
|
1336 |
+
|
1337 |
+
var halfHeight = this.height/2;
|
1338 |
+
|
1339 |
+
//Check to ensure the height will fit on the canvas
|
1340 |
+
//The three is to buffer form the very
|
1341 |
+
if (this.y - halfHeight < 0 ){
|
1342 |
+
this.y = halfHeight;
|
1343 |
+
} else if (this.y + halfHeight > this.chart.height){
|
1344 |
+
this.y = this.chart.height - halfHeight;
|
1345 |
+
}
|
1346 |
+
|
1347 |
+
//Decide whether to align left or right based on position on canvas
|
1348 |
+
if (this.x > this.chart.width/2){
|
1349 |
+
this.x -= this.xOffset + this.width;
|
1350 |
+
} else {
|
1351 |
+
this.x += this.xOffset;
|
1352 |
+
}
|
1353 |
+
|
1354 |
+
|
1355 |
+
},
|
1356 |
+
getLineHeight : function(index){
|
1357 |
+
var baseLineHeight = this.y - (this.height/2) + this.yPadding,
|
1358 |
+
afterTitleIndex = index-1;
|
1359 |
+
|
1360 |
+
//If the index is zero, we're getting the title
|
1361 |
+
if (index === 0){
|
1362 |
+
return baseLineHeight + this.titleFontSize/2;
|
1363 |
+
} else{
|
1364 |
+
return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
|
1365 |
+
}
|
1366 |
+
|
1367 |
+
},
|
1368 |
+
draw : function(){
|
1369 |
+
drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
|
1370 |
+
var ctx = this.ctx;
|
1371 |
+
ctx.fillStyle = this.fillColor;
|
1372 |
+
ctx.fill();
|
1373 |
+
ctx.closePath();
|
1374 |
+
|
1375 |
+
ctx.textAlign = "left";
|
1376 |
+
ctx.textBaseline = "middle";
|
1377 |
+
ctx.fillStyle = this.titleTextColor;
|
1378 |
+
ctx.font = this.titleFont;
|
1379 |
+
|
1380 |
+
ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
|
1381 |
+
|
1382 |
+
ctx.font = this.font;
|
1383 |
+
helpers.each(this.labels,function(label,index){
|
1384 |
+
ctx.fillStyle = this.textColor;
|
1385 |
+
ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
|
1386 |
+
|
1387 |
+
//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
|
1388 |
+
//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1389 |
+
//Instead we'll make a white filled block to put the legendColour palette over.
|
1390 |
+
|
1391 |
+
ctx.fillStyle = this.legendColorBackground;
|
1392 |
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1393 |
+
|
1394 |
+
ctx.fillStyle = this.legendColors[index].fill;
|
1395 |
+
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
|
1396 |
+
|
1397 |
+
|
1398 |
+
},this);
|
1399 |
+
}
|
1400 |
+
});
|
1401 |
+
|
1402 |
+
Chart.Scale = Chart.Element.extend({
|
1403 |
+
initialize : function(){
|
1404 |
+
this.fit();
|
1405 |
+
},
|
1406 |
+
buildYLabels : function(){
|
1407 |
+
this.yLabels = [];
|
1408 |
+
|
1409 |
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
1410 |
+
|
1411 |
+
for (var i=0; i<=this.steps; i++){
|
1412 |
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
1413 |
+
}
|
1414 |
+
this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
|
1415 |
+
},
|
1416 |
+
addXLabel : function(label){
|
1417 |
+
this.xLabels.push(label);
|
1418 |
+
this.valuesCount++;
|
1419 |
+
this.fit();
|
1420 |
+
},
|
1421 |
+
removeXLabel : function(){
|
1422 |
+
this.xLabels.shift();
|
1423 |
+
this.valuesCount--;
|
1424 |
+
this.fit();
|
1425 |
+
},
|
1426 |
+
// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
|
1427 |
+
fit: function(){
|
1428 |
+
// First we need the width of the yLabels, assuming the xLabels aren't rotated
|
1429 |
+
|
1430 |
+
// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
|
1431 |
+
this.startPoint = (this.display) ? this.fontSize : 0;
|
1432 |
+
this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
|
1433 |
+
|
1434 |
+
// Apply padding settings to the start and end point.
|
1435 |
+
this.startPoint += this.padding;
|
1436 |
+
this.endPoint -= this.padding;
|
1437 |
+
|
1438 |
+
// Cache the starting height, so can determine if we need to recalculate the scale yAxis
|
1439 |
+
var cachedHeight = this.endPoint - this.startPoint,
|
1440 |
+
cachedYLabelWidth;
|
1441 |
+
|
1442 |
+
// Build the current yLabels so we have an idea of what size they'll be to start
|
1443 |
+
/*
|
1444 |
+
* This sets what is returned from calculateScaleRange as static properties of this class:
|
1445 |
+
*
|
1446 |
+
this.steps;
|
1447 |
+
this.stepValue;
|
1448 |
+
this.min;
|
1449 |
+
this.max;
|
1450 |
+
*
|
1451 |
+
*/
|
1452 |
+
this.calculateYRange(cachedHeight);
|
1453 |
+
|
1454 |
+
// With these properties set we can now build the array of yLabels
|
1455 |
+
// and also the width of the largest yLabel
|
1456 |
+
this.buildYLabels();
|
1457 |
+
|
1458 |
+
this.calculateXLabelRotation();
|
1459 |
+
|
1460 |
+
while((cachedHeight > this.endPoint - this.startPoint)){
|
1461 |
+
cachedHeight = this.endPoint - this.startPoint;
|
1462 |
+
cachedYLabelWidth = this.yLabelWidth;
|
1463 |
+
|
1464 |
+
this.calculateYRange(cachedHeight);
|
1465 |
+
this.buildYLabels();
|
1466 |
+
|
1467 |
+
// Only go through the xLabel loop again if the yLabel width has changed
|
1468 |
+
if (cachedYLabelWidth < this.yLabelWidth){
|
1469 |
+
this.calculateXLabelRotation();
|
1470 |
+
}
|
1471 |
+
}
|
1472 |
+
|
1473 |
+
},
|
1474 |
+
calculateXLabelRotation : function(){
|
1475 |
+
//Get the width of each grid by calculating the difference
|
1476 |
+
//between x offsets between 0 and 1.
|
1477 |
+
|
1478 |
+
this.ctx.font = this.font;
|
1479 |
+
|
1480 |
+
var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
|
1481 |
+
lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
|
1482 |
+
firstRotated,
|
1483 |
+
lastRotated;
|
1484 |
+
|
1485 |
+
|
1486 |
+
this.xScalePaddingRight = lastWidth/2 + 3;
|
1487 |
+
this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
|
1488 |
+
|
1489 |
+
this.xLabelRotation = 0;
|
1490 |
+
if (this.display){
|
1491 |
+
var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
|
1492 |
+
cosRotation,
|
1493 |
+
firstRotatedWidth;
|
1494 |
+
this.xLabelWidth = originalLabelWidth;
|
1495 |
+
//Allow 3 pixels x2 padding either side for label readability
|
1496 |
+
var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
|
1497 |
+
|
1498 |
+
//Max label rotate should be 90 - also act as a loop counter
|
1499 |
+
while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
|
1500 |
+
cosRotation = Math.cos(toRadians(this.xLabelRotation));
|
1501 |
+
|
1502 |
+
firstRotated = cosRotation * firstWidth;
|
1503 |
+
lastRotated = cosRotation * lastWidth;
|
1504 |
+
|
1505 |
+
// We're right aligning the text now.
|
1506 |
+
if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
|
1507 |
+
this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
|
1508 |
+
}
|
1509 |
+
this.xScalePaddingRight = this.fontSize/2;
|
1510 |
+
|
1511 |
+
|
1512 |
+
this.xLabelRotation++;
|
1513 |
+
this.xLabelWidth = cosRotation * originalLabelWidth;
|
1514 |
+
|
1515 |
+
}
|
1516 |
+
if (this.xLabelRotation > 0){
|
1517 |
+
this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
|
1518 |
+
}
|
1519 |
+
}
|
1520 |
+
else{
|
1521 |
+
this.xLabelWidth = 0;
|
1522 |
+
this.xScalePaddingRight = this.padding;
|
1523 |
+
this.xScalePaddingLeft = this.padding;
|
1524 |
+
}
|
1525 |
+
|
1526 |
+
},
|
1527 |
+
// Needs to be overidden in each Chart type
|
1528 |
+
// Otherwise we need to pass all the data into the scale class
|
1529 |
+
calculateYRange: noop,
|
1530 |
+
drawingArea: function(){
|
1531 |
+
return this.startPoint - this.endPoint;
|
1532 |
+
},
|
1533 |
+
calculateY : function(value){
|
1534 |
+
var scalingFactor = this.drawingArea() / (this.min - this.max);
|
1535 |
+
return this.endPoint - (scalingFactor * (value - this.min));
|
1536 |
+
},
|
1537 |
+
calculateX : function(index){
|
1538 |
+
var isRotated = (this.xLabelRotation > 0),
|
1539 |
+
// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
|
1540 |
+
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
|
1541 |
+
valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
|
1542 |
+
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
|
1543 |
+
|
1544 |
+
if (this.offsetGridLines){
|
1545 |
+
valueOffset += (valueWidth/2);
|
1546 |
+
}
|
1547 |
+
|
1548 |
+
return Math.round(valueOffset);
|
1549 |
+
},
|
1550 |
+
update : function(newProps){
|
1551 |
+
helpers.extend(this, newProps);
|
1552 |
+
this.fit();
|
1553 |
+
},
|
1554 |
+
draw : function(){
|
1555 |
+
var ctx = this.ctx,
|
1556 |
+
yLabelGap = (this.endPoint - this.startPoint) / this.steps,
|
1557 |
+
xStart = Math.round(this.xScalePaddingLeft);
|
1558 |
+
if (this.display){
|
1559 |
+
ctx.fillStyle = this.textColor;
|
1560 |
+
ctx.font = this.font;
|
1561 |
+
each(this.yLabels,function(labelString,index){
|
1562 |
+
var yLabelCenter = this.endPoint - (yLabelGap * index),
|
1563 |
+
linePositionY = Math.round(yLabelCenter);
|
1564 |
+
|
1565 |
+
ctx.textAlign = "right";
|
1566 |
+
ctx.textBaseline = "middle";
|
1567 |
+
if (this.showLabels){
|
1568 |
+
ctx.fillText(labelString,xStart - 10,yLabelCenter);
|
1569 |
+
}
|
1570 |
+
ctx.beginPath();
|
1571 |
+
if (index > 0){
|
1572 |
+
// This is a grid line in the centre, so drop that
|
1573 |
+
ctx.lineWidth = this.gridLineWidth;
|
1574 |
+
ctx.strokeStyle = this.gridLineColor;
|
1575 |
+
} else {
|
1576 |
+
// This is the first line on the scale
|
1577 |
+
ctx.lineWidth = this.lineWidth;
|
1578 |
+
ctx.strokeStyle = this.lineColor;
|
1579 |
+
}
|
1580 |
+
|
1581 |
+
linePositionY += helpers.aliasPixel(ctx.lineWidth);
|
1582 |
+
|
1583 |
+
ctx.moveTo(xStart, linePositionY);
|
1584 |
+
ctx.lineTo(this.width, linePositionY);
|
1585 |
+
ctx.stroke();
|
1586 |
+
ctx.closePath();
|
1587 |
+
|
1588 |
+
ctx.lineWidth = this.lineWidth;
|
1589 |
+
ctx.strokeStyle = this.lineColor;
|
1590 |
+
ctx.beginPath();
|
1591 |
+
ctx.moveTo(xStart - 5, linePositionY);
|
1592 |
+
ctx.lineTo(xStart, linePositionY);
|
1593 |
+
ctx.stroke();
|
1594 |
+
ctx.closePath();
|
1595 |
+
|
1596 |
+
},this);
|
1597 |
+
|
1598 |
+
each(this.xLabels,function(label,index){
|
1599 |
+
var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
|
1600 |
+
// Check to see if line/bar here and decide where to place the line
|
1601 |
+
linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
|
1602 |
+
isRotated = (this.xLabelRotation > 0);
|
1603 |
+
|
1604 |
+
ctx.beginPath();
|
1605 |
+
|
1606 |
+
if (index > 0){
|
1607 |
+
// This is a grid line in the centre, so drop that
|
1608 |
+
ctx.lineWidth = this.gridLineWidth;
|
1609 |
+
ctx.strokeStyle = this.gridLineColor;
|
1610 |
+
} else {
|
1611 |
+
// This is the first line on the scale
|
1612 |
+
ctx.lineWidth = this.lineWidth;
|
1613 |
+
ctx.strokeStyle = this.lineColor;
|
1614 |
+
}
|
1615 |
+
ctx.moveTo(linePos,this.endPoint);
|
1616 |
+
ctx.lineTo(linePos,this.startPoint - 3);
|
1617 |
+
ctx.stroke();
|
1618 |
+
ctx.closePath();
|
1619 |
+
|
1620 |
+
|
1621 |
+
ctx.lineWidth = this.lineWidth;
|
1622 |
+
ctx.strokeStyle = this.lineColor;
|
1623 |
+
|
1624 |
+
|
1625 |
+
// Small lines at the bottom of the base grid line
|
1626 |
+
ctx.beginPath();
|
1627 |
+
ctx.moveTo(linePos,this.endPoint);
|
1628 |
+
ctx.lineTo(linePos,this.endPoint + 5);
|
1629 |
+
ctx.stroke();
|
1630 |
+
ctx.closePath();
|
1631 |
+
|
1632 |
+
ctx.save();
|
1633 |
+
ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
|
1634 |
+
ctx.rotate(toRadians(this.xLabelRotation)*-1);
|
1635 |
+
ctx.font = this.font;
|
1636 |
+
ctx.textAlign = (isRotated) ? "right" : "center";
|
1637 |
+
ctx.textBaseline = (isRotated) ? "middle" : "top";
|
1638 |
+
ctx.fillText(label, 0, 0);
|
1639 |
+
ctx.restore();
|
1640 |
+
},this);
|
1641 |
+
|
1642 |
+
}
|
1643 |
+
}
|
1644 |
+
|
1645 |
+
});
|
1646 |
+
|
1647 |
+
Chart.RadialScale = Chart.Element.extend({
|
1648 |
+
initialize: function(){
|
1649 |
+
this.size = min([this.height, this.width]);
|
1650 |
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
1651 |
+
},
|
1652 |
+
calculateCenterOffset: function(value){
|
1653 |
+
// Take into account half font size + the yPadding of the top value
|
1654 |
+
var scalingFactor = this.drawingArea / (this.max - this.min);
|
1655 |
+
|
1656 |
+
return (value - this.min) * scalingFactor;
|
1657 |
+
},
|
1658 |
+
update : function(){
|
1659 |
+
if (!this.lineArc){
|
1660 |
+
this.setScaleSize();
|
1661 |
+
} else {
|
1662 |
+
this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
|
1663 |
+
}
|
1664 |
+
this.buildYLabels();
|
1665 |
+
},
|
1666 |
+
buildYLabels: function(){
|
1667 |
+
this.yLabels = [];
|
1668 |
+
|
1669 |
+
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
1670 |
+
|
1671 |
+
for (var i=0; i<=this.steps; i++){
|
1672 |
+
this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
|
1673 |
+
}
|
1674 |
+
},
|
1675 |
+
getCircumference : function(){
|
1676 |
+
return ((Math.PI*2) / this.valuesCount);
|
1677 |
+
},
|
1678 |
+
setScaleSize: function(){
|
1679 |
+
/*
|
1680 |
+
* Right, this is really confusing and there is a lot of maths going on here
|
1681 |
+
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
|
1682 |
+
*
|
1683 |
+
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
|
1684 |
+
*
|
1685 |
+
* Solution:
|
1686 |
+
*
|
1687 |
+
* We assume the radius of the polygon is half the size of the canvas at first
|
1688 |
+
* at each index we check if the text overlaps.
|
1689 |
+
*
|
1690 |
+
* Where it does, we store that angle and that index.
|
1691 |
+
*
|
1692 |
+
* After finding the largest index and angle we calculate how much we need to remove
|
1693 |
+
* from the shape radius to move the point inwards by that x.
|
1694 |
+
*
|
1695 |
+
* We average the left and right distances to get the maximum shape radius that can fit in the box
|
1696 |
+
* along with labels.
|
1697 |
+
*
|
1698 |
+
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
|
1699 |
+
* on each side, removing that from the size, halving it and adding the left x protrusion width.
|
1700 |
+
*
|
1701 |
+
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
|
1702 |
+
* and position it in the most space efficient manner
|
1703 |
+
*
|
1704 |
+
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
|
1705 |
+
*/
|
1706 |
+
|
1707 |
+
|
1708 |
+
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
|
1709 |
+
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
|
1710 |
+
var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
|
1711 |
+
pointPosition,
|
1712 |
+
i,
|
1713 |
+
textWidth,
|
1714 |
+
halfTextWidth,
|
1715 |
+
furthestRight = this.width,
|
1716 |
+
furthestRightIndex,
|
1717 |
+
furthestRightAngle,
|
1718 |
+
furthestLeft = 0,
|
1719 |
+
furthestLeftIndex,
|
1720 |
+
furthestLeftAngle,
|
1721 |
+
xProtrusionLeft,
|
1722 |
+
xProtrusionRight,
|
1723 |
+
radiusReductionRight,
|
1724 |
+
radiusReductionLeft,
|
1725 |
+
maxWidthRadius;
|
1726 |
+
this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
1727 |
+
for (i=0;i<this.valuesCount;i++){
|
1728 |
+
// 5px to space the text slightly out - similar to what we do in the draw function.
|
1729 |
+
pointPosition = this.getPointPosition(i, largestPossibleRadius);
|
1730 |
+
textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
|
1731 |
+
if (i === 0 || i === this.valuesCount/2){
|
1732 |
+
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
|
1733 |
+
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
|
1734 |
+
// w/left and right text sizes
|
1735 |
+
halfTextWidth = textWidth/2;
|
1736 |
+
if (pointPosition.x + halfTextWidth > furthestRight) {
|
1737 |
+
furthestRight = pointPosition.x + halfTextWidth;
|
1738 |
+
furthestRightIndex = i;
|
1739 |
+
}
|
1740 |
+
if (pointPosition.x - halfTextWidth < furthestLeft) {
|
1741 |
+
furthestLeft = pointPosition.x - halfTextWidth;
|
1742 |
+
furthestLeftIndex = i;
|
1743 |
+
}
|
1744 |
+
}
|
1745 |
+
else if (i < this.valuesCount/2) {
|
1746 |
+
// Less than half the values means we'll left align the text
|
1747 |
+
if (pointPosition.x + textWidth > furthestRight) {
|
1748 |
+
furthestRight = pointPosition.x + textWidth;
|
1749 |
+
furthestRightIndex = i;
|
1750 |
+
}
|
1751 |
+
}
|
1752 |
+
else if (i > this.valuesCount/2){
|
1753 |
+
// More than half the values means we'll right align the text
|
1754 |
+
if (pointPosition.x - textWidth < furthestLeft) {
|
1755 |
+
furthestLeft = pointPosition.x - textWidth;
|
1756 |
+
furthestLeftIndex = i;
|
1757 |
+
}
|
1758 |
+
}
|
1759 |
+
}
|
1760 |
+
|
1761 |
+
xProtrusionLeft = furthestLeft;
|
1762 |
+
|
1763 |
+
xProtrusionRight = Math.ceil(furthestRight - this.width);
|
1764 |
+
|
1765 |
+
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
|
1766 |
+
|
1767 |
+
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
|
1768 |
+
|
1769 |
+
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
|
1770 |
+
|
1771 |
+
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
|
1772 |
+
|
1773 |
+
// Ensure we actually need to reduce the size of the chart
|
1774 |
+
radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
|
1775 |
+
radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
|
1776 |
+
|
1777 |
+
this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
|
1778 |
+
|
1779 |
+
//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
|
1780 |
+
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
|
1781 |
+
|
1782 |
+
},
|
1783 |
+
setCenterPoint: function(leftMovement, rightMovement){
|
1784 |
+
|
1785 |
+
var maxRight = this.width - rightMovement - this.drawingArea,
|
1786 |
+
maxLeft = leftMovement + this.drawingArea;
|
1787 |
+
|
1788 |
+
this.xCenter = (maxLeft + maxRight)/2;
|
1789 |
+
// Always vertically in the centre as the text height doesn't change
|
1790 |
+
this.yCenter = (this.height/2);
|
1791 |
+
},
|
1792 |
+
|
1793 |
+
getIndexAngle : function(index){
|
1794 |
+
var angleMultiplier = (Math.PI * 2) / this.valuesCount;
|
1795 |
+
// Start from the top instead of right, so remove a quarter of the circle
|
1796 |
+
|
1797 |
+
return index * angleMultiplier - (Math.PI/2);
|
1798 |
+
},
|
1799 |
+
getPointPosition : function(index, distanceFromCenter){
|
1800 |
+
var thisAngle = this.getIndexAngle(index);
|
1801 |
+
return {
|
1802 |
+
x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
|
1803 |
+
y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
|
1804 |
+
};
|
1805 |
+
},
|
1806 |
+
draw: function(){
|
1807 |
+
if (this.display){
|
1808 |
+
var ctx = this.ctx;
|
1809 |
+
each(this.yLabels, function(label, index){
|
1810 |
+
// Don't draw a centre value
|
1811 |
+
if (index > 0){
|
1812 |
+
var yCenterOffset = index * (this.drawingArea/this.steps),
|
1813 |
+
yHeight = this.yCenter - yCenterOffset,
|
1814 |
+
pointPosition;
|
1815 |
+
|
1816 |
+
// Draw circular lines around the scale
|
1817 |
+
if (this.lineWidth > 0){
|
1818 |
+
ctx.strokeStyle = this.lineColor;
|
1819 |
+
ctx.lineWidth = this.lineWidth;
|
1820 |
+
|
1821 |
+
if(this.lineArc){
|
1822 |
+
ctx.beginPath();
|
1823 |
+
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
|
1824 |
+
ctx.closePath();
|
1825 |
+
ctx.stroke();
|
1826 |
+
} else{
|
1827 |
+
ctx.beginPath();
|
1828 |
+
for (var i=0;i<this.valuesCount;i++)
|
1829 |
+
{
|
1830 |
+
pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
|
1831 |
+
if (i === 0){
|
1832 |
+
ctx.moveTo(pointPosition.x, pointPosition.y);
|
1833 |
+
} else {
|
1834 |
+
ctx.lineTo(pointPosition.x, pointPosition.y);
|
1835 |
+
}
|
1836 |
+
}
|
1837 |
+
ctx.closePath();
|
1838 |
+
ctx.stroke();
|
1839 |
+
}
|
1840 |
+
}
|
1841 |
+
if(this.showLabels){
|
1842 |
+
ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
|
1843 |
+
if (this.showLabelBackdrop){
|
1844 |
+
var labelWidth = ctx.measureText(label).width;
|
1845 |
+
ctx.fillStyle = this.backdropColor;
|
1846 |
+
ctx.fillRect(
|
1847 |
+
this.xCenter - labelWidth/2 - this.backdropPaddingX,
|
1848 |
+
yHeight - this.fontSize/2 - this.backdropPaddingY,
|
1849 |
+
labelWidth + this.backdropPaddingX*2,
|
1850 |
+
this.fontSize + this.backdropPaddingY*2
|
1851 |
+
);
|
1852 |
+
}
|
1853 |
+
ctx.textAlign = 'center';
|
1854 |
+
ctx.textBaseline = "middle";
|
1855 |
+
ctx.fillStyle = this.fontColor;
|
1856 |
+
ctx.fillText(label, this.xCenter, yHeight);
|
1857 |
+
}
|
1858 |
+
}
|
1859 |
+
}, this);
|
1860 |
+
|
1861 |
+
if (!this.lineArc){
|
1862 |
+
ctx.lineWidth = this.angleLineWidth;
|
1863 |
+
ctx.strokeStyle = this.angleLineColor;
|
1864 |
+
for (var i = this.valuesCount - 1; i >= 0; i--) {
|
1865 |
+
if (this.angleLineWidth > 0){
|
1866 |
+
var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
|
1867 |
+
ctx.beginPath();
|
1868 |
+
ctx.moveTo(this.xCenter, this.yCenter);
|
1869 |
+
ctx.lineTo(outerPosition.x, outerPosition.y);
|
1870 |
+
ctx.stroke();
|
1871 |
+
ctx.closePath();
|
1872 |
+
}
|
1873 |
+
// Extra 3px out for some label spacing
|
1874 |
+
var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
|
1875 |
+
ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
|
1876 |
+
ctx.fillStyle = this.pointLabelFontColor;
|
1877 |
+
|
1878 |
+
var labelsCount = this.labels.length,
|
1879 |
+
halfLabelsCount = this.labels.length/2,
|
1880 |
+
quarterLabelsCount = halfLabelsCount/2,
|
1881 |
+
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
|
1882 |
+
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
|
1883 |
+
if (i === 0){
|
1884 |
+
ctx.textAlign = 'center';
|
1885 |
+
} else if(i === halfLabelsCount){
|
1886 |
+
ctx.textAlign = 'center';
|
1887 |
+
} else if (i < halfLabelsCount){
|
1888 |
+
ctx.textAlign = 'left';
|
1889 |
+
} else {
|
1890 |
+
ctx.textAlign = 'right';
|
1891 |
+
}
|
1892 |
+
|
1893 |
+
// Set the correct text baseline based on outer positioning
|
1894 |
+
if (exactQuarter){
|
1895 |
+
ctx.textBaseline = 'middle';
|
1896 |
+
} else if (upperHalf){
|
1897 |
+
ctx.textBaseline = 'bottom';
|
1898 |
+
} else {
|
1899 |
+
ctx.textBaseline = 'top';
|
1900 |
+
}
|
1901 |
+
|
1902 |
+
ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
|
1903 |
+
}
|
1904 |
+
}
|
1905 |
+
}
|
1906 |
+
}
|
1907 |
+
});
|
1908 |
+
|
1909 |
+
// Attach global event to resize each chart instance when the browser resizes
|
1910 |
+
helpers.addEvent(window, "resize", (function(){
|
1911 |
+
// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
|
1912 |
+
var timeout;
|
1913 |
+
return function(){
|
1914 |
+
clearTimeout(timeout);
|
1915 |
+
timeout = setTimeout(function(){
|
1916 |
+
each(Chart.instances,function(instance){
|
1917 |
+
// If the responsive flag is set in the chart instance config
|
1918 |
+
// Cascade the resize event down to the chart.
|
1919 |
+
if (instance.options.responsive){
|
1920 |
+
instance.resize(instance.render, true);
|
1921 |
+
}
|
1922 |
+
});
|
1923 |
+
}, 50);
|
1924 |
+
};
|
1925 |
+
})());
|
1926 |
+
|
1927 |
+
|
1928 |
+
if (amd) {
|
1929 |
+
define(function(){
|
1930 |
+
return Chart;
|
1931 |
+
});
|
1932 |
+
} else if (typeof module === 'object' && module.exports) {
|
1933 |
+
module.exports = Chart;
|
1934 |
+
}
|
1935 |
+
|
1936 |
+
root.Chart = Chart;
|
1937 |
+
|
1938 |
+
Chart.noConflict = function(){
|
1939 |
+
root.Chart = previous;
|
1940 |
+
return Chart;
|
1941 |
+
};
|
1942 |
+
|
1943 |
+
}).call(this);
|
1944 |
+
|
1945 |
+
(function(){
|
1946 |
+
"use strict";
|
1947 |
+
|
1948 |
+
var root = this,
|
1949 |
+
Chart = root.Chart,
|
1950 |
+
helpers = Chart.helpers;
|
1951 |
+
|
1952 |
+
|
1953 |
+
var defaultConfig = {
|
1954 |
+
//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
1955 |
+
scaleBeginAtZero : true,
|
1956 |
+
|
1957 |
+
//Boolean - Whether grid lines are shown across the chart
|
1958 |
+
scaleShowGridLines : true,
|
1959 |
+
|
1960 |
+
//String - Colour of the grid lines
|
1961 |
+
scaleGridLineColor : "rgba(0,0,0,.05)",
|
1962 |
+
|
1963 |
+
//Number - Width of the grid lines
|
1964 |
+
scaleGridLineWidth : 1,
|
1965 |
+
|
1966 |
+
//Boolean - If there is a stroke on each bar
|
1967 |
+
barShowStroke : true,
|
1968 |
+
|
1969 |
+
//Number - Pixel width of the bar stroke
|
1970 |
+
barStrokeWidth : 2,
|
1971 |
+
|
1972 |
+
//Number - Spacing between each of the X value sets
|
1973 |
+
barValueSpacing : 5,
|
1974 |
+
|
1975 |
+
//Number - Spacing between data sets within X values
|
1976 |
+
barDatasetSpacing : 1,
|
1977 |
+
|
1978 |
+
//String - A legend template
|
1979 |
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
1980 |
+
|
1981 |
+
};
|
1982 |
+
|
1983 |
+
|
1984 |
+
Chart.Type.extend({
|
1985 |
+
name: "Bar",
|
1986 |
+
defaults : defaultConfig,
|
1987 |
+
initialize: function(data){
|
1988 |
+
|
1989 |
+
//Expose options as a scope variable here so we can access it in the ScaleClass
|
1990 |
+
var options = this.options;
|
1991 |
+
|
1992 |
+
this.ScaleClass = Chart.Scale.extend({
|
1993 |
+
offsetGridLines : true,
|
1994 |
+
calculateBarX : function(datasetCount, datasetIndex, barIndex){
|
1995 |
+
//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
|
1996 |
+
var xWidth = this.calculateBaseWidth(),
|
1997 |
+
xAbsolute = this.calculateX(barIndex) - (xWidth/2),
|
1998 |
+
barWidth = this.calculateBarWidth(datasetCount);
|
1999 |
+
|
2000 |
+
return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
|
2001 |
+
},
|
2002 |
+
calculateBaseWidth : function(){
|
2003 |
+
return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
|
2004 |
+
},
|
2005 |
+
calculateBarWidth : function(datasetCount){
|
2006 |
+
//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
|
2007 |
+
var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
|
2008 |
+
|
2009 |
+
return (baseWidth / datasetCount);
|
2010 |
+
}
|
2011 |
+
});
|
2012 |
+
|
2013 |
+
this.datasets = [];
|
2014 |
+
|
2015 |
+
//Set up tooltip events on the chart
|
2016 |
+
if (this.options.showTooltips){
|
2017 |
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2018 |
+
var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
|
2019 |
+
|
2020 |
+
this.eachBars(function(bar){
|
2021 |
+
bar.restore(['fillColor', 'strokeColor']);
|
2022 |
+
});
|
2023 |
+
helpers.each(activeBars, function(activeBar){
|
2024 |
+
activeBar.fillColor = activeBar.highlightFill;
|
2025 |
+
activeBar.strokeColor = activeBar.highlightStroke;
|
2026 |
+
});
|
2027 |
+
this.showTooltip(activeBars);
|
2028 |
+
});
|
2029 |
+
}
|
2030 |
+
|
2031 |
+
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
2032 |
+
this.BarClass = Chart.Rectangle.extend({
|
2033 |
+
strokeWidth : this.options.barStrokeWidth,
|
2034 |
+
showStroke : this.options.barShowStroke,
|
2035 |
+
ctx : this.chart.ctx
|
2036 |
+
});
|
2037 |
+
|
2038 |
+
//Iterate through each of the datasets, and build this into a property of the chart
|
2039 |
+
helpers.each(data.datasets,function(dataset,datasetIndex){
|
2040 |
+
|
2041 |
+
var datasetObject = {
|
2042 |
+
label : dataset.label || null,
|
2043 |
+
fillColor : dataset.fillColor,
|
2044 |
+
strokeColor : dataset.strokeColor,
|
2045 |
+
bars : []
|
2046 |
+
};
|
2047 |
+
|
2048 |
+
this.datasets.push(datasetObject);
|
2049 |
+
|
2050 |
+
helpers.each(dataset.data,function(dataPoint,index){
|
2051 |
+
//Add a new point for each piece of data, passing any required data to draw.
|
2052 |
+
datasetObject.bars.push(new this.BarClass({
|
2053 |
+
value : dataPoint,
|
2054 |
+
label : data.labels[index],
|
2055 |
+
datasetLabel: dataset.label,
|
2056 |
+
strokeColor : dataset.strokeColor,
|
2057 |
+
fillColor : dataset.fillColor,
|
2058 |
+
highlightFill : dataset.highlightFill || dataset.fillColor,
|
2059 |
+
highlightStroke : dataset.highlightStroke || dataset.strokeColor
|
2060 |
+
}));
|
2061 |
+
},this);
|
2062 |
+
|
2063 |
+
},this);
|
2064 |
+
|
2065 |
+
this.buildScale(data.labels);
|
2066 |
+
|
2067 |
+
this.BarClass.prototype.base = this.scale.endPoint;
|
2068 |
+
|
2069 |
+
this.eachBars(function(bar, index, datasetIndex){
|
2070 |
+
helpers.extend(bar, {
|
2071 |
+
width : this.scale.calculateBarWidth(this.datasets.length),
|
2072 |
+
x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
|
2073 |
+
y: this.scale.endPoint
|
2074 |
+
});
|
2075 |
+
bar.save();
|
2076 |
+
}, this);
|
2077 |
+
|
2078 |
+
this.render();
|
2079 |
+
},
|
2080 |
+
update : function(){
|
2081 |
+
this.scale.update();
|
2082 |
+
// Reset any highlight colours before updating.
|
2083 |
+
helpers.each(this.activeElements, function(activeElement){
|
2084 |
+
activeElement.restore(['fillColor', 'strokeColor']);
|
2085 |
+
});
|
2086 |
+
|
2087 |
+
this.eachBars(function(bar){
|
2088 |
+
bar.save();
|
2089 |
+
});
|
2090 |
+
this.render();
|
2091 |
+
},
|
2092 |
+
eachBars : function(callback){
|
2093 |
+
helpers.each(this.datasets,function(dataset, datasetIndex){
|
2094 |
+
helpers.each(dataset.bars, callback, this, datasetIndex);
|
2095 |
+
},this);
|
2096 |
+
},
|
2097 |
+
getBarsAtEvent : function(e){
|
2098 |
+
var barsArray = [],
|
2099 |
+
eventPosition = helpers.getRelativePosition(e),
|
2100 |
+
datasetIterator = function(dataset){
|
2101 |
+
barsArray.push(dataset.bars[barIndex]);
|
2102 |
+
},
|
2103 |
+
barIndex;
|
2104 |
+
|
2105 |
+
for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
|
2106 |
+
for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
|
2107 |
+
if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
|
2108 |
+
helpers.each(this.datasets, datasetIterator);
|
2109 |
+
return barsArray;
|
2110 |
+
}
|
2111 |
+
}
|
2112 |
+
}
|
2113 |
+
|
2114 |
+
return barsArray;
|
2115 |
+
},
|
2116 |
+
buildScale : function(labels){
|
2117 |
+
var self = this;
|
2118 |
+
|
2119 |
+
var dataTotal = function(){
|
2120 |
+
var values = [];
|
2121 |
+
self.eachBars(function(bar){
|
2122 |
+
values.push(bar.value);
|
2123 |
+
});
|
2124 |
+
return values;
|
2125 |
+
};
|
2126 |
+
|
2127 |
+
var scaleOptions = {
|
2128 |
+
templateString : this.options.scaleLabel,
|
2129 |
+
height : this.chart.height,
|
2130 |
+
width : this.chart.width,
|
2131 |
+
ctx : this.chart.ctx,
|
2132 |
+
textColor : this.options.scaleFontColor,
|
2133 |
+
fontSize : this.options.scaleFontSize,
|
2134 |
+
fontStyle : this.options.scaleFontStyle,
|
2135 |
+
fontFamily : this.options.scaleFontFamily,
|
2136 |
+
valuesCount : labels.length,
|
2137 |
+
beginAtZero : this.options.scaleBeginAtZero,
|
2138 |
+
integersOnly : this.options.scaleIntegersOnly,
|
2139 |
+
calculateYRange: function(currentHeight){
|
2140 |
+
var updatedRanges = helpers.calculateScaleRange(
|
2141 |
+
dataTotal(),
|
2142 |
+
currentHeight,
|
2143 |
+
this.fontSize,
|
2144 |
+
this.beginAtZero,
|
2145 |
+
this.integersOnly
|
2146 |
+
);
|
2147 |
+
helpers.extend(this, updatedRanges);
|
2148 |
+
},
|
2149 |
+
xLabels : labels,
|
2150 |
+
font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
2151 |
+
lineWidth : this.options.scaleLineWidth,
|
2152 |
+
lineColor : this.options.scaleLineColor,
|
2153 |
+
gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
2154 |
+
gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
2155 |
+
padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
|
2156 |
+
showLabels : this.options.scaleShowLabels,
|
2157 |
+
display : this.options.showScale
|
2158 |
+
};
|
2159 |
+
|
2160 |
+
if (this.options.scaleOverride){
|
2161 |
+
helpers.extend(scaleOptions, {
|
2162 |
+
calculateYRange: helpers.noop,
|
2163 |
+
steps: this.options.scaleSteps,
|
2164 |
+
stepValue: this.options.scaleStepWidth,
|
2165 |
+
min: this.options.scaleStartValue,
|
2166 |
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2167 |
+
});
|
2168 |
+
}
|
2169 |
+
|
2170 |
+
this.scale = new this.ScaleClass(scaleOptions);
|
2171 |
+
},
|
2172 |
+
addData : function(valuesArray,label){
|
2173 |
+
//Map the values array for each of the datasets
|
2174 |
+
helpers.each(valuesArray,function(value,datasetIndex){
|
2175 |
+
//Add a new point for each piece of data, passing any required data to draw.
|
2176 |
+
this.datasets[datasetIndex].bars.push(new this.BarClass({
|
2177 |
+
value : value,
|
2178 |
+
label : label,
|
2179 |
+
x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
|
2180 |
+
y: this.scale.endPoint,
|
2181 |
+
width : this.scale.calculateBarWidth(this.datasets.length),
|
2182 |
+
base : this.scale.endPoint,
|
2183 |
+
strokeColor : this.datasets[datasetIndex].strokeColor,
|
2184 |
+
fillColor : this.datasets[datasetIndex].fillColor
|
2185 |
+
}));
|
2186 |
+
},this);
|
2187 |
+
|
2188 |
+
this.scale.addXLabel(label);
|
2189 |
+
//Then re-render the chart.
|
2190 |
+
this.update();
|
2191 |
+
},
|
2192 |
+
removeData : function(){
|
2193 |
+
this.scale.removeXLabel();
|
2194 |
+
//Then re-render the chart.
|
2195 |
+
helpers.each(this.datasets,function(dataset){
|
2196 |
+
dataset.bars.shift();
|
2197 |
+
},this);
|
2198 |
+
this.update();
|
2199 |
+
},
|
2200 |
+
reflow : function(){
|
2201 |
+
helpers.extend(this.BarClass.prototype,{
|
2202 |
+
y: this.scale.endPoint,
|
2203 |
+
base : this.scale.endPoint
|
2204 |
+
});
|
2205 |
+
var newScaleProps = helpers.extend({
|
2206 |
+
height : this.chart.height,
|
2207 |
+
width : this.chart.width
|
2208 |
+
});
|
2209 |
+
this.scale.update(newScaleProps);
|
2210 |
+
},
|
2211 |
+
draw : function(ease){
|
2212 |
+
var easingDecimal = ease || 1;
|
2213 |
+
this.clear();
|
2214 |
+
|
2215 |
+
var ctx = this.chart.ctx;
|
2216 |
+
|
2217 |
+
this.scale.draw(easingDecimal);
|
2218 |
+
|
2219 |
+
//Draw all the bars for each dataset
|
2220 |
+
helpers.each(this.datasets,function(dataset,datasetIndex){
|
2221 |
+
helpers.each(dataset.bars,function(bar,index){
|
2222 |
+
if (bar.hasValue()){
|
2223 |
+
bar.base = this.scale.endPoint;
|
2224 |
+
//Transition then draw
|
2225 |
+
bar.transition({
|
2226 |
+
x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
|
2227 |
+
y : this.scale.calculateY(bar.value),
|
2228 |
+
width : this.scale.calculateBarWidth(this.datasets.length)
|
2229 |
+
}, easingDecimal).draw();
|
2230 |
+
}
|
2231 |
+
},this);
|
2232 |
+
|
2233 |
+
},this);
|
2234 |
+
}
|
2235 |
+
});
|
2236 |
+
|
2237 |
+
|
2238 |
+
}).call(this);
|
2239 |
+
(function(){
|
2240 |
+
"use strict";
|
2241 |
+
|
2242 |
+
var root = this,
|
2243 |
+
Chart = root.Chart,
|
2244 |
+
//Cache a local reference to Chart.helpers
|
2245 |
+
helpers = Chart.helpers;
|
2246 |
+
|
2247 |
+
var defaultConfig = {
|
2248 |
+
//Boolean - Whether we should show a stroke on each segment
|
2249 |
+
segmentShowStroke : true,
|
2250 |
+
|
2251 |
+
//String - The colour of each segment stroke
|
2252 |
+
segmentStrokeColor : "#fff",
|
2253 |
+
|
2254 |
+
//Number - The width of each segment stroke
|
2255 |
+
segmentStrokeWidth : 2,
|
2256 |
+
|
2257 |
+
//The percentage of the chart that we cut out of the middle.
|
2258 |
+
percentageInnerCutout : 50,
|
2259 |
+
|
2260 |
+
//Number - Amount of animation steps
|
2261 |
+
animationSteps : 100,
|
2262 |
+
|
2263 |
+
//String - Animation easing effect
|
2264 |
+
animationEasing : "easeOutBounce",
|
2265 |
+
|
2266 |
+
//Boolean - Whether we animate the rotation of the Doughnut
|
2267 |
+
animateRotate : true,
|
2268 |
+
|
2269 |
+
//Boolean - Whether we animate scaling the Doughnut from the centre
|
2270 |
+
animateScale : false,
|
2271 |
+
|
2272 |
+
//String - A legend template
|
2273 |
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
2274 |
+
|
2275 |
+
};
|
2276 |
+
|
2277 |
+
|
2278 |
+
Chart.Type.extend({
|
2279 |
+
//Passing in a name registers this chart in the Chart namespace
|
2280 |
+
name: "Doughnut",
|
2281 |
+
//Providing a defaults will also register the deafults in the chart namespace
|
2282 |
+
defaults : defaultConfig,
|
2283 |
+
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
2284 |
+
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
2285 |
+
initialize: function(data){
|
2286 |
+
|
2287 |
+
//Declare segments as a static property to prevent inheriting across the Chart type prototype
|
2288 |
+
this.segments = [];
|
2289 |
+
this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
|
2290 |
+
|
2291 |
+
this.SegmentArc = Chart.Arc.extend({
|
2292 |
+
ctx : this.chart.ctx,
|
2293 |
+
x : this.chart.width/2,
|
2294 |
+
y : this.chart.height/2
|
2295 |
+
});
|
2296 |
+
|
2297 |
+
//Set up tooltip events on the chart
|
2298 |
+
if (this.options.showTooltips){
|
2299 |
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2300 |
+
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
2301 |
+
|
2302 |
+
helpers.each(this.segments,function(segment){
|
2303 |
+
segment.restore(["fillColor"]);
|
2304 |
+
});
|
2305 |
+
helpers.each(activeSegments,function(activeSegment){
|
2306 |
+
activeSegment.fillColor = activeSegment.highlightColor;
|
2307 |
+
});
|
2308 |
+
this.showTooltip(activeSegments);
|
2309 |
+
});
|
2310 |
+
}
|
2311 |
+
this.calculateTotal(data);
|
2312 |
+
|
2313 |
+
helpers.each(data,function(datapoint, index){
|
2314 |
+
this.addData(datapoint, index, true);
|
2315 |
+
},this);
|
2316 |
+
|
2317 |
+
this.render();
|
2318 |
+
},
|
2319 |
+
getSegmentsAtEvent : function(e){
|
2320 |
+
var segmentsArray = [];
|
2321 |
+
|
2322 |
+
var location = helpers.getRelativePosition(e);
|
2323 |
+
|
2324 |
+
helpers.each(this.segments,function(segment){
|
2325 |
+
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
2326 |
+
},this);
|
2327 |
+
return segmentsArray;
|
2328 |
+
},
|
2329 |
+
addData : function(segment, atIndex, silent){
|
2330 |
+
var index = atIndex || this.segments.length;
|
2331 |
+
this.segments.splice(index, 0, new this.SegmentArc({
|
2332 |
+
value : segment.value,
|
2333 |
+
outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
|
2334 |
+
innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
|
2335 |
+
fillColor : segment.color,
|
2336 |
+
highlightColor : segment.highlight || segment.color,
|
2337 |
+
showStroke : this.options.segmentShowStroke,
|
2338 |
+
strokeWidth : this.options.segmentStrokeWidth,
|
2339 |
+
strokeColor : this.options.segmentStrokeColor,
|
2340 |
+
startAngle : Math.PI * 1.5,
|
2341 |
+
circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
|
2342 |
+
label : segment.label
|
2343 |
+
}));
|
2344 |
+
if (!silent){
|
2345 |
+
this.reflow();
|
2346 |
+
this.update();
|
2347 |
+
}
|
2348 |
+
},
|
2349 |
+
calculateCircumference : function(value){
|
2350 |
+
return (Math.PI*2)*(value / this.total);
|
2351 |
+
},
|
2352 |
+
calculateTotal : function(data){
|
2353 |
+
this.total = 0;
|
2354 |
+
helpers.each(data,function(segment){
|
2355 |
+
this.total += segment.value;
|
2356 |
+
},this);
|
2357 |
+
},
|
2358 |
+
update : function(){
|
2359 |
+
this.calculateTotal(this.segments);
|
2360 |
+
|
2361 |
+
// Reset any highlight colours before updating.
|
2362 |
+
helpers.each(this.activeElements, function(activeElement){
|
2363 |
+
activeElement.restore(['fillColor']);
|
2364 |
+
});
|
2365 |
+
|
2366 |
+
helpers.each(this.segments,function(segment){
|
2367 |
+
segment.save();
|
2368 |
+
});
|
2369 |
+
this.render();
|
2370 |
+
},
|
2371 |
+
|
2372 |
+
removeData: function(atIndex){
|
2373 |
+
var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
|
2374 |
+
this.segments.splice(indexToDelete, 1);
|
2375 |
+
this.reflow();
|
2376 |
+
this.update();
|
2377 |
+
},
|
2378 |
+
|
2379 |
+
reflow : function(){
|
2380 |
+
helpers.extend(this.SegmentArc.prototype,{
|
2381 |
+
x : this.chart.width/2,
|
2382 |
+
y : this.chart.height/2
|
2383 |
+
});
|
2384 |
+
this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
|
2385 |
+
helpers.each(this.segments, function(segment){
|
2386 |
+
segment.update({
|
2387 |
+
outerRadius : this.outerRadius,
|
2388 |
+
innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
|
2389 |
+
});
|
2390 |
+
}, this);
|
2391 |
+
},
|
2392 |
+
draw : function(easeDecimal){
|
2393 |
+
var animDecimal = (easeDecimal) ? easeDecimal : 1;
|
2394 |
+
this.clear();
|
2395 |
+
helpers.each(this.segments,function(segment,index){
|
2396 |
+
segment.transition({
|
2397 |
+
circumference : this.calculateCircumference(segment.value),
|
2398 |
+
outerRadius : this.outerRadius,
|
2399 |
+
innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
|
2400 |
+
},animDecimal);
|
2401 |
+
|
2402 |
+
segment.endAngle = segment.startAngle + segment.circumference;
|
2403 |
+
|
2404 |
+
segment.draw();
|
2405 |
+
if (index === 0){
|
2406 |
+
segment.startAngle = Math.PI * 1.5;
|
2407 |
+
}
|
2408 |
+
//Check to see if it's the last segment, if not get the next and update the start angle
|
2409 |
+
if (index < this.segments.length-1){
|
2410 |
+
this.segments[index+1].startAngle = segment.endAngle;
|
2411 |
+
}
|
2412 |
+
},this);
|
2413 |
+
|
2414 |
+
}
|
2415 |
+
});
|
2416 |
+
|
2417 |
+
Chart.types.Doughnut.extend({
|
2418 |
+
name : "Pie",
|
2419 |
+
defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
|
2420 |
+
});
|
2421 |
+
|
2422 |
+
}).call(this);
|
2423 |
+
(function(){
|
2424 |
+
"use strict";
|
2425 |
+
|
2426 |
+
var root = this,
|
2427 |
+
Chart = root.Chart,
|
2428 |
+
helpers = Chart.helpers;
|
2429 |
+
|
2430 |
+
var defaultConfig = {
|
2431 |
+
|
2432 |
+
///Boolean - Whether grid lines are shown across the chart
|
2433 |
+
scaleShowGridLines : true,
|
2434 |
+
|
2435 |
+
//String - Colour of the grid lines
|
2436 |
+
scaleGridLineColor : "rgba(0,0,0,.05)",
|
2437 |
+
|
2438 |
+
//Number - Width of the grid lines
|
2439 |
+
scaleGridLineWidth : 1,
|
2440 |
+
|
2441 |
+
//Boolean - Whether the line is curved between points
|
2442 |
+
bezierCurve : true,
|
2443 |
+
|
2444 |
+
//Number - Tension of the bezier curve between points
|
2445 |
+
bezierCurveTension : 0.4,
|
2446 |
+
|
2447 |
+
//Boolean - Whether to show a dot for each point
|
2448 |
+
pointDot : true,
|
2449 |
+
|
2450 |
+
//Number - Radius of each point dot in pixels
|
2451 |
+
pointDotRadius : 4,
|
2452 |
+
|
2453 |
+
//Number - Pixel width of point dot stroke
|
2454 |
+
pointDotStrokeWidth : 1,
|
2455 |
+
|
2456 |
+
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
2457 |
+
pointHitDetectionRadius : 20,
|
2458 |
+
|
2459 |
+
//Boolean - Whether to show a stroke for datasets
|
2460 |
+
datasetStroke : true,
|
2461 |
+
|
2462 |
+
//Number - Pixel width of dataset stroke
|
2463 |
+
datasetStrokeWidth : 2,
|
2464 |
+
|
2465 |
+
//Boolean - Whether to fill the dataset with a colour
|
2466 |
+
datasetFill : true,
|
2467 |
+
|
2468 |
+
//String - A legend template
|
2469 |
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
2470 |
+
|
2471 |
+
};
|
2472 |
+
|
2473 |
+
|
2474 |
+
Chart.Type.extend({
|
2475 |
+
name: "Line",
|
2476 |
+
defaults : defaultConfig,
|
2477 |
+
initialize: function(data){
|
2478 |
+
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
2479 |
+
this.PointClass = Chart.Point.extend({
|
2480 |
+
strokeWidth : this.options.pointDotStrokeWidth,
|
2481 |
+
radius : this.options.pointDotRadius,
|
2482 |
+
display: this.options.pointDot,
|
2483 |
+
hitDetectionRadius : this.options.pointHitDetectionRadius,
|
2484 |
+
ctx : this.chart.ctx,
|
2485 |
+
inRange : function(mouseX){
|
2486 |
+
return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
|
2487 |
+
}
|
2488 |
+
});
|
2489 |
+
|
2490 |
+
this.datasets = [];
|
2491 |
+
|
2492 |
+
//Set up tooltip events on the chart
|
2493 |
+
if (this.options.showTooltips){
|
2494 |
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2495 |
+
var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
2496 |
+
this.eachPoints(function(point){
|
2497 |
+
point.restore(['fillColor', 'strokeColor']);
|
2498 |
+
});
|
2499 |
+
helpers.each(activePoints, function(activePoint){
|
2500 |
+
activePoint.fillColor = activePoint.highlightFill;
|
2501 |
+
activePoint.strokeColor = activePoint.highlightStroke;
|
2502 |
+
});
|
2503 |
+
this.showTooltip(activePoints);
|
2504 |
+
});
|
2505 |
+
}
|
2506 |
+
|
2507 |
+
//Iterate through each of the datasets, and build this into a property of the chart
|
2508 |
+
helpers.each(data.datasets,function(dataset){
|
2509 |
+
|
2510 |
+
var datasetObject = {
|
2511 |
+
label : dataset.label || null,
|
2512 |
+
fillColor : dataset.fillColor,
|
2513 |
+
strokeColor : dataset.strokeColor,
|
2514 |
+
pointColor : dataset.pointColor,
|
2515 |
+
pointStrokeColor : dataset.pointStrokeColor,
|
2516 |
+
points : []
|
2517 |
+
};
|
2518 |
+
|
2519 |
+
this.datasets.push(datasetObject);
|
2520 |
+
|
2521 |
+
|
2522 |
+
helpers.each(dataset.data,function(dataPoint,index){
|
2523 |
+
//Add a new point for each piece of data, passing any required data to draw.
|
2524 |
+
datasetObject.points.push(new this.PointClass({
|
2525 |
+
value : dataPoint,
|
2526 |
+
label : data.labels[index],
|
2527 |
+
datasetLabel: dataset.label,
|
2528 |
+
strokeColor : dataset.pointStrokeColor,
|
2529 |
+
fillColor : dataset.pointColor,
|
2530 |
+
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
2531 |
+
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
|
2532 |
+
}));
|
2533 |
+
},this);
|
2534 |
+
|
2535 |
+
this.buildScale(data.labels);
|
2536 |
+
|
2537 |
+
|
2538 |
+
this.eachPoints(function(point, index){
|
2539 |
+
helpers.extend(point, {
|
2540 |
+
x: this.scale.calculateX(index),
|
2541 |
+
y: this.scale.endPoint
|
2542 |
+
});
|
2543 |
+
point.save();
|
2544 |
+
}, this);
|
2545 |
+
|
2546 |
+
},this);
|
2547 |
+
|
2548 |
+
|
2549 |
+
this.render();
|
2550 |
+
},
|
2551 |
+
update : function(){
|
2552 |
+
this.scale.update();
|
2553 |
+
// Reset any highlight colours before updating.
|
2554 |
+
helpers.each(this.activeElements, function(activeElement){
|
2555 |
+
activeElement.restore(['fillColor', 'strokeColor']);
|
2556 |
+
});
|
2557 |
+
this.eachPoints(function(point){
|
2558 |
+
point.save();
|
2559 |
+
});
|
2560 |
+
this.render();
|
2561 |
+
},
|
2562 |
+
eachPoints : function(callback){
|
2563 |
+
helpers.each(this.datasets,function(dataset){
|
2564 |
+
helpers.each(dataset.points,callback,this);
|
2565 |
+
},this);
|
2566 |
+
},
|
2567 |
+
getPointsAtEvent : function(e){
|
2568 |
+
var pointsArray = [],
|
2569 |
+
eventPosition = helpers.getRelativePosition(e);
|
2570 |
+
helpers.each(this.datasets,function(dataset){
|
2571 |
+
helpers.each(dataset.points,function(point){
|
2572 |
+
if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
|
2573 |
+
});
|
2574 |
+
},this);
|
2575 |
+
return pointsArray;
|
2576 |
+
},
|
2577 |
+
buildScale : function(labels){
|
2578 |
+
var self = this;
|
2579 |
+
|
2580 |
+
var dataTotal = function(){
|
2581 |
+
var values = [];
|
2582 |
+
self.eachPoints(function(point){
|
2583 |
+
values.push(point.value);
|
2584 |
+
});
|
2585 |
+
|
2586 |
+
return values;
|
2587 |
+
};
|
2588 |
+
|
2589 |
+
var scaleOptions = {
|
2590 |
+
templateString : this.options.scaleLabel,
|
2591 |
+
height : this.chart.height,
|
2592 |
+
width : this.chart.width,
|
2593 |
+
ctx : this.chart.ctx,
|
2594 |
+
textColor : this.options.scaleFontColor,
|
2595 |
+
fontSize : this.options.scaleFontSize,
|
2596 |
+
fontStyle : this.options.scaleFontStyle,
|
2597 |
+
fontFamily : this.options.scaleFontFamily,
|
2598 |
+
valuesCount : labels.length,
|
2599 |
+
beginAtZero : this.options.scaleBeginAtZero,
|
2600 |
+
integersOnly : this.options.scaleIntegersOnly,
|
2601 |
+
calculateYRange : function(currentHeight){
|
2602 |
+
var updatedRanges = helpers.calculateScaleRange(
|
2603 |
+
dataTotal(),
|
2604 |
+
currentHeight,
|
2605 |
+
this.fontSize,
|
2606 |
+
this.beginAtZero,
|
2607 |
+
this.integersOnly
|
2608 |
+
);
|
2609 |
+
helpers.extend(this, updatedRanges);
|
2610 |
+
},
|
2611 |
+
xLabels : labels,
|
2612 |
+
font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
2613 |
+
lineWidth : this.options.scaleLineWidth,
|
2614 |
+
lineColor : this.options.scaleLineColor,
|
2615 |
+
gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
2616 |
+
gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
2617 |
+
padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
|
2618 |
+
showLabels : this.options.scaleShowLabels,
|
2619 |
+
display : this.options.showScale
|
2620 |
+
};
|
2621 |
+
|
2622 |
+
if (this.options.scaleOverride){
|
2623 |
+
helpers.extend(scaleOptions, {
|
2624 |
+
calculateYRange: helpers.noop,
|
2625 |
+
steps: this.options.scaleSteps,
|
2626 |
+
stepValue: this.options.scaleStepWidth,
|
2627 |
+
min: this.options.scaleStartValue,
|
2628 |
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2629 |
+
});
|
2630 |
+
}
|
2631 |
+
|
2632 |
+
|
2633 |
+
this.scale = new Chart.Scale(scaleOptions);
|
2634 |
+
},
|
2635 |
+
addData : function(valuesArray,label){
|
2636 |
+
//Map the values array for each of the datasets
|
2637 |
+
|
2638 |
+
helpers.each(valuesArray,function(value,datasetIndex){
|
2639 |
+
//Add a new point for each piece of data, passing any required data to draw.
|
2640 |
+
this.datasets[datasetIndex].points.push(new this.PointClass({
|
2641 |
+
value : value,
|
2642 |
+
label : label,
|
2643 |
+
x: this.scale.calculateX(this.scale.valuesCount+1),
|
2644 |
+
y: this.scale.endPoint,
|
2645 |
+
strokeColor : this.datasets[datasetIndex].pointStrokeColor,
|
2646 |
+
fillColor : this.datasets[datasetIndex].pointColor
|
2647 |
+
}));
|
2648 |
+
},this);
|
2649 |
+
|
2650 |
+
this.scale.addXLabel(label);
|
2651 |
+
//Then re-render the chart.
|
2652 |
+
this.update();
|
2653 |
+
},
|
2654 |
+
removeData : function(){
|
2655 |
+
this.scale.removeXLabel();
|
2656 |
+
//Then re-render the chart.
|
2657 |
+
helpers.each(this.datasets,function(dataset){
|
2658 |
+
dataset.points.shift();
|
2659 |
+
},this);
|
2660 |
+
this.update();
|
2661 |
+
},
|
2662 |
+
reflow : function(){
|
2663 |
+
var newScaleProps = helpers.extend({
|
2664 |
+
height : this.chart.height,
|
2665 |
+
width : this.chart.width
|
2666 |
+
});
|
2667 |
+
this.scale.update(newScaleProps);
|
2668 |
+
},
|
2669 |
+
draw : function(ease){
|
2670 |
+
var easingDecimal = ease || 1;
|
2671 |
+
this.clear();
|
2672 |
+
|
2673 |
+
var ctx = this.chart.ctx;
|
2674 |
+
|
2675 |
+
// Some helper methods for getting the next/prev points
|
2676 |
+
var hasValue = function(item){
|
2677 |
+
return item.value !== null;
|
2678 |
+
},
|
2679 |
+
nextPoint = function(point, collection, index){
|
2680 |
+
return helpers.findNextWhere(collection, hasValue, index) || point;
|
2681 |
+
},
|
2682 |
+
previousPoint = function(point, collection, index){
|
2683 |
+
return helpers.findPreviousWhere(collection, hasValue, index) || point;
|
2684 |
+
};
|
2685 |
+
|
2686 |
+
this.scale.draw(easingDecimal);
|
2687 |
+
|
2688 |
+
|
2689 |
+
helpers.each(this.datasets,function(dataset){
|
2690 |
+
var pointsWithValues = helpers.where(dataset.points, hasValue);
|
2691 |
+
|
2692 |
+
//Transition each point first so that the line and point drawing isn't out of sync
|
2693 |
+
//We can use this extra loop to calculate the control points of this dataset also in this loop
|
2694 |
+
|
2695 |
+
helpers.each(dataset.points, function(point, index){
|
2696 |
+
if (point.hasValue()){
|
2697 |
+
point.transition({
|
2698 |
+
y : this.scale.calculateY(point.value),
|
2699 |
+
x : this.scale.calculateX(index)
|
2700 |
+
}, easingDecimal);
|
2701 |
+
}
|
2702 |
+
},this);
|
2703 |
+
|
2704 |
+
|
2705 |
+
// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
|
2706 |
+
// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
|
2707 |
+
if (this.options.bezierCurve){
|
2708 |
+
helpers.each(pointsWithValues, function(point, index){
|
2709 |
+
var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
|
2710 |
+
point.controlPoints = helpers.splineCurve(
|
2711 |
+
previousPoint(point, pointsWithValues, index),
|
2712 |
+
point,
|
2713 |
+
nextPoint(point, pointsWithValues, index),
|
2714 |
+
tension
|
2715 |
+
);
|
2716 |
+
|
2717 |
+
// Prevent the bezier going outside of the bounds of the graph
|
2718 |
+
|
2719 |
+
// Cap puter bezier handles to the upper/lower scale bounds
|
2720 |
+
if (point.controlPoints.outer.y > this.scale.endPoint){
|
2721 |
+
point.controlPoints.outer.y = this.scale.endPoint;
|
2722 |
+
}
|
2723 |
+
else if (point.controlPoints.outer.y < this.scale.startPoint){
|
2724 |
+
point.controlPoints.outer.y = this.scale.startPoint;
|
2725 |
+
}
|
2726 |
+
|
2727 |
+
// Cap inner bezier handles to the upper/lower scale bounds
|
2728 |
+
if (point.controlPoints.inner.y > this.scale.endPoint){
|
2729 |
+
point.controlPoints.inner.y = this.scale.endPoint;
|
2730 |
+
}
|
2731 |
+
else if (point.controlPoints.inner.y < this.scale.startPoint){
|
2732 |
+
point.controlPoints.inner.y = this.scale.startPoint;
|
2733 |
+
}
|
2734 |
+
},this);
|
2735 |
+
}
|
2736 |
+
|
2737 |
+
|
2738 |
+
//Draw the line between all the points
|
2739 |
+
ctx.lineWidth = this.options.datasetStrokeWidth;
|
2740 |
+
ctx.strokeStyle = dataset.strokeColor;
|
2741 |
+
ctx.beginPath();
|
2742 |
+
|
2743 |
+
helpers.each(pointsWithValues, function(point, index){
|
2744 |
+
if (index === 0){
|
2745 |
+
ctx.moveTo(point.x, point.y);
|
2746 |
+
}
|
2747 |
+
else{
|
2748 |
+
if(this.options.bezierCurve){
|
2749 |
+
var previous = previousPoint(point, pointsWithValues, index);
|
2750 |
+
|
2751 |
+
ctx.bezierCurveTo(
|
2752 |
+
previous.controlPoints.outer.x,
|
2753 |
+
previous.controlPoints.outer.y,
|
2754 |
+
point.controlPoints.inner.x,
|
2755 |
+
point.controlPoints.inner.y,
|
2756 |
+
point.x,
|
2757 |
+
point.y
|
2758 |
+
);
|
2759 |
+
}
|
2760 |
+
else{
|
2761 |
+
ctx.lineTo(point.x,point.y);
|
2762 |
+
}
|
2763 |
+
}
|
2764 |
+
}, this);
|
2765 |
+
|
2766 |
+
ctx.stroke();
|
2767 |
+
|
2768 |
+
if (this.options.datasetFill && pointsWithValues.length > 0){
|
2769 |
+
//Round off the line by going to the base of the chart, back to the start, then fill.
|
2770 |
+
ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
|
2771 |
+
ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
|
2772 |
+
ctx.fillStyle = dataset.fillColor;
|
2773 |
+
ctx.closePath();
|
2774 |
+
ctx.fill();
|
2775 |
+
}
|
2776 |
+
|
2777 |
+
//Now draw the points over the line
|
2778 |
+
//A little inefficient double looping, but better than the line
|
2779 |
+
//lagging behind the point positions
|
2780 |
+
helpers.each(pointsWithValues,function(point){
|
2781 |
+
point.draw();
|
2782 |
+
});
|
2783 |
+
},this);
|
2784 |
+
}
|
2785 |
+
});
|
2786 |
+
|
2787 |
+
|
2788 |
+
}).call(this);
|
2789 |
+
(function(){
|
2790 |
+
"use strict";
|
2791 |
+
|
2792 |
+
var root = this,
|
2793 |
+
Chart = root.Chart,
|
2794 |
+
//Cache a local reference to Chart.helpers
|
2795 |
+
helpers = Chart.helpers;
|
2796 |
+
|
2797 |
+
var defaultConfig = {
|
2798 |
+
//Boolean - Show a backdrop to the scale label
|
2799 |
+
scaleShowLabelBackdrop : true,
|
2800 |
+
|
2801 |
+
//String - The colour of the label backdrop
|
2802 |
+
scaleBackdropColor : "rgba(255,255,255,0.75)",
|
2803 |
+
|
2804 |
+
// Boolean - Whether the scale should begin at zero
|
2805 |
+
scaleBeginAtZero : true,
|
2806 |
+
|
2807 |
+
//Number - The backdrop padding above & below the label in pixels
|
2808 |
+
scaleBackdropPaddingY : 2,
|
2809 |
+
|
2810 |
+
//Number - The backdrop padding to the side of the label in pixels
|
2811 |
+
scaleBackdropPaddingX : 2,
|
2812 |
+
|
2813 |
+
//Boolean - Show line for each value in the scale
|
2814 |
+
scaleShowLine : true,
|
2815 |
+
|
2816 |
+
//Boolean - Stroke a line around each segment in the chart
|
2817 |
+
segmentShowStroke : true,
|
2818 |
+
|
2819 |
+
//String - The colour of the stroke on each segement.
|
2820 |
+
segmentStrokeColor : "#fff",
|
2821 |
+
|
2822 |
+
//Number - The width of the stroke value in pixels
|
2823 |
+
segmentStrokeWidth : 2,
|
2824 |
+
|
2825 |
+
//Number - Amount of animation steps
|
2826 |
+
animationSteps : 100,
|
2827 |
+
|
2828 |
+
//String - Animation easing effect.
|
2829 |
+
animationEasing : "easeOutBounce",
|
2830 |
+
|
2831 |
+
//Boolean - Whether to animate the rotation of the chart
|
2832 |
+
animateRotate : true,
|
2833 |
+
|
2834 |
+
//Boolean - Whether to animate scaling the chart from the centre
|
2835 |
+
animateScale : false,
|
2836 |
+
|
2837 |
+
//String - A legend template
|
2838 |
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
2839 |
+
};
|
2840 |
+
|
2841 |
+
|
2842 |
+
Chart.Type.extend({
|
2843 |
+
//Passing in a name registers this chart in the Chart namespace
|
2844 |
+
name: "PolarArea",
|
2845 |
+
//Providing a defaults will also register the deafults in the chart namespace
|
2846 |
+
defaults : defaultConfig,
|
2847 |
+
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
2848 |
+
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
2849 |
+
initialize: function(data){
|
2850 |
+
this.segments = [];
|
2851 |
+
//Declare segment class as a chart instance specific class, so it can share props for this instance
|
2852 |
+
this.SegmentArc = Chart.Arc.extend({
|
2853 |
+
showStroke : this.options.segmentShowStroke,
|
2854 |
+
strokeWidth : this.options.segmentStrokeWidth,
|
2855 |
+
strokeColor : this.options.segmentStrokeColor,
|
2856 |
+
ctx : this.chart.ctx,
|
2857 |
+
innerRadius : 0,
|
2858 |
+
x : this.chart.width/2,
|
2859 |
+
y : this.chart.height/2
|
2860 |
+
});
|
2861 |
+
this.scale = new Chart.RadialScale({
|
2862 |
+
display: this.options.showScale,
|
2863 |
+
fontStyle: this.options.scaleFontStyle,
|
2864 |
+
fontSize: this.options.scaleFontSize,
|
2865 |
+
fontFamily: this.options.scaleFontFamily,
|
2866 |
+
fontColor: this.options.scaleFontColor,
|
2867 |
+
showLabels: this.options.scaleShowLabels,
|
2868 |
+
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
2869 |
+
backdropColor: this.options.scaleBackdropColor,
|
2870 |
+
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
2871 |
+
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
2872 |
+
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
2873 |
+
lineColor: this.options.scaleLineColor,
|
2874 |
+
lineArc: true,
|
2875 |
+
width: this.chart.width,
|
2876 |
+
height: this.chart.height,
|
2877 |
+
xCenter: this.chart.width/2,
|
2878 |
+
yCenter: this.chart.height/2,
|
2879 |
+
ctx : this.chart.ctx,
|
2880 |
+
templateString: this.options.scaleLabel,
|
2881 |
+
valuesCount: data.length
|
2882 |
+
});
|
2883 |
+
|
2884 |
+
this.updateScaleRange(data);
|
2885 |
+
|
2886 |
+
this.scale.update();
|
2887 |
+
|
2888 |
+
helpers.each(data,function(segment,index){
|
2889 |
+
this.addData(segment,index,true);
|
2890 |
+
},this);
|
2891 |
+
|
2892 |
+
//Set up tooltip events on the chart
|
2893 |
+
if (this.options.showTooltips){
|
2894 |
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
2895 |
+
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
2896 |
+
helpers.each(this.segments,function(segment){
|
2897 |
+
segment.restore(["fillColor"]);
|
2898 |
+
});
|
2899 |
+
helpers.each(activeSegments,function(activeSegment){
|
2900 |
+
activeSegment.fillColor = activeSegment.highlightColor;
|
2901 |
+
});
|
2902 |
+
this.showTooltip(activeSegments);
|
2903 |
+
});
|
2904 |
+
}
|
2905 |
+
|
2906 |
+
this.render();
|
2907 |
+
},
|
2908 |
+
getSegmentsAtEvent : function(e){
|
2909 |
+
var segmentsArray = [];
|
2910 |
+
|
2911 |
+
var location = helpers.getRelativePosition(e);
|
2912 |
+
|
2913 |
+
helpers.each(this.segments,function(segment){
|
2914 |
+
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
2915 |
+
},this);
|
2916 |
+
return segmentsArray;
|
2917 |
+
},
|
2918 |
+
addData : function(segment, atIndex, silent){
|
2919 |
+
var index = atIndex || this.segments.length;
|
2920 |
+
|
2921 |
+
this.segments.splice(index, 0, new this.SegmentArc({
|
2922 |
+
fillColor: segment.color,
|
2923 |
+
highlightColor: segment.highlight || segment.color,
|
2924 |
+
label: segment.label,
|
2925 |
+
value: segment.value,
|
2926 |
+
outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
|
2927 |
+
circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
|
2928 |
+
startAngle: Math.PI * 1.5
|
2929 |
+
}));
|
2930 |
+
if (!silent){
|
2931 |
+
this.reflow();
|
2932 |
+
this.update();
|
2933 |
+
}
|
2934 |
+
},
|
2935 |
+
removeData: function(atIndex){
|
2936 |
+
var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
|
2937 |
+
this.segments.splice(indexToDelete, 1);
|
2938 |
+
this.reflow();
|
2939 |
+
this.update();
|
2940 |
+
},
|
2941 |
+
calculateTotal: function(data){
|
2942 |
+
this.total = 0;
|
2943 |
+
helpers.each(data,function(segment){
|
2944 |
+
this.total += segment.value;
|
2945 |
+
},this);
|
2946 |
+
this.scale.valuesCount = this.segments.length;
|
2947 |
+
},
|
2948 |
+
updateScaleRange: function(datapoints){
|
2949 |
+
var valuesArray = [];
|
2950 |
+
helpers.each(datapoints,function(segment){
|
2951 |
+
valuesArray.push(segment.value);
|
2952 |
+
});
|
2953 |
+
|
2954 |
+
var scaleSizes = (this.options.scaleOverride) ?
|
2955 |
+
{
|
2956 |
+
steps: this.options.scaleSteps,
|
2957 |
+
stepValue: this.options.scaleStepWidth,
|
2958 |
+
min: this.options.scaleStartValue,
|
2959 |
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
2960 |
+
} :
|
2961 |
+
helpers.calculateScaleRange(
|
2962 |
+
valuesArray,
|
2963 |
+
helpers.min([this.chart.width, this.chart.height])/2,
|
2964 |
+
this.options.scaleFontSize,
|
2965 |
+
this.options.scaleBeginAtZero,
|
2966 |
+
this.options.scaleIntegersOnly
|
2967 |
+
);
|
2968 |
+
|
2969 |
+
helpers.extend(
|
2970 |
+
this.scale,
|
2971 |
+
scaleSizes,
|
2972 |
+
{
|
2973 |
+
size: helpers.min([this.chart.width, this.chart.height]),
|
2974 |
+
xCenter: this.chart.width/2,
|
2975 |
+
yCenter: this.chart.height/2
|
2976 |
+
}
|
2977 |
+
);
|
2978 |
+
|
2979 |
+
},
|
2980 |
+
update : function(){
|
2981 |
+
this.calculateTotal(this.segments);
|
2982 |
+
|
2983 |
+
helpers.each(this.segments,function(segment){
|
2984 |
+
segment.save();
|
2985 |
+
});
|
2986 |
+
this.render();
|
2987 |
+
},
|
2988 |
+
reflow : function(){
|
2989 |
+
helpers.extend(this.SegmentArc.prototype,{
|
2990 |
+
x : this.chart.width/2,
|
2991 |
+
y : this.chart.height/2
|
2992 |
+
});
|
2993 |
+
this.updateScaleRange(this.segments);
|
2994 |
+
this.scale.update();
|
2995 |
+
|
2996 |
+
helpers.extend(this.scale,{
|
2997 |
+
xCenter: this.chart.width/2,
|
2998 |
+
yCenter: this.chart.height/2
|
2999 |
+
});
|
3000 |
+
|
3001 |
+
helpers.each(this.segments, function(segment){
|
3002 |
+
segment.update({
|
3003 |
+
outerRadius : this.scale.calculateCenterOffset(segment.value)
|
3004 |
+
});
|
3005 |
+
}, this);
|
3006 |
+
|
3007 |
+
},
|
3008 |
+
draw : function(ease){
|
3009 |
+
var easingDecimal = ease || 1;
|
3010 |
+
//Clear & draw the canvas
|
3011 |
+
this.clear();
|
3012 |
+
helpers.each(this.segments,function(segment, index){
|
3013 |
+
segment.transition({
|
3014 |
+
circumference : this.scale.getCircumference(),
|
3015 |
+
outerRadius : this.scale.calculateCenterOffset(segment.value)
|
3016 |
+
},easingDecimal);
|
3017 |
+
|
3018 |
+
segment.endAngle = segment.startAngle + segment.circumference;
|
3019 |
+
|
3020 |
+
// If we've removed the first segment we need to set the first one to
|
3021 |
+
// start at the top.
|
3022 |
+
if (index === 0){
|
3023 |
+
segment.startAngle = Math.PI * 1.5;
|
3024 |
+
}
|
3025 |
+
|
3026 |
+
//Check to see if it's the last segment, if not get the next and update the start angle
|
3027 |
+
if (index < this.segments.length - 1){
|
3028 |
+
this.segments[index+1].startAngle = segment.endAngle;
|
3029 |
+
}
|
3030 |
+
segment.draw();
|
3031 |
+
}, this);
|
3032 |
+
this.scale.draw();
|
3033 |
+
}
|
3034 |
+
});
|
3035 |
+
|
3036 |
+
}).call(this);
|
3037 |
+
(function(){
|
3038 |
+
"use strict";
|
3039 |
+
|
3040 |
+
var root = this,
|
3041 |
+
Chart = root.Chart,
|
3042 |
+
helpers = Chart.helpers;
|
3043 |
+
|
3044 |
+
|
3045 |
+
|
3046 |
+
Chart.Type.extend({
|
3047 |
+
name: "Radar",
|
3048 |
+
defaults:{
|
3049 |
+
//Boolean - Whether to show lines for each scale point
|
3050 |
+
scaleShowLine : true,
|
3051 |
+
|
3052 |
+
//Boolean - Whether we show the angle lines out of the radar
|
3053 |
+
angleShowLineOut : true,
|
3054 |
+
|
3055 |
+
//Boolean - Whether to show labels on the scale
|
3056 |
+
scaleShowLabels : false,
|
3057 |
+
|
3058 |
+
// Boolean - Whether the scale should begin at zero
|
3059 |
+
scaleBeginAtZero : true,
|
3060 |
+
|
3061 |
+
//String - Colour of the angle line
|
3062 |
+
angleLineColor : "rgba(0,0,0,.1)",
|
3063 |
+
|
3064 |
+
//Number - Pixel width of the angle line
|
3065 |
+
angleLineWidth : 1,
|
3066 |
+
|
3067 |
+
//String - Point label font declaration
|
3068 |
+
pointLabelFontFamily : "'Arial'",
|
3069 |
+
|
3070 |
+
//String - Point label font weight
|
3071 |
+
pointLabelFontStyle : "normal",
|
3072 |
+
|
3073 |
+
//Number - Point label font size in pixels
|
3074 |
+
pointLabelFontSize : 10,
|
3075 |
+
|
3076 |
+
//String - Point label font colour
|
3077 |
+
pointLabelFontColor : "#666",
|
3078 |
+
|
3079 |
+
//Boolean - Whether to show a dot for each point
|
3080 |
+
pointDot : true,
|
3081 |
+
|
3082 |
+
//Number - Radius of each point dot in pixels
|
3083 |
+
pointDotRadius : 3,
|
3084 |
+
|
3085 |
+
//Number - Pixel width of point dot stroke
|
3086 |
+
pointDotStrokeWidth : 1,
|
3087 |
+
|
3088 |
+
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
3089 |
+
pointHitDetectionRadius : 20,
|
3090 |
+
|
3091 |
+
//Boolean - Whether to show a stroke for datasets
|
3092 |
+
datasetStroke : true,
|
3093 |
+
|
3094 |
+
//Number - Pixel width of dataset stroke
|
3095 |
+
datasetStrokeWidth : 2,
|
3096 |
+
|
3097 |
+
//Boolean - Whether to fill the dataset with a colour
|
3098 |
+
datasetFill : true,
|
3099 |
+
|
3100 |
+
//String - A legend template
|
3101 |
+
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
|
3102 |
+
|
3103 |
+
},
|
3104 |
+
|
3105 |
+
initialize: function(data){
|
3106 |
+
this.PointClass = Chart.Point.extend({
|
3107 |
+
strokeWidth : this.options.pointDotStrokeWidth,
|
3108 |
+
radius : this.options.pointDotRadius,
|
3109 |
+
display: this.options.pointDot,
|
3110 |
+
hitDetectionRadius : this.options.pointHitDetectionRadius,
|
3111 |
+
ctx : this.chart.ctx
|
3112 |
+
});
|
3113 |
+
|
3114 |
+
this.datasets = [];
|
3115 |
+
|
3116 |
+
this.buildScale(data);
|
3117 |
+
|
3118 |
+
//Set up tooltip events on the chart
|
3119 |
+
if (this.options.showTooltips){
|
3120 |
+
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
3121 |
+
var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
3122 |
+
|
3123 |
+
this.eachPoints(function(point){
|
3124 |
+
point.restore(['fillColor', 'strokeColor']);
|
3125 |
+
});
|
3126 |
+
helpers.each(activePointsCollection, function(activePoint){
|
3127 |
+
activePoint.fillColor = activePoint.highlightFill;
|
3128 |
+
activePoint.strokeColor = activePoint.highlightStroke;
|
3129 |
+
});
|
3130 |
+
|
3131 |
+
this.showTooltip(activePointsCollection);
|
3132 |
+
});
|
3133 |
+
}
|
3134 |
+
|
3135 |
+
//Iterate through each of the datasets, and build this into a property of the chart
|
3136 |
+
helpers.each(data.datasets,function(dataset){
|
3137 |
+
|
3138 |
+
var datasetObject = {
|
3139 |
+
label: dataset.label || null,
|
3140 |
+
fillColor : dataset.fillColor,
|
3141 |
+
strokeColor : dataset.strokeColor,
|
3142 |
+
pointColor : dataset.pointColor,
|
3143 |
+
pointStrokeColor : dataset.pointStrokeColor,
|
3144 |
+
points : []
|
3145 |
+
};
|
3146 |
+
|
3147 |
+
this.datasets.push(datasetObject);
|
3148 |
+
|
3149 |
+
helpers.each(dataset.data,function(dataPoint,index){
|
3150 |
+
//Add a new point for each piece of data, passing any required data to draw.
|
3151 |
+
var pointPosition;
|
3152 |
+
if (!this.scale.animation){
|
3153 |
+
pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
|
3154 |
+
}
|
3155 |
+
datasetObject.points.push(new this.PointClass({
|
3156 |
+
value : dataPoint,
|
3157 |
+
label : data.labels[index],
|
3158 |
+
datasetLabel: dataset.label,
|
3159 |
+
x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
|
3160 |
+
y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
|
3161 |
+
strokeColor : dataset.pointStrokeColor,
|
3162 |
+
fillColor : dataset.pointColor,
|
3163 |
+
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
3164 |
+
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
|
3165 |
+
}));
|
3166 |
+
},this);
|
3167 |
+
|
3168 |
+
},this);
|
3169 |
+
|
3170 |
+
this.render();
|
3171 |
+
},
|
3172 |
+
eachPoints : function(callback){
|
3173 |
+
helpers.each(this.datasets,function(dataset){
|
3174 |
+
helpers.each(dataset.points,callback,this);
|
3175 |
+
},this);
|
3176 |
+
},
|
3177 |
+
|
3178 |
+
getPointsAtEvent : function(evt){
|
3179 |
+
var mousePosition = helpers.getRelativePosition(evt),
|
3180 |
+
fromCenter = helpers.getAngleFromPoint({
|
3181 |
+
x: this.scale.xCenter,
|
3182 |
+
y: this.scale.yCenter
|
3183 |
+
}, mousePosition);
|
3184 |
+
|
3185 |
+
var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
|
3186 |
+
pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
|
3187 |
+
activePointsCollection = [];
|
3188 |
+
|
3189 |
+
// If we're at the top, make the pointIndex 0 to get the first of the array.
|
3190 |
+
if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
|
3191 |
+
pointIndex = 0;
|
3192 |
+
}
|
3193 |
+
|
3194 |
+
if (fromCenter.distance <= this.scale.drawingArea){
|
3195 |
+
helpers.each(this.datasets, function(dataset){
|
3196 |
+
activePointsCollection.push(dataset.points[pointIndex]);
|
3197 |
+
});
|
3198 |
+
}
|
3199 |
+
|
3200 |
+
return activePointsCollection;
|
3201 |
+
},
|
3202 |
+
|
3203 |
+
buildScale : function(data){
|
3204 |
+
this.scale = new Chart.RadialScale({
|
3205 |
+
display: this.options.showScale,
|
3206 |
+
fontStyle: this.options.scaleFontStyle,
|
3207 |
+
fontSize: this.options.scaleFontSize,
|
3208 |
+
fontFamily: this.options.scaleFontFamily,
|
3209 |
+
fontColor: this.options.scaleFontColor,
|
3210 |
+
showLabels: this.options.scaleShowLabels,
|
3211 |
+
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
3212 |
+
backdropColor: this.options.scaleBackdropColor,
|
3213 |
+
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
3214 |
+
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
3215 |
+
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
3216 |
+
lineColor: this.options.scaleLineColor,
|
3217 |
+
angleLineColor : this.options.angleLineColor,
|
3218 |
+
angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
|
3219 |
+
// Point labels at the edge of each line
|
3220 |
+
pointLabelFontColor : this.options.pointLabelFontColor,
|
3221 |
+
pointLabelFontSize : this.options.pointLabelFontSize,
|
3222 |
+
pointLabelFontFamily : this.options.pointLabelFontFamily,
|
3223 |
+
pointLabelFontStyle : this.options.pointLabelFontStyle,
|
3224 |
+
height : this.chart.height,
|
3225 |
+
width: this.chart.width,
|
3226 |
+
xCenter: this.chart.width/2,
|
3227 |
+
yCenter: this.chart.height/2,
|
3228 |
+
ctx : this.chart.ctx,
|
3229 |
+
templateString: this.options.scaleLabel,
|
3230 |
+
labels: data.labels,
|
3231 |
+
valuesCount: data.datasets[0].data.length
|
3232 |
+
});
|
3233 |
+
|
3234 |
+
this.scale.setScaleSize();
|
3235 |
+
this.updateScaleRange(data.datasets);
|
3236 |
+
this.scale.buildYLabels();
|
3237 |
+
},
|
3238 |
+
updateScaleRange: function(datasets){
|
3239 |
+
var valuesArray = (function(){
|
3240 |
+
var totalDataArray = [];
|
3241 |
+
helpers.each(datasets,function(dataset){
|
3242 |
+
if (dataset.data){
|
3243 |
+
totalDataArray = totalDataArray.concat(dataset.data);
|
3244 |
+
}
|
3245 |
+
else {
|
3246 |
+
helpers.each(dataset.points, function(point){
|
3247 |
+
totalDataArray.push(point.value);
|
3248 |
+
});
|
3249 |
+
}
|
3250 |
+
});
|
3251 |
+
return totalDataArray;
|
3252 |
+
})();
|
3253 |
+
|
3254 |
+
|
3255 |
+
var scaleSizes = (this.options.scaleOverride) ?
|
3256 |
+
{
|
3257 |
+
steps: this.options.scaleSteps,
|
3258 |
+
stepValue: this.options.scaleStepWidth,
|
3259 |
+
min: this.options.scaleStartValue,
|
3260 |
+
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
3261 |
+
} :
|
3262 |
+
helpers.calculateScaleRange(
|
3263 |
+
valuesArray,
|
3264 |
+
helpers.min([this.chart.width, this.chart.height])/2,
|
3265 |
+
this.options.scaleFontSize,
|
3266 |
+
this.options.scaleBeginAtZero,
|
3267 |
+
this.options.scaleIntegersOnly
|
3268 |
+
);
|
3269 |
+
|
3270 |
+
helpers.extend(
|
3271 |
+
this.scale,
|
3272 |
+
scaleSizes
|
3273 |
+
);
|
3274 |
+
|
3275 |
+
},
|
3276 |
+
addData : function(valuesArray,label){
|
3277 |
+
//Map the values array for each of the datasets
|
3278 |
+
this.scale.valuesCount++;
|
3279 |
+
helpers.each(valuesArray,function(value,datasetIndex){
|
3280 |
+
var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
|
3281 |
+
this.datasets[datasetIndex].points.push(new this.PointClass({
|
3282 |
+
value : value,
|
3283 |
+
label : label,
|
3284 |
+
x: pointPosition.x,
|
3285 |
+
y: pointPosition.y,
|
3286 |
+
strokeColor : this.datasets[datasetIndex].pointStrokeColor,
|
3287 |
+
fillColor : this.datasets[datasetIndex].pointColor
|
3288 |
+
}));
|
3289 |
+
},this);
|
3290 |
+
|
3291 |
+
this.scale.labels.push(label);
|
3292 |
+
|
3293 |
+
this.reflow();
|
3294 |
+
|
3295 |
+
this.update();
|
3296 |
+
},
|
3297 |
+
removeData : function(){
|
3298 |
+
this.scale.valuesCount--;
|
3299 |
+
this.scale.labels.shift();
|
3300 |
+
helpers.each(this.datasets,function(dataset){
|
3301 |
+
dataset.points.shift();
|
3302 |
+
},this);
|
3303 |
+
this.reflow();
|
3304 |
+
this.update();
|
3305 |
+
},
|
3306 |
+
update : function(){
|
3307 |
+
this.eachPoints(function(point){
|
3308 |
+
point.save();
|
3309 |
+
});
|
3310 |
+
this.reflow();
|
3311 |
+
this.render();
|
3312 |
+
},
|
3313 |
+
reflow: function(){
|
3314 |
+
helpers.extend(this.scale, {
|
3315 |
+
width : this.chart.width,
|
3316 |
+
height: this.chart.height,
|
3317 |
+
size : helpers.min([this.chart.width, this.chart.height]),
|
3318 |
+
xCenter: this.chart.width/2,
|
3319 |
+
yCenter: this.chart.height/2
|
3320 |
+
});
|
3321 |
+
this.updateScaleRange(this.datasets);
|
3322 |
+
this.scale.setScaleSize();
|
3323 |
+
this.scale.buildYLabels();
|
3324 |
+
},
|
3325 |
+
draw : function(ease){
|
3326 |
+
var easeDecimal = ease || 1,
|
3327 |
+
ctx = this.chart.ctx;
|
3328 |
+
this.clear();
|
3329 |
+
this.scale.draw();
|
3330 |
+
|
3331 |
+
helpers.each(this.datasets,function(dataset){
|
3332 |
+
|
3333 |
+
//Transition each point first so that the line and point drawing isn't out of sync
|
3334 |
+
helpers.each(dataset.points,function(point,index){
|
3335 |
+
if (point.hasValue()){
|
3336 |
+
point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
|
3337 |
+
}
|
3338 |
+
},this);
|
3339 |
+
|
3340 |
+
|
3341 |
+
|
3342 |
+
//Draw the line between all the points
|
3343 |
+
ctx.lineWidth = this.options.datasetStrokeWidth;
|
3344 |
+
ctx.strokeStyle = dataset.strokeColor;
|
3345 |
+
ctx.beginPath();
|
3346 |
+
helpers.each(dataset.points,function(point,index){
|
3347 |
+
if (index === 0){
|
3348 |
+
ctx.moveTo(point.x,point.y);
|
3349 |
+
}
|
3350 |
+
else{
|
3351 |
+
ctx.lineTo(point.x,point.y);
|
3352 |
+
}
|
3353 |
+
},this);
|
3354 |
+
ctx.closePath();
|
3355 |
+
ctx.stroke();
|
3356 |
+
|
3357 |
+
ctx.fillStyle = dataset.fillColor;
|
3358 |
+
ctx.fill();
|
3359 |
+
|
3360 |
+
//Now draw the points over the line
|
3361 |
+
//A little inefficient double looping, but better than the line
|
3362 |
+
//lagging behind the point positions
|
3363 |
+
helpers.each(dataset.points,function(point){
|
3364 |
+
if (point.hasValue()){
|
3365 |
+
point.draw();
|
3366 |
+
}
|
3367 |
+
});
|
3368 |
+
|
3369 |
+
},this);
|
3370 |
+
|
3371 |
+
}
|
3372 |
+
|
3373 |
+
});
|
3374 |
+
|
3375 |
+
|
3376 |
+
|
3377 |
+
|
3378 |
+
|
3379 |
+
}).call(this);
|
js/dashboard-chart.js
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
function optimizationChecking(call_back) {
|
2 |
+
var ret;
|
3 |
+
jQuery.ajax({
|
4 |
+
url: myAjax.ajax_url,
|
5 |
+
async: false,
|
6 |
+
type: 'post',
|
7 |
+
data: {'action': 'opt_checking'},
|
8 |
+
dataType: 'json',
|
9 |
+
beforeSend: function() {
|
10 |
+
},
|
11 |
+
success: function(response) {
|
12 |
+
console.log(response);
|
13 |
+
ret = response;
|
14 |
+
for (sid in response) {
|
15 |
+
jQuery('#' + sid).removeClass('hz-loading').text(response[sid][0] + '/' + response[sid][1]);
|
16 |
+
}
|
17 |
+
console.log(typeof (call_back));
|
18 |
+
if (typeof (call_back) == "function") {
|
19 |
+
call_back(response);
|
20 |
+
}
|
21 |
+
},
|
22 |
+
error: function() {
|
23 |
+
|
24 |
+
}
|
25 |
+
|
26 |
+
});
|
27 |
+
|
28 |
+
return ret;
|
29 |
+
}
|
30 |
+
|
31 |
+
|
32 |
+
function drawChart(metacontent,notmetacontent, imagemeta,notimagemeta, imageoptimi,notimageoptimi) {
|
33 |
+
|
34 |
+
var doughnutData = [
|
35 |
+
{
|
36 |
+
value: metacontent,
|
37 |
+
color: "rgba(92,155,213,1)",
|
38 |
+
highlight: "#5c9bd5",
|
39 |
+
label: "Content Meta- Completed"
|
40 |
+
},
|
41 |
+
{
|
42 |
+
value: notmetacontent,
|
43 |
+
color: "#CECECE",
|
44 |
+
label: "Content Meta- Imcompleted"
|
45 |
+
},
|
46 |
+
{
|
47 |
+
value: imagemeta,
|
48 |
+
color: "rgba(241,145,79,1)",
|
49 |
+
highlight: "#ed7d31",
|
50 |
+
label: "Image Meta - Completed"
|
51 |
+
},
|
52 |
+
{
|
53 |
+
value: notimagemeta,
|
54 |
+
color: "#CECECE",
|
55 |
+
label: "Image Meta - Imcompleted"
|
56 |
+
},
|
57 |
+
|
58 |
+
{
|
59 |
+
value: imageoptimi,
|
60 |
+
color: "rgba(70,191,189,1)",
|
61 |
+
highlight: "#46BFBD",
|
62 |
+
label: "Image Optimized"
|
63 |
+
},
|
64 |
+
{
|
65 |
+
value: notimageoptimi,
|
66 |
+
color: "#CECECE",
|
67 |
+
label: "Image not Optimized"
|
68 |
+
}
|
69 |
+
|
70 |
+
|
71 |
+
];
|
72 |
+
|
73 |
+
console.log(doughnutData);
|
74 |
+
var ctx = document.getElementById("chart-area").getContext("2d");
|
75 |
+
window.myDoughnut = new Chart(ctx).Doughnut(doughnutData, {
|
76 |
+
responsive: true,
|
77 |
+
tooltipTitleFontSize: 13,
|
78 |
+
tooltipXOffset: 5,
|
79 |
+
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= ((value*3) %1 !=0)?(value*3).toFixed(2):(value*3) %> %",
|
80 |
+
percentageInnerCutout: 70,
|
81 |
+
scaleShowLabels: true,
|
82 |
+
segmentShowStroke : false,
|
83 |
+
|
84 |
+
});
|
85 |
+
|
86 |
+
|
87 |
+
}
|
js/metaseo_admin.js
ADDED
@@ -0,0 +1,784 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* To change this license header, choose License Headers in Project Properties.
|
3 |
+
* To change this template file, choose Tools | Templates
|
4 |
+
* and open the template in the editor.
|
5 |
+
*/
|
6 |
+
var title_max_len = 69;
|
7 |
+
var desc_max_len = 156;
|
8 |
+
var metaseoValueHolder = {};
|
9 |
+
var iUnchecked = 0;
|
10 |
+
|
11 |
+
function metaseo_clean(str) {
|
12 |
+
if (str == '' || str == undefined)
|
13 |
+
return '';
|
14 |
+
try {
|
15 |
+
str = jQuery('<div/>').html(str).text();
|
16 |
+
str = str.replace(/<\/?[^>]+>/gi, '');
|
17 |
+
str = str.replace(/\[(.+?)\](.+?\[\/\\1\])?/g, '');
|
18 |
+
} catch (e) {
|
19 |
+
}
|
20 |
+
|
21 |
+
return str;
|
22 |
+
}
|
23 |
+
|
24 |
+
var oldTitleValues = {};
|
25 |
+
var oldDescValues = {};
|
26 |
+
var metaContentChangeWait;
|
27 |
+
function metaseo_updateTitle(metatitle_id, needToSave, updateSnippet) {
|
28 |
+
var title = jQuery.trim(metaseo_clean(jQuery('#' + metatitle_id).val()));
|
29 |
+
var postid = metatitle_id.replace('metaseo-metatitle-', '');
|
30 |
+
var counter_id = 'metaseo-metatitle-len' + postid;
|
31 |
+
jQuery('#' + counter_id).text(title_max_len-title.length);
|
32 |
+
if (title.length >= title_max_len) {
|
33 |
+
jQuery('#' + counter_id).addClass('word-exceed');//#FEFB04
|
34 |
+
}
|
35 |
+
else {
|
36 |
+
jQuery('#' + counter_id).removeClass('word-exceed');
|
37 |
+
}
|
38 |
+
|
39 |
+
if(title.length > title_max_len){
|
40 |
+
jQuery('#snippet_title' + postid).empty().text(title.substr(0, title_max_len));
|
41 |
+
}
|
42 |
+
|
43 |
+
if(typeof updateSnippet == "undefined" || updateSnippet !== false) {
|
44 |
+
jQuery('#snippet_title' + postid).text(title.substr(0, title_max_len) );
|
45 |
+
}
|
46 |
+
|
47 |
+
if (needToSave === true && oldTitleValues[postid] != title ) {
|
48 |
+
|
49 |
+
clearTimeout(metaContentChangeWait);
|
50 |
+
metaContentChangeWait = setTimeout(function() {
|
51 |
+
saveMetaContentChanges('metatitle', postid, title);
|
52 |
+
}, 1000);
|
53 |
+
}
|
54 |
+
|
55 |
+
//Push the new value into the array
|
56 |
+
oldTitleValues[postid] = title;
|
57 |
+
}
|
58 |
+
|
59 |
+
function metaseo_updateDesc(metadesc_id, needToSave) {
|
60 |
+
var desc = jQuery.trim(metaseo_clean(jQuery('#' + metadesc_id).val()));
|
61 |
+
var postid = metadesc_id.replace('metaseo-metadesc-', '');
|
62 |
+
var counter_id = 'metaseo-metadesc-len' + postid;
|
63 |
+
jQuery('#' + counter_id).text(desc_max_len-desc.length);
|
64 |
+
|
65 |
+
if (desc.length >= desc_max_len) {
|
66 |
+
jQuery('#' + counter_id).addClass('word-exceed');
|
67 |
+
}
|
68 |
+
else {
|
69 |
+
jQuery('#' + counter_id).removeClass('word-exceed');
|
70 |
+
}
|
71 |
+
|
72 |
+
jQuery('#snippet_desc' + postid).text(desc.substr(0, desc_max_len) );
|
73 |
+
|
74 |
+
if (needToSave === true && oldDescValues[postid] != desc) {
|
75 |
+
|
76 |
+
clearTimeout(metaContentChangeWait);
|
77 |
+
metaContentChangeWait = setTimeout(function() {
|
78 |
+
saveMetaContentChanges('metadesc', postid, desc);
|
79 |
+
}, 1000);
|
80 |
+
}
|
81 |
+
|
82 |
+
//Push the new value into the array
|
83 |
+
oldDescValues[postid] = desc;
|
84 |
+
}
|
85 |
+
|
86 |
+
var autosaveNotification;
|
87 |
+
function saveMetaContentChanges(metakey, postid, data) {
|
88 |
+
|
89 |
+
var postData = {
|
90 |
+
'action': 'updateContentMeta',
|
91 |
+
'metakey': metakey,
|
92 |
+
'postid': postid,
|
93 |
+
'value': data
|
94 |
+
};
|
95 |
+
// We can also pass the url value separately from ajaxurl for front end AJAX implementations
|
96 |
+
jQuery.post(myAjax.ajax_url, postData, function(response) {
|
97 |
+
result = jQuery.parseJSON(response);
|
98 |
+
|
99 |
+
if (result.updated == true) {
|
100 |
+
autosaveNotification = setTimeout(function() {
|
101 |
+
jQuery('#savedInfo' + postid).text(result.msg);
|
102 |
+
jQuery('#savedInfo' + postid).fadeIn(200).delay(2000).fadeOut(1000);
|
103 |
+
}, 1000);
|
104 |
+
} else {
|
105 |
+
alert(result.msg);
|
106 |
+
}
|
107 |
+
|
108 |
+
});
|
109 |
+
|
110 |
+
|
111 |
+
}
|
112 |
+
|
113 |
+
function checkspecial(element_id){
|
114 |
+
var element = jQuery(element_id);
|
115 |
+
var meta_type = element.data('meta-type');
|
116 |
+
|
117 |
+
if(meta_type=='change_image_name'){
|
118 |
+
var str = (element.val());
|
119 |
+
if( /^[\w\d\-\s+_.$]*$/.test( str ) == false ) {
|
120 |
+
clearTimeout(metaChangeWait);
|
121 |
+
return false;
|
122 |
+
}else{
|
123 |
+
return true;
|
124 |
+
}
|
125 |
+
}else{
|
126 |
+
return true;
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
var metaChangeWait;
|
131 |
+
function metaseo_update(element_id) {
|
132 |
+
//metaseo-img-alt-4
|
133 |
+
var element = jQuery(element_id);
|
134 |
+
var post_id = element.data('post-id');
|
135 |
+
var meta_type = element.data('meta-type');
|
136 |
+
var meta_value = element.val();
|
137 |
+
|
138 |
+
clearTimeout(metaChangeWait);
|
139 |
+
metaChangeWait = setTimeout(function() {
|
140 |
+
if(saveChanges(element_id, post_id, meta_type, meta_value)){
|
141 |
+
//jQuery(element_id).parent().find('span.meta-update').removeClass('update-loader');
|
142 |
+
|
143 |
+
if(meta_type === 'change_image_name'){
|
144 |
+
jQuery('a.img-resize[data-post-id="'+ post_id +'"]').data('img-name', meta_value);
|
145 |
+
}
|
146 |
+
|
147 |
+
}
|
148 |
+
}, 1000);
|
149 |
+
}
|
150 |
+
|
151 |
+
|
152 |
+
|
153 |
+
function saveChanges(element_id, post_id, meta_type, meta_value) {
|
154 |
+
|
155 |
+
var element = jQuery(element_id);
|
156 |
+
var savedInfo = element.parent().find('span.saved-info');
|
157 |
+
if(savedInfo.length < 1) { savedInfo = element.closest('td').find('span.saved-info'); }
|
158 |
+
var updated = false;
|
159 |
+
var postData = {
|
160 |
+
'action': 'updateMeta',
|
161 |
+
'post_id': post_id,
|
162 |
+
'meta_type': meta_type,
|
163 |
+
'meta_value':meta_value,
|
164 |
+
'addition' : {
|
165 |
+
'meta_key' : element.data('meta-key'),
|
166 |
+
'meta_type': element.data('meta-type'),
|
167 |
+
'meta_value' : element.val(),
|
168 |
+
'meta_order' : element.data('meta-order'),
|
169 |
+
'img_post_id' : element.data('img-post-id'),
|
170 |
+
'post_id' : element.data('post-id'),
|
171 |
+
}
|
172 |
+
};
|
173 |
+
|
174 |
+
// We can also pass the url value separately from ajaxurl for front end AJAX implementations
|
175 |
+
jQuery.ajax({
|
176 |
+
url: myAjax.ajax_url,
|
177 |
+
async: false,
|
178 |
+
type: 'post',
|
179 |
+
data: postData,
|
180 |
+
dataType: 'json',
|
181 |
+
beforeSend: function(){
|
182 |
+
savedInfo.empty().append('<span style="position:absolute" class="meta-update"></span>');
|
183 |
+
element.parent().find('span.meta-update').addClass('update-loader').fadeIn(300);
|
184 |
+
},
|
185 |
+
success: function(response){
|
186 |
+
if(response == 0){
|
187 |
+
saveChanges(element_id, post_id, meta_type, meta_value);
|
188 |
+
}
|
189 |
+
|
190 |
+
metaChangeWait = -1 ;
|
191 |
+
updated = response.updated;
|
192 |
+
|
193 |
+
if (updated == true) {
|
194 |
+
autosaveNotification = setTimeout(function() {
|
195 |
+
element.parent().find('span.meta-update').removeClass('update-loader');
|
196 |
+
savedInfo.removeClass('metaseo-msg-warning').addClass('metaseo-msg-success')
|
197 |
+
.text(response.msg).fadeIn(200);
|
198 |
+
|
199 |
+
setTimeout(function(){
|
200 |
+
savedInfo.empty().append('<span class="meta-update" style="position:absolute"></span>');
|
201 |
+
}, 3000);
|
202 |
+
|
203 |
+
}, 200);
|
204 |
+
|
205 |
+
//update image's data-name attribute
|
206 |
+
if(typeof element.data('extension') != 'undefined'){
|
207 |
+
jQuery('[data-img-post-id="'+element.data('post-id')+'"]').data('name', element.val() + element.data('extension'));
|
208 |
+
}
|
209 |
+
//Scan post and update post_meta
|
210 |
+
var img = jQuery('[data-img-post-id="'+ postData['addition']['img_post_id'] +'"]');
|
211 |
+
|
212 |
+
if(img.length > 0){
|
213 |
+
_metaSeoScanImages(
|
214 |
+
[
|
215 |
+
{
|
216 |
+
'name':img.data('name'),
|
217 |
+
'img_post_id':postData['addition']['img_post_id']
|
218 |
+
}
|
219 |
+
|
220 |
+
]);
|
221 |
+
}
|
222 |
+
|
223 |
+
} else {
|
224 |
+
element.val(response.iname);
|
225 |
+
savedInfo.removeClass('metaseo-msg-success').addClass('metaseo-msg-warning')
|
226 |
+
.text(response.msg).fadeIn(200).delay(2000).fadeOut(200);
|
227 |
+
}
|
228 |
+
},
|
229 |
+
error: function(){
|
230 |
+
|
231 |
+
}
|
232 |
+
});
|
233 |
+
|
234 |
+
return updated;
|
235 |
+
}
|
236 |
+
|
237 |
+
//Scan all posts to find a group of images in their content
|
238 |
+
function metaSeoScanImages(){
|
239 |
+
var imgs = [];
|
240 |
+
jQuery('.metaseo-image').each(function(i){
|
241 |
+
if(jQuery(this).data('name') != ''){
|
242 |
+
imgs[i] = {
|
243 |
+
'name':jQuery(this).data('name'),
|
244 |
+
'img_post_id':jQuery(this).data('img-post-id')
|
245 |
+
};
|
246 |
+
}
|
247 |
+
});
|
248 |
+
|
249 |
+
_metaSeoScanImages(imgs);
|
250 |
+
|
251 |
+
}
|
252 |
+
|
253 |
+
function _metaSeoScanImages(imgs){
|
254 |
+
if(imgs.length < 1){
|
255 |
+
//alert('No images choosen for scanning, please check again!');
|
256 |
+
return false;
|
257 |
+
}
|
258 |
+
|
259 |
+
jQuery.ajax({
|
260 |
+
url: myAjax.ajax_url,
|
261 |
+
type: 'post',
|
262 |
+
data: {'action':'scanPosts', 'imgs':imgs},
|
263 |
+
dataType: 'json',
|
264 |
+
beforeSend: function(){},
|
265 |
+
success: function(response){
|
266 |
+
if(response == 0) { _metaSeoScanImages(imgs); }
|
267 |
+
//clog(imgs);
|
268 |
+
if(response.success === true){
|
269 |
+
//Clear content holder first
|
270 |
+
if(imgs.length === 1){
|
271 |
+
jQuery('#opt-info-'+imgs[0]['img_post_id']).removeClass('opt-info-warning').empty();
|
272 |
+
}
|
273 |
+
|
274 |
+
//id is refered to image post id
|
275 |
+
for(var iID in response.msg){
|
276 |
+
//Change css position property of td tag to default
|
277 |
+
jQuery('#opt-info-'+iID).parent().css('position', 'static');
|
278 |
+
jQuery('#opt-info-'+iID).append('<p class="btn-wrapper"></p>');
|
279 |
+
|
280 |
+
for(var msgType in response.msg[iID]){
|
281 |
+
if(response.msg[iID][msgType]['warning'] == true
|
282 |
+
&& !jQuery('#opt-info-'+iID).hasClass('opt-info-warning')){
|
283 |
+
jQuery('#opt-info-'+iID).addClass('opt-info-warning');
|
284 |
+
}
|
285 |
+
|
286 |
+
jQuery('#opt-info-'+iID).find('p.btn-wrapper').append(response.msg[iID][msgType]['button']);
|
287 |
+
if(typeof response.msg[iID][msgType]['msg'] != 'object'){
|
288 |
+
var hlight = !response.msg[iID][msgType]['warning'] ? 'metaseo-msg-success' : '';
|
289 |
+
jQuery('#opt-info-'+iID).prepend('<p class="'+hlight+'">'+response.msg[iID][msgType]['msg']+'</p>');
|
290 |
+
}
|
291 |
+
else{
|
292 |
+
for(var k in response.msg[iID][msgType]['msg']){
|
293 |
+
jQuery('#opt-info-'+iID).prepend('<p>'+response.msg[iID][msgType]['msg'][k]+'</p>');
|
294 |
+
}
|
295 |
+
}
|
296 |
+
}
|
297 |
+
|
298 |
+
}
|
299 |
+
|
300 |
+
jQuery('span.metaseo-loading').hide();
|
301 |
+
jQuery('.opt-info-warning').fadeIn(200);
|
302 |
+
|
303 |
+
//
|
304 |
+
jQuery('input.metaseo-checkin').each(function(i, input){
|
305 |
+
uncheck(input);
|
306 |
+
});
|
307 |
+
}
|
308 |
+
},
|
309 |
+
error: function(){
|
310 |
+
alert('Errors occured while scanning posts for optimization');
|
311 |
+
}
|
312 |
+
});
|
313 |
+
}
|
314 |
+
|
315 |
+
//To fix meta of a specified image
|
316 |
+
function metaseo_fix_meta(that){
|
317 |
+
var $this = jQuery(that);
|
318 |
+
|
319 |
+
if(checkspecial(that) === true){
|
320 |
+
|
321 |
+
if(that.jquery === undefined){
|
322 |
+
$this.bind('input propertychange', function(){
|
323 |
+
metaseo_update(that);
|
324 |
+
});
|
325 |
+
}
|
326 |
+
else{
|
327 |
+
metaseo_update(that);
|
328 |
+
}
|
329 |
+
|
330 |
+
}
|
331 |
+
}
|
332 |
+
|
333 |
+
function add_meta_default(that){
|
334 |
+
var $this = jQuery(that);
|
335 |
+
var input = $this.parent().find('input');
|
336 |
+
var id = input.attr('id');
|
337 |
+
|
338 |
+
input.val($this.data('default-value'));
|
339 |
+
metaseo_fix_meta(input);
|
340 |
+
}
|
341 |
+
//--------------------------------
|
342 |
+
//Optimize a single post
|
343 |
+
function optimize_imgs(element){
|
344 |
+
var $this = jQuery(element);
|
345 |
+
var post_id = $this.data('post-id');
|
346 |
+
var img_post_id = $this.data('img-post-id');
|
347 |
+
var checkin = jQuery('.checkin-'+post_id);
|
348 |
+
var img_exclude = [];
|
349 |
+
var not_checked_counter = 0;
|
350 |
+
var updated = false;
|
351 |
+
|
352 |
+
var j=0;
|
353 |
+
checkin.each(function(i,el){
|
354 |
+
if(!(jQuery(el).is(':checked'))){
|
355 |
+
not_checked_counter++;
|
356 |
+
if(jQuery(el).val() != '' || jQuery(el).val() != 'undefined'){
|
357 |
+
img_exclude[j] = parseInt(jQuery(el).val());
|
358 |
+
j++;
|
359 |
+
}
|
360 |
+
}
|
361 |
+
});
|
362 |
+
|
363 |
+
if(checkin.length <= not_checked_counter){
|
364 |
+
//alert('No images has choosen. \\nPlease click on the checkbox in what image you want to replace!');
|
365 |
+
return false;
|
366 |
+
}
|
367 |
+
|
368 |
+
if(!post_id && !img_post_id){
|
369 |
+
alert('Cant do the optimization because of missing image ID.\\nPlease check again!');
|
370 |
+
}
|
371 |
+
else{
|
372 |
+
jQuery.ajax({
|
373 |
+
url:myAjax.ajax_url,
|
374 |
+
async: false,
|
375 |
+
data:{
|
376 |
+
'action': 'optimize_imgs',
|
377 |
+
'post_id' : post_id,
|
378 |
+
'img_post_id' : img_post_id,
|
379 |
+
'img_exclude' : img_exclude
|
380 |
+
},
|
381 |
+
dataType: 'json',
|
382 |
+
type: 'post',
|
383 |
+
beforeSend: function(){
|
384 |
+
$this.parent().find('span.spinner').show();
|
385 |
+
},
|
386 |
+
success: function(response){
|
387 |
+
if(response == 0){ optimize_imgs(element); }
|
388 |
+
|
389 |
+
if(response.success){
|
390 |
+
updated = true;
|
391 |
+
|
392 |
+
checkin.each(function(i,e){
|
393 |
+
if(jQuery.inArray(parseInt(jQuery(this).val()), img_exclude) == -1){
|
394 |
+
|
395 |
+
var img_choosen = jQuery(this).parent();
|
396 |
+
jQuery(this).remove();
|
397 |
+
|
398 |
+
img_choosen.empty().append('<span class="metaseo-checked"></span>');
|
399 |
+
img_choosen.parent().find('p.metaseo-msg').removeClass('msg-error').addClass('msg-success').empty().text(response.msg).fadeIn(200);
|
400 |
+
setTimeout(function(){
|
401 |
+
img_choosen.find('p.metaseo-msg').fadeOut(300);
|
402 |
+
}, 5000);
|
403 |
+
|
404 |
+
}
|
405 |
+
});
|
406 |
+
|
407 |
+
var checked = jQuery('.checkin-'+post_id);
|
408 |
+
if(checked.length == 0){
|
409 |
+
$this.addClass('disabled');
|
410 |
+
}
|
411 |
+
|
412 |
+
$this.parent().find('span.spinner').fadeOut(300);
|
413 |
+
//Disable Replace all button if all image were resized
|
414 |
+
var metaseo_checkin = jQuery('.metaseo-checkin');
|
415 |
+
|
416 |
+
if(metaseo_checkin.length == 0){
|
417 |
+
jQuery('#metaseo-replace-all').addClass('disabled');
|
418 |
+
}
|
419 |
+
//Scan post and update post_meta
|
420 |
+
var img = jQuery('[data-img-post-id="'+ img_post_id +'"]');
|
421 |
+
_metaSeoScanImages([{'name':img.data('name'), 'img_post_id':img_post_id}]);
|
422 |
+
|
423 |
+
}
|
424 |
+
else{
|
425 |
+
$this.parent().find('span.spinner').hide();
|
426 |
+
$this.parent().find('p.metaseo-msg').removeClass('msg-success').addClass('msg-error');
|
427 |
+
}
|
428 |
+
|
429 |
+
},
|
430 |
+
error: function(){
|
431 |
+
|
432 |
+
}
|
433 |
+
});
|
434 |
+
}
|
435 |
+
|
436 |
+
img_exclude = [];
|
437 |
+
return updated;
|
438 |
+
}
|
439 |
+
|
440 |
+
//Optimize all posts in list displayed
|
441 |
+
function optimize_imgs_group(that){
|
442 |
+
jQuery('a.metaseo-optimize').each(function(i, el){
|
443 |
+
if(i == 0){
|
444 |
+
jQuery(that).parent().find('span.spinner').show();
|
445 |
+
}
|
446 |
+
|
447 |
+
jQuery(this).click();
|
448 |
+
|
449 |
+
if(i == (jQuery(el).length - 1)){
|
450 |
+
jQuery(that).parent().find('span.spinner').hide();
|
451 |
+
}
|
452 |
+
});
|
453 |
+
|
454 |
+
}
|
455 |
+
|
456 |
+
function uncheck(that){
|
457 |
+
var $this = jQuery(that);
|
458 |
+
var post_id = that.className.substr(that.className.lastIndexOf('-')+1);
|
459 |
+
var checked = jQuery('.checkin-'+post_id);
|
460 |
+
var not_checked_counter = 0;
|
461 |
+
|
462 |
+
checked.each(function(i,e){
|
463 |
+
if(!(jQuery(this).is(':checked'))){
|
464 |
+
not_checked_counter++;
|
465 |
+
}
|
466 |
+
});
|
467 |
+
|
468 |
+
//Toggle disable Replace button if all images in a post were resized
|
469 |
+
if(not_checked_counter >= checked.length){
|
470 |
+
jQuery('a.metaseo-optimize[data-post-id="'+post_id+'"]').addClass('disabled');
|
471 |
+
}
|
472 |
+
else{
|
473 |
+
jQuery('a.metaseo-optimize[data-post-id="'+post_id+'"]').removeClass('disabled');
|
474 |
+
}
|
475 |
+
|
476 |
+
//Toggle disable Replace all button if all images in posts were resized
|
477 |
+
var replaceBtns = jQuery('.metaseo-optimize');
|
478 |
+
var disable = true;
|
479 |
+
replaceBtns.each(function(i, btn){
|
480 |
+
if(!jQuery(btn).hasClass('disabled')){
|
481 |
+
disable = false;
|
482 |
+
return;
|
483 |
+
}
|
484 |
+
});
|
485 |
+
|
486 |
+
if(disable === true){
|
487 |
+
jQuery('#metaseo-replace-all').addClass('disabled');
|
488 |
+
}
|
489 |
+
else{
|
490 |
+
jQuery('#metaseo-replace-all').removeClass('disabled');
|
491 |
+
}
|
492 |
+
}
|
493 |
+
|
494 |
+
//Show posts list for resizing or update meta info of an image with specified id
|
495 |
+
function showPostsList(element){
|
496 |
+
var that = jQuery(element);
|
497 |
+
var data = {
|
498 |
+
'action': 'load_posts',
|
499 |
+
'img_name': that.data('img-name'),
|
500 |
+
'post_id': that.data('post-id'),
|
501 |
+
'opt_key': that.data('opt-key')
|
502 |
+
};
|
503 |
+
|
504 |
+
if(that.data('img-name') != ''){
|
505 |
+
jQuery.ajax({
|
506 |
+
url: myAjax.ajax_url,
|
507 |
+
type: 'post',
|
508 |
+
dataType: 'html',
|
509 |
+
data: data,
|
510 |
+
beforeSend:function(){
|
511 |
+
that.find('.spinner-light').show();
|
512 |
+
},
|
513 |
+
success: function(response){
|
514 |
+
if(response == 0){ showPostsList(element); }
|
515 |
+
that.parent().find('.spinner-light').hide();
|
516 |
+
that.closest('td.col_image_info').find('div.popup > .popup-content').empty().html(response).fadeIn(300);
|
517 |
+
|
518 |
+
//jQuery('.img-choosen').css({'right' : -(jQuery('.metaseo-action').outerWidth()/2 + 20), 'bottom' : '3px'});
|
519 |
+
|
520 |
+
//to set background-color of popup-header to like adminmenu active
|
521 |
+
var metaseo_bg = jQuery('#adminmenu li.wp-has-current-submenu a.wp-has-current-submenu').css('background-color');
|
522 |
+
|
523 |
+
if (metaseo_bg !== 'undified') {
|
524 |
+
jQuery('.popup-content .content-header').css({'background-color' : metaseo_bg, 'color': '#FFF'});
|
525 |
+
jQuery('span.popup-close').css({'color' : '#FFF'});
|
526 |
+
}
|
527 |
+
|
528 |
+
that.showPopup(that);
|
529 |
+
}
|
530 |
+
});
|
531 |
+
}
|
532 |
+
else{
|
533 |
+
alert('Something went wrong, please check Image name if it\'s empty before click Resize button');
|
534 |
+
}
|
535 |
+
}
|
536 |
+
|
537 |
+
//Import meta data from other plugin into Wp Meta Seo
|
538 |
+
function importMetaData(that, event){
|
539 |
+
var plugin = that.id;
|
540 |
+
var element = jQuery('#'+that.id);
|
541 |
+
|
542 |
+
event.preventDefault();
|
543 |
+
if(that.id === '_aio_' || that.id === '_yoast_'){
|
544 |
+
jQuery.ajax({
|
545 |
+
url: myAjax.ajax_url,
|
546 |
+
type: 'post',
|
547 |
+
data: {'action': 'import_meta_data', 'plugin' : that.id},
|
548 |
+
dataType: 'json',
|
549 |
+
beforeSend: function(){
|
550 |
+
element.find('span.spinner-light').show();
|
551 |
+
},
|
552 |
+
success: function(response){
|
553 |
+
if(response.success == true){
|
554 |
+
element.find('span.spinner-light').fadeOut(500);
|
555 |
+
jQuery('.metaseo-import-wrn').closest('.error').fadeOut(1500);
|
556 |
+
console.log(location.href);
|
557 |
+
//Refresh the page to see al changed after import Yoast or AIO data into MetaSEO
|
558 |
+
if( location.href.search('page=metaseo_content_meta') != -1 ){
|
559 |
+
location.reload();
|
560 |
+
}
|
561 |
+
//setTimeout(function(){
|
562 |
+
//jQuery('.mseo-import-action').closest('.error').remove();
|
563 |
+
//},5000);
|
564 |
+
|
565 |
+
}
|
566 |
+
},
|
567 |
+
error: function(){
|
568 |
+
alert('Something went wrong in import processing!');
|
569 |
+
}
|
570 |
+
|
571 |
+
});
|
572 |
+
}
|
573 |
+
}
|
574 |
+
|
575 |
+
//Update once input changed and blur
|
576 |
+
function updateInputBlur(that){
|
577 |
+
var element = jQuery(that);
|
578 |
+
var post_id = element.data('post-id');
|
579 |
+
var meta_type = element.data('meta-type');
|
580 |
+
var meta_value = element.val();
|
581 |
+
|
582 |
+
if(typeof metaChangeWait != "undefined" && (metaChangeWait != -1) ){
|
583 |
+
clearTimeout(metaChangeWait);
|
584 |
+
if(saveChanges(that, post_id, meta_type, meta_value)){
|
585 |
+
if(meta_type === 'change_image_name'){
|
586 |
+
jQuery('a.img-resize[data-post-id="'+ post_id +'"]').data('img-name', meta_value);
|
587 |
+
}
|
588 |
+
}
|
589 |
+
}
|
590 |
+
}
|
591 |
+
|
592 |
+
function checkeyCode(event,that){
|
593 |
+
if(event.which == 13 || event.keyCode == 13){
|
594 |
+
return false;
|
595 |
+
}
|
596 |
+
}
|
597 |
+
|
598 |
+
/**
|
599 |
+
* Check if a name is existed
|
600 |
+
*/
|
601 |
+
function is_existed(iname){
|
602 |
+
jQuery.each(metaseoValueHolder, function(i, iName){
|
603 |
+
for(var id in iName){
|
604 |
+
if(typeof iName[id+'_prev'] != 'undefined' && iname == iName[id+'_prev']){
|
605 |
+
return true;
|
606 |
+
}
|
607 |
+
}
|
608 |
+
});
|
609 |
+
}
|
610 |
+
|
611 |
+
/**
|
612 |
+
* Check if a image name is valid or not
|
613 |
+
*/
|
614 |
+
function validateiName(iname){
|
615 |
+
var is_only_spaces = iname.length > 0 ? true : false;
|
616 |
+
iname = iname.trim();
|
617 |
+
var msg = '';
|
618 |
+
|
619 |
+
if( iname.length < 1 ){
|
620 |
+
msg = !is_only_spaces ? 'Should not be empty' : 'Should not only spaces';
|
621 |
+
return {msg: msg, name: ''};
|
622 |
+
}
|
623 |
+
|
624 |
+
return {msg: '', name: iname};
|
625 |
+
}
|
626 |
+
|
627 |
+
jQuery(document).ready(function($) {
|
628 |
+
|
629 |
+
//Cursor changes on any ajax start and end
|
630 |
+
//Thanks to iambriansreed from stacoverflow.com
|
631 |
+
$('body').ajaxStart(function() {
|
632 |
+
$(this).css({'cursor':'wait'});
|
633 |
+
}).ajaxStop(function() {
|
634 |
+
$(this).css({'cursor':'default'});
|
635 |
+
});
|
636 |
+
|
637 |
+
$('span.pagination-links a.disabled').click(function(e) {
|
638 |
+
e.preventDefault();
|
639 |
+
});
|
640 |
+
|
641 |
+
$('.metaseo_imgs_per_page').bind('input propertychange', function(){
|
642 |
+
var perpage = $(this).val();
|
643 |
+
$('.metaseo_imgs_per_page').each(function(i,e){
|
644 |
+
if($(e).val() != perpage){
|
645 |
+
$(e).val(perpage);
|
646 |
+
}
|
647 |
+
});
|
648 |
+
});
|
649 |
+
|
650 |
+
$('.metaseo-filter').bind('change', function(){
|
651 |
+
var value = $(this).val();
|
652 |
+
$('.metaseo-filter').each(function(i,e){
|
653 |
+
if($(e).val() != value){
|
654 |
+
$(e).val(value);
|
655 |
+
}
|
656 |
+
});
|
657 |
+
});
|
658 |
+
|
659 |
+
$('.metaseo-img-name').bind('input propertychange', function() {
|
660 |
+
var savedInfo = $(this).parent().find('span.saved-info');
|
661 |
+
var iname = validateiName( $(this).val() );
|
662 |
+
var msg = iname.msg;
|
663 |
+
|
664 |
+
if( iname.name.length > 0 ){
|
665 |
+
if(!checkspecial(this)){
|
666 |
+
msg = 'Should not special char';
|
667 |
+
}
|
668 |
+
else{
|
669 |
+
metaseo_update(this);
|
670 |
+
}
|
671 |
+
}
|
672 |
+
|
673 |
+
if(msg.length > 0){
|
674 |
+
//Set this value to metaseoValueHolder
|
675 |
+
metaseoValueHolder[this.id] = iname.name.substr(0, iname.name.length - 1);
|
676 |
+
|
677 |
+
savedInfo.removeClass('metaseo-msg-success')
|
678 |
+
.addClass('metaseo-msg-warning').empty().text(msg);
|
679 |
+
}
|
680 |
+
});
|
681 |
+
|
682 |
+
$('.metaseo-img-meta').bind('input propertychange', function(){
|
683 |
+
var savedInfo = $(this).parent().find('span.saved-info');
|
684 |
+
var metaseoValue = $(this).val();
|
685 |
+
|
686 |
+
if(checkspecial(this) === true){
|
687 |
+
if($(this).hasClass('metaseo-img-name')){
|
688 |
+
if(metaseoValue.trim().length > 0
|
689 |
+
&& typeof metaseoValueHolder[this.id] != 'undefined'
|
690 |
+
&& metaseoValueHolder[this.id] !== metaseoValue) {
|
691 |
+
metaseo_update(this);
|
692 |
+
}
|
693 |
+
}
|
694 |
+
else{
|
695 |
+
metaseo_update(this);
|
696 |
+
}
|
697 |
+
|
698 |
+
}
|
699 |
+
});
|
700 |
+
|
701 |
+
$('.metaseo-img-meta').each(function(i, element){
|
702 |
+
if($(this).hasClass('metaseo-img-name')){
|
703 |
+
metaseoValueHolder[this.id+'_prev'] = jQuery(this).val();
|
704 |
+
}
|
705 |
+
$(element).bind('keydown', function(event){
|
706 |
+
if(event.which == 13 || event.keyCode == 13){
|
707 |
+
return false;
|
708 |
+
}
|
709 |
+
});
|
710 |
+
});
|
711 |
+
|
712 |
+
$('.metaseo-img-meta').blur(function() {
|
713 |
+
if(jQuery(this).val() == ''){
|
714 |
+
jQuery(this).val(metaseoValueHolder[this.id+'_prev']);
|
715 |
+
$(this).parent().find('span.saved-info').empty().append('<span style="position:absolute" class="meta-update"></span>');
|
716 |
+
}
|
717 |
+
if(checkspecial(this) === true){
|
718 |
+
updateInputBlur(this);
|
719 |
+
}
|
720 |
+
});
|
721 |
+
|
722 |
+
$('.dissmiss-import').bind('click', function(e){
|
723 |
+
e.preventDefault();
|
724 |
+
$(this).closest('.error').fadeOut(1000);
|
725 |
+
setTimeout(function(){
|
726 |
+
$(this).closest('.error').remove();
|
727 |
+
},5000);
|
728 |
+
|
729 |
+
var plugin = $(this).parent().find('a.button').attr('id');
|
730 |
+
|
731 |
+
if(plugin === '_aio_' || plugin === '_yoast_'){
|
732 |
+
$.ajax({
|
733 |
+
url: myAjax.ajax_url,
|
734 |
+
type: 'post',
|
735 |
+
data: {'action': 'dismiss_import_meta', 'plugin' : plugin},
|
736 |
+
dataType: 'json',
|
737 |
+
beforeSend: function(){
|
738 |
+
|
739 |
+
},
|
740 |
+
success: function(response){
|
741 |
+
if(response.success !== true){
|
742 |
+
alert('Dismiss failed!');
|
743 |
+
}
|
744 |
+
}
|
745 |
+
});
|
746 |
+
}
|
747 |
+
});
|
748 |
+
//----------------------------------------------------------
|
749 |
+
//Pop-up declaration
|
750 |
+
$.fn.absoluteCenter = function() {
|
751 |
+
this.each(function() {
|
752 |
+
var top = -($(this).outerHeight() / 2) + 'px';
|
753 |
+
var left = -($(this).outerWidth() / 2) + 'px';
|
754 |
+
$(this).css({'position': 'fixed', 'top': $('div.wrap').offset().top, 'left': $('div.wrap').offset().left, 'right': '25px', 'bottom': '10px'});
|
755 |
+
|
756 |
+
return this;
|
757 |
+
});
|
758 |
+
};
|
759 |
+
|
760 |
+
$.fn.showPopup = function(that){
|
761 |
+
var bg = $('div.popup-bg');
|
762 |
+
var obj = that.closest('.col_image_info').find('div.popup');
|
763 |
+
var btnClose = obj.find('.popup-close');
|
764 |
+
bg.animate({opacity: 0.2}, 0).fadeIn(200);
|
765 |
+
//obj.fadeIn(200).draggable({cursor: 'move', handle: '.popup-header'}).absoluteCenter();
|
766 |
+
obj.fadeIn(200).absoluteCenter();
|
767 |
+
btnClose.click(function() {
|
768 |
+
bg.fadeOut(100);
|
769 |
+
obj.fadeOut(100).find('div.popup-content').empty();
|
770 |
+
});
|
771 |
+
bg.click(function() {
|
772 |
+
btnClose.click();
|
773 |
+
});
|
774 |
+
$(document).keydown(function(e) {
|
775 |
+
if (e.keyCode == 27) {
|
776 |
+
btnClose.click();
|
777 |
+
}
|
778 |
+
});
|
779 |
+
return false;
|
780 |
+
};
|
781 |
+
$('a.show-popup').bind('click', function() {
|
782 |
+
$(this).showPopup($(this));
|
783 |
+
});
|
784 |
+
});
|
js/pop-up.js
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery(document).ready(function() {
|
2 |
+
(function($) {
|
3 |
+
$.fn.absoluteCenter = function() {
|
4 |
+
this.each(function() {
|
5 |
+
var top = -($(this).outerHeight() / 2) + 'px';
|
6 |
+
var left = -($(this).outerWidth() / 2) + 'px';
|
7 |
+
$(this).css({'position': 'absolute', 'position':'fixed', 'margin-top': top, 'margin-left': left, 'top': '40%', 'left': '50%'});
|
8 |
+
return this;
|
9 |
+
});
|
10 |
+
}
|
11 |
+
})(jQuery);
|
12 |
+
|
13 |
+
$('a.show-popup').click(function() {
|
14 |
+
var bg = $('div.popup-bg');
|
15 |
+
var obj = $(this).parent().find('div.popup');
|
16 |
+
var btnClose = obj.find('.popup-close');
|
17 |
+
bg.animate({opacity: 0.2}, 0).fadeIn(200);
|
18 |
+
obj.fadeIn(200).draggable({cursor: 'move', handle: '.popup-header'}).absoluteCenter();
|
19 |
+
btnClose.click(function() {
|
20 |
+
bg.fadeOut(100);
|
21 |
+
obj.fadeOut(100);
|
22 |
+
});
|
23 |
+
bg.click(function() {
|
24 |
+
btnClose.click();
|
25 |
+
});
|
26 |
+
$(document).keydown(function(e) {
|
27 |
+
if (e.keyCode == 27) {
|
28 |
+
btnClose.click();
|
29 |
+
n
|
30 |
+
}
|
31 |
+
});
|
32 |
+
return false;
|
33 |
+
});
|
34 |
+
});
|
readme.txt
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WP Meta SEO ===
|
2 |
+
Contributors: JoomUnited
|
3 |
+
Tags: bing, description, google, google webmaster tools, keywords, meta, meta description, meta keywords, robots meta, search engine optimization, seo, Webmaster Tools, wordpress seo, yahoo, image optimization, image resize, photo, custom post, post, posts, page
|
4 |
+
Requires at least: 4.0
|
5 |
+
Tested up to: 4.1.0
|
6 |
+
Stable tag: trunk
|
7 |
+
License: GPLv2 or later
|
8 |
+
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
+
|
10 |
+
WP Meta SEO give you the control over all your content and image meta. In one view you will be able to add some meta, alt tag and resize photos.
|
11 |
+
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
WP meta SEO got 2 major functionnalities: bulk edit all website meta with intuitive interface and optimize wrong image size.
|
16 |
+
|
17 |
+
It was so annoying before and almost nobody was able to edit all meta content and image meta.
|
18 |
+
WP Meta SEO is going to list all articles, pages, custom post and all images. Update meta and image size and everything is AJAX saved.
|
19 |
+
You can now optimize the size of your images in articles. A bunch of people reduce the size of the pictures handling the border and resizing in html/css.
|
20 |
+
|
21 |
+
**More details here:** http://www.joomunited.com/wordpress-products/wp-meta-seo
|
22 |
+
|
23 |
+
= Video demo: =
|
24 |
+
[vimeo https://vimeo.com/113695156]
|
25 |
+
|
26 |
+
|
27 |
+
**Features**
|
28 |
+
|
29 |
+
* Reduce page weight by fix image size
|
30 |
+
* All alt tags edition on single view
|
31 |
+
* See all your snippet in one glance
|
32 |
+
* Image name, title, description and legend
|
33 |
+
* Automatic AJAX saving
|
34 |
+
* No additional complex params
|
35 |
+
* Custom type mate edition
|
36 |
+
* Yoast SEO import & sync
|
37 |
+
* Alexa world rank
|
38 |
+
* Meta content filtering
|
39 |
+
* Optimise images in all your post and pages
|
40 |
+
* Live Google snippet preview
|
41 |
+
* Edit images file name in single view
|
42 |
+
* All in one SEO importer and sync Filter content by post type: post, page, custom
|
43 |
+
* Image date filtering
|
44 |
+
* Content meta length limit (Google + Yahoo + Bing)
|
45 |
+
* SEO problem warning
|
46 |
+
* Bulk image resizing
|
47 |
+
* One click image title copy
|
48 |
+
|
49 |
+
= Support =
|
50 |
+
|
51 |
+
A PDF support documant is provided and a dedicated support can be provided additionnaly with a membership.
|
52 |
+
**More details here:** http://www.joomunited.com/wordpress-products/wp-meta-seo
|
53 |
+
|
54 |
+
== Installation ==
|
55 |
+
|
56 |
+
WordPress installation is fully supported.
|
57 |
+
Once the plugin is installed, just load the main plugin view to configure all meta and image optimization
|
58 |
+
|
59 |
+
|
60 |
+
== Screenshots ==
|
61 |
+
|
62 |
+
1. Main dashboad of the plugin with missing optimization and alexa rank
|
63 |
+
1. Bulk content meta edition
|
64 |
+
1. Content lenght is optimized for Google, Bing and Yahoo
|
65 |
+
1. Image resized in HTML (with handles) is automatically detected and can be resized
|
66 |
+
1. SEO Yoast and AIO SEO plugin and detected and data sync
|
67 |
+
1. Detail and alert on the element that you can optimize
|
68 |
+
1. Image replace confirmation
|
69 |
+
1. Title and filename image edition without and broken link
|
70 |
+
|
71 |
+
|
72 |
+
== Revisions ==
|
73 |
+
|
74 |
+
* 1.0.0 Initial (free) release
|
75 |
+
|
76 |
+
== Changelog ==
|
77 |
+
|
78 |
+
= 1.0.0 =
|
79 |
+
|
80 |
+
* Initial release
|
81 |
+
|
82 |
+
|
83 |
+
== Upgrade Notice ==
|
84 |
+
|
85 |
+
Install new version over the old one
|
86 |
+
|
87 |
+
|
88 |
+
== Requirements ==
|
89 |
+
|
90 |
+
PHP 5.3+
|
wp-meta-seo.php
ADDED
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Plugin Name: WP Meta SEO
|
5 |
+
* Plugin URI: http://www.joomunited.com/wordpress-products/wp-meta-seo
|
6 |
+
* Description: WP Meta SEO is a plugin for WordPress to fill meta for content, images and main SEO info in a single view.
|
7 |
+
* Version: 1.0.0
|
8 |
+
* Author: JoomUnited
|
9 |
+
* Author URI: http://www.joomunited.com
|
10 |
+
* License: GPL2
|
11 |
+
*/
|
12 |
+
/**
|
13 |
+
* @copyright 2014 Joomunited ( email : contact _at_ joomunited.com )
|
14 |
+
*
|
15 |
+
* Original development of this plugin was kindly funded by Joomunited
|
16 |
+
*
|
17 |
+
* This program is free software; you can redistribute it and/or modify
|
18 |
+
* it under the terms of the GNU General Public License as published by
|
19 |
+
* the Free Software Foundation; either version 2 of the License, or
|
20 |
+
* (at your option) any later version.
|
21 |
+
*
|
22 |
+
* This program is distributed in the hope that it will be useful,
|
23 |
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
24 |
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
25 |
+
* GNU General Public License for more details.
|
26 |
+
*
|
27 |
+
* You should have received a copy of the GNU General Public License
|
28 |
+
* along with this program; if not, write to the Free Software
|
29 |
+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
30 |
+
*/
|
31 |
+
# error_reporting(E_ALL);
|
32 |
+
// Make sure we don't expose any info if called directly
|
33 |
+
if (!function_exists('add_action')) {
|
34 |
+
echo 'Hi there! I\'m just a plugin, not much I can do when called directly.';
|
35 |
+
exit;
|
36 |
+
}
|
37 |
+
|
38 |
+
if (!defined('WPMETASEO_MINIMUM_WP_VERSION'))
|
39 |
+
define('WPMETASEO_MINIMUM_WP_VERSION', '3.1');
|
40 |
+
if (!defined('WPMETASEO_PLUGIN_URL'))
|
41 |
+
define('WPMETASEO_PLUGIN_URL', plugin_dir_url(__FILE__));
|
42 |
+
if (!defined('WPMETASEO_PLUGIN_DIR'))
|
43 |
+
define('WPMETASEO_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
44 |
+
if (!defined('URL'))
|
45 |
+
define('URL', get_site_url());
|
46 |
+
|
47 |
+
register_activation_hook(__FILE__, array('WpMetaSeo', 'plugin_activation'));
|
48 |
+
register_deactivation_hook(__FILE__, array('WpMetaSeo', 'plugin_deactivation'));
|
49 |
+
|
50 |
+
|
51 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/class.wp-metaseo.php' );
|
52 |
+
add_action('init', array('WpMetaSeo', 'init'));
|
53 |
+
|
54 |
+
if (is_admin()) {
|
55 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/class.metaseo-content-list-table.php' );
|
56 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/class.metaseo-image-list-table.php' );
|
57 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/class.metaseo-dashboard.php' );
|
58 |
+
require_once( WPMETASEO_PLUGIN_DIR . 'inc/class.metaseo-admin.php' );
|
59 |
+
|
60 |
+
$GLOBALS['metaseo_admin'] = new MetaSeo_Admin;
|
61 |
+
|
62 |
+
add_filter('wp_prepare_attachment_for_js', array('MetaSeo_Image_List_Table', 'add_more_attachment_sizes_js'), 10, 2);
|
63 |
+
add_filter('image_size_names_choose', array('MetaSeo_Image_List_Table', 'add_more_attachment_sizes_choose'), 10, 1);
|
64 |
+
} else {
|
65 |
+
/******** Check again and modify title, meta title, meta description before output ********/
|
66 |
+
//add_filter('wp_title', array('WpMetaSeo', 'new_title'), 99);
|
67 |
+
add_action('init', 'buffer_start');
|
68 |
+
add_action('wp_head', 'buffer_end');
|
69 |
+
|
70 |
+
function buffer_start() { ob_start("callback"); }
|
71 |
+
|
72 |
+
function buffer_end() { ob_end_flush(); }
|
73 |
+
|
74 |
+
function callback($buffer) {
|
75 |
+
// modify buffer here, and then return the updated code
|
76 |
+
global $wp_query;
|
77 |
+
$meta_title = get_post_meta($wp_query->post->ID, '_metaseo_metatitle', true);
|
78 |
+
$meta_description = get_post_meta($wp_query->post->ID, '_metaseo_metadesc', true);
|
79 |
+
$patterns = array(
|
80 |
+
'_title' => array('#<title>[^<>]+?<\/title>#i', '<title>'.$meta_title.'</title>',
|
81 |
+
($meta_title != '' ? true : false) ),
|
82 |
+
'title' => array(
|
83 |
+
'#<meta name="title" [^<>]+ ?>#i',
|
84 |
+
'<meta name="title" content="'. $meta_title .'" />',
|
85 |
+
($meta_title != '' ? true : false) ),
|
86 |
+
'description' => array(
|
87 |
+
'#<meta name="description" [^<>]+ ?>#i',
|
88 |
+
'<meta name="description" content="'. $meta_description .'" />',
|
89 |
+
($meta_description != '' ? true : false) ),
|
90 |
+
'og:title' => array(
|
91 |
+
'#<meta property="og:title" [^<>]+ ?>#i',
|
92 |
+
'<meta name="og:title" content="'. $meta_title .'" />',
|
93 |
+
($meta_title != '' ? true : false) ),
|
94 |
+
'og:description' => array(
|
95 |
+
'#<meta property="og:description" [^<>]+ ?>#i',
|
96 |
+
'<meta name="og:description" content="'. $meta_description .'" />',
|
97 |
+
($meta_description != '' ? true : false) )
|
98 |
+
);
|
99 |
+
|
100 |
+
//
|
101 |
+
foreach($patterns as $k => $pattern){
|
102 |
+
if(preg_match_all($pattern[0], $buffer, $matches)){
|
103 |
+
$replacement = array();
|
104 |
+
foreach($matches[0] as $key => $match){
|
105 |
+
if($key < 1){
|
106 |
+
$replacement[] = $pattern[2] ? $pattern[1] : $match."\n";
|
107 |
+
} else { $replacement[] = ''; }
|
108 |
+
}
|
109 |
+
|
110 |
+
$buffer = str_ireplace($matches[0], $replacement, $buffer);
|
111 |
+
}
|
112 |
+
else{
|
113 |
+
$buffer = str_ireplace('</title>', "</title>\n" . $pattern[1], $buffer);
|
114 |
+
}
|
115 |
+
}
|
116 |
+
|
117 |
+
return $buffer;
|
118 |
+
}
|
119 |
+
/***********************************************/
|
120 |
+
}
|
121 |
+
|
122 |
+
/******** Check and import meta data from other installed plugins for SEO ********/
|
123 |
+
/**
|
124 |
+
* Handle import of meta data from other installed plugins for SEO
|
125 |
+
*
|
126 |
+
* @since 1.5.0
|
127 |
+
*/
|
128 |
+
function wpmetaseo_aio_yoast_message() {
|
129 |
+
//update_option('_aio_import_notice_flag', 0);
|
130 |
+
//update_option('_yoast_import_notice_flag', 0);
|
131 |
+
$activated = 0;
|
132 |
+
// Check if All In One Pack is active
|
133 |
+
if(!get_option('_aio_import_notice_flag')){
|
134 |
+
if ( is_plugin_active( 'all-in-one-seo-pack/all_in_one_seo_pack.php' ) ) {
|
135 |
+
add_action( 'admin_notices', 'wpmetaseo_import_aio_meta_notice', 2 );
|
136 |
+
$activated++;
|
137 |
+
}
|
138 |
+
|
139 |
+
if(get_option('_aio_import_notice_flag') === false){
|
140 |
+
update_option('_aio_import_notice_flag', 0);
|
141 |
+
}
|
142 |
+
}
|
143 |
+
// Check if Yoast is active
|
144 |
+
if(!get_option('_yoast_import_notice_flag', false)){
|
145 |
+
if ( is_plugin_active( 'wordpress-seo/wp-seo.php' ) ) {
|
146 |
+
add_action( 'admin_notices', 'wpmetaseo_import_yoast_meta_notice', 3 );
|
147 |
+
$activated++;
|
148 |
+
}
|
149 |
+
|
150 |
+
if(get_option('_yoast_import_notice_flag') === false){
|
151 |
+
update_option('_yoast_import_notice_flag', 0);
|
152 |
+
}
|
153 |
+
}
|
154 |
+
|
155 |
+
|
156 |
+
if($activated === 2 && !get_option('plugin_to_sync_with', false)){
|
157 |
+
add_action('admin_notices', create_function('$notImportant', 'echo "<div class=\"error metaseo-import-wrn\"><p>". __("Be careful you installed 2 extensions doing almost the same thing, please deactivate AIOSEO or Yoast in order to work more clearly!", "wp-meta-seo") ."</p></div>";'), 1);
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
add_action( 'admin_init', 'wpmetaseo_aio_yoast_message' );
|
162 |
+
|
163 |
+
function wpmetaseo_import_aio_meta_notice(){
|
164 |
+
echo '<div class="error metaseo-import-wrn"><p>'. sprintf( __('We have found that you’re using All In One Pack Plugin, WP Meta SEO can import the meta from this plugin, %s', 'wp-meta-seo'), '<a href="#" class="button mseo-import-action" style="position:relative" onclick="importMetaData(this, event)" id="_aio_"><span class="spinner-light"></span>Import now</a> or <a href="#" class="dissmiss-import">dismiss this</a>' ) .'</p></div>';
|
165 |
+
}
|
166 |
+
|
167 |
+
function wpmetaseo_import_yoast_meta_notice(){
|
168 |
+
echo '<div class="error metaseo-import-wrn"><p>'. sprintf( __('We have found that you’re using Yoast SEO Plugin, WP Meta SEO can import the meta from this plugin, %s', 'wp-meta-seo'), '<a href="#" class="button mseo-import-action" style="position:relative" onclick="importMetaData(this, event)" id="_yoast_">Import now<span class="spinner-light"></span></a> or <a href="#" class="dissmiss-import">dismiss this</a>' ) .'</p></div>';
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Encode or decode all values in string format of an array
|
173 |
+
*/
|
174 |
+
function metaseo_utf8($obj, $action = 'encode'){
|
175 |
+
$action = strtolower(trim($action));
|
176 |
+
$fn = "utf8_$action";
|
177 |
+
if(is_array($obj)){
|
178 |
+
foreach($obj as &$el){
|
179 |
+
if(is_array($el)){
|
180 |
+
if(is_callable($fn)){
|
181 |
+
$el = metaseo_utf8($el, $action);
|
182 |
+
}
|
183 |
+
}
|
184 |
+
elseif(is_string($el)){
|
185 |
+
//var_dump(mb_detect_encoding($el));
|
186 |
+
$isASCII = mb_detect_encoding($el, 'ASCII');
|
187 |
+
if($action === 'encode' && !$isASCII){
|
188 |
+
$el = mb_convert_encoding($el, "UTF-8", "auto");
|
189 |
+
}
|
190 |
+
|
191 |
+
$el = $fn($el);
|
192 |
+
}
|
193 |
+
}
|
194 |
+
}elseif (is_object($obj)) {
|
195 |
+
$vars = array_keys(get_object_vars($obj));
|
196 |
+
foreach ($vars as $var) {
|
197 |
+
metaseo_utf8($obj->$var, $action);
|
198 |
+
}
|
199 |
+
}
|
200 |
+
|
201 |
+
return $obj;
|
202 |
+
}
|
203 |
+
/**********************************************************************************/
|