Version Description
- Added audit log reporting.
- Added more settings for better control.
- Added support for more actions.
- Improved multisite support.
- Added support for reverse proxies.
- Various bugfixes and improvements.
Download this release
Release Info
Developer | akresic |
Plugin | Sucuri Security – Auditing, Malware Scanner and Security Hardening |
Version | 1.7.6 |
Comparing to | |
See all releases |
Code changes from version 1.7.5 to 1.7.6
- inc/css/sucuriscan-default-css.css +84 -19
- inc/js/c3.min.js +5 -0
- inc/js/d3.v3.min.js +5 -0
- inc/tpl/base.html.tpl +5 -2
- inc/tpl/infosys-errorlogs.html.tpl +23 -4
- inc/tpl/infosys-errorlogs.snippet.tpl +1 -1
- inc/tpl/infosys-wpconfig.snippet.tpl +1 -1
- inc/tpl/integrity-auditlogs.html.tpl +7 -5
- inc/tpl/integrity-auditlogs.snippet.tpl +9 -3
- inc/tpl/integrity-auditreport.html.tpl +112 -0
- inc/tpl/integrity-corefiles.html.tpl +11 -16
- inc/tpl/integrity-corefiles.snippet.tpl +2 -1
- inc/tpl/integrity-modifiedfiles.html.tpl +32 -3
- inc/tpl/integrity-modifiedfiles.snippet.tpl +1 -1
- inc/tpl/integrity.html.tpl +2 -0
- inc/tpl/lastlogins-admins.snippet.tpl +1 -1
- inc/tpl/lastlogins-failedlogins.html.tpl +21 -16
- inc/tpl/posthack-resetpassword.html.tpl +8 -0
- inc/tpl/posthack-resetpassword.snippet.tpl +1 -1
- inc/tpl/settings-general.html.tpl +60 -12
- inc/tpl/settings-ignorerules.html.tpl +3 -3
- inc/tpl/settings-ignorescanning.html.tpl +1 -1
- inc/tpl/settings-notifications.snippet.tpl +1 -1
- inc/tpl/settings-scanner.html.tpl +96 -45
- readme.txt +10 -2
- sucuri.php +3682 -2595
inc/css/sucuriscan-default-css.css
CHANGED
@@ -17,14 +17,14 @@
|
|
17 |
.sucuriscan-list li{list-style:disc;margin:0 0 5px 15px}
|
18 |
.sucuriscan-gradient, .sucuriscan-modal-header, .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9)}
|
19 |
/* WordPress Extra Buttons (success) */
|
20 |
-
.wp-core-ui .button-success.button-success{background:#
|
21 |
-
.wp-core-ui .button-success.focus, .wp-core-ui .button-success.hover, .wp-core-ui .button-success:focus, .wp-core-ui .button-success:hover{background:#
|
22 |
.wp-core-ui .button-success.focus, .wp-core-ui .button-success:focus{border-color:#23500e}
|
23 |
.wp-core-ui .button-success.active, .wp-core-ui .button-success.active:focus, .wp-core-ui .button-success.active:hover, .wp-core-ui .button-success:active{background:#47a61b;border-color:#358400}
|
24 |
.wp-core-ui .button-success-disabled, .wp-core-ui .button-success.disabled, .wp-core-ui .button-success:disabled, .wp-core-ui .button-success[disabled]{color:#b2e794 !important;background:#74ba29 !important;border-color:#3f7f1b !important}
|
25 |
/* WordPress Extra Buttons (danger) */
|
26 |
-
.wp-core-ui .button-danger.button-danger{background:#
|
27 |
-
.wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#
|
28 |
.wp-core-ui .button-danger.focus, .wp-core-ui .button-danger:focus{border-color:#500e0e}
|
29 |
.wp-core-ui .button-danger.active, .wp-core-ui .button-danger.active:focus, .wp-core-ui .button-danger.active:hover, .wp-core-ui .button-danger:active{background:#a61b1b;border-color:#840000}
|
30 |
.wp-core-ui .button-danger-disabled, .wp-core-ui .button-danger.disabled, .wp-core-ui .button-danger:disabled, .wp-core-ui .button-danger[disabled]{color:#e79494 !important;background:#ba2929 !important;border-color:#7f1b1b !important}
|
@@ -50,6 +50,8 @@
|
|
50 |
.sucuriscan-wrap{margin-top:20px}
|
51 |
.sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
|
52 |
.sucuriscan-wrap .sucuriscan-leftside{width:73.5%;float:left}
|
|
|
|
|
53 |
.sucuriscan-wrap .sucuriscan-sidebar{width:25%;float:right}
|
54 |
.sucuriscan-wrap #warnings_hook{line-height:initial;padding:0}
|
55 |
.sucuriscan-wrap .sucuriscan-navbar{padding-top:20px;padding-left:6px}
|
@@ -62,6 +64,7 @@
|
|
62 |
.sucuriscan-wrap .sucuriscan-logo img{display:block}
|
63 |
.sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{color:#fff;line-height:38px;margin-left:10px;text-shadow:#000 0 1px 0}
|
64 |
.sucuriscan-leftside #poststuff .postbox:last-child{margin-bottom:0}
|
|
|
65 |
/* Page Setup Notice */
|
66 |
.wrap div.sucuriscan-setup-notice{background:#bbe8f5;margin:0 0 20px 0;padding:0;border:1px solid #bbb;border-radius:3px;-webkit-box-shadow:none;box-shadow:none}
|
67 |
.wrap div.sucuriscan-setup-notice .sucuriscan-setup-image, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image img{border-radius:3px 0 0 3px}
|
@@ -69,6 +72,7 @@
|
|
69 |
.wrap div.sucuriscan-setup-notice .sucuriscan-setup-form{padding:4px;padding-left:0}
|
70 |
.wrap div.sucuriscan-setup-notice p{font-size:14px;line-height:20px;margin:0 0 0 10px;padding:7px 0}
|
71 |
.wrap div.sucuriscan-setup-notice, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image{border-color:#4393ac}
|
|
|
72 |
/* Table Styles */
|
73 |
.sucuriscan-maincontent .sucuriscan-table{margin-top:12px}
|
74 |
.sucuriscan-maincontent .sucuriscan-table tr > th{border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5}
|
@@ -84,7 +88,7 @@
|
|
84 |
.widefat td.td-with-button button{min-width:90px}
|
85 |
.widefat td.td-with-button select{height:initial;line-height:initial;vertical-align:top;margin:0;padding:2px 0 3px 0}
|
86 |
.sucuriscan-list-as-table{background:#fff;border:1px solid #e5e5e5}
|
87 |
-
.sucuriscan-list-as-table li{line-height:30px;margin:0;padding:0 10px}
|
88 |
.sucuriscan-list-as-table li:nth-child(odd){background:#f5f5f5}
|
89 |
.sucuriscan-list-as-table-scrollable{height:300px;overflow:hidden;overflow-y:scroll}
|
90 |
/* Table Top-Right Buttons */
|
@@ -97,6 +101,7 @@
|
|
97 |
.sucuriscan-sidebar .sucuriscan-ad{border:1px solid #ccc;margin:0 0 20px 0;padding:20px;border-radius:4px}
|
98 |
.sucuriscan-sidebar .sucuriscan-ad h2{font-size:18px;line-height:normal;padding:0}
|
99 |
.sucuriscan-sidebar .sucuriscan-ad p:last-child{margin-bottom:0}
|
|
|
100 |
.sucuriscan-sidebar .sucuriscan-ad:nth-child(odd){background-color:#bbe8f5;border-color:#4393ac}
|
101 |
.sucuriscan-sidebar .sucuriscan-ad:nth-child(even){background-color:#ececec;border-color:#999}
|
102 |
.sucuriscan-scanner-video{width:100%;background:#fff;border:1px solid #ddd}
|
@@ -142,31 +147,50 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
|
|
142 |
.sucuriscan-maincontent .sucuriscan-border > h3, .sucuriscan-maincontent .sucuriscan-border > .inside{border-top:1px solid #e5e5e5;border-right:1px solid #e5e5e5}
|
143 |
.sucuriscan-maincontent .sucuriscan-border > h3{border-bottom:0}
|
144 |
.sucuriscan-maincontent .sucuriscan-border > .inside{margin-top:0 !important;border-bottom:1px solid #ddd}
|
145 |
-
.sucuriscan-maincontent .sucuriscan-border-good{border-left-color:#7ad03a}
|
146 |
-
.sucuriscan-maincontent .sucuriscan-border-bad{border-left-color:#dd3d36}
|
147 |
.sucuriscan-maincontent .sucuriscan-border-info{border-left-color:#2ea2cc}
|
148 |
.sucuriscan-maincontent .sucuriscan-cleanup-btn{display:block;text-align:center;margin:20px 0 0 0}
|
149 |
.sucuriscan-scanner-results .sucuriscan-scanner-details tr:nth-child(even),
|
150 |
.sucuriscan-scanner-results .sucuriscan-scanner-links tr:nth-child(even){background:#f5f5f5}
|
151 |
.sucuriscan-scanner-results td.sucuriscan-border-bad{border-left-width:4px;border-left-style:solid}
|
152 |
-
|
153 |
-
.sucuriscan-
|
154 |
-
|
155 |
-
.sucuriscan-status-modified{background:#fcf8e3;color:#8a6d3b;border-color:#faebcc}
|
156 |
-
.sucuriscan-status-removed, td.sucuriscan-corefiles-warning > div{background:#f2dede;color:#a94442;border-color:#ebccd1}
|
157 |
.sucuriscan-maincontent .sucuriscan-corefiles,
|
158 |
.sucuriscan-maincontent .sucuriscan-integrity-message,
|
159 |
.sucuriscan-maincontent .sucuriscan-wordpress-outdated,
|
160 |
.sucuriscan-maincontent .sucuriscan-auditlogs{margin-top:0;margin-bottom:20px}
|
161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning, .sucuriscan-maincontent td.sucuriscan-corefiles-warning p{margin:0;padding:0}
|
163 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning div{padding:10px;border-width:1px;border-style:solid}
|
164 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning code{font-size:12px;padding:0 5px}
|
165 |
.sucuriscan-maincontent .sucuriscan-integrity-message{position:relative}
|
166 |
.sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark{position:absolute;top:1px;right:1px;background:#7ad03a;font-weight:bold;color:#fff;line-height:35px;padding:0 10px;border-left:1px solid #ddd}
|
167 |
-
.sucuriscan-maincontent .sucuriscan-auditlogs{margin-bottom:0}
|
168 |
-
.sucuriscan-maincontent .sucuriscan-auditlogs .sucuriscan-list-as-table{margin-bottom:0}
|
169 |
-
.sucuriscan-maincontent .sucuriscan-auditlogs .sucuriscan-maxper-page{text-align:right}
|
170 |
.sucuriscan-maincontent .sucuriscan-ignoredfiles{margin-top:0}
|
171 |
.sucuriscan-maincontent .sucuriscan-modifiedfiles .sucuriscan-ellipsis{width:100px}
|
172 |
/* Monitoring Styles */
|
@@ -194,9 +218,11 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
|
|
194 |
.sucuriscan-request-summary td+td{font-family:monospace;word-break:break-all}
|
195 |
/* Hardening Status */
|
196 |
.sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
|
197 |
-
.sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
|
198 |
.sucuriscan-hstatus-0{background-color:#f2dede;color:#a94442;border-color:#ebccd1}
|
|
|
|
|
199 |
.sucuriscan-hstatus .button-primary, .sucuriscan-hstatus .button-secondary{position:absolute;top:5px;right:5px}
|
|
|
200 |
/* Last Logins Styles */
|
201 |
.sucuriscan-lastlogin-outof{font-style:italic;color:#999;margin-right:10px}
|
202 |
.sucuriscan-admins-lastlogins .sucuriscan-ellipsis{width:170px}
|
@@ -224,8 +250,9 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
|
|
224 |
.sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
|
225 |
.sucuriscan-maincontent .sucuriscan-settings{margin-top:0}
|
226 |
.sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
|
227 |
-
.sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{width:220px}
|
228 |
.sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
|
|
|
229 |
.sucuriscan-maincontent .sucuriscan-settings-ignorescanning{margin-top:0}
|
230 |
.sucuriscan-maincontent .sucuriscan-settings-trustip{margin-top:0}
|
231 |
.sucuriscan-maincontent .sucuriscan-settings-heartbeat{}
|
@@ -242,6 +269,43 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
|
|
242 |
.admin-color-midnight .wrap div.sucuriscan-setup-notice, .admin-color-midnight .sucuriscan-ad:nth-child(odd){background:#f1b8b4;border-color:#d02a21}
|
243 |
.admin-color-ocean .wrap div.sucuriscan-setup-notice, .admin-color-ocean .sucuriscan-ad:nth-child(odd){background:#c6e7c8;border-color:#719a74}
|
244 |
.admin-color-sunrise .wrap div.sucuriscan-setup-notice, .admin-color-sunrise .sucuriscan-ad:nth-child(odd){background:#ecc2a2;border-color:#c36822}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
/* Responsive Styles */
|
246 |
@media (max-width: 620px) {
|
247 |
.sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
|
@@ -258,8 +322,9 @@ div.sucuriscan-alert > a.close{position:absolute;top:10px;right:10px;font-size:1
|
|
258 |
}
|
259 |
@media (max-width: 920px) {
|
260 |
.sucuriscan-wrap .sucuriscan-navbar{padding-left:0;padding-right:0}
|
261 |
-
.sucuriscan-wrap .sucuriscan-navbar .nav-tab{display:block}
|
262 |
.sucuriscan-wrap .sucuriscan-navbar .nav-tab:last-child{border-bottom:1px solid #ccc}
|
|
|
263 |
}
|
264 |
/* Old styles */
|
265 |
.sucuriscan-maincontent #poststuff{min-width:initial;padding-top:0}
|
17 |
.sucuriscan-list li{list-style:disc;margin:0 0 5px 15px}
|
18 |
.sucuriscan-gradient, .sucuriscan-modal-header, .sucuriscan-maincontent .sucuriscan-table tr > th{background:#f1f1f1;background-image:-webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-moz-linear-gradient(bottom,#ececec,#f9f9f9);background-image:-o-linear-gradient(bottom,#ececec,#f9f9f9);background-image:linear-gradient(to top,#ececec,#f9f9f9)}
|
19 |
/* WordPress Extra Buttons (success) */
|
20 |
+
.wp-core-ui .button-success, .wp-core-ui .button-success.focus, .wp-core-ui .button-success.hover, .wp-core-ui .button-success:focus, .wp-core-ui .button-success:hover{background:#8dcd5a;border-color:#48a325;box-shadow:inset 0 1px 0 rgba(195, 230, 180, 0.6)}
|
21 |
+
.wp-core-ui .button-success.focus, .wp-core-ui .button-success.hover, .wp-core-ui .button-success:focus, .wp-core-ui .button-success:hover{background:#69be48}
|
22 |
.wp-core-ui .button-success.focus, .wp-core-ui .button-success:focus{border-color:#23500e}
|
23 |
.wp-core-ui .button-success.active, .wp-core-ui .button-success.active:focus, .wp-core-ui .button-success.active:hover, .wp-core-ui .button-success:active{background:#47a61b;border-color:#358400}
|
24 |
.wp-core-ui .button-success-disabled, .wp-core-ui .button-success.disabled, .wp-core-ui .button-success:disabled, .wp-core-ui .button-success[disabled]{color:#b2e794 !important;background:#74ba29 !important;border-color:#3f7f1b !important}
|
25 |
/* WordPress Extra Buttons (danger) */
|
26 |
+
.wp-core-ui .button-danger, .wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#cd5050;border-color:#a52121;box-shadow:inset 0 1px 0 rgba(230, 170, 170, 0.6)}
|
27 |
+
.wp-core-ui .button-danger.focus, .wp-core-ui .button-danger.hover, .wp-core-ui .button-danger:focus, .wp-core-ui .button-danger:hover{background:#be4242}
|
28 |
.wp-core-ui .button-danger.focus, .wp-core-ui .button-danger:focus{border-color:#500e0e}
|
29 |
.wp-core-ui .button-danger.active, .wp-core-ui .button-danger.active:focus, .wp-core-ui .button-danger.active:hover, .wp-core-ui .button-danger:active{background:#a61b1b;border-color:#840000}
|
30 |
.wp-core-ui .button-danger-disabled, .wp-core-ui .button-danger.disabled, .wp-core-ui .button-danger:disabled, .wp-core-ui .button-danger[disabled]{color:#e79494 !important;background:#ba2929 !important;border-color:#7f1b1b !important}
|
50 |
.sucuriscan-wrap{margin-top:20px}
|
51 |
.sucuriscan-wrap .sucuriscan-maincontent{margin:20px 0}
|
52 |
.sucuriscan-wrap .sucuriscan-leftside{width:73.5%;float:left}
|
53 |
+
.sucuriscan-wrap .sucuriscan-twocolumns{}
|
54 |
+
.sucuriscan-wrap .sucuriscan-onecolumn{width:100%}
|
55 |
.sucuriscan-wrap .sucuriscan-sidebar{width:25%;float:right}
|
56 |
.sucuriscan-wrap #warnings_hook{line-height:initial;padding:0}
|
57 |
.sucuriscan-wrap .sucuriscan-navbar{padding-top:20px;padding-left:6px}
|
64 |
.sucuriscan-wrap .sucuriscan-logo img{display:block}
|
65 |
.sucuriscan-wrap .sucuriscan-header h2, .sucuriscan-wrap .sucuriscan-footer h2{color:#fff;line-height:38px;margin-left:10px;text-shadow:#000 0 1px 0}
|
66 |
.sucuriscan-leftside #poststuff .postbox:last-child{margin-bottom:0}
|
67 |
+
.sucuriscan-maincontent abbr{text-decoration:underline;cursor:help}
|
68 |
/* Page Setup Notice */
|
69 |
.wrap div.sucuriscan-setup-notice{background:#bbe8f5;margin:0 0 20px 0;padding:0;border:1px solid #bbb;border-radius:3px;-webkit-box-shadow:none;box-shadow:none}
|
70 |
.wrap div.sucuriscan-setup-notice .sucuriscan-setup-image, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image img{border-radius:3px 0 0 3px}
|
72 |
.wrap div.sucuriscan-setup-notice .sucuriscan-setup-form{padding:4px;padding-left:0}
|
73 |
.wrap div.sucuriscan-setup-notice p{font-size:14px;line-height:20px;margin:0 0 0 10px;padding:7px 0}
|
74 |
.wrap div.sucuriscan-setup-notice, .wrap div.sucuriscan-setup-notice .sucuriscan-setup-image{border-color:#4393ac}
|
75 |
+
.wp-core-ui .sucuriscan-review-hero, .wp-core-ui .button.sucuriscan-review-hero{position:relative;top:-2px;right:-15px;height:initial;line-height:30px;float:right;padding:0 20px}
|
76 |
/* Table Styles */
|
77 |
.sucuriscan-maincontent .sucuriscan-table{margin-top:12px}
|
78 |
.sucuriscan-maincontent .sucuriscan-table tr > th{border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5}
|
88 |
.widefat td.td-with-button button{min-width:90px}
|
89 |
.widefat td.td-with-button select{height:initial;line-height:initial;vertical-align:top;margin:0;padding:2px 0 3px 0}
|
90 |
.sucuriscan-list-as-table{background:#fff;border:1px solid #e5e5e5}
|
91 |
+
.sucuriscan-list-as-table li{line-height:30px;word-break:break-all;margin:0;padding:0 10px}
|
92 |
.sucuriscan-list-as-table li:nth-child(odd){background:#f5f5f5}
|
93 |
.sucuriscan-list-as-table-scrollable{height:300px;overflow:hidden;overflow-y:scroll}
|
94 |
/* Table Top-Right Buttons */
|
101 |
.sucuriscan-sidebar .sucuriscan-ad{border:1px solid #ccc;margin:0 0 20px 0;padding:20px;border-radius:4px}
|
102 |
.sucuriscan-sidebar .sucuriscan-ad h2{font-size:18px;line-height:normal;padding:0}
|
103 |
.sucuriscan-sidebar .sucuriscan-ad p:last-child{margin-bottom:0}
|
104 |
+
.sucuriscan-sidebar .sucuriscan-ad .sucuriscan-list li{margin-left:17px}
|
105 |
.sucuriscan-sidebar .sucuriscan-ad:nth-child(odd){background-color:#bbe8f5;border-color:#4393ac}
|
106 |
.sucuriscan-sidebar .sucuriscan-ad:nth-child(even){background-color:#ececec;border-color:#999}
|
107 |
.sucuriscan-scanner-video{width:100%;background:#fff;border:1px solid #ddd}
|
147 |
.sucuriscan-maincontent .sucuriscan-border > h3, .sucuriscan-maincontent .sucuriscan-border > .inside{border-top:1px solid #e5e5e5;border-right:1px solid #e5e5e5}
|
148 |
.sucuriscan-maincontent .sucuriscan-border > h3{border-bottom:0}
|
149 |
.sucuriscan-maincontent .sucuriscan-border > .inside{margin-top:0 !important;border-bottom:1px solid #ddd}
|
150 |
+
.sucuriscan-maincontent .sucuriscan-border-good, .sucuriscan-maincontent .sucuriscan-border-success{border-left-color:#7ad03a}
|
151 |
+
.sucuriscan-maincontent .sucuriscan-border-bad, .sucuriscan-maincontent .sucuriscan-border-danger{border-left-color:#dd3d36}
|
152 |
.sucuriscan-maincontent .sucuriscan-border-info{border-left-color:#2ea2cc}
|
153 |
.sucuriscan-maincontent .sucuriscan-cleanup-btn{display:block;text-align:center;margin:20px 0 0 0}
|
154 |
.sucuriscan-scanner-results .sucuriscan-scanner-details tr:nth-child(even),
|
155 |
.sucuriscan-scanner-results .sucuriscan-scanner-links tr:nth-child(even){background:#f5f5f5}
|
156 |
.sucuriscan-scanner-results td.sucuriscan-border-bad{border-left-width:4px;border-left-style:solid}
|
157 |
+
.sucuriscan-scanner-results .sucuriscan-malware-link{text-align:right}
|
158 |
+
.sucuriscan-scanner-results .sucuriscan-malware-link a:hover{color:#fff}
|
159 |
+
/* Generic Panel Magin Styles */
|
|
|
|
|
160 |
.sucuriscan-maincontent .sucuriscan-corefiles,
|
161 |
.sucuriscan-maincontent .sucuriscan-integrity-message,
|
162 |
.sucuriscan-maincontent .sucuriscan-wordpress-outdated,
|
163 |
.sucuriscan-maincontent .sucuriscan-auditlogs{margin-top:0;margin-bottom:20px}
|
164 |
+
/* Audit Logs Styles */
|
165 |
+
.sucuriscan-maincontent .sucuriscan-auditlogs{margin-bottom:0}
|
166 |
+
.sucuriscan-auditlogs .sucuriscan-list-as-table{margin-bottom:0}
|
167 |
+
.sucuriscan-auditlogs .sucuriscan-maxper-page{text-align:right}
|
168 |
+
.sucuriscan-auditlogs .sucuriscan-label{display:inline-block;width:56px;line-height:13px}
|
169 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-success, .sucuriscan-label-added{background:#5cb85c}
|
170 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-debug{background:#c690ec}
|
171 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-info{background:#5bc0de}
|
172 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-notice{background:#428bca}
|
173 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-warning, .sucuriscan-label-modified{background:#f0ad4e}
|
174 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-error, .sucuriscan-label-removed{background:#f27d7d}
|
175 |
+
.sucuriscan-auditlogs .sucuriscan-auditlog-critical{background:#000}
|
176 |
+
/* Audit Report Styles */
|
177 |
+
.sucuriscan-maincontent .sucuriscan-audit-report{border-left-width:1px}
|
178 |
+
.sucuriscan-audit-report .sucuriscan-report-row{margin-bottom:10px}
|
179 |
+
.sucuriscan-audit-report .sucuriscan-report-row:last-child{margin-bottom:0}
|
180 |
+
.sucuriscan-audit-report .sucuriscan-report-chart{width:49%;border:1px solid #ddd}
|
181 |
+
.sucuriscan-audit-report .sucuriscan-report-chart h4, .sucuriscan-audit-report .sucuriscan-report-chart h5{font-weight:normal;text-align:center;margin:0}
|
182 |
+
.sucuriscan-audit-report .sucuriscan-report-chart h4{font-size:18px;margin-top:10px}
|
183 |
+
.sucuriscan-audit-report .sucuriscan-report-chart h5{font-size:12px;margin-top:5px}
|
184 |
+
.sucuriscan-maincontent .sucuriscan-audit-report .sucuriscan-inline-alert-info{margin-top:10px}
|
185 |
+
/* Integrity Styles */
|
186 |
+
.sucuriscan-status-type{display:inline-block;width:20px;background:#ddd;text-align:center;text-transform:uppercase;margin-right:10px;padding:0 3px;border:1px solid transparent;border-radius:3px}
|
187 |
+
td.sucuriscan-corefiles-warning > div{background:#f2dede;color:#a94442;border-color:#ebccd1}
|
188 |
+
.sucuriscan-maincontent .sucuriscan-corefiles .sucuriscan-label{text-transform:capitalize}
|
189 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning, .sucuriscan-maincontent td.sucuriscan-corefiles-warning p{margin:0;padding:0}
|
190 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning div{padding:10px;border-width:1px;border-style:solid}
|
191 |
.sucuriscan-maincontent td.sucuriscan-corefiles-warning code{font-size:12px;padding:0 5px}
|
192 |
.sucuriscan-maincontent .sucuriscan-integrity-message{position:relative}
|
193 |
.sucuriscan-maincontent .sucuriscan-integrity-message .sucuriscan-integrity-mark{position:absolute;top:1px;right:1px;background:#7ad03a;font-weight:bold;color:#fff;line-height:35px;padding:0 10px;border-left:1px solid #ddd}
|
|
|
|
|
|
|
194 |
.sucuriscan-maincontent .sucuriscan-ignoredfiles{margin-top:0}
|
195 |
.sucuriscan-maincontent .sucuriscan-modifiedfiles .sucuriscan-ellipsis{width:100px}
|
196 |
/* Monitoring Styles */
|
218 |
.sucuriscan-request-summary td+td{font-family:monospace;word-break:break-all}
|
219 |
/* Hardening Status */
|
220 |
.sucuriscan-hstatus{position:relative;margin:0 -12px;padding:10px 12px;border:1px solid transparent}
|
|
|
221 |
.sucuriscan-hstatus-0{background-color:#f2dede;color:#a94442;border-color:#ebccd1}
|
222 |
+
.sucuriscan-hstatus-1{background-color:#dff0d8;color:#3c763d;border-color:#d6e9c6}
|
223 |
+
.sucuriscan-hstatus-2{background-color:#dee4f2;color:#4263a9;border-color:#ccd0eb}
|
224 |
.sucuriscan-hstatus .button-primary, .sucuriscan-hstatus .button-secondary{position:absolute;top:5px;right:5px}
|
225 |
+
.sucuriscan-hardening .postbox .inside pre{background:#eaeaea;padding:10px}
|
226 |
/* Last Logins Styles */
|
227 |
.sucuriscan-lastlogin-outof{font-style:italic;color:#999;margin-right:10px}
|
228 |
.sucuriscan-admins-lastlogins .sucuriscan-ellipsis{width:170px}
|
250 |
.sucuriscan-maincontent .sucuriscan-full-textarea{width:100%;height:400px;line-height:normal;resize:vertical;padding:10px}
|
251 |
.sucuriscan-maincontent .sucuriscan-settings{margin-top:0}
|
252 |
.sucuriscan-maincontent .sucuriscan-settings form{display:inline-block}
|
253 |
+
.sucuriscan-maincontent .sucuriscan-settings select, .sucuriscan-maincontent .sucuriscan-settings .input-text{width:220px;margin:0}
|
254 |
.sucuriscan-maincontent .sucuriscan-settings-notifications{margin-top:0}
|
255 |
+
.sucuriscan-maincontent .sucuriscan-settings-notifications .dashicons-before:before{margin-right:5px}
|
256 |
.sucuriscan-maincontent .sucuriscan-settings-ignorescanning{margin-top:0}
|
257 |
.sucuriscan-maincontent .sucuriscan-settings-trustip{margin-top:0}
|
258 |
.sucuriscan-maincontent .sucuriscan-settings-heartbeat{}
|
269 |
.admin-color-midnight .wrap div.sucuriscan-setup-notice, .admin-color-midnight .sucuriscan-ad:nth-child(odd){background:#f1b8b4;border-color:#d02a21}
|
270 |
.admin-color-ocean .wrap div.sucuriscan-setup-notice, .admin-color-ocean .sucuriscan-ad:nth-child(odd){background:#c6e7c8;border-color:#719a74}
|
271 |
.admin-color-sunrise .wrap div.sucuriscan-setup-notice, .admin-color-sunrise .sucuriscan-ad:nth-child(odd){background:#ecc2a2;border-color:#c36822}
|
272 |
+
/* 3CJS Chart styles */
|
273 |
+
.c3 svg{font:10px sans-serif}
|
274 |
+
.c3 line,.c3 path{fill:none;stroke:#000}
|
275 |
+
.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}
|
276 |
+
.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}
|
277 |
+
.c3-chart-arc path{stroke:#fff}
|
278 |
+
.c3-chart-arc text{fill:#fff;font-size:13px}
|
279 |
+
.c3-grid line{stroke:#aaa}
|
280 |
+
.c3-grid text{fill:#aaa}
|
281 |
+
.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}
|
282 |
+
.c3-text.c3-empty{fill:gray;font-size:2em}
|
283 |
+
.c3-line{stroke-width:1px}
|
284 |
+
.c3-circle._expanded_{stroke-width:1px;stroke:#fff}
|
285 |
+
.c3-selected-circle{fill:#fff;stroke-width:2px}
|
286 |
+
.c3-bar{stroke-width:0}
|
287 |
+
.c3-bar._expanded_{fill-opacity:.75}
|
288 |
+
.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}
|
289 |
+
.c3-target.c3-focused{opacity:1}
|
290 |
+
.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}
|
291 |
+
.c3-target.c3-defocused{opacity:.3!important}
|
292 |
+
.c3-region{fill:#4682b4;fill-opacity:.1}
|
293 |
+
.c3-brush .extent{fill-opacity:.1}
|
294 |
+
.c3-legend-item{font-size:12px}
|
295 |
+
.c3-legend-item-hidden{opacity:.15}
|
296 |
+
.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}
|
297 |
+
.c3-tooltip-container{z-index:10}
|
298 |
+
.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777}
|
299 |
+
.c3-tooltip tr{border:1px solid #CCC}
|
300 |
+
.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}
|
301 |
+
.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}
|
302 |
+
.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}
|
303 |
+
.c3-tooltip td.value{text-align:right}
|
304 |
+
.c3-area{stroke-width:0;opacity:.2}
|
305 |
+
.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}
|
306 |
+
.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}
|
307 |
+
.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}
|
308 |
+
.c3-chart-arc .c3-gauge-value{fill:#000}
|
309 |
/* Responsive Styles */
|
310 |
@media (max-width: 620px) {
|
311 |
.sucuriscan-tabs > ul li, .sucuriscan-tabs > ul li > a{display:block}
|
322 |
}
|
323 |
@media (max-width: 920px) {
|
324 |
.sucuriscan-wrap .sucuriscan-navbar{padding-left:0;padding-right:0}
|
325 |
+
.sucuriscan-wrap .sucuriscan-navbar .nav-tab{display:block;line-height:20px;margin:0}
|
326 |
.sucuriscan-wrap .sucuriscan-navbar .nav-tab:last-child{border-bottom:1px solid #ccc}
|
327 |
+
.wp-core-ui .sucuriscan-review-hero, .wp-core-ui .button.sucuriscan-review-hero{top:0;right:0;display:block;width:100%;margin:10px 0}
|
328 |
}
|
329 |
/* Old styles */
|
330 |
.sucuriscan-maincontent #poststuff{min-width:initial;padding-top:0}
|
inc/js/c3.min.js
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
!function(a){"use strict";function b(a){var b=this.internal=new c(this);b.loadConfig(a),b.init(),function d(a,b,c){Object.keys(a).forEach(function(e){b[e]=a[e].bind(c),Object.keys(a[e]).length>0&&d(a[e],b[e],c)})}(e,this,this)}function c(b){var c=this;c.d3=a.d3?a.d3:"undefined"!=typeof require?require("d3"):void 0,c.api=b,c.config=c.getDefaultConfig(),c.data={},c.cache={},c.axes={}}function d(a,b){function c(a,b){a.attr("transform",function(a){return"translate("+Math.ceil(b(a)+t)+", 0)"})}function d(a,b){a.attr("transform",function(a){return"translate(0,"+Math.ceil(b(a))+")"})}function e(a){var b=a[0],c=a[a.length-1];return c>b?[b,c]:[c,b]}function f(a){var b,c,d=[];if(a.ticks)return a.ticks.apply(a,m);for(c=a.domain(),b=Math.ceil(c[0]);b<c[1];b++)d.push(b);return d.length>0&&d[0]>0&&d.unshift(d[0]-(d[1]-d[0])),d}function g(){var a,c=o.copy();return b.isCategory&&(a=o.domain(),c.domain([a[0],a[1]-1])),c}function h(a){return l?l(a):a}function i(a){if(w)return w;var b={h:11.5,w:5.5};return a.select("text").text(h).each(function(a){var c=this.getBoundingClientRect(),d=h(a),e=c.height,f=d?c.width/d.length:void 0;e&&f&&(b.h=e,b.w=f)}).text(""),w=b,b}function j(j){j.each(function(){function j(a,c){function d(a,b){f=void 0;for(var h=1;h<b.length;h++)if(" "===b.charAt(h)&&(f=h),e=b.substr(0,h+1),g=O.w*e.length,g>c)return d(a.concat(b.substr(0,f?f:h)),b.slice(f?f+1:h));return a.concat(b)}var e,f,g,i=h(a),j=[];return"[object Array]"===Object.prototype.toString.call(i)?i:((!c||0>=c)&&(c=R?95:b.isCategory?Math.ceil(z(A[1])-z(A[0]))-12:110),d(j,i+""))}function l(a,b){var c=O.h;return 0===b&&(c="left"===p||"right"===p?-((P[a.index]-1)*(O.h/2)-3):".71em"),c}function m(a){var b=o(a)+(n?0:t);return F[0]<b&&b<F[1]?q:0}var u,v,w,x=a.select(this),y=this.__chart__||o,z=this.__chart__=g(),A=s?s:f(z),B=x.selectAll(".tick").data(A,z),C=B.enter().insert("g",".domain").attr("class","tick").style("opacity",1e-6),D=B.exit().remove(),E=a.transition(B).style("opacity",1),F=o.rangeExtent?o.rangeExtent():e(o.range()),G=x.selectAll(".domain").data([0]),H=(G.enter().append("path").attr("class","domain"),a.transition(G));C.append("line"),C.append("text");var I=C.select("line"),J=E.select("line"),K=C.select("text"),L=E.select("text");b.isCategory?(t=Math.ceil((z(1)-z(0))/2),v=n?0:t,w=n?t:0):t=v=0;var M,N,O=i(x.select(".tick")),P=[],Q=Math.max(q,0)+r,R="left"===p||"right"===p;switch(M=B.select("text"),N=M.selectAll("tspan").data(function(a,c){var d=b.tickMultiline?j(a,b.tickWidth):[].concat(h(a));return P[c]=d.length,d.map(function(a){return{index:c,splitted:a}})}),N.enter().append("tspan"),N.exit().remove(),N.text(function(a){return a.splitted}),p){case"bottom":u=c,I.attr("y2",q),K.attr("y",Q),J.attr("x1",v).attr("x2",v).attr("y2",m),L.attr("x",0).attr("y",Q),M.style("text-anchor","middle"),N.attr("x",0).attr("dy",l),H.attr("d","M"+F[0]+","+k+"V0H"+F[1]+"V"+k);break;case"top":u=c,I.attr("y2",-q),K.attr("y",-Q),J.attr("x2",0).attr("y2",-q),L.attr("x",0).attr("y",-Q),M.style("text-anchor","middle"),N.attr("x",0).attr("dy","0em"),H.attr("d","M"+F[0]+","+-k+"V0H"+F[1]+"V"+-k);break;case"left":u=d,I.attr("x2",-q),K.attr("x",-Q),J.attr("x2",-q).attr("y1",w).attr("y2",w),L.attr("x",-Q).attr("y",t),M.style("text-anchor","end"),N.attr("x",-Q).attr("dy",l),H.attr("d","M"+-k+","+F[0]+"H0V"+F[1]+"H"+-k);break;case"right":u=d,I.attr("x2",q),K.attr("x",Q),J.attr("x2",q).attr("y2",0),L.attr("x",Q).attr("y",0),M.style("text-anchor","start"),N.attr("x",Q).attr("dy",l),H.attr("d","M"+k+","+F[0]+"H0V"+F[1]+"H"+k)}if(z.rangeBand){var S=z,T=S.rangeBand()/2;y=z=function(a){return S(a)+T}}else y.rangeBand?y=z:D.call(u,z);C.call(u,y),E.call(u,z)})}var k,l,m,n,o=a.scale.linear(),p="bottom",q=6,r=3,s=null,t=0,u=!0;return b=b||{},k=b.withOuterTick?6:0,j.scale=function(a){return arguments.length?(o=a,j):o},j.orient=function(a){return arguments.length?(p=a in{top:1,right:1,bottom:1,left:1}?a+"":"bottom",j):p},j.tickFormat=function(a){return arguments.length?(l=a,j):l},j.tickCentered=function(a){return arguments.length?(n=a,j):n},j.tickOffset=function(){return t},j.ticks=function(){return arguments.length?(m=arguments,j):m},j.tickCulling=function(a){return arguments.length?(u=a,j):u},j.tickValues=function(a){if("function"==typeof a)s=function(){return a(o.domain())};else{if(!arguments.length)return s;s=a}return j},j}var e,f,g={version:"0.4.8"};g.generate=function(a){return new b(a)},g.chart={fn:b.prototype,internal:{fn:c.prototype}},e=g.chart.fn,f=g.chart.internal.fn,f.init=function(){var a=this,b=a.config;if(a.initParams(),b.data_url)a.convertUrlToData(b.data_url,b.data_mimeType,b.data_keys,a.initWithData);else if(b.data_json)a.initWithData(a.convertJsonToData(b.data_json,b.data_keys));else if(b.data_rows)a.initWithData(a.convertRowsToData(b.data_rows));else{if(!b.data_columns)throw Error("url or json or rows or columns is required.");a.initWithData(a.convertColumnsToData(b.data_columns))}},f.initParams=function(){var a=this,b=a.d3,c=a.config;a.clipId="c3-"+ +new Date+"-clip",a.clipIdForXAxis=a.clipId+"-xaxis",a.clipIdForYAxis=a.clipId+"-yaxis",a.clipIdForGrid=a.clipId+"-grid",a.clipIdForSubchart=a.clipId+"-subchart",a.clipPath=a.getClipPath(a.clipId),a.clipPathForXAxis=a.getClipPath(a.clipIdForXAxis),a.clipPathForYAxis=a.getClipPath(a.clipIdForYAxis),a.clipPathForGrid=a.getClipPath(a.clipIdForGrid),a.clipPathForSubchart=a.getClipPath(a.clipIdForSubchart),a.dragStart=null,a.dragging=!1,a.flowing=!1,a.cancelClick=!1,a.mouseover=!1,a.transiting=!1,a.color=a.generateColor(),a.levelColor=a.generateLevelColor(),a.dataTimeFormat=c.data_xLocaltime?b.time.format:b.time.format.utc,a.axisTimeFormat=c.axis_x_localtime?b.time.format:b.time.format.utc,a.defaultAxisTimeFormat=a.axisTimeFormat.multi([[".%L",function(a){return a.getMilliseconds()}],[":%S",function(a){return a.getSeconds()}],["%I:%M",function(a){return a.getMinutes()}],["%I %p",function(a){return a.getHours()}],["%-m/%-d",function(a){return a.getDay()&&1!==a.getDate()}],["%-m/%-d",function(a){return 1!==a.getDate()}],["%-m/%-d",function(a){return a.getMonth()}],["%Y/%-m/%-d",function(){return!0}]]),a.hiddenTargetIds=[],a.hiddenLegendIds=[],a.focusedTargetIds=[],a.defocusedTargetIds=[],a.xOrient=c.axis_rotated?"left":"bottom",a.yOrient=c.axis_rotated?c.axis_y_inner?"top":"bottom":c.axis_y_inner?"right":"left",a.y2Orient=c.axis_rotated?c.axis_y2_inner?"bottom":"top":c.axis_y2_inner?"left":"right",a.subXOrient=c.axis_rotated?"left":"bottom",a.isLegendRight="right"===c.legend_position,a.isLegendInset="inset"===c.legend_position,a.isLegendTop="top-left"===c.legend_inset_anchor||"top-right"===c.legend_inset_anchor,a.isLegendLeft="top-left"===c.legend_inset_anchor||"bottom-left"===c.legend_inset_anchor,a.legendStep=0,a.legendItemWidth=0,a.legendItemHeight=0,a.currentMaxTickWidths={x:0,y:0,y2:0},a.rotated_padding_left=30,a.rotated_padding_right=c.axis_rotated&&!c.axis_x_show?0:30,a.rotated_padding_top=5,a.withoutFadeIn={},a.intervalForObserveInserted=void 0,a.axes.subx=b.selectAll([])},f.initChartElements=function(){this.initBar&&this.initBar(),this.initLine&&this.initLine(),this.initArc&&this.initArc(),this.initGauge&&this.initGauge(),this.initText&&this.initText()},f.initWithData=function(b){var c,d,e=this,f=e.d3,g=e.config,h=!0;e.initPie&&e.initPie(),e.initBrush&&e.initBrush(),e.initZoom&&e.initZoom(),e.selectChart="function"==typeof g.bindto.node?g.bindto:f.select(g.bindto),e.selectChart.empty()&&(e.selectChart=f.select(document.createElement("div")).style("opacity",0),e.observeInserted(e.selectChart),h=!1),e.selectChart.html("").classed("c3",!0),e.data.xs={},e.data.targets=e.convertDataToTargets(b),g.data_filter&&(e.data.targets=e.data.targets.filter(g.data_filter)),g.data_hide&&e.addHiddenTargetIds(g.data_hide===!0?e.mapToIds(e.data.targets):g.data_hide),g.legend_hide&&e.addHiddenLegendIds(g.legend_hide===!0?e.mapToIds(e.data.targets):g.legend_hide),e.hasType("gauge")&&(g.legend_show=!1),e.updateSizes(),e.updateScales(),e.x.domain(f.extent(e.getXDomain(e.data.targets))),e.y.domain(e.getYDomain(e.data.targets,"y")),e.y2.domain(e.getYDomain(e.data.targets,"y2")),e.subX.domain(e.x.domain()),e.subY.domain(e.y.domain()),e.subY2.domain(e.y2.domain()),e.orgXDomain=e.x.domain(),e.brush&&e.brush.scale(e.subX),g.zoom_enabled&&e.zoom.scale(e.x),e.svg=e.selectChart.append("svg").style("overflow","hidden").on("mouseenter",function(){return g.onmouseover.call(e)}).on("mouseleave",function(){return g.onmouseout.call(e)}),c=e.svg.append("defs"),e.clipChart=e.appendClip(c,e.clipId),e.clipXAxis=e.appendClip(c,e.clipIdForXAxis),e.clipYAxis=e.appendClip(c,e.clipIdForYAxis),e.clipGrid=e.appendClip(c,e.clipIdForGrid),e.clipSubchart=e.appendClip(c,e.clipIdForSubchart),e.updateSvgSize(),d=e.main=e.svg.append("g").attr("transform",e.getTranslate("main")),e.initSubchart&&e.initSubchart(),e.initTooltip&&e.initTooltip(),e.initLegend&&e.initLegend(),d.append("text").attr("class",i.text+" "+i.empty).attr("text-anchor","middle").attr("dominant-baseline","middle"),e.initRegion(),e.initGrid(),d.append("g").attr("clip-path",e.clipPath).attr("class",i.chart),g.grid_lines_front&&e.initGridLines(),e.initEventRect(),e.initChartElements(),d.insert("rect",g.zoom_privileged?null:"g."+i.regions).attr("class",i.zoomRect).attr("width",e.width).attr("height",e.height).style("opacity",0).on("dblclick.zoom",null),g.axis_x_extent&&e.brush.extent(e.getDefaultExtent()),e.initAxis(),e.updateTargets(e.data.targets),h&&(e.updateDimension(),e.config.oninit.call(e),e.redraw({withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransitionForAxis:!1})),null==a.onresize&&(a.onresize=e.generateResize()),a.onresize.add&&(a.onresize.add(function(){g.onresize.call(e)}),a.onresize.add(function(){e.api.flush()}),a.onresize.add(function(){g.onresized.call(e)})),e.api.element=e.selectChart.node()},f.smoothLines=function(a,b){var c=this;"grid"===b&&a.each(function(){var a=c.d3.select(this),b=a.attr("x1"),d=a.attr("x2"),e=a.attr("y1"),f=a.attr("y2");a.attr({x1:Math.ceil(b),x2:Math.ceil(d),y1:Math.ceil(e),y2:Math.ceil(f)})})},f.updateSizes=function(){var a=this,b=a.config,c=a.legend?a.getLegendHeight():0,d=a.legend?a.getLegendWidth():0,e=a.isLegendRight||a.isLegendInset?0:c,f=a.hasArcType(),g=b.axis_rotated||f?0:a.getHorizontalAxisHeight("x"),h=b.subchart_show&&!f?b.subchart_size_height+g:0;a.currentWidth=a.getCurrentWidth(),a.currentHeight=a.getCurrentHeight(),a.margin=b.axis_rotated?{top:a.getHorizontalAxisHeight("y2")+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:a.getHorizontalAxisHeight("y")+e+a.getCurrentPaddingBottom(),left:h+(f?0:a.getCurrentPaddingLeft())}:{top:4+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:g+h+e+a.getCurrentPaddingBottom(),left:f?0:a.getCurrentPaddingLeft()},a.margin2=b.axis_rotated?{top:a.margin.top,right:0/0,bottom:20+e,left:a.rotated_padding_left}:{top:a.currentHeight-h-e,right:0/0,bottom:g+e,left:a.margin.left},a.margin3={top:0,right:0/0,bottom:0,left:0},a.updateSizeForLegend&&a.updateSizeForLegend(c,d),a.width=a.currentWidth-a.margin.left-a.margin.right,a.height=a.currentHeight-a.margin.top-a.margin.bottom,a.width<0&&(a.width=0),a.height<0&&(a.height=0),a.width2=b.axis_rotated?a.margin.left-a.rotated_padding_left-a.rotated_padding_right:a.width,a.height2=b.axis_rotated?a.height:a.currentHeight-a.margin2.top-a.margin2.bottom,a.width2<0&&(a.width2=0),a.height2<0&&(a.height2=0),a.arcWidth=a.width-(a.isLegendRight?d+10:0),a.arcHeight=a.height-(a.isLegendRight?0:10),a.hasType("gauge")&&(a.arcHeight+=a.height-a.getGaugeLabelHeight()),a.updateRadius&&a.updateRadius(),a.isLegendRight&&f&&(a.margin3.left=a.arcWidth/2+1.1*a.radiusExpanded)},f.updateTargets=function(a){var b=this,c=b.config;b.updateTargetsForText(a),b.updateTargetsForBar(a),b.updateTargetsForLine(a),b.updateTargetsForArc&&b.updateTargetsForArc(a),b.updateTargetsForSubchart&&b.updateTargetsForSubchart(a),b.svg.selectAll("."+i.target).filter(function(a){return b.isTargetToShow(a.id)}).transition().duration(c.transition_duration).style("opacity",1)},f.redraw=function(a,b){var c,d,e,f,g,h,j,k,l,m,n,o,p,q,r,s,u,v,w,x,y,z,A,B,C,D,E,F,G,H=this,I=H.main,J=H.d3,K=H.config,L=H.getShapeIndices(H.isAreaType),M=H.getShapeIndices(H.isBarType),N=H.getShapeIndices(H.isLineType),O=H.hasArcType(),P=H.filterTargetsToShow(H.data.targets),Q=H.xv.bind(H);if(a=a||{},c=t(a,"withY",!0),d=t(a,"withSubchart",!0),e=t(a,"withTransition",!0),h=t(a,"withTransform",!1),j=t(a,"withUpdateXDomain",!1),k=t(a,"withUpdateOrgXDomain",!1),l=t(a,"withTrimXDomain",!0),p=t(a,"withUpdateXAxis",j),m=t(a,"withLegend",!1),n=t(a,"withEventRect",!0),o=t(a,"withDimension",!0),f=t(a,"withTransitionForExit",e),g=t(a,"withTransitionForAxis",e),w=e?K.transition_duration:0,x=f?w:0,y=g?w:0,b=b||H.generateAxisTransitions(y),m&&K.legend_show?H.updateLegend(H.mapToIds(H.data.targets),a,b):o&&H.updateDimension(!0),H.isCategorized()&&0===P.length&&H.x.domain([0,H.axes.x.selectAll(".tick").size()]),P.length?(H.updateXDomain(P,j,k,l),K.axis_x_tick_values||(B=K.axis_x_tick_fit||K.axis_x_tick_count?H.generateTickValues(H.mapTargetsToUniqueXs(P),K.axis_x_tick_count,H.isTimeSeries()):void 0,H.xAxis.tickValues(B),H.subXAxis.tickValues(B))):(H.xAxis.tickValues([]),H.subXAxis.tickValues([])),K.zoom_rescale&&!a.flow&&(E=H.x.orgDomain()),H.y.domain(H.getYDomain(P,"y",E)),H.y2.domain(H.getYDomain(P,"y2",E)),!K.axis_y_tick_values&&K.axis_y_tick_count&&H.yAxis.tickValues(H.generateTickValues(H.y.domain(),K.axis_y_tick_count)),!K.axis_y2_tick_values&&K.axis_y2_tick_count&&H.y2Axis.tickValues(H.generateTickValues(H.y2.domain(),K.axis_y2_tick_count)),H.redrawAxis(b,O),H.updateAxisLabels(e),(j||p)&&P.length)if(K.axis_x_tick_culling&&B){for(C=1;C<B.length;C++)if(B.length/C<K.axis_x_tick_culling_max){D=C;break}H.svg.selectAll("."+i.axisX+" .tick text").each(function(a){var b=B.indexOf(a);b>=0&&J.select(this).style("display",b%D?"none":"block")})}else H.svg.selectAll("."+i.axisX+" .tick text").style("display","block");q=H.generateDrawArea?H.generateDrawArea(L,!1):void 0,r=H.generateDrawBar?H.generateDrawBar(M):void 0,s=H.generateDrawLine?H.generateDrawLine(N,!1):void 0,u=H.generateXYForText(L,M,N,!0),v=H.generateXYForText(L,M,N,!1),c&&(H.subY.domain(H.getYDomain(P,"y")),H.subY2.domain(H.getYDomain(P,"y2"))),H.tooltip.style("display","none"),H.updateXgridFocus(),I.select("text."+i.text+"."+i.empty).attr("x",H.width/2).attr("y",H.height/2).text(K.data_empty_label_text).transition().style("opacity",P.length?0:1),H.redrawGrid(w),H.redrawRegion(w),H.redrawBar(x),H.redrawLine(x),H.redrawArea(x),H.redrawCircle(),H.hasDataLabel()&&H.redrawText(x),H.redrawArc&&H.redrawArc(w,x,h),H.redrawSubchart&&H.redrawSubchart(d,b,w,x,L,M,N),I.selectAll("."+i.selectedCircles).filter(H.isBarType.bind(H)).selectAll("circle").remove(),K.interaction_enabled&&!a.flow&&n&&(H.redrawEventRect(),H.updateZoom&&H.updateZoom()),H.updateCircleY(),F=(H.config.axis_rotated?H.circleY:H.circleX).bind(H),G=(H.config.axis_rotated?H.circleX:H.circleY).bind(H),J.transition().duration(w).each(function(){var b=[];H.addTransitionForBar(b,r),H.addTransitionForLine(b,s),H.addTransitionForArea(b,q),H.addTransitionForCircle(b,F,G),H.addTransitionForText(b,u,v,a.flow),H.addTransitionForRegion(b),H.addTransitionForGrid(b),a.flow&&(z=H.generateWait(),b.forEach(function(a){z.add(a)}),A=H.generateFlow({targets:P,flow:a.flow,duration:w,drawBar:r,drawLine:s,drawArea:q,cx:F,cy:G,xv:Q,xForText:u,yForText:v}))}).call(z||function(){},A||function(){}),H.mapToIds(H.data.targets).forEach(function(a){H.withoutFadeIn[a]=!0})},f.updateAndRedraw=function(a){var b,c=this,d=c.config;a=a||{},a.withTransition=t(a,"withTransition",!0),a.withTransform=t(a,"withTransform",!1),a.withLegend=t(a,"withLegend",!1),a.withUpdateXDomain=!0,a.withUpdateOrgXDomain=!0,a.withTransitionForExit=!1,a.withTransitionForTransform=t(a,"withTransitionForTransform",a.withTransition),c.updateSizes(),a.withLegend&&d.legend_show||(b=c.generateAxisTransitions(a.withTransitionForAxis?d.transition_duration:0),c.updateScales(),c.updateSvgSize(),c.transformAll(a.withTransitionForTransform,b)),c.redraw(a,b)},f.redrawWithoutRescale=function(){this.redraw({withY:!1,withSubchart:!1,withEventRect:!1,withTransitionForAxis:!1})},f.isTimeSeries=function(){return"timeseries"===this.config.axis_x_type},f.isCategorized=function(){return this.config.axis_x_type.indexOf("categor")>=0},f.isCustomX=function(){var a=this,b=a.config;return!a.isTimeSeries()&&(b.data_x||s(b.data_xs))},f.isTimeSeriesY=function(){return"timeseries"===this.config.axis_y_type},f.getTranslate=function(a){var b,c,d=this,e=d.config;return"main"===a?(b=p(d.margin.left),c=p(d.margin.top)):"context"===a?(b=p(d.margin2.left),c=p(d.margin2.top)):"legend"===a?(b=d.margin3.left,c=d.margin3.top):"x"===a?(b=0,c=e.axis_rotated?0:d.height):"y"===a?(b=0,c=e.axis_rotated?d.height:0):"y2"===a?(b=e.axis_rotated?0:d.width,c=e.axis_rotated?1:0):"subx"===a?(b=0,c=e.axis_rotated?0:d.height2):"arc"===a&&(b=d.arcWidth/2,c=d.arcHeight/2),"translate("+b+","+c+")"},f.initialOpacity=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?1:0},f.initialOpacityForCircle=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?this.opacityForCircle(a):0},f.opacityForCircle=function(a){var b=this.config.point_show?1:0;return j(a.value)?this.isScatterType(a)?.5:b:0},f.opacityForText=function(){return this.hasDataLabel()?1:0},f.xx=function(a){return a?this.x(a.x):null},f.xv=function(a){var b=this,c=a.value;return b.isTimeSeries()?c=b.parseDate(a.value):b.isCategorized()&&"string"==typeof a.value&&(c=b.config.axis_x_categories.indexOf(a.value)),Math.ceil(b.x(c))},f.yv=function(a){var b=this,c=a.axis&&"y2"===a.axis?b.y2:b.y;return Math.ceil(c(a.value))},f.subxx=function(a){return a?this.subX(a.x):null},f.transformMain=function(a,b){var c,d,e,f=this;b&&b.axisX?c=b.axisX:(c=f.main.select("."+i.axisX),a&&(c=c.transition())),b&&b.axisY?d=b.axisY:(d=f.main.select("."+i.axisY),a&&(d=d.transition())),b&&b.axisY2?e=b.axisY2:(e=f.main.select("."+i.axisY2),a&&(e=e.transition())),(a?f.main.transition():f.main).attr("transform",f.getTranslate("main")),c.attr("transform",f.getTranslate("x")),d.attr("transform",f.getTranslate("y")),e.attr("transform",f.getTranslate("y2")),f.main.select("."+i.chartArcs).attr("transform",f.getTranslate("arc"))},f.transformAll=function(a,b){var c=this;c.transformMain(a,b),c.config.subchart_show&&c.transformContext(a,b),c.legend&&c.transformLegend(a)},f.updateSvgSize=function(){var a=this,b=a.svg.select(".c3-brush .background");a.svg.attr("width",a.currentWidth).attr("height",a.currentHeight),a.svg.selectAll(["#"+a.clipId,"#"+a.clipIdForGrid]).select("rect").attr("width",a.width).attr("height",a.height),a.svg.select("#"+a.clipIdForXAxis).select("rect").attr("x",a.getXAxisClipX.bind(a)).attr("y",a.getXAxisClipY.bind(a)).attr("width",a.getXAxisClipWidth.bind(a)).attr("height",a.getXAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForYAxis).select("rect").attr("x",a.getYAxisClipX.bind(a)).attr("y",a.getYAxisClipY.bind(a)).attr("width",a.getYAxisClipWidth.bind(a)).attr("height",a.getYAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForSubchart).select("rect").attr("width",a.width).attr("height",b.size()?b.attr("height"):0),a.svg.select("."+i.zoomRect).attr("width",a.width).attr("height",a.height),a.selectChart.style("max-height",a.currentHeight+"px")},f.updateDimension=function(a){var b=this;a||(b.config.axis_rotated?(b.axes.x.call(b.xAxis),b.axes.subx.call(b.subXAxis)):(b.axes.y.call(b.yAxis),b.axes.y2.call(b.y2Axis))),b.updateSizes(),b.updateScales(),b.updateSvgSize(),b.transformAll(!1)},f.observeInserted=function(b){var c=this,d=new MutationObserver(function(e){e.forEach(function(e){"childList"===e.type&&e.previousSibling&&(d.disconnect(),c.intervalForObserveInserted=a.setInterval(function(){b.node().parentNode&&(a.clearInterval(c.intervalForObserveInserted),c.updateDimension(),c.config.oninit.call(c),c.redraw({withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransition:!1,withTransitionForTransform:!1,withLegend:!0}),b.transition().style("opacity",1))},10))})});d.observe(b.node(),{attributes:!0,childList:!0,characterData:!0})},f.generateResize=function(){function a(){b.forEach(function(a){a()})}var b=[];return a.add=function(a){b.push(a)},a},f.endall=function(a,b){var c=0;a.each(function(){++c}).each("end",function(){--c||b.apply(this,arguments)})},f.generateWait=function(){var a=[],b=function(b,c){var d=setInterval(function(){var b=0;a.forEach(function(a){if(a.empty())return void(b+=1);try{a.transition()}catch(c){b+=1}}),b===a.length&&(clearInterval(d),c&&c())},10)};return b.add=function(b){a.push(b)},b},f.parseDate=function(b){var c,d=this;return c=b instanceof Date?b:"number"!=typeof b&&isNaN(b)?d.dataTimeFormat(d.config.data_xFormat).parse(b):new Date(+b),(!c||isNaN(+c))&&a.console.error("Failed to parse x '"+b+"' to Date object"),c},f.getDefaultConfig=function(){var a={bindto:"#chart",size_width:void 0,size_height:void 0,padding_left:void 0,padding_right:void 0,padding_top:void 0,padding_bottom:void 0,zoom_enabled:!1,zoom_extent:void 0,zoom_privileged:!1,zoom_rescale:!1,zoom_onzoom:function(){},zoom_onzoomstart:function(){},zoom_onzoomend:function(){},interaction_enabled:!0,onmouseover:function(){},onmouseout:function(){},onresize:function(){},onresized:function(){},oninit:function(){},transition_duration:350,data_x:void 0,data_xs:{},data_xFormat:"%Y-%m-%d",data_xLocaltime:!0,data_xSort:!0,data_idConverter:function(a){return a},data_names:{},data_classes:{},data_groups:[],data_axes:{},data_type:void 0,data_types:{},data_labels:{},data_order:"desc",data_regions:{},data_color:void 0,data_colors:{},data_hide:!1,data_filter:void 0,data_selection_enabled:!1,data_selection_grouped:!1,data_selection_isselectable:function(){return!0},data_selection_multiple:!0,data_onclick:function(){},data_onmouseover:function(){},data_onmouseout:function(){},data_onselected:function(){},data_onunselected:function(){},data_ondragstart:function(){},data_ondragend:function(){},data_url:void 0,data_json:void 0,data_rows:void 0,data_columns:void 0,data_mimeType:void 0,data_keys:void 0,data_empty_label_text:"",subchart_show:!1,subchart_size_height:60,subchart_onbrush:function(){},color_pattern:[],color_threshold:{},legend_show:!0,legend_hide:!1,legend_position:"bottom",legend_inset_anchor:"top-left",legend_inset_x:10,legend_inset_y:0,legend_inset_step:void 0,legend_item_onclick:void 0,legend_item_onmouseover:void 0,legend_item_onmouseout:void 0,legend_equally:!1,axis_rotated:!1,axis_x_show:!0,axis_x_type:"indexed",axis_x_localtime:!0,axis_x_categories:[],axis_x_tick_centered:!1,axis_x_tick_format:void 0,axis_x_tick_culling:{},axis_x_tick_culling_max:10,axis_x_tick_count:void 0,axis_x_tick_fit:!0,axis_x_tick_values:null,axis_x_tick_rotate:0,axis_x_tick_outer:!0,axis_x_tick_multiline:!0,axis_x_tick_width:null,axis_x_max:void 0,axis_x_min:void 0,axis_x_padding:{},axis_x_height:void 0,axis_x_extent:void 0,axis_x_label:{},axis_y_show:!0,axis_y_type:void 0,axis_y_max:void 0,axis_y_min:void 0,axis_y_center:void 0,axis_y_inner:void 0,axis_y_label:{},axis_y_tick_format:void 0,axis_y_tick_outer:!0,axis_y_tick_values:null,axis_y_tick_count:void 0,axis_y_tick_time_value:void 0,axis_y_tick_time_interval:void 0,axis_y_padding:{},axis_y_default:void 0,axis_y2_show:!1,axis_y2_max:void 0,axis_y2_min:void 0,axis_y2_center:void 0,axis_y2_inner:void 0,axis_y2_label:{},axis_y2_tick_format:void 0,axis_y2_tick_outer:!0,axis_y2_tick_values:null,axis_y2_tick_count:void 0,axis_y2_padding:{},axis_y2_default:void 0,grid_x_show:!1,grid_x_type:"tick",grid_x_lines:[],grid_y_show:!1,grid_y_lines:[],grid_y_ticks:10,grid_focus_show:!0,grid_lines_front:!0,point_show:!0,point_r:2.5,point_focus_expand_enabled:!0,point_focus_expand_r:void 0,point_select_r:void 0,line_connectNull:!1,line_step_type:"step",bar_width:void 0,bar_width_ratio:.6,bar_width_max:void 0,bar_zerobased:!0,area_zerobased:!0,pie_label_show:!0,pie_label_format:void 0,pie_label_threshold:.05,pie_expand:!0,gauge_label_show:!0,gauge_label_format:void 0,gauge_expand:!0,gauge_min:0,gauge_max:100,gauge_units:void 0,gauge_width:void 0,donut_label_show:!0,donut_label_format:void 0,donut_label_threshold:.05,donut_width:void 0,donut_expand:!0,donut_title:"",regions:[],tooltip_show:!0,tooltip_grouped:!0,tooltip_format_title:void 0,tooltip_format_name:void 0,tooltip_format_value:void 0,tooltip_contents:function(a,b,c,d){return this.getTooltipContent?this.getTooltipContent(a,b,c,d):""},tooltip_init_show:!1,tooltip_init_x:0,tooltip_init_position:{top:"0px",left:"50px"}};return Object.keys(this.additionalConfig).forEach(function(b){a[b]=this.additionalConfig[b]},this),a},f.additionalConfig={},f.loadConfig=function(a){function b(){var a=d.shift();return a&&c&&"object"==typeof c&&a in c?(c=c[a],b()):a?void 0:c}var c,d,e,f=this.config;Object.keys(f).forEach(function(g){c=a,d=g.split("_"),e=b(),n(e)&&(f[g]=e)})},f.getScale=function(a,b,c){return(c?this.d3.time.scale():this.d3.scale.linear()).range([a,b])},f.getX=function(a,b,c,d){var e,f=this,g=f.getScale(a,b,f.isTimeSeries()),h=c?g.domain(c):g;f.isCategorized()?(d=d||function(){return 0},g=function(a,b){var c=h(a)+d(a);return b?c:Math.ceil(c)}):g=function(a,b){var c=h(a);return b?c:Math.ceil(c)};for(e in h)g[e]=h[e];return g.orgDomain=function(){return h.domain()},f.isCategorized()&&(g.domain=function(a){return arguments.length?(h.domain(a),g):(a=this.orgDomain(),[a[0],a[1]+1])}),g},f.getY=function(a,b,c){var d=this.getScale(a,b,this.isTimeSeriesY());return c&&d.domain(c),d},f.getYScale=function(a){return"y2"===this.getAxisId(a)?this.y2:this.y},f.getSubYScale=function(a){return"y2"===this.getAxisId(a)?this.subY2:this.subY},f.updateScales=function(){var a=this,b=a.config,c=!a.x;a.xMin=b.axis_rotated?1:0,a.xMax=b.axis_rotated?a.height:a.width,a.yMin=b.axis_rotated?0:a.height,a.yMax=b.axis_rotated?a.width:1,a.subXMin=a.xMin,a.subXMax=a.xMax,a.subYMin=b.axis_rotated?0:a.height2,a.subYMax=b.axis_rotated?a.width2:1,a.x=a.getX(a.xMin,a.xMax,c?void 0:a.x.orgDomain(),function(){return a.xAxis.tickOffset()}),a.y=a.getY(a.yMin,a.yMax,c?b.axis_y_default:a.y.domain()),a.y2=a.getY(a.yMin,a.yMax,c?b.axis_y2_default:a.y2.domain()),a.subX=a.getX(a.xMin,a.xMax,a.orgXDomain,function(b){return b%1?0:a.subXAxis.tickOffset()}),a.subY=a.getY(a.subYMin,a.subYMax,c?b.axis_y_default:a.subY.domain()),a.subY2=a.getY(a.subYMin,a.subYMax,c?b.axis_y2_default:a.subY2.domain()),a.xAxisTickFormat=a.getXAxisTickFormat(),a.xAxisTickValues=a.getXAxisTickValues(),a.yAxisTickValues=a.getYAxisTickValues(),a.y2AxisTickValues=a.getY2AxisTickValues(),a.xAxis=a.getXAxis(a.x,a.xOrient,a.xAxisTickFormat,a.xAxisTickValues,b.axis_x_tick_outer),a.subXAxis=a.getXAxis(a.subX,a.subXOrient,a.xAxisTickFormat,a.xAxisTickValues,b.axis_x_tick_outer),a.yAxis=a.getYAxis(a.y,a.yOrient,b.axis_y_tick_format,a.yAxisTickValues,b.axis_y_tick_outer),a.y2Axis=a.getYAxis(a.y2,a.y2Orient,b.axis_y2_tick_format,a.y2AxisTickValues,b.axis_y2_tick_outer),c||(a.brush&&a.brush.scale(a.subX),b.zoom_enabled&&a.zoom.scale(a.x)),a.updateArc&&a.updateArc()},f.getYDomainMin=function(a){var b,c,d,e,f,g,h=this,i=h.config,j=h.mapToIds(a),k=h.getValuesAsIdKeyed(a);if(i.data_groups.length>0)for(g=h.hasNegativeValueInTargets(a),b=0;b<i.data_groups.length;b++)if(e=i.data_groups[b].filter(function(a){return j.indexOf(a)>=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=0>a?a:0}),c=1;c<e.length;c++)f=e[c],k[f]&&k[f].forEach(function(a,b){h.getAxisId(f)!==h.getAxisId(d)||!k[d]||g&&+a>0||(k[d][b]+=+a)});return h.d3.min(Object.keys(k).map(function(a){return h.d3.min(k[a])}))},f.getYDomainMax=function(a){var b,c,d,e,f,g,h=this,i=h.config,j=h.mapToIds(a),k=h.getValuesAsIdKeyed(a);if(i.data_groups.length>0)for(g=h.hasPositiveValueInTargets(a),b=0;b<i.data_groups.length;b++)if(e=i.data_groups[b].filter(function(a){return j.indexOf(a)>=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=a>0?a:0}),c=1;c<e.length;c++)f=e[c],k[f]&&k[f].forEach(function(a,b){h.getAxisId(f)!==h.getAxisId(d)||!k[d]||g&&0>+a||(k[d][b]+=+a)});return h.d3.max(Object.keys(k).map(function(a){return h.d3.max(k[a])}))},f.getYDomain=function(a,b,c){var d,e,f,g,h,i,k,l,m,n,o=this,p=o.config,r=a.filter(function(a){return o.getAxisId(a.id)===b}),t=c?o.filterByXDomain(r,c):r,u="y2"===b?p.axis_y2_min:p.axis_y_min,v="y2"===b?p.axis_y2_max:p.axis_y_max,w=j(u)?u:o.getYDomainMin(t),x=j(v)?v:o.getYDomainMax(t),y="y2"===b?p.axis_y2_center:p.axis_y_center,z=o.hasType("bar",t)&&p.bar_zerobased||o.hasType("area",t)&&p.area_zerobased,A=o.hasDataLabel()&&p.axis_rotated,B=o.hasDataLabel()&&!p.axis_rotated;return w>x&&(j(u)?x=w+10:w=x-10),0===t.length?"y2"===b?o.y2.domain():o.y.domain():(isNaN(w)&&(w=0),isNaN(x)&&(x=w),w===x&&(0>w?x=0:w=0),m=w>=0&&x>=0,n=0>=w&&0>=x,(j(u)&&m||j(v)&&n)&&(z=!1),z&&(m&&(w=0),n&&(x=0)),d=Math.abs(x-w),e=f=g=.1*d,"undefined"!=typeof y&&(h=Math.max(Math.abs(w),Math.abs(x)),x=y+h,w=y-h),A?(i=o.getDataLabelLength(w,x,b,"width"),k=q(o.y.range()),l=[i[0]/k,i[1]/k],f+=d*(l[1]/(1-l[0]-l[1])),g+=d*(l[0]/(1-l[0]-l[1]))):B&&(i=o.getDataLabelLength(w,x,b,"height"),f+=this.convertPixelsToAxisPadding(i[1],d),g+=this.convertPixelsToAxisPadding(i[0],d)),"y"===b&&s(p.axis_y_padding)&&(f=o.getAxisPadding(p.axis_y_padding,"top",f,d),g=o.getAxisPadding(p.axis_y_padding,"bottom",g,d)),"y2"===b&&s(p.axis_y2_padding)&&(f=o.getAxisPadding(p.axis_y2_padding,"top",f,d),g=o.getAxisPadding(p.axis_y2_padding,"bottom",g,d)),z&&(m&&(g=w),n&&(f=-x)),[w-g,x+f])},f.getXDomainMin=function(a){var b=this,c=b.config;return n(c.axis_x_min)?b.isTimeSeries()?this.parseDate(c.axis_x_min):c.axis_x_min:b.d3.min(a,function(a){return b.d3.min(a.values,function(a){return a.x})})},f.getXDomainMax=function(a){var b=this,c=b.config;return n(c.axis_x_max)?b.isTimeSeries()?this.parseDate(c.axis_x_max):c.axis_x_max:b.d3.max(a,function(a){return b.d3.max(a.values,function(a){return a.x})})},f.getXDomainPadding=function(a){var b,c,d,e,f=this,g=f.config,h=a[1]-a[0];return f.isCategorized()?c=0:f.hasType("bar")?(b=f.getMaxDataCount(),c=b>1?h/(b-1)/2:.5):c=.01*h,"object"==typeof g.axis_x_padding&&s(g.axis_x_padding)?(d=j(g.axis_x_padding.left)?g.axis_x_padding.left:c,e=j(g.axis_x_padding.right)?g.axis_x_padding.right:c):d=e="number"==typeof g.axis_x_padding?g.axis_x_padding:c,{left:d,right:e}},f.getXDomain=function(a){var b=this,c=[b.getXDomainMin(a),b.getXDomainMax(a)],d=c[0],e=c[1],f=b.getXDomainPadding(c),g=0,h=0;return d-e!==0||b.isCategorized()||(b.isTimeSeries()?(d=new Date(.5*d.getTime()),e=new Date(1.5*e.getTime())):(d=0===d?1:.5*d,e=0===e?-1:1.5*e)),(d||0===d)&&(g=b.isTimeSeries()?new Date(d.getTime()-f.left):d-f.left),(e||0===e)&&(h=b.isTimeSeries()?new Date(e.getTime()+f.right):e+f.right),[g,h]},f.updateXDomain=function(a,b,c,d,e){var f=this,g=f.config;return c&&(f.x.domain(e?e:f.d3.extent(f.getXDomain(a))),f.orgXDomain=f.x.domain(),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent(),f.subX.domain(f.x.domain()),f.brush&&f.brush.scale(f.subX)),b&&(f.x.domain(e?e:!f.brush||f.brush.empty()?f.orgXDomain:f.brush.extent()),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent()),d&&f.x.domain(f.trimXDomain(f.x.orgDomain())),f.x.domain()},f.trimXDomain=function(a){var b=this;return a[0]<=b.orgXDomain[0]&&(a[1]=+a[1]+(b.orgXDomain[0]-a[0]),a[0]=b.orgXDomain[0]),b.orgXDomain[1]<=a[1]&&(a[0]=+a[0]-(a[1]-b.orgXDomain[1]),a[1]=b.orgXDomain[1]),a},f.isX=function(a){var b=this,c=b.config;return c.data_x&&a===c.data_x||s(c.data_xs)&&u(c.data_xs,a)},f.isNotX=function(a){return!this.isX(a)},f.getXKey=function(a){var b=this,c=b.config;return c.data_x?c.data_x:s(c.data_xs)?c.data_xs[a]:null},f.getXValuesOfXKey=function(a,b){var c,d=this,e=b&&s(b)?d.mapToIds(b):[];return e.forEach(function(b){d.getXKey(b)===a&&(c=d.data.xs[b])}),c},f.getIndexByX=function(a){var b=this,c=b.filterByX(b.data.targets,a);return c.length?c[0].index:null},f.getXValue=function(a,b){var c=this;return a in c.data.xs&&c.data.xs[a]&&j(c.data.xs[a][b])?c.data.xs[a][b]:b
|
2 |
+
},f.getOtherTargetXs=function(){var a=this,b=Object.keys(a.data.xs);return b.length?a.data.xs[b[0]]:null},f.getOtherTargetX=function(a){var b=this.getOtherTargetXs();return b&&a<b.length?b[a]:null},f.addXs=function(a){var b=this;Object.keys(a).forEach(function(c){b.config.data_xs[c]=a[c]})},f.hasMultipleX=function(a){return this.d3.set(Object.keys(a).map(function(b){return a[b]})).size()>1},f.isMultipleX=function(){return s(this.config.data_xs)||!this.config.data_xSort||this.hasType("scatter")},f.addName=function(a){var b,c=this;return a&&(b=c.config.data_names[a.id],a.name=b?b:a.id),a},f.getValueOnIndex=function(a,b){var c=a.filter(function(a){return a.index===b});return c.length?c[0]:null},f.updateTargetX=function(a,b){var c=this;a.forEach(function(a){a.values.forEach(function(d,e){d.x=c.generateTargetX(b[e],a.id,e)}),c.data.xs[a.id]=b})},f.updateTargetXs=function(a,b){var c=this;a.forEach(function(a){b[a.id]&&c.updateTargetX([a],b[a.id])})},f.generateTargetX=function(a,b,c){var d,e=this;return d=e.isTimeSeries()?e.parseDate(a?a:e.getXValue(b,c)):e.isCustomX()&&!e.isCategorized()?j(a)?+a:e.getXValue(b,c):c},f.cloneTarget=function(a){return{id:a.id,id_org:a.id_org,values:a.values.map(function(a){return{x:a.x,value:a.value,id:a.id}})}},f.updateXs=function(){var a=this;a.data.targets.length&&(a.xs=[],a.data.targets[0].values.forEach(function(b){a.xs[b.index]=b.x}))},f.getPrevX=function(a){var b=this.xs[a-1];return"undefined"!=typeof b?b:null},f.getNextX=function(a){var b=this.xs[a+1];return"undefined"!=typeof b?b:null},f.getMaxDataCount=function(){var a=this;return a.d3.max(a.data.targets,function(a){return a.values.length})},f.getMaxDataCountTarget=function(a){var b,c=a.length,d=0;return c>1?a.forEach(function(a){a.values.length>d&&(b=a,d=a.values.length)}):b=c?a[0]:null,b},f.getEdgeX=function(a){var b=this;return a.length?[b.d3.min(a,function(a){return a.values[0].x}),b.d3.max(a,function(a){return a.values[a.values.length-1].x})]:[0,0]},f.mapToIds=function(a){return a.map(function(a){return a.id})},f.mapToTargetIds=function(a){var b=this;return a?l(a)?[a]:a:b.mapToIds(b.data.targets)},f.hasTarget=function(a,b){var c,d=this.mapToIds(a);for(c=0;c<d.length;c++)if(d[c]===b)return!0;return!1},f.isTargetToShow=function(a){return this.hiddenTargetIds.indexOf(a)<0},f.isLegendToShow=function(a){return this.hiddenLegendIds.indexOf(a)<0},f.filterTargetsToShow=function(a){var b=this;return a.filter(function(a){return b.isTargetToShow(a.id)})},f.mapTargetsToUniqueXs=function(a){var b=this,c=b.d3.set(b.d3.merge(a.map(function(a){return a.values.map(function(a){return+a.x})}))).values();return c.map(b.isTimeSeries()?function(a){return new Date(+a)}:function(a){return+a})},f.addHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.concat(a)},f.removeHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.filter(function(b){return a.indexOf(b)<0})},f.addHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.concat(a)},f.removeHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.filter(function(b){return a.indexOf(b)<0})},f.getValuesAsIdKeyed=function(a){var b={};return a.forEach(function(a){b[a.id]=[],a.values.forEach(function(c){b[a.id].push(c.value)})}),b},f.checkValueInTargets=function(a,b){var c,d,e,f=Object.keys(a);for(c=0;c<f.length;c++)for(e=a[f[c]].values,d=0;d<e.length;d++)if(b(e[d].value))return!0;return!1},f.hasNegativeValueInTargets=function(a){return this.checkValueInTargets(a,function(a){return 0>a})},f.hasPositiveValueInTargets=function(a){return this.checkValueInTargets(a,function(a){return a>0})},f.isOrderDesc=function(){var a=this.config;return"string"==typeof a.data_order&&"desc"===a.data_order.toLowerCase()},f.isOrderAsc=function(){var a=this.config;return"string"==typeof a.data_order&&"asc"===a.data_order.toLowerCase()},f.orderTargets=function(a){var b=this,c=b.config,d=b.isOrderAsc(),e=b.isOrderDesc();return d||e?a.sort(function(a,b){var c=function(a,b){return a+Math.abs(b.value)},e=a.values.reduce(c,0),f=b.values.reduce(c,0);return d?f-e:e-f}):k(c.data_order)&&a.sort(c.data_order),a},f.filterByX=function(a,b){return this.d3.merge(a.map(function(a){return a.values})).filter(function(a){return a.x-b===0})},f.filterRemoveNull=function(a){return a.filter(function(a){return j(a.value)})},f.filterByXDomain=function(a,b){return a.map(function(a){return{id:a.id,id_org:a.id_org,values:a.values.filter(function(a){return b[0]<=a.x&&a.x<=b[1]})}})},f.hasDataLabel=function(){var a=this.config;return"boolean"==typeof a.data_labels&&a.data_labels?!0:"object"==typeof a.data_labels&&s(a.data_labels)?!0:!1},f.getDataLabelLength=function(a,b,c,d){var e=this,f=[0,0],g=1.3;return e.selectChart.select("svg").selectAll(".dummy").data([a,b]).enter().append("text").text(function(a){return e.formatByAxisId(c)(a)}).each(function(a,b){f[b]=this.getBoundingClientRect()[d]*g}).remove(),f},f.isNoneArc=function(a){return this.hasTarget(this.data.targets,a.id)},f.isArc=function(a){return"data"in a&&this.hasTarget(this.data.targets,a.data.id)},f.findSameXOfValues=function(a,b){var c,d=a[b].x,e=[];for(c=b-1;c>=0&&d===a[c].x;c--)e.push(a[c]);for(c=b;c<a.length&&d===a[c].x;c++)e.push(a[c]);return e},f.findClosestFromTargets=function(a,b){var c,d=this;return c=a.map(function(a){return d.findClosest(a.values,b)}),d.findClosest(c,b)},f.findClosest=function(a,b){var c,d=this,e=100;return a.filter(function(a){return a&&d.isBarType(a.id)}).forEach(function(a){var b=d.main.select("."+i.bars+d.getTargetSelectorSuffix(a.id)+" ."+i.bar+"-"+a.index).node();!c&&d.isWithinBar(b)&&(c=a)}),a.filter(function(a){return a&&!d.isBarType(a.id)}).forEach(function(a){var f=d.dist(a,b);e>f&&(e=f,c=a)}),c},f.dist=function(a,b){var c=this,d=c.config,e=d.axis_rotated?1:0,f=d.axis_rotated?0:1,g=c.circleY(a,a.index),h=c.x(a.x);return Math.pow(h-b[e],2)+Math.pow(g-b[f],2)},f.convertValuesToStep=function(a){var b,c=[].concat(a);if(!this.isCategorized())return a;for(b=a.length+1;b>0;b--)c[b]=c[b-1];return c[0]={x:c[0].x-1,value:c[0].value,id:c[0].id},c[a.length+1]={x:c[a.length].x+1,value:c[a.length].value,id:c[a.length].id},c},f.updateDataAttributes=function(a,b){var c=this,d=c.config,e=d["data_"+a];return"undefined"==typeof b?e:(Object.keys(b).forEach(function(a){e[a]=b[a]}),c.redraw({withLegend:!0}),e)},f.convertUrlToData=function(a,b,c,d){var e=this,f=b?b:"csv";e.d3.xhr(a,function(a,b){var g;g="json"===f?e.convertJsonToData(JSON.parse(b.response),c):"tsv"===f?e.convertTsvToData(b.response):e.convertCsvToData(b.response),d.call(e,g)})},f.convertXsvToData=function(a,b){var c,d=b.parseRows(a);return 1===d.length?(c=[{}],d[0].forEach(function(a){c[0][a]=null})):c=b.parse(a),c},f.convertCsvToData=function(a){return this.convertXsvToData(a,this.d3.csv)},f.convertTsvToData=function(a){return this.convertXsvToData(a,this.d3.tsv)},f.convertJsonToData=function(a,b){var c,d,e=this,f=[];return b?(c=b.value,b.x&&(c.push(b.x),e.config.data_x=b.x),f.push(c),a.forEach(function(a){var b=[];c.forEach(function(c){var d=m(a[c])?null:a[c];b.push(d)}),f.push(b)}),d=e.convertRowsToData(f)):(Object.keys(a).forEach(function(b){f.push([b].concat(a[b]))}),d=e.convertColumnsToData(f)),d},f.convertRowsToData=function(a){var b,c,d=a[0],e={},f=[];for(b=1;b<a.length;b++){for(e={},c=0;c<a[b].length;c++){if(m(a[b][c]))throw new Error("Source data is missing a component at ("+b+","+c+")!");e[d[c]]=a[b][c]}f.push(e)}return f},f.convertColumnsToData=function(a){var b,c,d,e=[];for(b=0;b<a.length;b++)for(d=a[b][0],c=1;c<a[b].length;c++){if(m(e[c-1])&&(e[c-1]={}),m(a[b][c]))throw new Error("Source data is missing a component at ("+b+","+c+")!");e[c-1][d]=a[b][c]}return e},f.convertDataToTargets=function(a,b){var c,d=this,e=d.config,f=d.d3.keys(a[0]).filter(d.isNotX,d),g=d.d3.keys(a[0]).filter(d.isX,d);return f.forEach(function(c){var f=d.getXKey(c);d.isCustomX()||d.isTimeSeries()?g.indexOf(f)>=0?d.data.xs[c]=(b&&d.data.xs[c]?d.data.xs[c]:[]).concat(a.map(function(a){return a[f]}).filter(j).map(function(a,b){return d.generateTargetX(a,c,b)})):e.data_x?d.data.xs[c]=d.getOtherTargetXs():s(e.data_xs)&&(d.data.xs[c]=d.getXValuesOfXKey(f,d.data.targets)):d.data.xs[c]=a.map(function(a,b){return b})}),f.forEach(function(a){if(!d.data.xs[a])throw new Error('x is not defined for id = "'+a+'".')}),c=f.map(function(b,c){var f=e.data_idConverter(b);return{id:f,id_org:b,values:a.map(function(a,g){var h=d.getXKey(b),i=a[h],j=d.generateTargetX(i,b,g);return d.isCustomX()&&d.isCategorized()&&0===c&&i&&(0===g&&(e.axis_x_categories=[]),e.axis_x_categories.push(i)),(m(a[b])||d.data.xs[b].length<=g)&&(j=void 0),{x:j,value:null===a[b]||isNaN(a[b])?null:+a[b],id:f}}).filter(function(a){return n(a.x)})}}),c.forEach(function(a){var b;e.data_xSort&&(a.values=a.values.sort(function(a,b){var c=a.x||0===a.x?a.x:1/0,d=b.x||0===b.x?b.x:1/0;return c-d})),b=0,a.values.forEach(function(a){a.index=b++}),d.data.xs[a.id].sort(function(a,b){return a-b})}),e.data_type&&d.setTargetType(d.mapToIds(c).filter(function(a){return!(a in e.data_types)}),e.data_type),c.forEach(function(a){d.addCache(a.id_org,a)}),c},f.load=function(a,b){var c=this;a&&(b.filter&&(a=a.filter(b.filter)),(b.type||b.types)&&a.forEach(function(a){c.setTargetType(a.id,b.types?b.types[a.id]:b.type)}),c.data.targets.forEach(function(b){for(var c=0;c<a.length;c++)if(b.id===a[c].id){b.values=a[c].values,a.splice(c,1);break}}),c.data.targets=c.data.targets.concat(a)),c.updateTargets(c.data.targets),c.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),b.done&&b.done()},f.loadFromArgs=function(a){var b=this;a.data?b.load(b.convertDataToTargets(a.data),a):a.url?b.convertUrlToData(a.url,a.mimeType,a.keys,function(c){b.load(b.convertDataToTargets(c),a)}):a.json?b.load(b.convertDataToTargets(b.convertJsonToData(a.json,a.keys)),a):a.rows?b.load(b.convertDataToTargets(b.convertRowsToData(a.rows)),a):a.columns?b.load(b.convertDataToTargets(b.convertColumnsToData(a.columns)),a):b.load(null,a)},f.unload=function(a,b){var c=this;return b||(b=function(){}),a=a.filter(function(a){return c.hasTarget(c.data.targets,a)}),a&&0!==a.length?(c.svg.selectAll(a.map(function(a){return c.selectorTarget(a)})).transition().style("opacity",0).remove().call(c.endall,b),void a.forEach(function(a){c.withoutFadeIn[a]=!1,c.legend&&c.legend.selectAll("."+i.legendItem+c.getTargetSelectorSuffix(a)).remove(),c.data.targets=c.data.targets.filter(function(b){return b.id!==a})})):void b()},f.categoryName=function(a){var b=this.config;return a<b.axis_x_categories.length?b.axis_x_categories[a]:a},f.initEventRect=function(){var a=this;a.main.select("."+i.chart).append("g").attr("class",i.eventRects).style("fill-opacity",0)},f.redrawEventRect=function(){var a,b,c=this,d=c.config,e=c.isMultipleX(),f=c.main.select("."+i.eventRects).style("cursor",d.zoom_enabled?d.axis_rotated?"ns-resize":"ew-resize":null).classed(i.eventRectsMultiple,e).classed(i.eventRectsSingle,!e);f.selectAll("."+i.eventRect).remove(),c.eventRect=f.selectAll("."+i.eventRect),e?(a=c.eventRect.data([0]),c.generateEventRectsForMultipleXs(a.enter()),c.updateEventRect(a)):(b=c.getMaxDataCountTarget(c.data.targets),f.datum(b?b.values:[]),c.eventRect=f.selectAll("."+i.eventRect),a=c.eventRect.data(function(a){return a}),c.generateEventRectsForSingleX(a.enter()),c.updateEventRect(a),a.exit().remove())},f.updateEventRect=function(a){var b,c,d,e,f,g,h=this,i=h.config;a=a||h.eventRect.data(function(a){return a}),h.isMultipleX()?(b=0,c=0,d=h.width,e=h.height):(!h.isCustomX()&&!h.isTimeSeries()||h.isCategorized()?(f=h.getEventRectWidth(),g=function(a){return h.x(a.x)-f/2}):(h.updateXs(),f=function(a){var b=h.getPrevX(a.index),c=h.getNextX(a.index);return null===b&&null===c?i.axis_rotated?h.height:h.width:(null===b&&(b=h.x.domain()[0]),null===c&&(c=h.x.domain()[1]),Math.max(0,(h.x(c)-h.x(b))/2))},g=function(a){var b=h.getPrevX(a.index),c=h.getNextX(a.index),d=h.data.xs[a.id][a.index];return null===b&&null===c?0:(null===b&&(b=h.x.domain()[0]),(h.x(d)+h.x(b))/2)}),b=i.axis_rotated?0:g,c=i.axis_rotated?g:0,d=i.axis_rotated?h.width:f,e=i.axis_rotated?f:h.height),a.attr("class",h.classEvent.bind(h)).attr("x",b).attr("y",c).attr("width",d).attr("height",e)},f.generateEventRectsForSingleX=function(a){var b=this,c=b.d3,d=b.config;a.append("rect").attr("class",b.classEvent.bind(b)).style("cursor",d.data_selection_enabled&&d.data_selection_grouped?"pointer":null).on("mouseover",function(a){var c,e,f=a.index;b.dragging||b.flowing||b.hasArcType()||(c=b.data.targets.map(function(a){return b.addName(b.getValueOnIndex(a.values,f))}),e=[],Object.keys(d.data_names).forEach(function(a){for(var b=0;b<c.length;b++)if(c[b]&&c[b].id===a){e.push(c[b]),c.shift(b);break}}),c=e.concat(c),d.point_focus_expand_enabled&&b.expandCircles(f,null,!0),b.expandBars(f,null,!0),b.main.selectAll("."+i.shape+"-"+f).each(function(a){d.data_onmouseover.call(b.api,a)}))}).on("mouseout",function(a){var c=a.index;b.hasArcType()||(b.hideXGridFocus(),b.hideTooltip(),b.unexpandCircles(),b.unexpandBars(),b.main.selectAll("."+i.shape+"-"+c).each(function(a){d.data_onmouseout.call(b.api,a)}))}).on("mousemove",function(a){var e,f=a.index,g=b.svg.select("."+i.eventRect+"-"+f);b.dragging||b.flowing||b.hasArcType()||(b.isStepType(a)&&"step-after"===b.config.line_step_type&&c.mouse(this)[0]<b.x(b.getXValue(a.id,f))&&(f-=1),e=b.filterTargetsToShow(b.data.targets).map(function(a){return b.addName(b.getValueOnIndex(a.values,f))}),d.tooltip_grouped&&(b.showTooltip(e,c.mouse(this)),b.showXGridFocus(e)),(!d.tooltip_grouped||d.data_selection_enabled&&!d.data_selection_grouped)&&b.main.selectAll("."+i.shape+"-"+f).each(function(){c.select(this).classed(i.EXPANDED,!0),d.data_selection_enabled&&g.style("cursor",d.data_selection_grouped?"pointer":null),d.tooltip_grouped||(b.hideXGridFocus(),b.hideTooltip(),d.data_selection_grouped||(b.unexpandCircles(f),b.unexpandBars(f)))}).filter(function(a){return b.isWithinShape(this,a)}).each(function(a){d.data_selection_enabled&&(d.data_selection_grouped||d.data_selection_isselectable(a))&&g.style("cursor","pointer"),d.tooltip_grouped||(b.showTooltip([a],c.mouse(this)),b.showXGridFocus([a]),d.point_focus_expand_enabled&&b.expandCircles(f,a.id,!0),b.expandBars(f,a.id,!0))}))}).on("click",function(a){var e=a.index;if(!b.hasArcType()&&b.toggleShape){if(b.cancelClick)return void(b.cancelClick=!1);b.isStepType(a)&&"step-after"===d.line_step_type&&c.mouse(this)[0]<b.x(b.getXValue(a.id,e))&&(e-=1),b.main.selectAll("."+i.shape+"-"+e).each(function(a){(d.data_selection_grouped||b.isWithinShape(this,a))&&(b.toggleShape(this,a,e),b.config.data_onclick.call(b.api,a,this))})}}).call(c.behavior.drag().origin(Object).on("drag",function(){b.drag(c.mouse(this))}).on("dragstart",function(){b.dragstart(c.mouse(this))}).on("dragend",function(){b.dragend()}))},f.generateEventRectsForMultipleXs=function(a){function b(){c.svg.select("."+i.eventRect).style("cursor",null),c.hideXGridFocus(),c.hideTooltip(),c.unexpandCircles(),c.unexpandBars()}var c=this,d=c.d3,e=c.config;a.append("rect").attr("x",0).attr("y",0).attr("width",c.width).attr("height",c.height).attr("class",i.eventRect).on("mouseout",function(){c.hasArcType()||b()}).on("mousemove",function(){var a,f,g,h,j=c.filterTargetsToShow(c.data.targets);if(!c.dragging&&!c.hasArcType(j)){if(a=d.mouse(this),f=c.findClosestFromTargets(j,a),!c.mouseover||f&&f.id===c.mouseover.id||(e.data_onmouseout.call(c.api,c.mouseover),c.mouseover=void 0),!f)return void b();g=c.isScatterType(f)||!e.tooltip_grouped?[f]:c.filterByX(j,f.x),h=g.map(function(a){return c.addName(a)}),c.showTooltip(h,a),e.point_focus_expand_enabled&&c.expandCircles(f.index,f.id,!0),c.expandBars(f.index,f.id,!0),c.showXGridFocus(h),(c.isBarType(f.id)||c.dist(f,a)<100)&&(c.svg.select("."+i.eventRect).style("cursor","pointer"),c.mouseover||(e.data_onmouseover.call(c.api,f),c.mouseover=f))}}).on("click",function(){var a,b,f=c.filterTargetsToShow(c.data.targets);c.hasArcType(f)||(a=d.mouse(this),b=c.findClosestFromTargets(f,a),b&&(c.isBarType(b.id)||c.dist(b,a)<100)&&c.main.selectAll("."+i.shapes+c.getTargetSelectorSuffix(b.id)).select("."+i.shape+"-"+b.index).each(function(){(e.data_selection_grouped||c.isWithinShape(this,b))&&(c.toggleShape(this,b,b.index),c.config.data_onclick.call(c.api,b,this))}))}).call(d.behavior.drag().origin(Object).on("drag",function(){c.drag(d.mouse(this))}).on("dragstart",function(){c.dragstart(d.mouse(this))}).on("dragend",function(){c.dragend()}))},f.dispatchEvent=function(b,c,d){var e=this,f="."+i.eventRect+(e.isMultipleX()?"":"-"+c),g=e.main.select(f).node(),h=g.getBoundingClientRect(),j=h.left+(d?d[0]:0),k=h.top+(d?d[1]:0),l=document.createEvent("MouseEvents");l.initMouseEvent(b,!0,!0,a,0,j,k,j,k,!1,!1,!1,!1,0,null),g.dispatchEvent(l)},f.getCurrentWidth=function(){var a=this,b=a.config;return b.size_width?b.size_width:a.getParentWidth()},f.getCurrentHeight=function(){var a=this,b=a.config,c=b.size_height?b.size_height:a.getParentHeight();return c>0?c:320/(a.hasType("gauge")?2:1)},f.getCurrentPaddingTop=function(){var a=this.config;return j(a.padding_top)?a.padding_top:0},f.getCurrentPaddingBottom=function(){var a=this.config;return j(a.padding_bottom)?a.padding_bottom:0},f.getCurrentPaddingLeft=function(a){var b=this,c=b.config;return j(c.padding_left)?c.padding_left:c.axis_rotated?c.axis_x_show?Math.max(o(b.getAxisWidthByAxisId("x",a)),40):1:!c.axis_y_show||c.axis_y_inner?b.getYAxisLabelPosition().isOuter?30:1:o(b.getAxisWidthByAxisId("y",a))},f.getCurrentPaddingRight=function(){var a=this,b=a.config,c=10,d=a.isLegendRight?a.getLegendWidth()+20:0;return j(b.padding_right)?b.padding_right+1:b.axis_rotated?c+d:!b.axis_y2_show||b.axis_y2_inner?2+d+(a.getY2AxisLabelPosition().isOuter?20:0):o(a.getAxisWidthByAxisId("y2"))+d},f.getParentRectValue=function(a){for(var b,c=this.selectChart.node();c&&"BODY"!==c.tagName&&!(b=c.getBoundingClientRect()[a]);)c=c.parentNode;return b},f.getParentWidth=function(){return this.getParentRectValue("width")},f.getParentHeight=function(){var a=this.selectChart.style("height");return a.indexOf("px")>0?+a.replace("px",""):0},f.getSvgLeft=function(a){var b=this,c=b.config,d=c.axis_rotated||!c.axis_rotated&&!c.axis_y_inner,e=c.axis_rotated?i.axisX:i.axisY,f=b.main.select("."+e).node(),g=f&&d?f.getBoundingClientRect():{right:0},h=b.selectChart.node().getBoundingClientRect(),j=b.hasArcType(),k=g.right-h.left-(j?0:b.getCurrentPaddingLeft(a));return k>0?k:0},f.getAxisWidthByAxisId=function(a,b){var c=this,d=c.getAxisLabelPositionById(a);return c.getMaxTickWidth(a,b)+(d.isInner?20:40)},f.getHorizontalAxisHeight=function(a){var b=this,c=b.config,d=30;return"x"!==a||c.axis_x_show?"x"===a&&c.axis_x_height?c.axis_x_height:"y"!==a||c.axis_y_show?"y2"!==a||c.axis_y2_show?("x"===a&&!c.axis_rotated&&c.axis_x_tick_rotate&&(d=b.getMaxTickWidth(a)*Math.cos(Math.PI*(90-c.axis_x_tick_rotate)/180)),d+(b.getAxisLabelPositionById(a).isInner?0:10)+("y2"===a?-10:0)):b.rotated_padding_top:!c.legend_show||b.isLegendRight||b.isLegendInset?1:10:8},f.getEventRectWidth=function(){var a,b,c,d,e,f,g=this,h=g.getMaxDataCountTarget(g.data.targets);return h?(a=h.values[0],b=h.values[h.values.length-1],c=g.x(b.x)-g.x(a.x),0===c?g.config.axis_rotated?g.height:g.width:(d=g.getMaxDataCount(),e=g.hasType("bar")?(d-(g.isCategorized()?.25:1))/d:1,f=d>1?c*e/(d-1):c,1>f?1:f)):0},f.getShapeIndices=function(a){var b,c,d=this,e=d.config,f={},g=0;return d.filterTargetsToShow(d.data.targets.filter(a,d)).forEach(function(a){for(b=0;b<e.data_groups.length;b++)if(!(e.data_groups[b].indexOf(a.id)<0))for(c=0;c<e.data_groups[b].length;c++)if(e.data_groups[b][c]in f){f[a.id]=f[e.data_groups[b][c]];break}m(f[a.id])&&(f[a.id]=g++)}),f.__max__=g-1,f},f.getShapeX=function(a,b,c,d){var e=this,f=d?e.subX:e.x;return function(d){var e=d.id in c?c[d.id]:0;return d.x||0===d.x?f(d.x)-a*(b/2-e):0}},f.getShapeY=function(a){var b=this;return function(c){var d=a?b.getSubYScale(c.id):b.getYScale(c.id);return d(c.value)}},f.getShapeOffset=function(a,b,c){var d=this,e=d.orderTargets(d.filterTargetsToShow(d.data.targets.filter(a,d))),f=e.map(function(a){return a.id});return function(a,g){var h=c?d.getSubYScale(a.id):d.getYScale(a.id),i=h(0),j=i;return e.forEach(function(c){var e=d.isStepType(a)?d.convertValuesToStep(c.values):c.values;c.id!==a.id&&b[c.id]===b[a.id]&&f.indexOf(c.id)<f.indexOf(a.id)&&e[g].value*a.value>=0&&(j+=h(e[g].value)-i)}),j}},f.isWithinShape=function(a,b){var c,d=this,e=d.d3.select(a);return d.isTargetToShow(b.id)?"circle"===a.nodeName?c=d.isStepType(b)?d.isWithinStep(a,d.getYScale(b.id)(b.value)):d.isWithinCircle(a,1.5*d.pointSelectR(b)):"path"===a.nodeName&&(c=e.classed(i.bar)?d.isWithinBar(a):!0):c=!1,c},f.getInterpolate=function(a){var b=this;return b.isSplineType(a)?"cardinal":b.isStepType(a)?b.config.line_step_type:"linear"},f.initLine=function(){var a=this;a.main.select("."+i.chart).append("g").attr("class",i.chartLines)},f.updateTargetsForLine=function(a){var b,c,d=this,e=d.config,f=d.classChartLine.bind(d),g=d.classLines.bind(d),h=d.classAreas.bind(d),j=d.classCircles.bind(d),k=d.classFocus.bind(d);b=d.main.select("."+i.chartLines).selectAll("."+i.chartLine).data(a).attr("class",function(a){return f(a)+k(a)}),c=b.enter().append("g").attr("class",f).style("opacity",0).style("pointer-events","none"),c.append("g").attr("class",g),c.append("g").attr("class",h),c.append("g").attr("class",function(a){return d.generateClass(i.selectedCircles,a.id)}),c.append("g").attr("class",j).style("cursor",function(a){return e.data_selection_isselectable(a)?"pointer":null}),a.forEach(function(a){d.main.selectAll("."+i.selectedCircles+d.getTargetSelectorSuffix(a.id)).selectAll("."+i.selectedCircle).each(function(b){b.value=a.values[b.index].value})})},f.redrawLine=function(a){var b=this;b.mainLine=b.main.selectAll("."+i.lines).selectAll("."+i.line).data(b.lineData.bind(b)),b.mainLine.enter().append("path").attr("class",b.classLine.bind(b)).style("stroke",b.color),b.mainLine.style("opacity",b.initialOpacity.bind(b)).style("shape-rendering",function(a){return b.isStepType(a)?"crispEdges":""}).attr("transform",null),b.mainLine.exit().transition().duration(a).style("opacity",0).remove()},f.addTransitionForLine=function(a,b){var c=this;a.push(c.mainLine.transition().attr("d",b).style("stroke",c.color).style("opacity",1))},f.generateDrawLine=function(a,b){var c=this,d=c.config,e=c.d3.svg.line(),f=c.generateGetLinePoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x(i).y(h):e.x(h).y(i),d.line_connectNull||(e=e.defined(function(a){return null!=a.value})),function(a){var f,h=d.line_connectNull?c.filterRemoveNull(a.values):a.values,i=b?c.x:c.subX,j=g.call(c,a.id),k=0,l=0;return c.isLineType(a)?d.data_regions[a.id]?f=c.lineWithRegions(h,i,j,d.data_regions[a.id]):(c.isStepType(a)&&(h=c.convertValuesToStep(h)),f=e.interpolate(c.getInterpolate(a))(h)):(h[0]&&(k=i(h[0].x),l=j(h[0].value)),f=d.axis_rotated?"M "+l+" "+k:"M "+k+" "+l),f?f:"M 0 0"}},f.generateGetLinePoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isLineType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0<a.value&&e>l||a.value<0&&l>e)&&(l=e),[[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)]]}},f.lineWithRegions=function(a,b,c,d){function e(a,b){var c;for(c=0;c<b.length;c++)if(b[c].start<a&&a<=b[c].end)return!0;return!1}var f,g,h,i,j,k,l,o,p,q,r,s,t=this,u=t.config,v=-1,w="M",x=[];if(n(d))for(f=0;f<d.length;f++)x[f]={},x[f].start=m(d[f].start)?a[0].x:t.isTimeSeries()?t.parseDate(d[f].start):d[f].start,x[f].end=m(d[f].end)?a[a.length-1].x:t.isTimeSeries()?t.parseDate(d[f].end):d[f].end;for(r=u.axis_rotated?function(a){return c(a.value)}:function(a){return b(a.x)},s=u.axis_rotated?function(a){return b(a.x)}:function(a){return c(a.value)},h=t.isTimeSeries()?function(a,d,e,f){var g=a.x.getTime(),h=d.x-a.x,i=new Date(g+h*e),k=new Date(g+h*(e+f));return"M"+b(i)+" "+c(j(e))+" "+b(k)+" "+c(j(e+f))}:function(a,d,e,f){return"M"+b(i(e),!0)+" "+c(j(e))+" "+b(i(e+f),!0)+" "+c(j(e+f))},f=0;f<a.length;f++){if(m(x)||!e(a[f].x,x))w+=" "+r(a[f])+" "+s(a[f]);else for(i=t.getScale(a[f-1].x,a[f].x,t.isTimeSeries()),j=t.getScale(a[f-1].value,a[f].value),k=b(a[f].x)-b(a[f-1].x),l=c(a[f].value)-c(a[f-1].value),o=Math.sqrt(Math.pow(k,2)+Math.pow(l,2)),p=2/o,q=2*p,g=p;1>=g;g+=q)w+=h(a[f-1],a[f],g,p);v=a[f].x}return w},f.redrawArea=function(a){var b=this,c=b.d3;b.mainArea=b.main.selectAll("."+i.areas).selectAll("."+i.area).data(b.lineData.bind(b)),b.mainArea.enter().append("path").attr("class",b.classArea.bind(b)).style("fill",b.color).style("opacity",function(){return b.orgAreaOpacity=+c.select(this).style("opacity"),0}),b.mainArea.style("opacity",b.orgAreaOpacity),b.mainArea.exit().transition().duration(a).style("opacity",0).remove()},f.addTransitionForArea=function(a,b){var c=this;a.push(c.mainArea.transition().attr("d",b).style("fill",c.color).style("opacity",c.orgAreaOpacity))},f.generateDrawArea=function(a,b){var c=this,d=c.config,e=c.d3.svg.area(),f=c.generateGetAreaPoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(0)},j=function(a,b){return d.data_groups.length>0?f(a,b)[1][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x0(i).x1(j).y(h):e.x(h).y0(i).y1(j),d.line_connectNull||(e=e.defined(function(a){return null!==a.value})),function(a){var b,f=d.line_connectNull?c.filterRemoveNull(a.values):a.values,g=0,h=0;return c.isAreaType(a)?(c.isStepType(a)&&(f=c.convertValuesToStep(f)),b=e.interpolate(c.getInterpolate(a))(f)):(f[0]&&(g=c.x(f[0].x),h=c.getYScale(a.id)(f[0].value)),b=d.axis_rotated?"M "+h+" "+g:"M "+g+" "+h),b?b:"M 0 0"}},f.generateGetAreaPoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isAreaType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0<a.value&&e>l||a.value<0&&l>e)&&(l=e),[[k,j],[k,l-(e-j)],[k,l-(e-j)],[k,j]]}},f.redrawCircle=function(){var a=this;a.mainCircle=a.main.selectAll("."+i.circles).selectAll("."+i.circle).data(a.lineOrScatterData.bind(a)),a.mainCircle.enter().append("circle").attr("class",a.classCircle.bind(a)).attr("r",a.pointR.bind(a)).style("fill",a.color),a.mainCircle.style("opacity",a.initialOpacityForCircle.bind(a)),a.mainCircle.exit().remove()},f.addTransitionForCircle=function(a,b,c){var d=this;a.push(d.mainCircle.transition().style("opacity",d.opacityForCircle.bind(d)).style("fill",d.color).attr("cx",b).attr("cy",c)),a.push(d.main.selectAll("."+i.selectedCircle).transition().attr("cx",b).attr("cy",c))},f.circleX=function(a){return a.x||0===a.x?this.x(a.x):null},f.updateCircleY=function(){var a,b,c=this;c.config.data_groups.length>0?(a=c.getShapeIndices(c.isLineType),b=c.generateGetLinePoints(a),c.circleY=function(a,c){return b(a,c)[0][1]}):c.circleY=function(a){return c.getYScale(a.id)(a.value)}},f.getCircles=function(a,b){var c=this;return(b?c.main.selectAll("."+i.circles+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+i.circle+(j(a)?"-"+a:""))},f.expandCircles=function(a,b,c){var d=this,e=d.pointExpandedR.bind(d);c&&d.unexpandCircles(),d.getCircles(a,b).classed(i.EXPANDED,!0).attr("r",e)},f.unexpandCircles=function(a){var b=this,c=b.pointR.bind(b);b.getCircles(a).filter(function(){return b.d3.select(this).classed(i.EXPANDED)}).classed(i.EXPANDED,!1).attr("r",c)},f.pointR=function(a){var b=this,c=b.config;return b.isStepType(a)?0:k(c.point_r)?c.point_r(a):c.point_r},f.pointExpandedR=function(a){var b=this,c=b.config;return c.point_focus_expand_enabled?c.point_focus_expand_r?c.point_focus_expand_r:1.75*b.pointR(a):b.pointR(a)},f.pointSelectR=function(a){var b=this,c=b.config;return c.point_select_r?c.point_select_r:4*b.pointR(a)},f.isWithinCircle=function(a,b){var c=this.d3,d=c.mouse(a),e=c.select(a),f=+e.attr("cx"),g=+e.attr("cy");return Math.sqrt(Math.pow(f-d[0],2)+Math.pow(g-d[1],2))<b},f.isWithinStep=function(a,b){return Math.abs(b-this.d3.mouse(a)[1])<30},f.initBar=function(){var a=this;a.main.select("."+i.chart).append("g").attr("class",i.chartBars)},f.updateTargetsForBar=function(a){var b,c,d=this,e=d.config,f=d.classChartBar.bind(d),g=d.classBars.bind(d),h=d.classFocus.bind(d);b=d.main.select("."+i.chartBars).selectAll("."+i.chartBar).data(a).attr("class",function(a){return f(a)+h(a)}),c=b.enter().append("g").attr("class",f).style("opacity",0).style("pointer-events","none"),c.append("g").attr("class",g).style("cursor",function(a){return e.data_selection_isselectable(a)?"pointer":null})},f.redrawBar=function(a){var b=this,c=b.barData.bind(b),d=b.classBar.bind(b),e=b.initialOpacity.bind(b),f=function(a){return b.color(a.id)};b.mainBar=b.main.selectAll("."+i.bars).selectAll("."+i.bar).data(c),b.mainBar.enter().append("path").attr("class",d).style("stroke",f).style("fill",f),b.mainBar.style("opacity",e),b.mainBar.exit().transition().duration(a).style("opacity",0).remove()},f.addTransitionForBar=function(a,b){var c=this;a.push(c.mainBar.transition().attr("d",b).style("fill",c.color).style("opacity",1))},f.getBarW=function(a,b){var c=this,d=c.config,e="number"==typeof d.bar_width?d.bar_width:b?2*a.tickOffset()*d.bar_width_ratio/b:0;return d.bar_width_max&&e>d.bar_width_max?d.bar_width_max:e},f.getBars=function(a,b){var c=this;return(b?c.main.selectAll("."+i.bars+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+i.bar+(j(a)?"-"+a:""))},f.expandBars=function(a,b,c){var d=this;c&&d.unexpandBars(),d.getBars(a,b).classed(i.EXPANDED,!0)},f.unexpandBars=function(a){var b=this;b.getBars(a).classed(i.EXPANDED,!1)},f.generateDrawBar=function(a,b){var c=this,d=c.config,e=c.generateGetBarPoints(a,b);return function(a,b){var c=e(a,b),f=d.axis_rotated?1:0,g=d.axis_rotated?0:1,h="M "+c[0][f]+","+c[0][g]+" L"+c[1][f]+","+c[1][g]+" L"+c[2][f]+","+c[2][g]+" L"+c[3][f]+","+c[3][g]+" z";return h}},f.generateGetBarPoints=function(a,b){var c=this,d=b?c.subXAxis:c.xAxis,e=a.__max__+1,f=c.getBarW(d,e),g=c.getShapeX(f,e,a,!!b),h=c.getShapeY(!!b),i=c.getShapeOffset(c.isBarType,a,!!b),j=b?c.getSubYScale:c.getYScale;return function(a,b){var d=j.call(c,a.id)(0),e=i(a,b)||d,k=g(a),l=h(a);return c.config.axis_rotated&&(0<a.value&&d>l||a.value<0&&l>d)&&(l=d),[[k,e],[k,l-(d-e)],[k+f,l-(d-e)],[k+f,e]]}},f.isWithinBar=function(a){var b=this.d3.mouse(a),c=a.getBoundingClientRect(),d=a.pathSegList.getItem(0),e=a.pathSegList.getItem(1),f=Math.min(d.x,e.x),g=Math.min(d.y,e.y),h=c.width,i=c.height,j=2,k=f-j,l=f+h+j,m=g+i+j,n=g-j;return k<b[0]&&b[0]<l&&n<b[1]&&b[1]<m},f.initText=function(){var a=this;a.main.select("."+i.chart).append("g").attr("class",i.chartTexts),a.mainText=a.d3.selectAll([])},f.updateTargetsForText=function(a){var b,c,d=this,e=d.classChartText.bind(d),f=d.classTexts.bind(d),g=d.classFocus.bind(d);b=d.main.select("."+i.chartTexts).selectAll("."+i.chartText).data(a).attr("class",function(a){return e(a)+g(a)}),c=b.enter().append("g").attr("class",e).style("opacity",0).style("pointer-events","none"),c.append("g").attr("class",f)},f.redrawText=function(a){var b=this,c=b.config,d=b.barOrLineData.bind(b),e=b.classText.bind(b);b.mainText=b.main.selectAll("."+i.texts).selectAll("."+i.text).data(d),b.mainText.enter().append("text").attr("class",e).attr("text-anchor",function(a){return c.axis_rotated?a.value<0?"end":"start":"middle"}).style("stroke","none").style("fill",function(a){return b.color(a)}).style("fill-opacity",0),b.mainText.text(function(a,c,d){return b.formatByAxisId(b.getAxisId(a.id))(a.value,a.id,c,d)}),b.mainText.exit().transition().duration(a).style("fill-opacity",0).remove()},f.addTransitionForText=function(a,b,c,d){var e=this,f=d?0:e.opacityForText.bind(e);a.push(e.mainText.transition().attr("x",b).attr("y",c).style("fill",e.color).style("fill-opacity",f))
|
3 |
+
},f.getTextRect=function(a,b){var c,d=this.d3.select("body").classed("c3",!0),e=d.append("svg").style("visibility","hidden");return e.selectAll(".dummy").data([a]).enter().append("text").classed(b?b:"",!0).text(a).each(function(){c=this.getBoundingClientRect()}),e.remove(),d.classed("c3",!1),c},f.generateXYForText=function(a,b,c,d){var e=this,f=e.generateGetAreaPoints(b,!1),g=e.generateGetBarPoints(b,!1),h=e.generateGetLinePoints(c,!1),i=d?e.getXForText:e.getYForText;return function(a,b){var c=e.isAreaType(a)?f:e.isBarType(a)?g:h;return i.call(e,c(a,b),a,this)}},f.getXForText=function(a,b,c){var d,e,f=this,g=c.getBoundingClientRect();return f.config.axis_rotated?(e=f.isBarType(b)?4:6,d=a[2][1]+e*(b.value<0?-1:1)):d=f.hasType("bar")?(a[2][0]+a[0][0])/2:a[0][0],null===b.value&&(d>f.width?d=f.width-g.width:0>d&&(d=4)),d},f.getYForText=function(a,b,c){var d,e=this,f=c.getBoundingClientRect();return d=e.config.axis_rotated?(a[0][0]+a[2][0]+.6*f.height)/2:a[2][1]+(b.value<0?f.height:e.isBarType(b)?-3:-6),null!==b.value||e.config.axis_rotated||(d<f.height?d=f.height:d>this.height&&(d=this.height-4)),d},f.setTargetType=function(a,b){var c=this,d=c.config;c.mapToTargetIds(a).forEach(function(a){c.withoutFadeIn[a]=b===d.data_types[a],d.data_types[a]=b}),a||(d.data_type=b)},f.hasType=function(a,b){var c=this,d=c.config.data_types,e=!1;return b=b||c.data.targets,b&&b.length?b.forEach(function(b){var c=d[b.id];(c&&c.indexOf(a)>=0||!c&&"line"===a)&&(e=!0)}):Object.keys(d).length?Object.keys(d).forEach(function(b){d[b]===a&&(e=!0)}):e=c.config.data_type===a,e},f.hasArcType=function(a){return this.hasType("pie",a)||this.hasType("donut",a)||this.hasType("gauge",a)},f.isLineType=function(a){var b=this.config,c=l(a)?a:a.id;return!b.data_types[c]||["line","spline","area","area-spline","step","area-step"].indexOf(b.data_types[c])>=0},f.isStepType=function(a){var b=l(a)?a:a.id;return["step","area-step"].indexOf(this.config.data_types[b])>=0},f.isSplineType=function(a){var b=l(a)?a:a.id;return["spline","area-spline"].indexOf(this.config.data_types[b])>=0},f.isAreaType=function(a){var b=l(a)?a:a.id;return["area","area-spline","area-step"].indexOf(this.config.data_types[b])>=0},f.isBarType=function(a){var b=l(a)?a:a.id;return"bar"===this.config.data_types[b]},f.isScatterType=function(a){var b=l(a)?a:a.id;return"scatter"===this.config.data_types[b]},f.isPieType=function(a){var b=l(a)?a:a.id;return"pie"===this.config.data_types[b]},f.isGaugeType=function(a){var b=l(a)?a:a.id;return"gauge"===this.config.data_types[b]},f.isDonutType=function(a){var b=l(a)?a:a.id;return"donut"===this.config.data_types[b]},f.isArcType=function(a){return this.isPieType(a)||this.isDonutType(a)||this.isGaugeType(a)},f.lineData=function(a){return this.isLineType(a)?[a]:[]},f.arcData=function(a){return this.isArcType(a.data)?[a]:[]},f.barData=function(a){return this.isBarType(a)?a.values:[]},f.lineOrScatterData=function(a){return this.isLineType(a)||this.isScatterType(a)?a.values:[]},f.barOrLineData=function(a){return this.isBarType(a)||this.isLineType(a)?a.values:[]},f.initGrid=function(){var a=this,b=a.config,c=a.d3;a.grid=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",i.grid),b.grid_x_show&&a.grid.append("g").attr("class",i.xgrids),b.grid_y_show&&a.grid.append("g").attr("class",i.ygrids),b.grid_focus_show&&a.grid.append("g").attr("class",i.xgridFocus).append("line").attr("class",i.xgridFocus),a.xgrid=c.selectAll([]),b.grid_lines_front||a.initGridLines()},f.initGridLines=function(){var a=this,b=a.d3;a.gridLines=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",i.grid+" "+i.gridLines),a.gridLines.append("g").attr("class",i.xgridLines),a.gridLines.append("g").attr("class",i.ygridLines),a.xgridLines=b.selectAll([])},f.updateXGrid=function(a){var b=this,c=b.config,d=b.d3,e=b.generateGridData(c.grid_x_type,b.x),f=b.isCategorized()?b.xAxis.tickOffset():0;b.xgridAttr=c.axis_rotated?{x1:0,x2:b.width,y1:function(a){return b.x(a)-f},y2:function(a){return b.x(a)-f}}:{x1:function(a){return b.x(a)+f},x2:function(a){return b.x(a)+f},y1:0,y2:b.height},b.xgrid=b.main.select("."+i.xgrids).selectAll("."+i.xgrid).data(e),b.xgrid.enter().append("line").attr("class",i.xgrid),a||b.xgrid.attr(b.xgridAttr).style("opacity",function(){return+d.select(this).attr(c.axis_rotated?"y1":"x1")===(c.axis_rotated?b.height:0)?0:1}),b.xgrid.exit().remove()},f.updateYGrid=function(){var a=this,b=a.config,c=a.yAxis.tickValues()||a.y.ticks(b.grid_y_ticks);a.ygrid=a.main.select("."+i.ygrids).selectAll("."+i.ygrid).data(c),a.ygrid.enter().append("line").attr("class",i.ygrid),a.ygrid.attr("x1",b.axis_rotated?a.y:0).attr("x2",b.axis_rotated?a.y:a.width).attr("y1",b.axis_rotated?0:a.y).attr("y2",b.axis_rotated?a.height:a.y),a.ygrid.exit().remove(),a.smoothLines(a.ygrid,"grid")},f.redrawGrid=function(a){var b,c,d,e=this,f=e.main,g=e.config;e.grid.style("visibility",e.hasArcType()?"hidden":"visible"),f.select("line."+i.xgridFocus).style("visibility","hidden"),g.grid_x_show&&e.updateXGrid(),e.xgridLines=f.select("."+i.xgridLines).selectAll("."+i.xgridLine).data(g.grid_x_lines),b=e.xgridLines.enter().append("g").attr("class",function(a){return i.xgridLine+(a["class"]?" "+a["class"]:"")}),b.append("line").style("opacity",0),b.append("text").attr("text-anchor","end").attr("transform",g.axis_rotated?"":"rotate(-90)").attr("dx",g.axis_rotated?0:-e.margin.top).attr("dy",-5).style("opacity",0),e.xgridLines.exit().transition().duration(a).style("opacity",0).remove(),g.grid_y_show&&e.updateYGrid(),e.ygridLines=f.select("."+i.ygridLines).selectAll("."+i.ygridLine).data(g.grid_y_lines),c=e.ygridLines.enter().append("g").attr("class",function(a){return i.ygridLine+(a["class"]?" "+a["class"]:"")}),c.append("line").style("opacity",0),c.append("text").attr("text-anchor","end").attr("transform",g.axis_rotated?"rotate(-90)":"").attr("dx",g.axis_rotated?0:-e.margin.top).attr("dy",-5).style("opacity",0),d=e.yv.bind(e),e.ygridLines.select("line").transition().duration(a).attr("x1",g.axis_rotated?d:0).attr("x2",g.axis_rotated?d:e.width).attr("y1",g.axis_rotated?0:d).attr("y2",g.axis_rotated?e.height:d).style("opacity",1),e.ygridLines.select("text").transition().duration(a).attr("x",g.axis_rotated?0:e.width).attr("y",d).text(function(a){return a.text}).style("opacity",1),e.ygridLines.exit().transition().duration(a).style("opacity",0).remove()},f.addTransitionForGrid=function(a){var b=this,c=b.config,d=b.xv.bind(b);a.push(b.xgridLines.select("line").transition().attr("x1",c.axis_rotated?0:d).attr("x2",c.axis_rotated?b.width:d).attr("y1",c.axis_rotated?d:b.margin.top).attr("y2",c.axis_rotated?d:b.height).style("opacity",1)),a.push(b.xgridLines.select("text").transition().attr("x",c.axis_rotated?b.width:0).attr("y",d).text(function(a){return a.text}).style("opacity",1))},f.showXGridFocus=function(a){var b=this,c=b.config,d=a.filter(function(a){return a&&j(a.value)}),e=b.main.selectAll("line."+i.xgridFocus),f=b.xx.bind(b);c.tooltip_show&&(b.hasType("scatter")||b.hasArcType()||(e.style("visibility","visible").data([d[0]]).attr(c.axis_rotated?"y1":"x1",f).attr(c.axis_rotated?"y2":"x2",f),b.smoothLines(e,"grid")))},f.hideXGridFocus=function(){this.main.select("line."+i.xgridFocus).style("visibility","hidden")},f.updateXgridFocus=function(){var a=this,b=a.config;a.main.select("line."+i.xgridFocus).attr("x1",b.axis_rotated?0:-10).attr("x2",b.axis_rotated?a.width:-10).attr("y1",b.axis_rotated?-10:0).attr("y2",b.axis_rotated?-10:a.height)},f.generateGridData=function(a,b){var c,d,e,f,g=this,h=[],j=g.main.select("."+i.axisX).selectAll(".tick").size();if("year"===a)for(c=g.getXDomain(),d=c[0].getFullYear(),e=c[1].getFullYear(),f=d;e>=f;f++)h.push(new Date(f+"-01-01 00:00:00"));else h=b.ticks(10),h.length>j&&(h=h.filter(function(a){return(""+a).indexOf(".")<0}));return h},f.getGridFilterToRemove=function(a){return a?function(b){var c=!1;return[].concat(a).forEach(function(a){("value"in a&&b.value===a.value||"class"in a&&b["class"]===a["class"])&&(c=!0)}),c}:function(){return!0}},f.removeGridLines=function(a,b){var c=this,d=c.config,e=c.getGridFilterToRemove(a),f=function(a){return!e(a)},g=b?i.xgridLines:i.ygridLines,h=b?i.xgridLine:i.ygridLine;c.main.select("."+g).selectAll("."+h).filter(e).transition().duration(d.transition_duration).style("opacity",0).remove(),b?d.grid_x_lines=d.grid_x_lines.filter(f):d.grid_y_lines=d.grid_y_lines.filter(f)},f.initTooltip=function(){var a,b=this,c=b.config;if(b.tooltip=b.selectChart.style("position","relative").append("div").attr("class",i.tooltipContainer).style("position","absolute").style("pointer-events","none").style("display","none"),c.tooltip_init_show){if(b.isTimeSeries()&&l(c.tooltip_init_x)){for(c.tooltip_init_x=b.parseDate(c.tooltip_init_x),a=0;a<b.data.targets[0].values.length&&b.data.targets[0].values[a].x-c.tooltip_init_x!==0;a++);c.tooltip_init_x=a}b.tooltip.html(c.tooltip_contents.call(b,b.data.targets.map(function(a){return b.addName(a.values[c.tooltip_init_x])}),b.getXAxisTickFormat(),b.getYFormat(b.hasArcType()),b.color)),b.tooltip.style("top",c.tooltip_init_position.top).style("left",c.tooltip_init_position.left).style("display","block")}},f.getTooltipContent=function(a,b,c,d){var e,f,g,h,j,k,l=this,m=l.config,n=m.tooltip_format_title||b,o=m.tooltip_format_name||function(a){return a},p=m.tooltip_format_value||c;for(f=0;f<a.length;f++)a[f]&&(a[f].value||0===a[f].value)&&(e||(g=n?n(a[f].x):a[f].x,e="<table class='"+i.tooltip+"'>"+(g||0===g?"<tr><th colspan='2'>"+g+"</th></tr>":"")),j=o(a[f].name,a[f].ratio,a[f].id,a[f].index),h=p(a[f].value,a[f].ratio,a[f].id,a[f].index),k=l.levelColor?l.levelColor(a[f].value):d(a[f].id),e+="<tr class='"+i.tooltipName+"-"+a[f].id+"'>",e+="<td class='name'><span style='background-color:"+k+"'></span>"+j+"</td>",e+="<td class='value'>"+h+"</td>",e+="</tr>");return e+"</table>"},f.showTooltip=function(a,b){var c,d,e,f,g,h,i,k=this,l=k.config,m=k.hasArcType(),n=a.filter(function(a){return a&&j(a.value)});0!==n.length&&l.tooltip_show&&(k.tooltip.html(l.tooltip_contents.call(k,a,k.getXAxisTickFormat(),k.getYFormat(m),k.color)).style("display","block"),c=k.tooltip.property("offsetWidth"),d=k.tooltip.property("offsetHeight"),m?(f=k.width/2+b[0],h=k.height/2+b[1]+20):(e=k.getSvgLeft(!0),l.axis_rotated?(f=e+b[0]+100,g=f+c,i=k.currentWidth-k.getCurrentPaddingRight(),h=k.x(n[0].x)+20):(f=e+k.getCurrentPaddingLeft(!0)+k.x(n[0].x)+20,g=f+c,i=e+k.currentWidth-k.getCurrentPaddingRight(),h=b[1]+15),g>i&&(f-=g-i),h+d>k.currentHeight&&(h-=d+30)),0>h&&(h=0),k.tooltip.style("top",h+"px").style("left",f+"px"))},f.hideTooltip=function(){this.tooltip.style("display","none")},f.initLegend=function(){var a=this;return a.legendHasRendered=!1,a.legend=a.svg.append("g").attr("transform",a.getTranslate("legend")),a.config.legend_show?void a.updateLegendWithDefaults():(a.legend.style("visibility","hidden"),void(a.hiddenLegendIds=a.mapToIds(a.data.targets)))},f.updateLegendWithDefaults=function(){var a=this;a.updateLegend(a.mapToIds(a.data.targets),{withTransform:!1,withTransitionForTransform:!1,withTransition:!1})},f.updateSizeForLegend=function(a,b){var c=this,d=c.config,e={top:c.isLegendTop?c.getCurrentPaddingTop()+d.legend_inset_y+5.5:c.currentHeight-a-c.getCurrentPaddingBottom()-d.legend_inset_y,left:c.isLegendLeft?c.getCurrentPaddingLeft()+d.legend_inset_x+.5:c.currentWidth-b-c.getCurrentPaddingRight()-d.legend_inset_x+.5};c.margin3={top:c.isLegendRight?0:c.isLegendInset?e.top:c.currentHeight-a,right:0/0,bottom:0,left:c.isLegendRight?c.currentWidth-b:c.isLegendInset?e.left:0}},f.transformLegend=function(a){var b=this;(a?b.legend.transition():b.legend).attr("transform",b.getTranslate("legend"))},f.updateLegendStep=function(a){this.legendStep=a},f.updateLegendItemWidth=function(a){this.legendItemWidth=a},f.updateLegendItemHeight=function(a){this.legendItemHeight=a},f.getLegendWidth=function(){var a=this;return a.config.legend_show?a.isLegendRight||a.isLegendInset?a.legendItemWidth*(a.legendStep+1):a.currentWidth:0},f.getLegendHeight=function(){var a=this,b=0;return a.config.legend_show&&(b=a.isLegendRight?a.currentHeight:Math.max(20,a.legendItemHeight)*(a.legendStep+1)),b},f.opacityForLegend=function(a){return a.classed(i.legendItemHidden)?null:1},f.opacityForUnfocusedLegend=function(a){return a.classed(i.legendItemHidden)?null:.3},f.toggleFocusLegend=function(a,b){var c=this;a=c.mapToTargetIds(a),c.legend.selectAll("."+i.legendItem).filter(function(b){return a.indexOf(b)>=0}).classed(i.legendItemFocused,b).transition().duration(100).style("opacity",function(){var a=b?c.opacityForLegend:c.opacityForUnfocusedLegend;return a.call(c,c.d3.select(this))})},f.revertLegend=function(){var a=this,b=a.d3;a.legend.selectAll("."+i.legendItem).classed(i.legendItemFocused,!1).transition().duration(100).style("opacity",function(){return a.opacityForLegend(b.select(this))})},f.showLegend=function(a){var b=this,c=b.config;c.legend_show||(c.legend_show=!0,b.legend.style("visibility","visible"),b.legendHasRendered||b.updateLegendWithDefaults()),b.removeHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("visibility","visible").transition().style("opacity",function(){return b.opacityForLegend(b.d3.select(this))})},f.hideLegend=function(a){var b=this,c=b.config;c.legend_show&&r(a)&&(c.legend_show=!1,b.legend.style("visibility","hidden")),b.addHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("opacity",0).style("visibility","hidden")};var h={};f.clearLegendItemTextBoxCache=function(){h={}},f.updateLegend=function(a,b,c){function d(a,b){return h[b]||(h[b]=w.getTextRect(a.textContent,i.legendItem)),h[b]}function e(b,c,e){function f(a,b){b||(g=(o-E-n)/2,C>g&&(g=(o-n)/2,E=0,K++)),J[a]=K,I[K]=w.isLegendInset?10:g,F[a]=E,E+=n}var g,h,i=0===e,j=e===a.length-1,k=d(b,c),l=k.width+D+(!j||w.isLegendRight||w.isLegendInset?z:0),m=k.height+y,n=w.isLegendRight||w.isLegendInset?m:l,o=w.isLegendRight||w.isLegendInset?w.getLegendHeight():w.getLegendWidth();return i&&(E=0,K=0,A=0,B=0),x.legend_show&&!w.isLegendToShow(c)?void(G[c]=H[c]=J[c]=F[c]=0):(G[c]=l,H[c]=m,(!A||l>=A)&&(A=l),(!B||m>=B)&&(B=m),h=w.isLegendRight||w.isLegendInset?B:A,void(x.legend_equally?(Object.keys(G).forEach(function(a){G[a]=A}),Object.keys(H).forEach(function(a){H[a]=B}),g=(o-h*a.length)/2,C>g?(E=0,K=0,a.forEach(function(a){f(a)})):f(c,!0)):f(c)))}var f,g,j,k,l,m,o,p,q,r,s,u,v,w=this,x=w.config,y=4,z=10,A=0,B=0,C=10,D=15,E=0,F={},G={},H={},I=[0],J={},K=0,L=w.legend.selectAll("."+i.legendItemFocused).size();b=b||{},p=t(b,"withTransition",!0),q=t(b,"withTransitionForTransform",!0),w.isLegendInset&&(K=x.legend_inset_step?x.legend_inset_step:a.length,w.updateLegendStep(K)),w.isLegendRight?(f=function(a){return A*J[a]},k=function(a){return I[J[a]]+F[a]}):w.isLegendInset?(f=function(a){return A*J[a]+10},k=function(a){return I[J[a]]+F[a]}):(f=function(a){return I[J[a]]+F[a]},k=function(a){return B*J[a]}),g=function(a,b){return f(a,b)+14},l=function(a,b){return k(a,b)+9},j=function(a,b){return f(a,b)},m=function(a,b){return k(a,b)-5},o=w.legend.selectAll("."+i.legendItem).data(a).enter().append("g").attr("class",function(a){return w.generateClass(i.legendItem,a)}).style("visibility",function(a){return w.isLegendToShow(a)?"visible":"hidden"}).style("cursor","pointer").on("click",function(a){x.legend_item_onclick?x.legend_item_onclick.call(w,a):w.d3.event.altKey?(w.api.hide(),w.api.show(a)):(w.api.toggle(a),w.isTargetToShow(a)?w.api.focus(a):w.api.revert())}).on("mouseover",function(a){w.d3.select(this).classed(i.legendItemFocused,!0),!w.transiting&&w.isTargetToShow(a)&&w.api.focus(a),x.legend_item_onmouseover&&x.legend_item_onmouseover.call(w,a)}).on("mouseout",function(a){w.d3.select(this).classed(i.legendItemFocused,!1),w.api.revert(),x.legend_item_onmouseout&&x.legend_item_onmouseout.call(w,a)}),o.append("text").text(function(a){return n(x.data_names[a])?x.data_names[a]:a}).each(function(a,b){e(this,a,b)}).style("pointer-events","none").attr("x",w.isLegendRight||w.isLegendInset?g:-200).attr("y",w.isLegendRight||w.isLegendInset?-200:l),o.append("rect").attr("class",i.legendItemEvent).style("fill-opacity",0).attr("x",w.isLegendRight||w.isLegendInset?j:-200).attr("y",w.isLegendRight||w.isLegendInset?-200:m),o.append("rect").attr("class",i.legendItemTile).style("pointer-events","none").style("fill",w.color).attr("x",w.isLegendRight||w.isLegendInset?g:-200).attr("y",w.isLegendRight||w.isLegendInset?-200:k).attr("width",10).attr("height",10),v=w.legend.select("."+i.legendBackground+" rect"),w.isLegendInset&&A>0&&0===v.size()&&(v=w.legend.insert("g","."+i.legendItem).attr("class",i.legendBackground).append("rect")),r=w.legend.selectAll("text").data(a).text(function(a){return n(x.data_names[a])?x.data_names[a]:a}).each(function(a,b){e(this,a,b)}),(p?r.transition():r).attr("x",g).attr("y",l),s=w.legend.selectAll("rect."+i.legendItemEvent).data(a),(p?s.transition():s).attr("width",function(a){return G[a]}).attr("height",function(a){return H[a]}).attr("x",j).attr("y",m),u=w.legend.selectAll("rect."+i.legendItemTile).data(a),(p?u.transition():u).style("fill",w.color).attr("x",f).attr("y",k),v&&(p?v.transition():v).attr("height",w.getLegendHeight()-12).attr("width",A*(K+1)+10),w.legend.selectAll("."+i.legendItem).classed(i.legendItemHidden,function(a){return!w.isTargetToShow(a)}).transition().style("opacity",function(a){var b=w.d3.select(this);return w.isTargetToShow(a)?!L||b.classed(i.legendItemFocused)?w.opacityForLegend(b):w.opacityForUnfocusedLegend(b):null}),w.updateLegendItemWidth(A),w.updateLegendItemHeight(B),w.updateLegendStep(K),w.updateSizes(),w.updateScales(),w.updateSvgSize(),w.transformAll(q,c),w.legendHasRendered=!0},f.initAxis=function(){var a=this,b=a.config,c=a.main;a.axes.x=c.append("g").attr("class",i.axis+" "+i.axisX).attr("clip-path",a.clipPathForXAxis).attr("transform",a.getTranslate("x")).style("visibility",b.axis_x_show?"visible":"hidden"),a.axes.x.append("text").attr("class",i.axisXLabel).attr("transform",b.axis_rotated?"rotate(-90)":"").style("text-anchor",a.textAnchorForXAxisLabel.bind(a)),a.axes.y=c.append("g").attr("class",i.axis+" "+i.axisY).attr("clip-path",b.axis_y_inner?"":a.clipPathForYAxis).attr("transform",a.getTranslate("y")).style("visibility",b.axis_y_show?"visible":"hidden"),a.axes.y.append("text").attr("class",i.axisYLabel).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",a.textAnchorForYAxisLabel.bind(a)),a.axes.y2=c.append("g").attr("class",i.axis+" "+i.axisY2).attr("transform",a.getTranslate("y2")).style("visibility",b.axis_y2_show?"visible":"hidden"),a.axes.y2.append("text").attr("class",i.axisY2Label).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",a.textAnchorForY2AxisLabel.bind(a))},f.getXAxis=function(a,b,c,e,f){var g=this,h=g.config,i={isCategory:g.isCategorized(),withOuterTick:f,tickMultiline:h.axis_x_tick_multiline,tickWidth:h.axis_x_tick_width},j=d(g.d3,i).scale(a).orient(b);return g.isTimeSeries()&&e&&(e=e.map(function(a){return g.parseDate(a)})),j.tickFormat(c).tickValues(e),g.isCategorized()?(j.tickCentered(h.axis_x_tick_centered),r(h.axis_x_tick_culling)&&(h.axis_x_tick_culling=!1)):j.tickOffset=function(){var a=this.scale(),b=g.getEdgeX(g.data.targets),c=a(b[1])-a(b[0]),d=c?c:h.axis_rotated?g.height:g.width;return d/g.getMaxDataCount()/2},j},f.getYAxis=function(a,b,c,e,f){var g={withOuterTick:f},h=d(this.d3,g).scale(a).orient(b).tickFormat(c);return this.isTimeSeriesY()?h.ticks(this.d3.time[this.config.axis_y_tick_time_value],this.config.axis_y_tick_time_interval):h.tickValues(e),h},f.getAxisId=function(a){var b=this.config;return a in b.data_axes?b.data_axes[a]:"y"},f.getXAxisTickFormat=function(){var a=this,b=a.config,c=a.isTimeSeries()?a.defaultAxisTimeFormat:a.isCategorized()?a.categoryName:function(a){return 0>a?a.toFixed(0):a};return b.axis_x_tick_format&&(k(b.axis_x_tick_format)?c=b.axis_x_tick_format:a.isTimeSeries()&&(c=function(c){return c?a.axisTimeFormat(b.axis_x_tick_format)(c):""})),k(c)?function(b){return c.call(a,b)}:c},f.getAxisTickValues=function(a,b){return a?a:b?b.tickValues():void 0},f.getXAxisTickValues=function(){return this.getAxisTickValues(this.config.axis_x_tick_values,this.xAxis)},f.getYAxisTickValues=function(){return this.getAxisTickValues(this.config.axis_y_tick_values,this.yAxis)},f.getY2AxisTickValues=function(){return this.getAxisTickValues(this.config.axis_y2_tick_values,this.y2Axis)},f.getAxisLabelOptionByAxisId=function(a){var b,c=this,d=c.config;return"y"===a?b=d.axis_y_label:"y2"===a?b=d.axis_y2_label:"x"===a&&(b=d.axis_x_label),b},f.getAxisLabelText=function(a){var b=this.getAxisLabelOptionByAxisId(a);return l(b)?b:b?b.text:null},f.setAxisLabelText=function(a,b){var c=this,d=c.config,e=c.getAxisLabelOptionByAxisId(a);l(e)?"y"===a?d.axis_y_label=b:"y2"===a?d.axis_y2_label=b:"x"===a&&(d.axis_x_label=b):e&&(e.text=b)},f.getAxisLabelPosition=function(a,b){var c=this.getAxisLabelOptionByAxisId(a),d=c&&"object"==typeof c&&c.position?c.position:b;return{isInner:d.indexOf("inner")>=0,isOuter:d.indexOf("outer")>=0,isLeft:d.indexOf("left")>=0,isCenter:d.indexOf("center")>=0,isRight:d.indexOf("right")>=0,isTop:d.indexOf("top")>=0,isMiddle:d.indexOf("middle")>=0,isBottom:d.indexOf("bottom")>=0}},f.getXAxisLabelPosition=function(){return this.getAxisLabelPosition("x",this.config.axis_rotated?"inner-top":"inner-right")},f.getYAxisLabelPosition=function(){return this.getAxisLabelPosition("y",this.config.axis_rotated?"inner-right":"inner-top")},f.getY2AxisLabelPosition=function(){return this.getAxisLabelPosition("y2",this.config.axis_rotated?"inner-right":"inner-top")},f.getAxisLabelPositionById=function(a){return"y2"===a?this.getY2AxisLabelPosition():"y"===a?this.getYAxisLabelPosition():this.getXAxisLabelPosition()},f.textForXAxisLabel=function(){return this.getAxisLabelText("x")},f.textForYAxisLabel=function(){return this.getAxisLabelText("y")},f.textForY2AxisLabel=function(){return this.getAxisLabelText("y2")},f.xForAxisLabel=function(a,b){var c=this;return a?b.isLeft?0:b.isCenter?c.width/2:c.width:b.isBottom?-c.height:b.isMiddle?-c.height/2:0},f.dxForAxisLabel=function(a,b){return a?b.isLeft?"0.5em":b.isRight?"-0.5em":"0":b.isTop?"-0.5em":b.isBottom?"0.5em":"0"},f.textAnchorForAxisLabel=function(a,b){return a?b.isLeft?"start":b.isCenter?"middle":"end":b.isBottom?"start":b.isMiddle?"middle":"end"},f.xForXAxisLabel=function(){return this.xForAxisLabel(!this.config.axis_rotated,this.getXAxisLabelPosition())},f.xForYAxisLabel=function(){return this.xForAxisLabel(this.config.axis_rotated,this.getYAxisLabelPosition())},f.xForY2AxisLabel=function(){return this.xForAxisLabel(this.config.axis_rotated,this.getY2AxisLabelPosition())},f.dxForXAxisLabel=function(){return this.dxForAxisLabel(!this.config.axis_rotated,this.getXAxisLabelPosition())},f.dxForYAxisLabel=function(){return this.dxForAxisLabel(this.config.axis_rotated,this.getYAxisLabelPosition())},f.dxForY2AxisLabel=function(){return this.dxForAxisLabel(this.config.axis_rotated,this.getY2AxisLabelPosition())},f.dyForXAxisLabel=function(){var a=this,b=a.config,c=a.getXAxisLabelPosition();return b.axis_rotated?c.isInner?"1.2em":-25-a.getMaxTickWidth("x"):c.isInner?"-0.5em":b.axis_x_height?b.axis_x_height-10:"3em"},f.dyForYAxisLabel=function(){var a=this,b=a.getYAxisLabelPosition();return a.config.axis_rotated?b.isInner?"-0.5em":"3em":b.isInner?"1.2em":-10-(a.config.axis_y_inner?0:a.getMaxTickWidth("y")+10)},f.dyForY2AxisLabel=function(){var a=this,b=a.getY2AxisLabelPosition();return a.config.axis_rotated?b.isInner?"1.2em":"-2.2em":b.isInner?"-0.5em":15+(a.config.axis_y2_inner?0:this.getMaxTickWidth("y2")+15)},f.textAnchorForXAxisLabel=function(){var a=this;return a.textAnchorForAxisLabel(!a.config.axis_rotated,a.getXAxisLabelPosition())},f.textAnchorForYAxisLabel=function(){var a=this;return a.textAnchorForAxisLabel(a.config.axis_rotated,a.getYAxisLabelPosition())},f.textAnchorForY2AxisLabel=function(){var a=this;return a.textAnchorForAxisLabel(a.config.axis_rotated,a.getY2AxisLabelPosition())},f.xForRotatedTickText=function(a){return 8*Math.sin(Math.PI*(a/180))},f.yForRotatedTickText=function(a){return 11.5-2.5*(a/15)*(a>0?1:-1)},f.rotateTickText=function(a,b,c){a.selectAll(".tick text").style("text-anchor",c>0?"start":"end"),b.selectAll(".tick text").attr("y",this.yForRotatedTickText(c)).attr("transform","rotate("+c+")").selectAll("tspan").attr("dx",this.xForRotatedTickText(c))},f.getMaxTickWidth=function(a,b){var c,d,e,f=this,g=f.config,h=0;return b&&f.currentMaxTickWidths[a]?f.currentMaxTickWidths[a]:(f.svg&&(c=f.filterTargetsToShow(f.data.targets),"y"===a?(d=f.y.copy().domain(f.getYDomain(c,"y")),e=f.getYAxis(d,f.yOrient,g.axis_y_tick_format,f.yAxisTickValues)):"y2"===a?(d=f.y2.copy().domain(f.getYDomain(c,"y2")),e=f.getYAxis(d,f.y2Orient,g.axis_y2_tick_format,f.y2AxisTickValues)):(d=f.x.copy().domain(f.getXDomain(c)),e=f.getXAxis(d,f.xOrient,f.xAxisTickFormat,f.xAxisTickValues)),f.d3.select("body").append("g").style("visibility","hidden").call(e).each(function(){f.d3.select(this).selectAll("text tspan").each(function(){var a=this.getBoundingClientRect();a.left>0&&h<a.width&&(h=a.width)})}).remove()),f.currentMaxTickWidths[a]=0>=h?f.currentMaxTickWidths[a]:h,f.currentMaxTickWidths[a])},f.updateAxisLabels=function(a){var b=this,c=b.main.select("."+i.axisX+" ."+i.axisXLabel),d=b.main.select("."+i.axisY+" ."+i.axisYLabel),e=b.main.select("."+i.axisY2+" ."+i.axisY2Label);(a?c.transition():c).attr("x",b.xForXAxisLabel.bind(b)).attr("dx",b.dxForXAxisLabel.bind(b)).attr("dy",b.dyForXAxisLabel.bind(b)).text(b.textForXAxisLabel.bind(b)),(a?d.transition():d).attr("x",b.xForYAxisLabel.bind(b)).attr("dx",b.dxForYAxisLabel.bind(b)).attr("dy",b.dyForYAxisLabel.bind(b)).text(b.textForYAxisLabel.bind(b)),(a?e.transition():e).attr("x",b.xForY2AxisLabel.bind(b)).attr("dx",b.dxForY2AxisLabel.bind(b)).attr("dy",b.dyForY2AxisLabel.bind(b)).text(b.textForY2AxisLabel.bind(b))},f.getAxisPadding=function(a,b,c,d){return j(a[b])?"ratio"===a.unit?a[b]*d:this.convertPixelsToAxisPadding(a[b],d):c},f.convertPixelsToAxisPadding=function(a,b){var c=this.config.axis_rotated?this.width:this.height;return b*(a/c)},f.generateTickValues=function(a,b,c){var d,e,f,g,h,i,j,l=a;if(b)if(d=k(b)?b():b,1===d)l=[a[0]];else if(2===d)l=[a[0],a[a.length-1]];else if(d>2){for(g=d-2,e=a[0],f=a[a.length-1],h=(f-e)/(g+1),l=[e],i=0;g>i;i++)j=+e+h*(i+1),l.push(c?new Date(j):j);l.push(f)}return c||(l=l.sort(function(a,b){return a-b})),l},f.generateAxisTransitions=function(a){var b=this,c=b.axes;return{axisX:a?c.x.transition().duration(a):c.x,axisY:a?c.y.transition().duration(a):c.y,axisY2:a?c.y2.transition().duration(a):c.y2,axisSubX:a?c.subx.transition().duration(a):c.subx}},f.redrawAxis=function(a,b){var c=this,d=c.config;c.axes.x.style("opacity",b?0:1),c.axes.y.style("opacity",b?0:1),c.axes.y2.style("opacity",b?0:1),c.axes.subx.style("opacity",b?0:1),a.axisX.call(c.xAxis),a.axisY.call(c.yAxis),a.axisY2.call(c.y2Axis),a.axisSubX.call(c.subXAxis),!d.axis_rotated&&d.axis_x_tick_rotate&&(c.rotateTickText(c.axes.x,a.axisX,d.axis_x_tick_rotate),c.rotateTickText(c.axes.subx,a.axisSubX,d.axis_x_tick_rotate))},f.getClipPath=function(b){var c=a.navigator.appVersion.toLowerCase().indexOf("msie 9.")>=0;return"url("+(c?"":document.URL.split("#")[0])+"#"+b+")"},f.appendClip=function(a,b){return a.append("clipPath").attr("id",b).append("rect")},f.getAxisClipX=function(a){var b=Math.max(30,this.margin.left);return a?-(1+b):-(b-1)},f.getAxisClipY=function(a){return a?-20:-this.margin.top},f.getXAxisClipX=function(){var a=this;return a.getAxisClipX(!a.config.axis_rotated)},f.getXAxisClipY=function(){var a=this;return a.getAxisClipY(!a.config.axis_rotated)},f.getYAxisClipX=function(){var a=this;return a.config.axis_y_inner?-1:a.getAxisClipX(a.config.axis_rotated)},f.getYAxisClipY=function(){var a=this;return a.getAxisClipY(a.config.axis_rotated)},f.getAxisClipWidth=function(a){var b=this,c=Math.max(30,b.margin.left),d=Math.max(30,b.margin.right);return a?b.width+2+c+d:b.margin.left+20},f.getAxisClipHeight=function(a){return(a?this.margin.bottom:this.margin.top+this.height)+20},f.getXAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(!a.config.axis_rotated)},f.getXAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(!a.config.axis_rotated)},f.getYAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(a.config.axis_rotated)+(a.config.axis_y_inner?20:0)},f.getYAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(a.config.axis_rotated)},f.initPie=function(){var a=this,b=a.d3,c=a.config;a.pie=b.layout.pie().value(function(a){return a.values.reduce(function(a,b){return a+b.value},0)}),c.data_order||a.pie.sort(null)},f.updateRadius=function(){var a=this,b=a.config,c=b.gauge_width||b.donut_width;a.radiusExpanded=Math.min(a.arcWidth,a.arcHeight)/2,a.radius=.95*a.radiusExpanded,a.innerRadiusRatio=c?(a.radius-c)/a.radius:.6,a.innerRadius=a.hasType("donut")||a.hasType("gauge")?a.radius*a.innerRadiusRatio:0},f.updateArc=function(){var a=this;a.svgArc=a.getSvgArc(),a.svgArcExpanded=a.getSvgArcExpanded(),a.svgArcExpandedSub=a.getSvgArcExpanded(.98)},f.updateAngle=function(a){var b,c,d=this,e=d.config,f=!1,g=0,h=e.gauge_min,i=e.gauge_max;return d.pie(d.filterTargetsToShow(d.data.targets)).forEach(function(b){f||b.data.id!==a.data.id||(f=!0,a=b,a.index=g),g++}),isNaN(a.endAngle)&&(a.endAngle=a.startAngle),d.isGaugeType(a.data)&&(b=Math.PI/(i-h),c=a.value<h?0:a.value<i?a.value-h:i-h,a.startAngle=-1*(Math.PI/2),a.endAngle=a.startAngle+b*c),f?a:null},f.getSvgArc=function(){var a=this,b=a.d3.svg.arc().outerRadius(a.radius).innerRadius(a.innerRadius),c=function(c,d){var e;return d?b(c):(e=a.updateAngle(c),e?b(e):"M 0 0")};return c.centroid=b.centroid,c},f.getSvgArcExpanded=function(a){var b=this,c=b.d3.svg.arc().outerRadius(b.radiusExpanded*(a?a:1)).innerRadius(b.innerRadius);return function(a){var d=b.updateAngle(a);return d?c(d):"M 0 0"}},f.getArc=function(a,b,c){return c||this.isArcType(a.data)?this.svgArc(a,b):"M 0 0"},f.transformForArcLabel=function(a){var b,c,d,e,f,g=this,h=g.updateAngle(a),i="";return h&&!g.hasType("gauge")&&(b=this.svgArc.centroid(h),c=isNaN(b[0])?0:b[0],d=isNaN(b[1])?0:b[1],e=Math.sqrt(c*c+d*d),f=g.radius&&e?(36/g.radius>.375?1.175-36/g.radius:.8)*g.radius/e:0,i="translate("+c*f+","+d*f+")"),i},f.getArcRatio=function(a){var b=this,c=b.hasType("gauge")?Math.PI:2*Math.PI;return a?(a.endAngle-a.startAngle)/c:null},f.convertToArcData=function(a){return this.addName({id:a.data.id,value:a.value,ratio:this.getArcRatio(a),index:a.index})},f.textForArcLabel=function(a){var b,c,d,e,f,g=this;return g.shouldShowArcLabel()?(b=g.updateAngle(a),c=b?b.value:null,d=g.getArcRatio(b),e=a.data.id,g.hasType("gauge")||g.meetsArcLabelThreshold(d)?(f=g.getArcLabelFormat(),f?f(c,d,e):g.defaultArcValueFormat(c,d)):""):""},f.expandArc=function(b){var c,d=this;return d.transiting?void(c=a.setInterval(function(){d.transiting||(a.clearInterval(c),d.legend.selectAll(".c3-legend-item-focused").size()>0&&d.expandArc(b))},10)):(b=d.mapToTargetIds(b),void d.svg.selectAll(d.selectorTargets(b,"."+i.chartArc)).each(function(a){d.shouldExpand(a.data.id)&&d.d3.select(this).selectAll("path").transition().duration(50).attr("d",d.svgArcExpanded).transition().duration(100).attr("d",d.svgArcExpandedSub).each(function(a){d.isDonutType(a.data)})}))},f.unexpandArc=function(a){var b=this;b.transiting||(a=b.mapToTargetIds(a),b.svg.selectAll(b.selectorTargets(a,"."+i.chartArc)).selectAll("path").transition().duration(50).attr("d",b.svgArc),b.svg.selectAll("."+i.arc).style("opacity",1))},f.shouldExpand=function(a){var b=this,c=b.config;return b.isDonutType(a)&&c.donut_expand||b.isGaugeType(a)&&c.gauge_expand||b.isPieType(a)&&c.pie_expand},f.shouldShowArcLabel=function(){var a=this,b=a.config,c=!0;return a.hasType("donut")?c=b.donut_label_show:a.hasType("pie")&&(c=b.pie_label_show),c},f.meetsArcLabelThreshold=function(a){var b=this,c=b.config,d=b.hasType("donut")?c.donut_label_threshold:c.pie_label_threshold;
|
4 |
+
return a>=d},f.getArcLabelFormat=function(){var a=this,b=a.config,c=b.pie_label_format;return a.hasType("gauge")?c=b.gauge_label_format:a.hasType("donut")&&(c=b.donut_label_format),c},f.getArcTitle=function(){var a=this;return a.hasType("donut")?a.config.donut_title:""},f.updateTargetsForArc=function(a){var b,c,d=this,e=d.main,f=d.classChartArc.bind(d),g=d.classArcs.bind(d),h=d.classFocus.bind(d);b=e.select("."+i.chartArcs).selectAll("."+i.chartArc).data(d.pie(a)).attr("class",function(a){return f(a)+h(a.data)}),c=b.enter().append("g").attr("class",f),c.append("g").attr("class",g),c.append("text").attr("dy",d.hasType("gauge")?"-.1em":".35em").style("opacity",0).style("text-anchor","middle").style("pointer-events","none")},f.initArc=function(){var a=this;a.arcs=a.main.select("."+i.chart).append("g").attr("class",i.chartArcs).attr("transform",a.getTranslate("arc")),a.arcs.append("text").attr("class",i.chartArcsTitle).style("text-anchor","middle").text(a.getArcTitle())},f.redrawArc=function(a,b,c){var d,e=this,f=e.d3,g=e.config,h=e.main;d=h.selectAll("."+i.arcs).selectAll("."+i.arc).data(e.arcData.bind(e)),d.enter().append("path").attr("class",e.classArc.bind(e)).style("fill",function(a){return e.color(a.data)}).style("cursor",function(a){return g.interaction_enabled&&g.data_selection_isselectable(a)?"pointer":null}).style("opacity",0).each(function(a){e.isGaugeType(a.data)&&(a.startAngle=a.endAngle=-1*(Math.PI/2)),this._current=a}),d.attr("transform",function(a){return!e.isGaugeType(a.data)&&c?"scale(0)":""}).style("opacity",function(a){return a===this._current?0:1}).on("mouseover",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),c=e.convertToArcData(b),e.expandArc(b.data.id),e.api.focus(b.data.id),e.toggleFocusLegend(b.data.id,!0),e.config.data_onmouseover(c,this))}:null).on("mousemove",g.interaction_enabled?function(a){var b=e.updateAngle(a),c=e.convertToArcData(b),d=[c];e.showTooltip(d,f.mouse(this))}:null).on("mouseout",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),c=e.convertToArcData(b),e.unexpandArc(b.data.id),e.api.revert(),e.revertLegend(),e.hideTooltip(),e.config.data_onmouseout(c,this))}:null).on("click",g.interaction_enabled?function(a,b){var c=e.updateAngle(a),d=e.convertToArcData(c);e.toggleShape&&e.toggleShape(this,d,b),e.config.data_onclick.call(e.api,d,this)}:null).each(function(){e.transiting=!0}).transition().duration(a).attrTween("d",function(a){var b,c=e.updateAngle(a);return c?(isNaN(this._current.endAngle)&&(this._current.endAngle=this._current.startAngle),b=f.interpolate(this._current,c),this._current=b(0),function(c){var d=b(c);return d.data=a.data,e.getArc(d,!0)}):function(){return"M 0 0"}}).attr("transform",c?"scale(1)":"").style("fill",function(a){return e.levelColor?e.levelColor(a.data.values[0].value):e.color(a.data.id)}).style("opacity",1).call(e.endall,function(){e.transiting=!1}),d.exit().transition().duration(b).style("opacity",0).remove(),h.selectAll("."+i.chartArc).select("text").style("opacity",0).attr("class",function(a){return e.isGaugeType(a.data)?i.gaugeValue:""}).text(e.textForArcLabel.bind(e)).attr("transform",e.transformForArcLabel.bind(e)).style("font-size",function(a){return e.isGaugeType(a.data)?Math.round(e.radius/5)+"px":""}).transition().duration(a).style("opacity",function(a){return e.isTargetToShow(a.data.id)&&e.isArcType(a.data)?1:0}),h.select("."+i.chartArcsTitle).style("opacity",e.hasType("donut")||e.hasType("gauge")?1:0),e.hasType("gauge")&&(e.arcs.select("."+i.chartArcsBackground).attr("d",function(){var a={data:[{value:g.gauge_max}],startAngle:-1*(Math.PI/2),endAngle:Math.PI/2};return e.getArc(a,!0,!0)}),e.arcs.select("."+i.chartArcsGaugeUnit).attr("dy",".75em").text(g.gauge_label_show?g.gauge_units:""),e.arcs.select("."+i.chartArcsGaugeMin).attr("dx",-1*(e.innerRadius+(e.radius-e.innerRadius)/2)+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_min:""),e.arcs.select("."+i.chartArcsGaugeMax).attr("dx",e.innerRadius+(e.radius-e.innerRadius)/2+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_max:""))},f.initGauge=function(){var a=this.arcs;this.hasType("gauge")&&(a.append("path").attr("class",i.chartArcsBackground),a.append("text").attr("class",i.chartArcsGaugeUnit).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",i.chartArcsGaugeMin).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",i.chartArcsGaugeMax).style("text-anchor","middle").style("pointer-events","none"))},f.getGaugeLabelHeight=function(){return this.config.gauge_label_show?20:0},f.initRegion=function(){var a=this;a.region=a.main.append("g").attr("clip-path",a.clipPath).attr("class",i.regions)},f.redrawRegion=function(a){var b=this,c=b.config;b.region.style("visibility",b.hasArcType()?"hidden":"visible"),b.mainRegion=b.main.select("."+i.regions).selectAll("."+i.region).data(c.regions),b.mainRegion.enter().append("g").attr("class",b.classRegion.bind(b)).append("rect").style("fill-opacity",0),b.mainRegion.exit().transition().duration(a).style("opacity",0).remove()},f.addTransitionForRegion=function(a){var b=this,c=b.regionX.bind(b),d=b.regionY.bind(b),e=b.regionWidth.bind(b),f=b.regionHeight.bind(b);a.push(b.mainRegion.selectAll("rect").transition().attr("x",c).attr("y",d).attr("width",e).attr("height",f).style("fill-opacity",function(a){return j(a.opacity)?a.opacity:.1}))},f.regionX=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"start"in a?e(a.start):0:d.axis_rotated?0:"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},f.regionY=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?0:"end"in a?e(a.end):0:d.axis_rotated&&"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},f.regionWidth=function(a){var b,c=this,d=c.config,e=c.regionX(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"end"in a?f(a.end):c.width:d.axis_rotated?c.width:"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.width,e>b?0:b-e},f.regionHeight=function(a){var b,c=this,d=c.config,e=this.regionY(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?c.height:"start"in a?f(a.start):c.height:d.axis_rotated&&"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.height,e>b?0:b-e},f.isRegionOnX=function(a){return!a.axis||"x"===a.axis},f.drag=function(a){var b,c,d,e,f,g,h,j,k=this,l=k.config,m=k.main,n=k.d3;k.hasArcType()||l.data_selection_enabled&&(!l.zoom_enabled||k.zoom.altDomain)&&l.data_selection_multiple&&(b=k.dragStart[0],c=k.dragStart[1],d=a[0],e=a[1],f=Math.min(b,d),g=Math.max(b,d),h=l.data_selection_grouped?k.margin.top:Math.min(c,e),j=l.data_selection_grouped?k.height:Math.max(c,e),m.select("."+i.dragarea).attr("x",f).attr("y",h).attr("width",g-f).attr("height",j-h),m.selectAll("."+i.shapes).selectAll("."+i.shape).filter(function(a){return l.data_selection_isselectable(a)}).each(function(a,b){var c,d,e,l,m,o,p=n.select(this),q=p.classed(i.SELECTED),r=p.classed(i.INCLUDED),s=!1;if(p.classed(i.circle))c=1*p.attr("cx"),d=1*p.attr("cy"),m=k.togglePoint,s=c>f&&g>c&&d>h&&j>d;else{if(!p.classed(i.bar))return;o=v(this),c=o.x,d=o.y,e=o.width,l=o.height,m=k.togglePath,s=!(c>g||f>c+e||d>j||h>d+l)}s^r&&(p.classed(i.INCLUDED,!r),p.classed(i.SELECTED,!q),m.call(k,!q,p,a,b))}))},f.dragstart=function(a){var b=this,c=b.config;b.hasArcType()||c.data_selection_enabled&&(b.dragStart=a,b.main.select("."+i.chart).append("rect").attr("class",i.dragarea).style("opacity",.1),b.dragging=!0,b.config.data_ondragstart.call(b.api))},f.dragend=function(){var a=this,b=a.config;a.hasArcType()||b.data_selection_enabled&&(a.main.select("."+i.dragarea).transition().duration(100).style("opacity",0).remove(),a.main.selectAll("."+i.shape).classed(i.INCLUDED,!1),a.dragging=!1,a.config.data_ondragend.call(a.api))},f.selectPoint=function(a,b,c){var d=this,e=d.config,f=(e.axis_rotated?d.circleY:d.circleX).bind(d),g=(e.axis_rotated?d.circleX:d.circleY).bind(d),h=d.pointSelectR.bind(d);e.data_onselected.call(d.api,b,a.node()),d.main.select("."+i.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+i.selectedCircle+"-"+c).data([b]).enter().append("circle").attr("class",function(){return d.generateClass(i.selectedCircle,c)}).attr("cx",f).attr("cy",g).attr("stroke",function(){return d.color(b)}).attr("r",function(a){return 1.4*d.pointSelectR(a)}).transition().duration(100).attr("r",h)},f.unselectPoint=function(a,b,c){var d=this;d.config.data_onunselected(b,a.node()),d.main.select("."+i.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+i.selectedCircle+"-"+c).transition().duration(100).attr("r",0).remove()},f.togglePoint=function(a,b,c,d){a?this.selectPoint(b,c,d):this.unselectPoint(b,c,d)},f.selectPath=function(a,b){var c=this;c.config.data_onselected.call(c,b,a.node()),a.transition().duration(100).style("fill",function(){return c.d3.rgb(c.color(b)).brighter(.75)})},f.unselectPath=function(a,b){var c=this;c.config.data_onunselected.call(c,b,a.node()),a.transition().duration(100).style("fill",function(){return c.color(b)})},f.togglePath=function(a,b,c,d){a?this.selectPath(b,c,d):this.unselectPath(b,c,d)},f.getToggle=function(a,b){var c,d=this;return"circle"===a.nodeName?c=d.isStepType(b)?function(){}:d.togglePoint:"path"===a.nodeName&&(c=d.togglePath),c},f.toggleShape=function(a,b,c){var d=this,e=d.d3,f=d.config,g=e.select(a),h=g.classed(i.SELECTED),j=d.getToggle(a,b).bind(d);f.data_selection_enabled&&f.data_selection_isselectable(b)&&(f.data_selection_multiple||d.main.selectAll("."+i.shapes+(f.data_selection_grouped?d.getTargetSelectorSuffix(b.id):"")).selectAll("."+i.shape).each(function(a,b){var c=e.select(this);c.classed(i.SELECTED)&&j(!1,c.classed(i.SELECTED,!1),a,b)}),g.classed(i.SELECTED,!h),j(!h,g,b,c))},f.initBrush=function(){var a=this,b=a.d3;a.brush=b.svg.brush().on("brush",function(){a.redrawForBrush()}),a.brush.update=function(){return a.context&&a.context.select("."+i.brush).call(this),this},a.brush.scale=function(b){return a.config.axis_rotated?this.y(b):this.x(b)}},f.initSubchart=function(){var a=this,b=a.config,c=a.context=a.svg.append("g").attr("transform",a.getTranslate("context"));b.subchart_show||c.style("visibility","hidden"),c.append("g").attr("clip-path",a.clipPathForSubchart).attr("class",i.chart),c.select("."+i.chart).append("g").attr("class",i.chartBars),c.select("."+i.chart).append("g").attr("class",i.chartLines),c.append("g").attr("clip-path",a.clipPath).attr("class",i.brush).call(a.brush).selectAll("rect").attr(b.axis_rotated?"width":"height",b.axis_rotated?a.width2:a.height2),a.axes.subx=c.append("g").attr("class",i.axisX).attr("transform",a.getTranslate("subx")).attr("clip-path",b.axis_rotated?"":a.clipPathForXAxis)},f.updateTargetsForSubchart=function(a){var b,c,d,e,f=this,g=f.context,h=f.config,j=f.classChartBar.bind(f),k=f.classBars.bind(f),l=f.classChartLine.bind(f),m=f.classLines.bind(f),n=f.classAreas.bind(f);h.subchart_show&&(e=g.select("."+i.chartBars).selectAll("."+i.chartBar).data(a).attr("class",j),d=e.enter().append("g").style("opacity",0).attr("class",j),d.append("g").attr("class",k),c=g.select("."+i.chartLines).selectAll("."+i.chartLine).data(a).attr("class",l),b=c.enter().append("g").style("opacity",0).attr("class",l),b.append("g").attr("class",m),b.append("g").attr("class",n))},f.redrawSubchart=function(a,b,c,d,e,f,g){var h,j,k,l,m,n,o=this,p=o.d3,q=o.context,r=o.config,s=o.barData.bind(o),t=o.lineData.bind(o),u=o.classBar.bind(o),v=o.classLine.bind(o),w=o.classArea.bind(o),x=o.initialOpacity.bind(o);r.subchart_show&&(p.event&&"zoom"===p.event.type&&o.brush.extent(o.x.orgDomain()).update(),a&&(o.brush.empty()||o.brush.extent(o.x.orgDomain()).update(),l=o.generateDrawArea(e,!0),m=o.generateDrawBar(f,!0),n=o.generateDrawLine(g,!0),k=q.selectAll("."+i.bars).selectAll("."+i.bar).data(s),k.enter().append("path").attr("class",u).style("stroke","none").style("fill",o.color),k.style("opacity",x).transition().duration(c).attr("d",m).style("opacity",1),k.exit().transition().duration(c).style("opacity",0).remove(),h=q.selectAll("."+i.lines).selectAll("."+i.line).data(t),h.enter().append("path").attr("class",v).style("stroke",o.color),h.style("opacity",x).transition().duration(c).attr("d",n).style("opacity",1),h.exit().transition().duration(c).style("opacity",0).remove(),j=q.selectAll("."+i.areas).selectAll("."+i.area).data(t),j.enter().append("path").attr("class",w).style("fill",o.color).style("opacity",function(){return o.orgAreaOpacity=+p.select(this).style("opacity"),0}),j.style("opacity",0).transition().duration(c).attr("d",l).style("fill",o.color).style("opacity",o.orgAreaOpacity),j.exit().transition().duration(d).style("opacity",0).remove()))},f.redrawForBrush=function(){var a=this,b=a.x;a.redraw({withTransition:!1,withY:a.config.zoom_rescale,withSubchart:!1,withUpdateXDomain:!0,withDimension:!1}),a.config.subchart_onbrush.call(a.api,b.orgDomain())},f.transformContext=function(a,b){var c,d=this;b&&b.axisSubX?c=b.axisSubX:(c=d.context.select("."+i.axisX),a&&(c=c.transition())),d.context.attr("transform",d.getTranslate("context")),c.attr("transform",d.getTranslate("subx"))},f.getDefaultExtent=function(){var a=this,b=a.config,c=k(b.axis_x_extent)?b.axis_x_extent(a.getXDomain(a.data.targets)):b.axis_x_extent;return a.isTimeSeries()&&(c=[a.parseDate(c[0]),a.parseDate(c[1])]),c},f.initZoom=function(){var a,b=this,c=b.d3,d=b.config;b.zoom=c.behavior.zoom().on("zoomstart",function(){a=c.event.sourceEvent,b.zoom.altDomain=c.event.sourceEvent.altKey?b.x.orgDomain():null,d.zoom_onzoomstart.call(b.api,c.event.sourceEvent)}).on("zoom",function(){b.redrawForZoom.call(b)}).on("zoomend",function(){var e=c.event.sourceEvent;e&&a.clientX===e.clientX&&a.clientY===e.clientY||(b.redrawEventRect(),b.updateZoom(),d.zoom_onzoomend.call(b.api,b.x.orgDomain()))}),b.zoom.scale=function(a){return d.axis_rotated?this.y(a):this.x(a)},b.zoom.orgScaleExtent=function(){var a=d.zoom_extent?d.zoom_extent:[1,10];return[a[0],Math.max(b.getMaxDataCount()/a[1],a[1])]},b.zoom.updateScaleExtent=function(){var a=q(b.x.orgDomain())/q(b.orgXDomain),c=this.orgScaleExtent();return this.scaleExtent([c[0]*a,c[1]*a]),this}},f.updateZoom=function(){var a=this,b=a.config.zoom_enabled?a.zoom:function(){};a.main.select("."+i.zoomRect).call(b).on("dblclick.zoom",null),a.main.selectAll("."+i.eventRect).call(b).on("dblclick.zoom",null)},f.redrawForZoom=function(){var a=this,b=a.d3,c=a.config,d=a.zoom,e=a.x;if(c.zoom_enabled&&0!==a.filterTargetsToShow(a.data.targets).length){if("mousemove"===b.event.sourceEvent.type&&d.altDomain)return e.domain(d.altDomain),void d.scale(e).updateScaleExtent();a.isCategorized()&&e.orgDomain()[0]===a.orgXDomain[0]&&e.domain([a.orgXDomain[0]-1e-10,e.orgDomain()[1]]),a.redraw({withTransition:!1,withY:c.zoom_rescale,withSubchart:!1,withEventRect:!1,withDimension:!1}),"mousemove"===b.event.sourceEvent.type&&(a.cancelClick=!0),c.zoom_onzoom.call(a.api,e.orgDomain())}},f.generateColor=function(){var a=this,b=a.config,c=a.d3,d=b.data_colors,e=s(b.color_pattern)?b.color_pattern:c.scale.category10().range(),f=b.data_color,g=[];return function(a){var b,c=a.id||a;return d[c]instanceof Function?b=d[c](a):d[c]?b=d[c]:(g.indexOf(c)<0&&g.push(c),b=e[g.indexOf(c)%e.length],d[c]=b),f instanceof Function?f(b,a):b}},f.generateLevelColor=function(){var a=this,b=a.config,c=b.color_pattern,d=b.color_threshold,e="value"===d.unit,f=d.values&&d.values.length?d.values:[],g=d.max||100;return s(b.color_threshold)?function(a){var b,d,h=c[c.length-1];for(b=0;b<f.length;b++)if(d=e?a:100*a/g,d<f[b]){h=c[b];break}return h}:null},f.getYFormat=function(a){var b=this,c=a&&!b.hasType("gauge")?b.defaultArcValueFormat:b.yFormat,d=a&&!b.hasType("gauge")?b.defaultArcValueFormat:b.y2Format;return function(a,e,f){var g="y2"===b.getAxisId(f)?d:c;return g.call(b,a,e)}},f.yFormat=function(a){var b=this,c=b.config,d=c.axis_y_tick_format?c.axis_y_tick_format:b.defaultValueFormat;return d(a)},f.y2Format=function(a){var b=this,c=b.config,d=c.axis_y2_tick_format?c.axis_y2_tick_format:b.defaultValueFormat;return d(a)},f.defaultValueFormat=function(a){return j(a)?+a:""},f.defaultArcValueFormat=function(a,b){return(100*b).toFixed(1)+"%"},f.formatByAxisId=function(a){var b=this,c=b.config.data_labels,d=function(a){return j(a)?+a:""};return"function"==typeof c.format?d=c.format:"object"==typeof c.format&&c.format[a]&&(d=c.format[a]),d},f.hasCaches=function(a){for(var b=0;b<a.length;b++)if(!(a[b]in this.cache))return!1;return!0},f.addCache=function(a,b){this.cache[a]=this.cloneTarget(b)},f.getCaches=function(a){var b,c=[];for(b=0;b<a.length;b++)a[b]in this.cache&&c.push(this.cloneTarget(this.cache[a[b]]));return c};var i=f.CLASS={target:"c3-target",chart:"c3-chart",chartLine:"c3-chart-line",chartLines:"c3-chart-lines",chartBar:"c3-chart-bar",chartBars:"c3-chart-bars",chartText:"c3-chart-text",chartTexts:"c3-chart-texts",chartArc:"c3-chart-arc",chartArcs:"c3-chart-arcs",chartArcsTitle:"c3-chart-arcs-title",chartArcsBackground:"c3-chart-arcs-background",chartArcsGaugeUnit:"c3-chart-arcs-gauge-unit",chartArcsGaugeMax:"c3-chart-arcs-gauge-max",chartArcsGaugeMin:"c3-chart-arcs-gauge-min",selectedCircle:"c3-selected-circle",selectedCircles:"c3-selected-circles",eventRect:"c3-event-rect",eventRects:"c3-event-rects",eventRectsSingle:"c3-event-rects-single",eventRectsMultiple:"c3-event-rects-multiple",zoomRect:"c3-zoom-rect",brush:"c3-brush",focused:"c3-focused",defocused:"c3-defocused",region:"c3-region",regions:"c3-regions",tooltipContainer:"c3-tooltip-container",tooltip:"c3-tooltip",tooltipName:"c3-tooltip-name",shape:"c3-shape",shapes:"c3-shapes",line:"c3-line",lines:"c3-lines",bar:"c3-bar",bars:"c3-bars",circle:"c3-circle",circles:"c3-circles",arc:"c3-arc",arcs:"c3-arcs",area:"c3-area",areas:"c3-areas",empty:"c3-empty",text:"c3-text",texts:"c3-texts",gaugeValue:"c3-gauge-value",grid:"c3-grid",gridLines:"c3-grid-lines",xgrid:"c3-xgrid",xgrids:"c3-xgrids",xgridLine:"c3-xgrid-line",xgridLines:"c3-xgrid-lines",xgridFocus:"c3-xgrid-focus",ygrid:"c3-ygrid",ygrids:"c3-ygrids",ygridLine:"c3-ygrid-line",ygridLines:"c3-ygrid-lines",axis:"c3-axis",axisX:"c3-axis-x",axisXLabel:"c3-axis-x-label",axisY:"c3-axis-y",axisYLabel:"c3-axis-y-label",axisY2:"c3-axis-y2",axisY2Label:"c3-axis-y2-label",legendBackground:"c3-legend-background",legendItem:"c3-legend-item",legendItemEvent:"c3-legend-item-event",legendItemTile:"c3-legend-item-tile",legendItemHidden:"c3-legend-item-hidden",legendItemFocused:"c3-legend-item-focused",dragarea:"c3-dragarea",EXPANDED:"_expanded_",SELECTED:"_selected_",INCLUDED:"_included_"};f.generateClass=function(a,b){return" "+a+" "+a+this.getTargetSelectorSuffix(b)},f.classText=function(a){return this.generateClass(i.text,a.index)},f.classTexts=function(a){return this.generateClass(i.texts,a.id)},f.classShape=function(a){return this.generateClass(i.shape,a.index)},f.classShapes=function(a){return this.generateClass(i.shapes,a.id)},f.classLine=function(a){return this.classShape(a)+this.generateClass(i.line,a.id)},f.classLines=function(a){return this.classShapes(a)+this.generateClass(i.lines,a.id)},f.classCircle=function(a){return this.classShape(a)+this.generateClass(i.circle,a.index)},f.classCircles=function(a){return this.classShapes(a)+this.generateClass(i.circles,a.id)},f.classBar=function(a){return this.classShape(a)+this.generateClass(i.bar,a.index)},f.classBars=function(a){return this.classShapes(a)+this.generateClass(i.bars,a.id)},f.classArc=function(a){return this.classShape(a.data)+this.generateClass(i.arc,a.data.id)},f.classArcs=function(a){return this.classShapes(a.data)+this.generateClass(i.arcs,a.data.id)},f.classArea=function(a){return this.classShape(a)+this.generateClass(i.area,a.id)},f.classAreas=function(a){return this.classShapes(a)+this.generateClass(i.areas,a.id)},f.classRegion=function(a,b){return this.generateClass(i.region,b)+" "+("class"in a?a["class"]:"")},f.classEvent=function(a){return this.generateClass(i.eventRect,a.index)},f.classTarget=function(a){var b=this,c=b.config.data_classes[a],d="";return c&&(d=" "+i.target+"-"+c),b.generateClass(i.target,a)+d},f.classFocus=function(a){return this.classFocused(a)+this.classDefocused(a)},f.classFocused=function(a){return" "+(this.focusedTargetIds.indexOf(a.id)>=0?i.focused:"")},f.classDefocused=function(a){return" "+(this.defocusedTargetIds.indexOf(a.id)>=0?i.defocused:"")},f.classChartText=function(a){return i.chartText+this.classTarget(a.id)},f.classChartLine=function(a){return i.chartLine+this.classTarget(a.id)},f.classChartBar=function(a){return i.chartBar+this.classTarget(a.id)},f.classChartArc=function(a){return i.chartArc+this.classTarget(a.data.id)},f.getTargetSelectorSuffix=function(a){return a||0===a?("-"+a).replace(/[\s?!@#$%^&*()_=+,.<>'":;\[\]\/|~`{}\\]/g,"-"):""},f.selectorTarget=function(a,b){return(b||"")+"."+i.target+this.getTargetSelectorSuffix(a)},f.selectorTargets=function(a,b){var c=this;return a=a||[],a.length?a.map(function(a){return c.selectorTarget(a,b)}):null},f.selectorLegend=function(a){return"."+i.legendItem+this.getTargetSelectorSuffix(a)},f.selectorLegends=function(a){var b=this;return a&&a.length?a.map(function(a){return b.selectorLegend(a)}):null};var j=f.isValue=function(a){return a||0===a},k=f.isFunction=function(a){return"function"==typeof a},l=f.isString=function(a){return"string"==typeof a},m=f.isUndefined=function(a){return"undefined"==typeof a},n=f.isDefined=function(a){return"undefined"!=typeof a},o=f.ceil10=function(a){return 10*Math.ceil(a/10)},p=f.asHalfPixel=function(a){return Math.ceil(a)+.5},q=f.diffDomain=function(a){return a[1]-a[0]},r=f.isEmpty=function(a){return!a||l(a)&&0===a.length||"object"==typeof a&&0===Object.keys(a).length},s=f.notEmpty=function(a){return Object.keys(a).length>0},t=f.getOption=function(a,b,c){return n(a[b])?a[b]:c},u=f.hasValue=function(a,b){var c=!1;return Object.keys(a).forEach(function(d){a[d]===b&&(c=!0)}),c},v=f.getPathBox=function(a){var b=a.getBoundingClientRect(),c=[a.pathSegList.getItem(0),a.pathSegList.getItem(1)],d=c[0].x,e=Math.min(c[0].y,c[1].y);return{x:d,y:e,width:b.width,height:b.height}};e.focus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),this.revert(),this.defocus(),b.classed(i.focused,!0).classed(i.defocused,!1),c.hasArcType()&&c.expandArc(a),c.toggleFocusLegend(a,!0),c.focusedTargetIds=a,c.defocusedTargetIds=c.defocusedTargetIds.filter(function(b){return a.indexOf(b)<0})},e.defocus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),this.revert(),b.classed(i.focused,!1).classed(i.defocused,!0),c.hasArcType()&&c.unexpandArc(a),c.toggleFocusLegend(a,!1),c.focusedTargetIds=c.focusedTargetIds.filter(function(b){return a.indexOf(b)<0}),c.defocusedTargetIds=a},e.revert=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a)),b.classed(i.focused,!1).classed(i.defocused,!1),c.hasArcType()&&c.unexpandArc(a),c.config.legend_show&&c.showLegend(a.filter(c.isLegendToShow.bind(c))),c.focusedTargetIds=[],c.defocusedTargetIds=[]},e.show=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a),b=b||{},d.removeHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",1,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",1)}),b.withLegend&&d.showLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},e.hide=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a),b=b||{},d.addHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",0,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",0)}),b.withLegend&&d.hideLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},e.toggle=function(a){var b=this,c=this.internal;c.mapToTargetIds(a).forEach(function(a){c.isTargetToShow(a)?b.hide(a):b.show(a)})},e.zoom=function(a){var b=this.internal;return a&&(b.isTimeSeries()&&(a=a.map(function(a){return b.parseDate(a)})),b.brush.extent(a),b.redraw({withUpdateXDomain:!0,withY:b.config.zoom_rescale}),b.config.zoom_onzoom.call(this,b.x.orgDomain())),b.brush.extent()},e.zoom.enable=function(a){var b=this.internal;b.config.zoom_enabled=a,b.updateAndRedraw()},e.unzoom=function(){var a=this.internal;a.brush.clear().update(),a.redraw({withUpdateXDomain:!0})},e.load=function(a){var b=this.internal,c=b.config;return a.xs&&b.addXs(a.xs),"classes"in a&&Object.keys(a.classes).forEach(function(b){c.data_classes[b]=a.classes[b]}),"categories"in a&&b.isCategorized()&&(c.axis_x_categories=a.categories),"axes"in a&&Object.keys(a.axes).forEach(function(b){c.data_axes[b]=a.axes[b]}),"cacheIds"in a&&b.hasCaches(a.cacheIds)?void b.load(b.getCaches(a.cacheIds),a.done):void("unload"in a?b.unload(b.mapToTargetIds("boolean"==typeof a.unload&&a.unload?null:a.unload),function(){b.loadFromArgs(a)}):b.loadFromArgs(a))},e.unload=function(a){var b=this.internal;a=a||{},a instanceof Array?a={ids:a}:"string"==typeof a&&(a={ids:[a]}),b.unload(b.mapToTargetIds(a.ids),function(){b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),a.done&&a.done()})},e.flow=function(a){var b,c,d,e,f,g,h,i,k=this.internal,l=[],m=k.getMaxDataCount(),o=0,p=0;if(a.json)c=k.convertJsonToData(a.json,a.keys);else if(a.rows)c=k.convertRowsToData(a.rows);else{if(!a.columns)return;c=k.convertColumnsToData(a.columns)}b=k.convertDataToTargets(c,!0),k.data.targets.forEach(function(a){var c,d,e=!1;for(c=0;c<b.length;c++)if(a.id===b[c].id){for(e=!0,a.values[a.values.length-1]&&(p=a.values[a.values.length-1].index+1),o=b[c].values.length,d=0;o>d;d++)b[c].values[d].index=p+d,k.isTimeSeries()||(b[c].values[d].x=p+d);a.values=a.values.concat(b[c].values),b.splice(c,1);break}e||l.push(a.id)}),k.data.targets.forEach(function(a){var b,c;for(b=0;b<l.length;b++)if(a.id===l[b])for(p=a.values[a.values.length-1].index+1,c=0;o>c;c++)a.values.push({id:a.id,index:p+c,x:k.isTimeSeries()?k.getOtherTargetX(p+c):p+c,value:null})}),k.data.targets.length&&b.forEach(function(a){var b,c=[];for(b=k.data.targets[0].values[0].index;p>b;b++)c.push({id:a.id,index:b,x:k.isTimeSeries()?k.getOtherTargetX(b):b,value:null});a.values.forEach(function(a){a.index+=p,k.isTimeSeries()||(a.x+=p)}),a.values=c.concat(a.values)}),k.data.targets=k.data.targets.concat(b),d=k.getMaxDataCount(),f=k.data.targets[0],g=f.values[0],n(a.to)?(o=0,i=k.isTimeSeries()?k.parseDate(a.to):a.to,f.values.forEach(function(a){a.x<i&&o++})):n(a.length)&&(o=a.length),m?1===m&&k.isTimeSeries()&&(h=(f.values[f.values.length-1].x-g.x)/2,e=[new Date(+g.x-h),new Date(+g.x+h)],k.updateXDomain(null,!0,!0,!1,e)):(h=k.isTimeSeries()?f.values.length>1?f.values[f.values.length-1].x-g.x:g.x-k.getXDomain(k.data.targets)[0]:1,e=[g.x-h,g.x],k.updateXDomain(null,!0,!0,!1,e)),k.updateTargets(k.data.targets),k.redraw({flow:{index:g.index,length:o,duration:j(a.duration)?a.duration:k.config.transition_duration,done:a.done,orgDataCount:m},withLegend:!0,withTransition:m>1,withTrimXDomain:!1,withUpdateXAxis:!0})},f.generateFlow=function(a){var b=this,c=b.config,d=b.d3;return function(){var e,f,g,h=a.targets,j=a.flow,k=a.drawBar,l=a.drawLine,m=a.drawArea,n=a.cx,o=a.cy,p=a.xv,r=a.xForText,s=a.yForText,t=a.duration,u=1,v=j.index,w=j.length,x=b.getValueOnIndex(b.data.targets[0].values,v),y=b.getValueOnIndex(b.data.targets[0].values,v+w),z=b.x.domain(),A=j.duration||t,B=j.done||function(){},C=b.generateWait(),D=b.xgrid||d.selectAll([]),E=b.xgridLines||d.selectAll([]),F=b.mainRegion||d.selectAll([]),G=b.mainText||d.selectAll([]),H=b.mainBar||d.selectAll([]),I=b.mainLine||d.selectAll([]),J=b.mainArea||d.selectAll([]),K=b.mainCircle||d.selectAll([]);b.flowing=!0,b.data.targets.forEach(function(a){a.values.splice(0,w)}),g=b.updateXDomain(h,!0,!0),b.updateXGrid&&b.updateXGrid(!0),j.orgDataCount?e=1===j.orgDataCount||x.x===y.x?b.x(z[0])-b.x(g[0]):b.isTimeSeries()?b.x(z[0])-b.x(g[0]):b.x(x.x)-b.x(y.x):1!==b.data.targets[0].values.length?e=b.x(z[0])-b.x(g[0]):b.isTimeSeries()?(x=b.getValueOnIndex(b.data.targets[0].values,0),y=b.getValueOnIndex(b.data.targets[0].values,b.data.targets[0].values.length-1),e=b.x(x.x)-b.x(y.x)):e=q(g)/2,u=q(z)/q(g),f="translate("+e+",0) scale("+u+",1)",b.hideXGridFocus(),b.hideTooltip(),d.transition().ease("linear").duration(A).each(function(){C.add(b.axes.x.transition().call(b.xAxis)),C.add(H.transition().attr("transform",f)),C.add(I.transition().attr("transform",f)),C.add(J.transition().attr("transform",f)),C.add(K.transition().attr("transform",f)),C.add(G.transition().attr("transform",f)),C.add(F.filter(b.isRegionOnX).transition().attr("transform",f)),C.add(D.transition().attr("transform",f)),C.add(E.transition().attr("transform",f))}).call(C,function(){var a,d=[],e=[],f=[];if(w){for(a=0;w>a;a++)d.push("."+i.shape+"-"+(v+a)),e.push("."+i.text+"-"+(v+a)),f.push("."+i.eventRect+"-"+(v+a));b.svg.selectAll("."+i.shapes).selectAll(d).remove(),b.svg.selectAll("."+i.texts).selectAll(e).remove(),b.svg.selectAll("."+i.eventRects).selectAll(f).remove(),b.svg.select("."+i.xgrid).remove()}D.attr("transform",null).attr(b.xgridAttr),E.attr("transform",null),E.select("line").attr("x1",c.axis_rotated?0:p).attr("x2",c.axis_rotated?b.width:p),E.select("text").attr("x",c.axis_rotated?b.width:0).attr("y",p),H.attr("transform",null).attr("d",k),I.attr("transform",null).attr("d",l),J.attr("transform",null).attr("d",m),K.attr("transform",null).attr("cx",n).attr("cy",o),G.attr("transform",null).attr("x",r).attr("y",s).style("fill-opacity",b.opacityForText.bind(b)),F.attr("transform",null),F.select("rect").filter(b.isRegionOnX).attr("x",b.regionX.bind(b)).attr("width",b.regionWidth.bind(b)),c.interaction_enabled&&b.redrawEventRect(),B(),b.flowing=!1})}},e.selected=function(a){var b=this.internal,c=b.d3;return c.merge(b.main.selectAll("."+i.shapes+b.getTargetSelectorSuffix(a)).selectAll("."+i.shape).filter(function(){return c.select(this).classed(i.SELECTED)}).map(function(a){return a.map(function(a){var b=a.__data__;return b.data?b.data:b})}))},e.select=function(a,b,c){var d=this.internal,e=d.d3,f=d.config;f.data_selection_enabled&&d.main.selectAll("."+i.shapes).selectAll("."+i.shape).each(function(g,h){var j=e.select(this),k=g.data?g.data.id:g.id,l=d.getToggle(this,g).bind(d),m=f.data_selection_grouped||!a||a.indexOf(k)>=0,o=!b||b.indexOf(h)>=0,p=j.classed(i.SELECTED);j.classed(i.line)||j.classed(i.area)||(m&&o?f.data_selection_isselectable(g)&&!p&&l(!0,j.classed(i.SELECTED,!0),g,h):n(c)&&c&&p&&l(!1,j.classed(i.SELECTED,!1),g,h))})},e.unselect=function(a,b){var c=this.internal,d=c.d3,e=c.config;e.data_selection_enabled&&c.main.selectAll("."+i.shapes).selectAll("."+i.shape).each(function(f,g){var h=d.select(this),j=f.data?f.data.id:f.id,k=c.getToggle(this,f).bind(c),l=e.data_selection_grouped||!a||a.indexOf(j)>=0,m=!b||b.indexOf(g)>=0,n=h.classed(i.SELECTED);h.classed(i.line)||h.classed(i.area)||l&&m&&e.data_selection_isselectable(f)&&n&&k(!1,h.classed(i.SELECTED,!1),f,g)})},e.transform=function(a,b){var c=this.internal,d=["pie","donut"].indexOf(a)>=0?{withTransform:!0}:null;c.transformTo(b,a,d)},f.transformTo=function(a,b,c){var d=this,e=!d.hasArcType(),f=c||{withTransitionForAxis:e};f.withTransitionForTransform=!1,d.transiting=!1,d.setTargetType(a,b),d.updateAndRedraw(f)},e.groups=function(a){var b=this.internal,c=b.config;return m(a)?c.data_groups:(c.data_groups=a,b.redraw(),c.data_groups)},e.xgrids=function(a){var b=this.internal,c=b.config;return a?(c.grid_x_lines=a,b.redrawWithoutRescale(),c.grid_x_lines):c.grid_x_lines},e.xgrids.add=function(a){var b=this.internal;return this.xgrids(b.config.grid_x_lines.concat(a?a:[]))},e.xgrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!0)},e.ygrids=function(a){var b=this.internal,c=b.config;
|
5 |
+
return a?(c.grid_y_lines=a,b.redrawWithoutRescale(),c.grid_y_lines):c.grid_y_lines},e.ygrids.add=function(a){var b=this.internal;return this.ygrids(b.config.grid_y_lines.concat(a?a:[]))},e.ygrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!1)},e.regions=function(a){var b=this.internal,c=b.config;return a?(c.regions=a,b.redrawWithoutRescale(),c.regions):c.regions},e.regions.add=function(a){var b=this.internal,c=b.config;return a?(c.regions=c.regions.concat(a),b.redrawWithoutRescale(),c.regions):c.regions},e.regions.remove=function(a){var b,c,d,e=this.internal,f=e.config;return a=a||{},b=e.getOption(a,"duration",f.transition_duration),c=e.getOption(a,"classes",[i.region]),d=e.main.select("."+i.regions).selectAll(c.map(function(a){return"."+a})),(b?d.transition().duration(b):d).style("opacity",0).remove(),f.regions=f.regions.filter(function(a){var b=!1;return a["class"]?(a["class"].split(" ").forEach(function(a){c.indexOf(a)>=0&&(b=!0)}),!b):!0}),f.regions},e.data=function(a){var b=this.internal.data.targets;return"undefined"==typeof a?b:b.filter(function(b){return[].concat(a).indexOf(b.id)>=0})},e.data.shown=function(a){return this.internal.filterTargetsToShow(this.data(a))},e.data.values=function(a){var b,c=null;return a&&(b=this.data(a),c=b[0]?b[0].values.map(function(a){return a.value}):null),c},e.data.names=function(a){return this.internal.clearLegendItemTextBoxCache(),this.internal.updateDataAttributes("names",a)},e.data.colors=function(a){return this.internal.updateDataAttributes("colors",a)},e.data.axes=function(a){return this.internal.updateDataAttributes("axes",a)},e.category=function(a,b){var c=this.internal,d=c.config;return arguments.length>1&&(d.axis_x_categories[a]=b,c.redraw()),d.axis_x_categories[a]},e.categories=function(a){var b=this.internal,c=b.config;return arguments.length?(c.axis_x_categories=a,b.redraw(),c.axis_x_categories):c.axis_x_categories},e.color=function(a){var b=this.internal;return b.color(a)},e.x=function(a){var b=this.internal;return arguments.length&&(b.updateTargetX(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},e.xs=function(a){var b=this.internal;return arguments.length&&(b.updateTargetXs(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},e.axis=function(){},e.axis.labels=function(a){var b=this.internal;arguments.length&&(Object.keys(a).forEach(function(c){b.setAxisLabelText(c,a[c])}),b.updateAxisLabels())},e.axis.max=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(j(a.x)&&(c.axis_x_max=a.x),j(a.y)&&(c.axis_y_max=a.y),j(a.y2)&&(c.axis_y2_max=a.y2)):c.axis_y_max=c.axis_y2_max=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_max,y:c.axis_y_max,y2:c.axis_y2_max}},e.axis.min=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(j(a.x)&&(c.axis_x_min=a.x),j(a.y)&&(c.axis_y_min=a.y),j(a.y2)&&(c.axis_y2_min=a.y2)):c.axis_y_min=c.axis_y2_min=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_min,y:c.axis_y_min,y2:c.axis_y2_min}},e.axis.range=function(a){return arguments.length?(n(a.max)&&this.axis.max(a.max),void(n(a.min)&&this.axis.min(a.min))):{max:this.axis.max(),min:this.axis.min()}},e.legend=function(){},e.legend.show=function(a){var b=this.internal;b.showLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},e.legend.hide=function(a){var b=this.internal;b.hideLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},e.resize=function(a){var b=this.internal,c=b.config;c.size_width=a?a.width:null,c.size_height=a?a.height:null,this.flush()},e.flush=function(){var a=this.internal;a.updateAndRedraw({withLegend:!0,withTransition:!1,withTransitionForTransform:!1})},e.destroy=function(){var b=this.internal;b.data.targets=void 0,b.data.xs={},b.selectChart.classed("c3",!1).html(""),a.clearInterval(b.intervalForObserveInserted),a.onresize=null},e.tooltip=function(){},e.tooltip.show=function(a){var b,c,d=this.internal;a.mouse&&(c=a.mouse),a.data?d.isMultipleX()?(c=[d.x(a.data.x),d.getYScale(a.data.id)(a.data.value)],b=null):b=j(a.data.index)?a.data.index:d.getIndexByX(a.data.x):"undefined"!=typeof a.x?b=d.getIndexByX(a.x):"undefined"!=typeof a.index&&(b=a.index),d.dispatchEvent("mouseover",b,c),d.dispatchEvent("mousemove",b,c)},e.tooltip.hide=function(){this.internal.dispatchEvent("mouseout",0)};var w;"function"==typeof define&&define.amd?define("c3",["d3"],g):"undefined"!=typeof exports&&"undefined"!=typeof module?module.exports=g:a.c3=g}(window);
|
inc/js/d3.v3.min.js
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
!function(){function n(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function t(n){return null===n?0/0:+n}function e(n){return!isNaN(n)}function r(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function u(n){return n.length}function i(n){for(var t=1;n*t%1;)t*=10;return t}function o(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function a(){this._=Object.create(null)}function c(n){return(n+="")===da||n[0]===ma?ma+n:n}function l(n){return(n+="")[0]===ma?n.slice(1):n}function s(n){return c(n)in this._}function f(n){return(n=c(n))in this._&&delete this._[n]}function h(){var n=[];for(var t in this._)n.push(l(t));return n}function g(){var n=0;for(var t in this._)++n;return n}function p(){for(var n in this._)return!1;return!0}function v(){this._=Object.create(null)}function d(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function m(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=ya.length;r>e;++e){var u=ya[e]+t;if(u in n)return u}}function y(){}function M(){}function x(n){function t(){for(var t,r=e,u=-1,i=r.length;++u<i;)(t=r[u].on)&&t.apply(this,arguments);return n}var e=[],r=new a;return t.on=function(t,u){var i,o=r.get(t);return arguments.length<2?o&&o.on:(o&&(o.on=null,e=e.slice(0,i=e.indexOf(o)).concat(e.slice(i+1)),r.remove(t)),u&&e.push(r.set(t,{on:u})),n)},t}function b(){ta.event.preventDefault()}function _(){for(var n,t=ta.event;n=t.sourceEvent;)t=n;return t}function w(n){for(var t=new M,e=0,r=arguments.length;++e<r;)t[arguments[e]]=x(t);return t.of=function(e,r){return function(u){try{var i=u.sourceEvent=ta.event;u.target=n,ta.event=u,t[u.type].apply(e,r)}finally{ta.event=i}}},t}function S(n){return xa(n,ka),n}function k(n){return"function"==typeof n?n:function(){return ba(n,this)}}function E(n){return"function"==typeof n?n:function(){return _a(n,this)}}function A(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function u(){this.setAttribute(n,t)}function i(){this.setAttributeNS(n.space,n.local,t)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=ta.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?a:o:n.local?i:u}function N(n){return n.trim().replace(/\s+/g," ")}function C(n){return new RegExp("(?:^|\\s+)"+ta.requote(n)+"(?:\\s+|$)","g")}function z(n){return(n+"").trim().split(/^|\s+/)}function q(n,t){function e(){for(var e=-1;++e<u;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<u;)n[e](this,r)}n=z(n).map(L);var u=n.length;return"function"==typeof t?r:e}function L(n){var t=C(n);return function(e,r){if(u=e.classList)return r?u.add(n):u.remove(n);var u=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(u)||e.setAttribute("class",N(u+" "+n))):e.setAttribute("class",N(u.replace(t," ")))}}function T(n,t,e){function r(){this.style.removeProperty(n)}function u(){this.style.setProperty(n,t,e)}function i(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?i:u}function R(n,t){function e(){delete this[n]}function r(){this[n]=t}function u(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?u:r}function D(n){return"function"==typeof n?n:(n=ta.ns.qualify(n)).local?function(){return this.ownerDocument.createElementNS(n.space,n.local)}:function(){return this.ownerDocument.createElementNS(this.namespaceURI,n)}}function P(){var n=this.parentNode;n&&n.removeChild(this)}function U(n){return{__data__:n}}function j(n){return function(){return Sa(this,n)}}function F(t){return arguments.length||(t=n),function(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}}function H(n,t){for(var e=0,r=n.length;r>e;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function O(n){return xa(n,Aa),n}function Y(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t<c;);return o}}function I(n,t,e){function r(){var t=this[o];t&&(this.removeEventListener(n,t,t.$),delete this[o])}function u(){var u=c(t,ra(arguments));r.call(this),this.addEventListener(n,this[o]=u,u.$=e),u._=t}function i(){var t,e=new RegExp("^__on([^.]+)"+ta.requote(n)+"$");for(var r in this)if(t=r.match(e)){var u=this[r];this.removeEventListener(t[1],u,u.$),delete this[r]}}var o="__on"+n,a=n.indexOf("."),c=Z;a>0&&(n=n.slice(0,a));var l=Ca.get(n);return l&&(n=l,c=V),a?t?u:r:t?y:i}function Z(n,t){return function(e){var r=ta.event;ta.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ta.event=r}}}function V(n,t){var e=Z(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function X(){var n=".dragsuppress-"+ ++qa,t="click"+n,e=ta.select(oa).on("touchmove"+n,b).on("dragstart"+n,b).on("selectstart"+n,b);if(za){var r=ia.style,u=r[za];r[za]="none"}return function(i){if(e.on(n,null),za&&(r[za]=u),i){var o=function(){e.on(t,null)};e.on(t,function(){b(),o()},!0),setTimeout(o,0)}}}function $(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>La&&(oa.scrollX||oa.scrollY)){e=ta.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();La=!(u.f||u.e),e.remove()}return La?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function B(){return ta.event.changedTouches[0].identifier}function W(){return ta.event.target}function J(){return oa}function G(n){return n>0?1:0>n?-1:0}function K(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function Q(n){return n>1?0:-1>n?Da:Math.acos(n)}function nt(n){return n>1?ja:-1>n?-ja:Math.asin(n)}function tt(n){return((n=Math.exp(n))-1/n)/2}function et(n){return((n=Math.exp(n))+1/n)/2}function rt(n){return((n=Math.exp(2*n))-1)/(n+1)}function ut(n){return(n=Math.sin(n/2))*n}function it(){}function ot(n,t,e){return this instanceof ot?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ot?new ot(n.h,n.s,n.l):xt(""+n,bt,ot):new ot(n,t,e)}function at(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new dt(u(n+120),u(n),u(n-120))}function ct(n,t,e){return this instanceof ct?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof ct?new ct(n.h,n.c,n.l):n instanceof st?ht(n.l,n.a,n.b):ht((n=_t((n=ta.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new ct(n,t,e)}function lt(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new st(e,Math.cos(n*=Fa)*t,Math.sin(n)*t)}function st(n,t,e){return this instanceof st?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof st?new st(n.l,n.a,n.b):n instanceof ct?lt(n.h,n.c,n.l):_t((n=dt(n)).r,n.g,n.b):new st(n,t,e)}function ft(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=gt(u)*Ja,r=gt(r)*Ga,i=gt(i)*Ka,new dt(vt(3.2404542*u-1.5371385*r-.4985314*i),vt(-.969266*u+1.8760108*r+.041556*i),vt(.0556434*u-.2040259*r+1.0572252*i))}function ht(n,t,e){return n>0?new ct(Math.atan2(e,t)*Ha,Math.sqrt(t*t+e*e),n):new ct(0/0,0/0,n)}function gt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function pt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function vt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function dt(n,t,e){return this instanceof dt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof dt?new dt(n.r,n.g,n.b):xt(""+n,dt,at):new dt(n,t,e)}function mt(n){return new dt(n>>16,255&n>>8,255&n)}function yt(n){return mt(n)+""}function Mt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function xt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(St(u[0]),St(u[1]),St(u[2]))}return(i=tc.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function bt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new ot(r,u,c)}function _t(n,t,e){n=wt(n),t=wt(t),e=wt(e);var r=pt((.4124564*n+.3575761*t+.1804375*e)/Ja),u=pt((.2126729*n+.7151522*t+.072175*e)/Ga),i=pt((.0193339*n+.119192*t+.9503041*e)/Ka);return st(116*u-16,500*(r-u),200*(u-i))}function wt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function St(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function kt(n){return"function"==typeof n?n:function(){return n}}function Et(n){return n}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=c.status;if(!t&&zt(c)||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=ta.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,l=null;return!oa.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=ta.event;ta.event=n;try{o.progress.call(i,c)}finally{ta.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(l=n,i):l},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ra(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var s in a)c.setRequestHeader(s,a[s]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},ta.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qt(){var n=Lt(),t=Tt()-n;t>24?(isFinite(t)&&(clearTimeout(ic),ic=setTimeout(qt,t)),uc=0):(uc=1,ac(qt))}function Lt(){var n=Date.now();for(oc=ec;oc;)n>=oc.t&&(oc.f=oc.c(n-oc.t)),oc=oc.n;return n}function Tt(){for(var n,t=ec,e=1/0;t;)t.f?t=n?n.n=t.n:ec=t.n:(t.t<e&&(e=t.t),t=(n=t).n);return rc=n,e}function Rt(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Dt(n,t){var e=Math.pow(10,3*va(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Pt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],c=0;u>0&&a>0&&(c+a+1>t&&(a=Math.max(1,t-c)),i.push(n.substring(u-=a,u+a)),!((c+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:Et;return function(n){var e=lc.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",c=e[4]||"",l=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(l||"0"===r&&"="===o)&&(l=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=sc.get(g)||Ut;var M=l&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var c=ta.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!l&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function jt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ft(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new hc(e-1)),1),e}function i(n,e){return t(n=new hc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{hc=jt;var r=new jt;return r._=n,o(r,t,e)}finally{hc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Ht(n);return c.floor=c,c.round=Ht(r),c.ceil=Ht(u),c.offset=Ht(i),c.range=a,n}function Ht(n){return function(t,e){try{hc=jt;var r=new jt;return r._=t,n(r,e)._}finally{hc=Date}}}function Ot(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++a<r;)37===n.charCodeAt(a)&&(o.push(n.slice(c,a)),null!=(u=pc[e=n.charAt(++a)])&&(e=n.charAt(++a)),(i=N[e])&&(e=i(t,null==u?"e"===e?" ":"0":u)),o.push(e),c=a+1);return o.push(n.slice(c,a)),o.join("")}var r=n.length;return t.parse=function(t){var r={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},u=e(r,n,t,0);if(u!=t.length)return null;"p"in r&&(r.H=r.H%12+12*r.p);var i=null!=r.Z&&hc!==jt,o=new(i?jt:hc);return"j"in r?o.setFullYear(r.y,0,r.j):"w"in r&&("W"in r||"U"in r)?(o.setFullYear(r.y,0,1),o.setFullYear(r.y,0,"W"in r?(r.w+6)%7+7*r.W-(o.getDay()+5)%7:r.w+7*r.U-(o.getDay()+6)%7)):o.setFullYear(r.y,r.m,r.d),o.setHours(r.H+(0|r.Z/100),r.M+r.Z%100,r.S,r.L),i?o._:o},t.toString=function(){return n},t}function e(n,t,e,r){for(var u,i,o,a=0,c=t.length,l=e.length;c>a;){if(r>=l)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in pc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function c(n,t,r){return e(n,N.x.toString(),t,r)}function l(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{hc=jt;var t=new hc;return t._=n,r(t)}finally{hc=Date}}var r=t(n);return e.parse=function(n){try{hc=jt;var t=r.parse(n);return t&&t._}finally{hc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ae;var M=ta.map(),x=It(v),b=Zt(v),_=It(d),w=Zt(d),S=It(m),k=Zt(m),E=It(y),A=Zt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Yt(n.getDate(),t,2)},e:function(n,t){return Yt(n.getDate(),t,2)},H:function(n,t){return Yt(n.getHours(),t,2)},I:function(n,t){return Yt(n.getHours()%12||12,t,2)},j:function(n,t){return Yt(1+fc.dayOfYear(n),t,3)},L:function(n,t){return Yt(n.getMilliseconds(),t,3)},m:function(n,t){return Yt(n.getMonth()+1,t,2)},M:function(n,t){return Yt(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Yt(n.getSeconds(),t,2)},U:function(n,t){return Yt(fc.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Yt(fc.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Yt(n.getFullYear()%100,t,2)},Y:function(n,t){return Yt(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:Qt,e:Qt,H:te,I:te,j:ne,L:ue,m:Kt,M:ee,p:s,S:re,U:Xt,w:Vt,W:$t,x:c,X:l,y:Wt,Y:Bt,Z:Jt,"%":oe};return t}function Yt(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function It(n){return new RegExp("^(?:"+n.map(ta.requote).join("|")+")","i")}function Zt(n){for(var t=new a,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function Vt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Xt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e));return r?(n.U=+r[0],e+r[0].length):-1}function $t(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e));return r?(n.W=+r[0],e+r[0].length):-1}function Bt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Wt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.y=Gt(+r[0]),e+r[0].length):-1}function Jt(n,t,e){return/^[+-]\d{4}$/.test(t=t.slice(e,e+5))?(n.Z=-t,e+5):-1}function Gt(n){return n+(n>68?1900:2e3)}function Kt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qt(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ne(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function te(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ee(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function re(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ue(n,t,e){vc.lastIndex=0;var r=vc.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=0|va(t)/60,u=va(t)%60;return e+Yt(r,"0",2)+Yt(u,"0",2)}function oe(n,t,e){dc.lastIndex=0;var r=dc.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ae(n){for(var t=n.length,e=-1;++e<t;)n[e][0]=this(n[e][0]);return function(t){for(var e=0,r=n[e];!r[1](t);)r=n[++e];return r[0](t)}}function ce(){}function le(n,t,e){var r=e.s=n+t,u=r-n,i=r-u;e.t=n-i+(t-u)}function se(n,t){n&&xc.hasOwnProperty(n.type)&&xc[n.type](n,t)}function fe(n,t,e){var r,u=-1,i=n.length-e;for(t.lineStart();++u<i;)r=n[u],t.point(r[0],r[1],r[2]);t.lineEnd()}function he(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)fe(n[e],t,1);t.polygonEnd()}function ge(){function n(n,t){n*=Fa,t=t*Fa/2+Da/4;var e=n-r,o=e>=0?1:-1,a=o*e,c=Math.cos(t),l=Math.sin(t),s=i*l,f=u*c+s*Math.cos(a),h=s*o*Math.sin(a);_c.add(Math.atan2(h,f)),r=n,u=c,i=l}var t,e,r,u,i;wc.point=function(o,a){wc.point=n,r=(t=o)*Fa,u=Math.cos(a=(e=a)*Fa/2+Da/4),i=Math.sin(a)},wc.lineEnd=function(){n(t,e)}}function pe(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function ve(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function de(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function me(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ye(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Me(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function xe(n){return[Math.atan2(n[1],n[0]),nt(n[2])]}function be(n,t){return va(n[0]-t[0])<Ta&&va(n[1]-t[1])<Ta}function _e(n,t){n*=Fa;var e=Math.cos(t*=Fa);we(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function we(n,t,e){++Sc,Ec+=(n-Ec)/Sc,Ac+=(t-Ac)/Sc,Nc+=(e-Nc)/Sc}function Se(){function n(n,u){n*=Fa;var i=Math.cos(u*=Fa),o=i*Math.cos(n),a=i*Math.sin(n),c=Math.sin(u),l=Math.atan2(Math.sqrt((l=e*c-r*a)*l+(l=r*o-t*c)*l+(l=t*a-e*o)*l),t*o+e*a+r*c);kc+=l,Cc+=l*(t+(t=o)),zc+=l*(e+(e=a)),qc+=l*(r+(r=c)),we(t,e,r)}var t,e,r;Dc.point=function(u,i){u*=Fa;var o=Math.cos(i*=Fa);t=o*Math.cos(u),e=o*Math.sin(u),r=Math.sin(i),Dc.point=n,we(t,e,r)}}function ke(){Dc.point=_e}function Ee(){function n(n,t){n*=Fa;var e=Math.cos(t*=Fa),o=e*Math.cos(n),a=e*Math.sin(n),c=Math.sin(t),l=u*c-i*a,s=i*o-r*c,f=r*a-u*o,h=Math.sqrt(l*l+s*s+f*f),g=r*o+u*a+i*c,p=h&&-Q(g)/h,v=Math.atan2(h,g);Lc+=p*l,Tc+=p*s,Rc+=p*f,kc+=v,Cc+=v*(r+(r=o)),zc+=v*(u+(u=a)),qc+=v*(i+(i=c)),we(r,u,i)}var t,e,r,u,i;Dc.point=function(o,a){t=o,e=a,Dc.point=n,o*=Fa;var c=Math.cos(a*=Fa);r=c*Math.cos(o),u=c*Math.sin(o),i=Math.sin(a),we(r,u,i)},Dc.lineEnd=function(){n(t,e),Dc.lineEnd=ke,Dc.point=_e}}function Ae(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function Ne(){return!0}function Ce(n,t,e,r,u){var i=[],o=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(be(e,r)){u.lineStart();for(var a=0;t>a;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new qe(e,n,null,!0),l=new qe(e,null,c,!1);c.o=l,i.push(c),o.push(l),c=new qe(r,n,null,!1),l=new qe(r,null,c,!0),c.o=l,i.push(c),o.push(l)}}),o.sort(t),ze(i),ze(o),i.length){for(var a=0,c=e,l=o.length;l>a;++a)o[a].e=c=!c;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,l=s.length;l>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ze(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r<t;)u.n=e=n[r],e.p=u,u=e;u.n=e=n[0],e.p=u}}function qe(n,t,e,r){this.x=n,this.z=t,this.o=e,this.e=r,this.v=!1,this.n=this.p=null}function Le(n,t,e,r){return function(u,i){function o(t,e){var r=u(t,e);n(t=r[0],e=r[1])&&i.point(t,e)}function a(n,t){var e=u(n,t);d.point(e[0],e[1])}function c(){y.point=a,d.lineStart()}function l(){y.point=o,d.lineEnd()}function s(n,t){v.push([n,t]);var e=u(n,t);x.point(e[0],e[1])}function f(){x.lineStart(),v=[]}function h(){s(v[0][0],v[0][1]),x.lineEnd();var n,t=x.clean(),e=M.buffer(),r=e.length;if(v.pop(),p.push(v),v=null,r)if(1&t){n=e[0];var u,r=n.length-1,o=-1;if(r>0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o<r;)i.point((u=n[o])[0],u[1]);i.lineEnd()}}else r>1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Te))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:l,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=l,g=ta.merge(g);var n=Fe(m,p);g.length?(b||(i.polygonStart(),b=!0),Ce(g,De,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Re(),x=t(M),b=!1;return y}}function Te(n){return n.length>1}function Re(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:y,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function De(n,t){return((n=n.x)[0]<0?n[1]-ja-Ta:ja-n[1])-((t=t.x)[0]<0?t[1]-ja-Ta:ja-t[1])}function Pe(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?Da:-Da,c=va(i-e);va(c-Da)<Ta?(n.point(e,r=(r+o)/2>0?ja:-ja),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=Da&&(va(e-u)<Ta&&(e-=u*Ta),va(i-a)<Ta&&(i-=a*Ta),r=Ue(e,r,i,o),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=i,r=o),u=a},lineEnd:function(){n.lineEnd(),e=r=0/0},clean:function(){return 2-t}}}function Ue(n,t,e,r){var u,i,o=Math.sin(n-e);return va(o)>Ta?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function je(n,t,e,r){var u;if(null==n)u=e*ja,r.point(-Da,u),r.point(0,u),r.point(Da,u),r.point(Da,0),r.point(Da,-u),r.point(0,-u),r.point(-Da,-u),r.point(-Da,0),r.point(-Da,u);else if(va(n[0]-t[0])>Ta){var i=n[0]<t[0]?Da:-Da;u=e*i/2,r.point(-i,u),r.point(0,u),r.point(i,u)}else r.point(t[0],t[1])}function Fe(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;_c.reset();for(var a=0,c=t.length;c>a;++a){var l=t[a],s=l.length;if(s)for(var f=l[0],h=f[0],g=f[1]/2+Da/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=l[d];var m=n[0],y=n[1]/2+Da/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>Da,k=p*M;if(_c.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Pa:b,S^h>=e^m>=e){var E=de(pe(f),pe(n));Me(E);var A=de(u,E);Me(A);var N=(S^b>=0?-1:1)*nt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Ta>i||Ta>i&&0>_c)^1&o}function He(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,l,s;return{lineStart:function(){l=c=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?Da:-Da),h):0;if(!e&&(l=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(be(e,g)||be(p,g))&&(p[0]+=Ta,p[1]+=Ta,v=t(p[0],p[1]))),v!==c)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&be(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return s|(l&&c)<<1}}}function r(n,t,e){var r=pe(n),u=pe(t),o=[1,0,0],a=de(r,u),c=ve(a,a),l=a[0],s=c-l*l;if(!s)return!e&&n;var f=i*c/s,h=-i*l/s,g=de(o,a),p=ye(o,f),v=ye(a,h);me(p,v);var d=g,m=ve(p,d),y=ve(d,d),M=m*m-y*(ve(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=ye(d,(-m-x)/y);if(me(b,p),b=xe(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=va(A-Da)<Ta,C=N||Ta>A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(va(b[0]-w)<Ta?k:E):k<=b[1]&&b[1]<=E:A>Da^(w<=b[0]&&b[0]<=S)){var z=ye(d,(-m+x)/y);return me(z,p),[b,xe(z)]}}}function u(t,e){var r=o?n:Da-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=va(i)>Ta,c=gr(n,6*Fa);return Le(t,e,c,o?[0,-n]:[-Da,n-Da])}function Oe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,l=o.y,s=a.x,f=a.y,h=0,g=1,p=s-c,v=f-l;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-l,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-l,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:l+h*v}),1>g&&(u.b={x:c+g*p,y:l+g*v}),u}}}}}}function Ye(n,t,e,r){function u(r,u){return va(r[0]-n)<Ta?u>0?0:3:va(r[0]-e)<Ta?u>0?2:1:va(r[1]-t)<Ta?u>0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&K(l,i,n)>0&&++t:i[1]<=r&&K(l,i,n)<0&&--t,l=i;return 0!==t}function l(i,a,c,l){var s=0,f=0;if(null==i||(s=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Uc,Math.min(Uc,n)),t=Math.max(-Uc,Math.min(Uc,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=Re(),N=Oe(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=ta.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Ce(v,i,t,l,a),a.polygonEnd()),v=d=m=null}};return C}}function Ie(n){var t=0,e=Da/3,r=ir(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*Da/180,e=n[1]*Da/180):[180*(t/Da),180*(e/Da)]},u}function Ze(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,nt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Ve(){function n(n,t){Fc+=u*n-r*t,r=n,u=t}var t,e,r,u;Zc.point=function(i,o){Zc.point=n,t=r=i,e=u=o},Zc.lineEnd=function(){n(t,e)}}function Xe(n,t){Hc>n&&(Hc=n),n>Yc&&(Yc=n),Oc>t&&(Oc=t),t>Ic&&(Ic=t)}function $e(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Be(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Be(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Be(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function We(n,t){Ec+=n,Ac+=t,++Nc}function Je(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);Cc+=o*(t+n)/2,zc+=o*(e+r)/2,qc+=o,We(t=n,e=r)}var t,e;Xc.point=function(r,u){Xc.point=n,We(t=r,e=u)}}function Ge(){Xc.point=We}function Ke(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);Cc+=o*(r+n)/2,zc+=o*(u+t)/2,qc+=o,o=u*n-r*t,Lc+=o*(r+n),Tc+=o*(u+t),Rc+=3*o,We(r=n,u=t)}var t,e,r,u;Xc.point=function(i,o){Xc.point=n,We(t=r=i,e=u=o)},Xc.lineEnd=function(){n(t,e)}}function Qe(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Pa)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:y};return a}function nr(n){function t(n){return(a?r:e)(n)}function e(t){return rr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=pe([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=l,S.lineEnd=s}function l(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,l,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=c+p,w=l+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=va(va(w)-1)<Ta||va(r-h)<Ta?(r+h)/2:Math.atan2(_,b),A=n(E,k),N=A[0],C=A[1],z=N-t,q=C-e,L=M*z-y*q;
|
2 |
+
(L*L/x>i||va((y*z+M*q)/x-.5)>.3||o>a*g+c*p+l*v)&&(u(t,e,r,a,c,l,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Fa),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function tr(n){var t=nr(function(t,e){return n([t*Ha,e*Ha])});return function(n){return or(t(n))}}function er(n){this.stream=n}function rr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ur(n){return ir(function(){return n})()}function ir(n){function t(n){return n=a(n[0]*Fa,n[1]*Fa),[n[0]*h+c,l-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*Ha,n[1]*Ha]}function r(){a=Ae(o=lr(m,y,M),i);var n=i(v,d);return c=g-n[0]*h,l=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,c,l,s,f=nr(function(n,t){return n=i(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,M=0,x=Pc,b=Et,_=null,w=null;return t.stream=function(n){return s&&(s.valid=!1),s=or(x(o,f(b(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(x=null==n?(_=n,Pc):He((_=+n)*Fa),u()):_},t.clipExtent=function(n){return arguments.length?(w=n,b=n?Ye(n[0][0],n[0][1],n[1][0],n[1][1]):Et,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Fa,d=n[1]%360*Fa,r()):[v*Ha,d*Ha]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Fa,y=n[1]%360*Fa,M=n.length>2?n[2]%360*Fa:0,r()):[m*Ha,y*Ha,M*Ha]},ta.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function or(n){return rr(n,function(t,e){n.point(t*Fa,e*Fa)})}function ar(n,t){return[n,t]}function cr(n,t){return[n>Da?n-Pa:-Da>n?n+Pa:n,t]}function lr(n,t,e){return n?t||e?Ae(fr(n),hr(t,e)):fr(n):t||e?hr(t,e):cr}function sr(n){return function(t,e){return t+=n,[t>Da?t-Pa:-Da>t?t+Pa:t,e]}}function fr(n){var t=sr(n);return t.invert=sr(-n),t}function hr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+a*u;return[Math.atan2(c*i-s*o,a*r-l*u),nt(s*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*i-c*o;return[Math.atan2(c*i+l*o,a*r+s*u),nt(s*r-a*u)]},e}function gr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=pr(e,u),i=pr(e,i),(o>0?i>u:u>i)&&(u+=o*Pa)):(u=n+o*Pa,i=n-.5*c);for(var l,s=u;o>0?s>i:i>s;s-=c)a.point((l=xe([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],l[1])}}function pr(n,t){var e=pe(t);e[0]-=n,Me(e);var r=Q(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ta)%(2*Math.PI)}function vr(n,t,e){var r=ta.range(n,t-Ta,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function dr(n,t,e){var r=ta.range(n,t-Ta,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function mr(n){return n.source}function yr(n){return n.target}function Mr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),l=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(ut(r-t)+u*o*ut(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,u=e*l+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ha,Math.atan2(o,Math.sqrt(r*r+u*u))*Ha]}:function(){return[n*Ha,t*Ha]};return p.distance=h,p}function xr(){function n(n,u){var i=Math.sin(u*=Fa),o=Math.cos(u),a=va((n*=Fa)-t),c=Math.cos(a);$c+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Bc.point=function(u,i){t=u*Fa,e=Math.sin(i*=Fa),r=Math.cos(i),Bc.point=n},Bc.lineEnd=function(){Bc.point=Bc.lineEnd=y}}function br(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function _r(n,t){function e(n,t){o>0?-ja+Ta>t&&(t=-ja+Ta):t>ja-Ta&&(t=ja-Ta);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(Da/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=G(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-ja]},e):Sr}function wr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return va(u)<Ta?ar:(e.invert=function(n,t){var e=i-t;return[Math.atan2(n,e)/u,i-G(u)*Math.sqrt(n*n+e*e)]},e)}function Sr(n,t){return[n,Math.log(Math.tan(Da/4+t/2))]}function kr(n){var t,e=ur(n),r=e.scale,u=e.translate,i=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=u.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var o=i.apply(e,arguments);if(o===e){if(t=null==n){var a=Da*r(),c=u();i([[c[0]-a,c[1]-a],[c[0]+a,c[1]+a]])}}else t&&(o=null);return o},e.clipExtent(null)}function Er(n,t){return[Math.log(Math.tan(Da/4+t/2)),-n]}function Ar(n){return n[0]}function Nr(n){return n[1]}function Cr(n){for(var t=n.length,e=[0,1],r=2,u=2;t>u;u++){for(;r>1&&K(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function zr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Lr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(a*(c-l)-f*(u-i))/(f*o-a*s);return[u+h*o,c+h*s]}function Tr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Rr(){tu(this),this.edge=this.site=this.circle=null}function Dr(n){var t=ol.pop()||new Rr;return t.site=n,t}function Pr(n){Xr(n),rl.remove(n),ol.push(n),tu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Pr(n);for(var c=i;c.circle&&va(e-c.circle.x)<Ta&&va(r-c.circle.cy)<Ta;)i=c.P,a.unshift(c),Pr(c),c=i;a.unshift(c),Xr(c);for(var l=o;l.circle&&va(e-l.circle.x)<Ta&&va(r-l.circle.cy)<Ta;)o=l.N,a.push(l),Pr(l),l=o;a.push(l),Xr(l);var s,f=a.length;for(s=1;f>s;++s)l=a[s],c=a[s-1],Kr(l.edge,c.site,l.site,u);c=a[0],l=a[f-1],l.edge=Jr(c.site,l.site,null,u),Vr(c),Vr(l)}function jr(n){for(var t,e,r,u,i=n.x,o=n.y,a=rl._;a;)if(r=Fr(a,o)-i,r>Ta)a=a.L;else{if(u=i-Hr(a,o),!(u>Ta)){r>-Ta?(t=a.P,e=a):u>-Ta?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Dr(n);if(rl.insert(t,c),t||e){if(t===e)return Xr(t),e=Dr(t.site),rl.insert(c,e),c.edge=e.edge=Jr(t.site,c.site),Vr(t),Vr(e),void 0;if(!e)return c.edge=Jr(t.site,c.site),void 0;Xr(t),Xr(e);var l=t.site,s=l.x,f=l.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Kr(e.edge,l,p,x),c.edge=Jr(l,n,null,x),e.edge=Jr(n,p,null,x),Vr(t),Vr(e)}}function Fr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,l=c-t;if(!l)return a;var s=a-r,f=1/i-1/l,h=s/l;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*l)-c+l/2+u-i/2)))/f+r:(r+a)/2}function Hr(n,t){var e=n.N;if(e)return Fr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Or(n){this.site=n,this.edges=[]}function Yr(n){for(var t,e,r,u,i,o,a,c,l,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=el,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)s=a[o].end(),r=s.x,u=s.y,l=a[++o%c].start(),t=l.x,e=l.y,(va(r-t)>Ta||va(u-e)>Ta)&&(a.splice(o,0,new Qr(Gr(i.site,s,va(r-f)<Ta&&p-u>Ta?{x:f,y:va(t-f)<Ta?e:p}:va(u-p)<Ta&&h-r>Ta?{x:va(e-p)<Ta?t:h,y:p}:va(r-h)<Ta&&u-g>Ta?{x:h,y:va(t-h)<Ta?e:g}:va(u-g)<Ta&&r-f>Ta?{x:va(e-g)<Ta?t:f,y:g}:null),i.site,null)),++c)}function Ir(n,t){return t.angle-n.angle}function Zr(){tu(this),this.x=this.y=this.arc=this.site=this.cy=null}function Vr(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,u=n.site,i=e.site;if(r!==i){var o=u.x,a=u.y,c=r.x-o,l=r.y-a,s=i.x-o,f=i.y-a,h=2*(c*f-l*s);if(!(h>=-Ra)){var g=c*c+l*l,p=s*s+f*f,v=(f*g-l*p)/h,d=(c*p-s*g)/h,f=d+a,m=al.pop()||new Zr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=il._;M;)if(m.y<M.y||m.y===M.y&&m.x<=M.x){if(!M.L){y=M.P;break}M=M.L}else{if(!M.R){y=M;break}M=M.R}il.insert(y,m),y||(ul=m)}}}}function Xr(n){var t=n.circle;t&&(t.P||(ul=t.N),il.remove(t),al.push(t),tu(t),n.circle=null)}function $r(n){for(var t,e=tl,r=Oe(n[0][0],n[0][1],n[1][0],n[1][1]),u=e.length;u--;)t=e[u],(!Br(t,n)||!r(t)||va(t.a.x-t.b.x)<Ta&&va(t.a.y-t.b.y)<Ta)&&(t.a=t.b=null,e.splice(u,1))}function Br(n,t){var e=n.b;if(e)return!0;var r,u,i=n.a,o=t[0][0],a=t[1][0],c=t[0][1],l=t[1][1],s=n.l,f=n.r,h=s.x,g=s.y,p=f.x,v=f.y,d=(h+p)/2,m=(g+v)/2;if(v===g){if(o>d||d>=a)return;if(h>p){if(i){if(i.y>=l)return}else i={x:d,y:c};e={x:d,y:l}}else{if(i){if(i.y<c)return}else i={x:d,y:l};e={x:d,y:c}}}else if(r=(h-p)/(v-g),u=m-r*d,-1>r||r>1)if(h>p){if(i){if(i.y>=l)return}else i={x:(c-u)/r,y:c};e={x:(l-u)/r,y:l}}else{if(i){if(i.y<c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else if(v>g){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.x<o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}return n.a=i,n.b=e,!0}function Wr(n,t){this.l=n,this.r=t,this.a=this.b=null}function Jr(n,t,e,r){var u=new Wr(n,t);return tl.push(u),e&&Kr(u,n,t,e),r&&Kr(u,t,n,r),el[n.i].edges.push(new Qr(u,n,t)),el[t.i].edges.push(new Qr(u,t,n)),u}function Gr(n,t,e){var r=new Wr(n,null);return r.a=t,r.b=e,tl.push(r),r}function Kr(n,t,e,r){n.a||n.b?n.l===e?n.b=r:n.a=r:(n.a=r,n.l=t,n.r=e)}function Qr(n,t,e){var r=n.a,u=n.b;this.edge=n,this.site=t,this.angle=e?Math.atan2(e.y-t.y,e.x-t.x):n.l===t?Math.atan2(u.x-r.x,r.y-u.y):Math.atan2(r.x-u.x,u.y-r.y)}function nu(){this._=null}function tu(n){n.U=n.C=n.L=n.R=n.P=n.N=null}function eu(n,t){var e=t,r=t.R,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.R=r.L,e.R&&(e.R.U=e),r.L=e}function ru(n,t){var e=t,r=t.L,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.L=r.R,e.L&&(e.L.U=e),r.R=e}function uu(n){for(;n.L;)n=n.L;return n}function iu(n,t){var e,r,u,i=n.sort(ou).pop();for(tl=[],el=new Array(n.length),rl=new nu,il=new nu;;)if(u=ul,i&&(!u||i.y<u.y||i.y===u.y&&i.x<u.x))(i.x!==e||i.y!==r)&&(el[i.i]=new Or(i),jr(i),e=i.x,r=i.y),i=n.pop();else{if(!u)break;Ur(u.arc)}t&&($r(t),Yr(t));var o={cells:el,edges:tl};return rl=il=tl=el=null,o}function ou(n,t){return t.y-n.y||t.x-n.x}function au(n,t,e){return(n.x-e.x)*(t.y-n.y)-(n.x-t.x)*(e.y-n.y)}function cu(n){return n.x}function lu(n){return n.y}function su(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function fu(n,t,e,r,u,i){if(!n(t,e,r,u,i)){var o=.5*(e+u),a=.5*(r+i),c=t.nodes;c[0]&&fu(n,c[0],e,r,o,a),c[1]&&fu(n,c[1],o,r,u,a),c[2]&&fu(n,c[2],e,a,o,i),c[3]&&fu(n,c[3],o,a,u,i)}}function hu(n,t,e,r,u,i,o){var a,c=1/0;return function l(n,s,f,h,g){if(!(s>i||f>o||r>h||u>g)){if(p=n.point){var p,v=t-p[0],d=e-p[1],m=v*v+d*d;if(c>m){var y=Math.sqrt(c=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:l(n,s,f,x,b);break;case 1:l(n,x,f,h,b);break;case 2:l(n,s,b,x,g);break;case 3:l(n,x,b,h,g)}}}(n,r,u,i,o),a}function gu(n,t){n=ta.rgb(n),t=ta.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+Mt(Math.round(e+i*n))+Mt(Math.round(r+o*n))+Mt(Math.round(u+a*n))}}function pu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=mu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function vu(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function du(n,t){var e,r,u,i=ll.lastIndex=sl.lastIndex=0,o=-1,a=[],c=[];for(n+="",t+="";(e=ll.exec(n))&&(r=sl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:vu(e,r)})),i=sl.lastIndex;return i<t.length&&(u=t.slice(i),a[o]?a[o]+=u:a[++o]=u),a.length<2?c[0]?(t=c[0].x,function(n){return t(n)+""}):function(){return t}:(t=c.length,function(n){for(var e,r=0;t>r;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function mu(n,t){for(var e,r=ta.interpolators.length;--r>=0&&!(e=ta.interpolators[r](n,t)););return e}function yu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(mu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Mu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function xu(n){return function(t){return 1-n(1-t)}}function bu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function _u(n){return n*n}function wu(n){return n*n*n}function Su(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function ku(n){return function(t){return Math.pow(t,n)}}function Eu(n){return 1-Math.cos(n*ja)}function Au(n){return Math.pow(2,10*(n-1))}function Nu(n){return 1-Math.sqrt(1-n*n)}function Cu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Pa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Pa/t)}}function zu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Lu(n,t){n=ta.hcl(n),t=ta.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return lt(e+i*n,r+o*n,u+a*n)+""}}function Tu(n,t){n=ta.hsl(n),t=ta.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return at(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=ta.lab(n),t=ta.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ft(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Pu(n){var t=[n.a,n.b],e=[n.c,n.d],r=ju(t),u=Uu(t,e),i=ju(Fu(e,t,-u))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,u*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*Ha,this.translate=[n.e,n.f],this.scale=[r,i],this.skew=i?Math.atan2(u,i)*Ha:0}function Uu(n,t){return n[0]*t[0]+n[1]*t[1]}function ju(n){var t=Math.sqrt(Uu(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Fu(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function Hu(n,t){var e,r=[],u=[],i=ta.transform(n),o=ta.transform(t),a=i.translate,c=o.translate,l=i.rotate,s=o.rotate,f=i.skew,h=o.skew,g=i.scale,p=o.scale;return a[0]!=c[0]||a[1]!=c[1]?(r.push("translate(",null,",",null,")"),u.push({i:1,x:vu(a[0],c[0])},{i:3,x:vu(a[1],c[1])})):c[0]||c[1]?r.push("translate("+c+")"):r.push(""),l!=s?(l-s>180?s+=360:s-l>180&&(l+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:vu(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:vu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:vu(g[0],p[0])},{i:e-2,x:vu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i<e;)r[(t=u[i]).i]=t.x(n);return r.join("")}}function Ou(n,t){return t=(t-=n=+n)||1/t,function(e){return(e-n)/t}}function Yu(n,t){return t=(t-=n=+n)||1/t,function(e){return Math.max(0,Math.min(1,(e-n)/t))}}function Iu(n){for(var t=n.source,e=n.target,r=Vu(t,e),u=[t];t!==r;)t=t.parent,u.push(t);for(var i=u.length;e!==r;)u.splice(i,0,e),e=e.parent;return u}function Zu(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function Vu(n,t){if(n===t)return n;for(var e=Zu(n),r=Zu(t),u=e.pop(),i=r.pop(),o=null;u===i;)o=u,u=e.pop(),i=r.pop();return o}function Xu(n){n.fixed|=2}function $u(n){n.fixed&=-7}function Bu(n){n.fixed|=4,n.px=n.x,n.py=n.y}function Wu(n){n.fixed&=-5}function Ju(n,t,e){var r=0,u=0;if(n.charge=0,!n.leaf)for(var i,o=n.nodes,a=o.length,c=-1;++c<a;)i=o[c],null!=i&&(Ju(i,t,e),n.charge+=i.charge,r+=i.charge*i.cx,u+=i.charge*i.cy);if(n.point){n.leaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var l=t*e[n.point.index];n.charge+=n.pointCharge=l,r+=l*n.point.x,u+=l*n.point.y}n.cx=r/n.charge,n.cy=u/n.charge}function Gu(n,t){return ta.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=ri,n}function Ku(n,t){for(var e=[n];null!=(n=e.pop());)if(t(n),(u=n.children)&&(r=u.length))for(var r,u;--r>=0;)e.push(u[r])}function Qu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++o<u;)e.push(i[o]);for(;null!=(n=r.pop());)t(n)}function ni(n){return n.children}function ti(n){return n.value}function ei(n,t){return t.value-n.value}function ri(n){return ta.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function ui(n){return n.x}function ii(n){return n.y}function oi(n,t,e){n.y0=t,n.y=e}function ai(n){return ta.range(n.length)}function ci(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function li(n){for(var t,e=1,r=0,u=n[0][1],i=n.length;i>e;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function si(n){return n.reduce(fi,0)}function fi(n,t){return n+t[1]}function hi(n,t){return gi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function gi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function pi(n){return[ta.min(n),ta.max(n)]}function vi(n,t){return n.value-t.value}function di(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function mi(n,t){n._pack_next=t,t._pack_prev=n}function yi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Mi(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,u,i,o,a,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(xi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(u=e[1],u.x=u.r,u.y=0,t(u),l>2))for(i=e[2],wi(r,u,i),t(i),di(r,i),r._pack_prev=i,di(i,u),u=r._pack_next,o=3;l>o;o++){wi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(yi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!yi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.r<r.r?mi(r,u=a):mi(r=c,u),o--):(di(r,i),u=i,t(i))}var m=(s+f)/2,y=(h+g)/2,M=0;for(o=0;l>o;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(bi)}}function xi(n){n._pack_next=n._pack_prev=n}function bi(n){delete n._pack_next,delete n._pack_prev}function _i(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i<o;)_i(u[i],t,e,r)}function wi(n,t,e){var r=n.r+e.r,u=t.x-n.x,i=t.y-n.y;if(r&&(u||i)){var o=t.r+e.r,a=u*u+i*i;o*=o,r*=r;var c=.5+(r-o)/(2*a),l=Math.sqrt(Math.max(0,2*o*(r+a)-(r-=a)*r-o*o))/(2*a);e.x=n.x+c*u+l*i,e.y=n.y+c*i-l*u}else e.x=n.x+r,e.y=n.y}function Si(n,t){return n.parent==t.parent?1:2}function ki(n){var t=n.children;return t.length?t[0]:n.t}function Ei(n){var t,e=n.children;return(t=e.length)?e[t-1]:n.t}function Ai(n,t,e){var r=e/(t.i-n.i);t.c-=r,t.s+=e,n.c+=r,t.z+=e,t.m+=e}function Ni(n){for(var t,e=0,r=0,u=n.children,i=u.length;--i>=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Ci(n,t,e){return n.a.parent===t.parent?n.a:e}function zi(n){return 1+ta.max(n,function(n){return n.y})}function qi(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Li(n){var t=n.children;return t&&t.length?Li(t[0]):n}function Ti(n){var t,e=n.children;return e&&(t=e.length)?Ti(e[t-1]):n}function Ri(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Di(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Pi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ui(n){return n.rangeExtent?n.rangeExtent():Pi(n.range())}function ji(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Fi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Hi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:bl}function Oi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++o<=a;)u.push(e(n[o-1],n[o])),i.push(r(t[o-1],t[o]));return function(t){var e=ta.bisect(n,t,1,a)-1;return i[e](u[e](t))}}function Yi(n,t,e,r){function u(){var u=Math.min(n.length,t.length)>2?Oi:ji,c=r?Yu:Ou;return o=u(n,t,c,e),a=u(t,n,c,mu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Du)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Xi(n,t)},i.tickFormat=function(t,e){return $i(n,t,e)},i.nice=function(t){return Zi(n,t),u()},i.copy=function(){return Yi(n,t,e,r)},u()}function Ii(n,t){return ta.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Zi(n,t){return Fi(n,Hi(Vi(n,t)[2]))}function Vi(n,t){null==t&&(t=10);var e=Pi(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Xi(n,t){return ta.range.apply(ta,Vi(n,t))}function $i(n,t,e){var r=Vi(n,t);if(e){var u=lc.exec(e);if(u.shift(),"s"===u[8]){var i=ta.formatPrefix(Math.max(va(r[0]),va(r[1])));return u[7]||(u[7]="."+Bi(i.scale(r[2]))),u[8]="f",e=ta.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Wi(u[8],r)),e=u.join("")}else e=",."+Bi(r[2])+"f";return ta.format(e)}function Bi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Wi(n,t){var e=Bi(t[2]);return n in _l?Math.abs(e-Bi(Math.max(va(t[0]),va(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Ji(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Fi(r.map(u),e?Math:Sl);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Pi(r),o=[],a=n[0],c=n[1],l=Math.floor(u(a)),s=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)o.push(i(l)*h);o.push(i(l))}else for(o.push(i(l));l++<s;)for(var h=f-1;h>0;h--)o.push(i(l)*h);for(l=0;o[l]<a;l++);for(s=o.length;o[s-1]>c;s--);o=o.slice(l,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return wl;arguments.length<2?t=wl:"function"!=typeof t&&(t=ta.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Ji(n.copy(),t,e,r)},Ii(o,n)}function Gi(n,t,e){function r(t){return n(u(t))}var u=Ki(t),i=Ki(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Xi(e,n)},r.tickFormat=function(n,t){return $i(e,n,t)},r.nice=function(n){return r.domain(Zi(e,n))},r.exponent=function(o){return arguments.length?(u=Ki(t=o),i=Ki(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Gi(n.copy(),t,e)},Ii(r,n)}function Ki(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Qi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return ta.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new a;for(var i,o=-1,c=r.length;++o<c;)u.has(i=r[o])||u.set(i,n.push(i));return e[t.t].apply(e,t.a)},e.range=function(n){return arguments.length?(i=n,o=0,t={t:"range",a:arguments},e):i},e.rangePoints=function(u,a){arguments.length<2&&(a=0);var c=u[0],l=u[1],s=n.length<2?(c=(c+l)/2,0):(l-c)/(n.length-1+a);return i=r(c+s*a/2,s),o=0,t={t:"rangePoints",a:arguments},e},e.rangeRoundPoints=function(u,a){arguments.length<2&&(a=0);var c=u[0],l=u[1],s=n.length<2?(c=l=Math.round((c+l)/2),0):0|(l-c)/(n.length-1+a);return i=r(c+Math.round(s*a/2+(l-c-(n.length-1+a)*s)/2),s),o=0,t={t:"rangeRoundPoints",a:arguments},e},e.rangeBands=function(u,a,c){arguments.length<2&&(a=0),arguments.length<3&&(c=a);var l=u[1]<u[0],s=u[l-0],f=u[1-l],h=(f-s)/(n.length-a+2*c);return i=r(s+h*c,h),l&&i.reverse(),o=h*(1-a),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(u,a,c){arguments.length<2&&(a=0),arguments.length<3&&(c=a);var l=u[1]<u[0],s=u[l-0],f=u[1-l],h=Math.floor((f-s)/(n.length-a+2*c));return i=r(s+Math.round((f-s-(n.length-a)*h)/2),h),l&&i.reverse(),o=Math.round(h*(1-a)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=function(){return o},e.rangeExtent=function(){return Pi(t.a[0])},e.copy=function(){return Qi(n,t)},e.domain(n)}function no(r,u){function i(){var n=0,t=u.length;for(a=[];++n<t;)a[n-1]=ta.quantile(r,n/t);return o}function o(n){return isNaN(n=+n)?void 0:u[ta.bisect(a,n)]}var a;return o.domain=function(u){return arguments.length?(r=u.map(t).filter(e).sort(n),i()):r},o.range=function(n){return arguments.length?(u=n,i()):u},o.quantiles=function(){return a},o.invertExtent=function(n){return n=u.indexOf(n),0>n?[0/0,0/0]:[n>0?a[n-1]:r[0],n<a.length?a[n]:r[r.length-1]]},o.copy=function(){return no(r,u)},i()}function to(n,t,e){function r(t){return e[Math.max(0,Math.min(o,Math.floor(i*(t-n))))]}function u(){return i=e.length/(t-n),o=e.length-1,r}var i,o;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],u()):[n,t]},r.range=function(n){return arguments.length?(e=n,u()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return to(n,t,e)},u()}function eo(n,t){function e(e){return e>=e?t[ta.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return eo(n,t)},e}function ro(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Xi(n,t)},t.tickFormat=function(t,e){return $i(n,t,e)},t.copy=function(){return ro(n)},t}function uo(){return 0}function io(n){return n.innerRadius}function oo(n){return n.outerRadius}function ao(n){return n.startAngle}function co(n){return n.endAngle}function lo(n){return n&&n.padAngle}function so(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function fo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),c=a*o,l=-a*i,s=n[0]+c,f=n[1]+l,h=t[0]+c,g=t[1]+l,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(M*M*y-x*x),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-c,w-l],[_*e/M,w*e/M]]}function ho(n){function t(t){function o(){l.push("M",i(n(s),a))}for(var c,l=[],s=[],f=-1,h=t.length,g=kt(e),p=kt(r);++f<h;)u.call(this,c=t[f],f)?s.push([+g.call(this,c,f),+p.call(this,c,f)]):s.length&&(o(),s=[]);return s.length&&o(),l.length?l.join(""):null}var e=Ar,r=Nr,u=Ne,i=go,o=i.key,a=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(u=n,t):u},t.interpolate=function(n){return arguments.length?(o="function"==typeof n?i=n:(i=zl.get(n)||go).key,t):o},t.tension=function(n){return arguments.length?(a=n,t):a},t}function go(n){return n.join("L")}function po(n){return go(n)+"Z"}function vo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1]);return e>1&&u.push("H",r[0]),u.join("")}function mo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("V",(r=n[t])[1],"H",r[0]);return u.join("")}function yo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r=n[t])[0],"V",r[1]);return u.join("")}function Mo(n,t){return n.length<4?go(n):n[1]+_o(n.slice(1,-1),wo(n,t))}function xo(n,t){return n.length<3?go(n):n[0]+_o((n.push(n[0]),n),wo([n[n.length-2]].concat(n,[n[1]]),t))}function bo(n,t){return n.length<3?go(n):n[0]+_o(n,wo(n,t))}function _o(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return go(n);var e=n.length!=t.length,r="",u=n[0],i=n[1],o=t[0],a=o,c=1;if(e&&(r+="Q"+(i[0]-2*o[0]/3)+","+(i[1]-2*o[1]/3)+","+i[0]+","+i[1],u=n[1],c=2),t.length>1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var l=2;l<t.length;l++,c++)i=n[c],a=t[l],r+="S"+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1]}if(e){var s=n[c];r+="Q"+(i[0]+2*a[0]/3)+","+(i[1]+2*a[1]/3)+","+s[0]+","+s[1]}return r}function wo(n,t){for(var e,r=[],u=(1-t)/2,i=n[0],o=n[1],a=1,c=n.length;++a<c;)e=i,i=o,o=n[a],r.push([u*(o[0]-e[0]),u*(o[1]-e[1])]);return r}function So(n){if(n.length<3)return go(n);var t=1,e=n.length,r=n[0],u=r[0],i=r[1],o=[u,u,u,(r=n[1])[0]],a=[i,i,i,r[1]],c=[u,",",i,"L",No(Tl,o),",",No(Tl,a)];for(n.push(n[e-1]);++t<=e;)r=n[t],o.shift(),o.push(r[0]),a.shift(),a.push(r[1]),Co(c,o,a);return n.pop(),c.push("L",r),c.join("")}function ko(n){if(n.length<4)return go(n);for(var t,e=[],r=-1,u=n.length,i=[0],o=[0];++r<3;)t=n[r],i.push(t[0]),o.push(t[1]);for(e.push(No(Tl,i)+","+No(Tl,o)),--r;++r<u;)t=n[r],i.shift(),i.push(t[0]),o.shift(),o.push(t[1]),Co(e,i,o);return e.join("")}function Eo(n){for(var t,e,r=-1,u=n.length,i=u+4,o=[],a=[];++r<4;)e=n[r%u],o.push(e[0]),a.push(e[1]);for(t=[No(Tl,o),",",No(Tl,a)],--r;++r<i;)e=n[r%u],o.shift(),o.push(e[0]),a.shift(),a.push(e[1]),Co(t,o,a);return t.join("")}function Ao(n,t){var e=n.length-1;if(e)for(var r,u,i=n[0][0],o=n[0][1],a=n[e][0]-i,c=n[e][1]-o,l=-1;++l<=e;)r=n[l],u=l/e,r[0]=t*r[0]+(1-t)*(i+u*a),r[1]=t*r[1]+(1-t)*(o+u*c);return So(n)}function No(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]}function Co(n,t,e){n.push("C",No(ql,t),",",No(ql,e),",",No(Ll,t),",",No(Ll,e),",",No(Tl,t),",",No(Tl,e))}function zo(n,t){return(t[1]-n[1])/(t[0]-n[0])}function qo(n){for(var t=0,e=n.length-1,r=[],u=n[0],i=n[1],o=r[0]=zo(u,i);++t<e;)r[t]=(o+(o=zo(u=i,i=n[t+1])))/2;return r[t]=o,r}function Lo(n){for(var t,e,r,u,i=[],o=qo(n),a=-1,c=n.length-1;++a<c;)t=zo(n[a],n[a+1]),va(t)<Ta?o[a]=o[a+1]=0:(e=o[a]/t,r=o[a+1]/t,u=e*e+r*r,u>9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function To(n){return n.length<3?go(n):n[0]+_o(n,Lo(n))}function Ro(n){for(var t,e,r,u=-1,i=n.length;++u<i;)t=n[u],e=t[0],r=t[1]-ja,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function Do(n){function t(t){function c(){v.push("M",a(n(m),f),s,l(n(d.reverse()),f),"Z")}for(var h,g,p,v=[],d=[],m=[],y=-1,M=t.length,x=kt(e),b=kt(u),_=e===r?function(){return g}:kt(r),w=u===i?function(){return p}:kt(i);++y<M;)o.call(this,h=t[y],y)?(d.push([g=+x.call(this,h,y),p=+b.call(this,h,y)]),m.push([+_.call(this,h,y),+w.call(this,h,y)])):d.length&&(c(),d=[],m=[]);return d.length&&c(),v.length?v.join(""):null}var e=Ar,r=Ar,u=0,i=Nr,o=Ne,a=go,c=a.key,l=a,s="L",f=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r},t.y=function(n){return arguments.length?(u=i=n,t):i},t.y0=function(n){return arguments.length?(u=n,t):u},t.y1=function(n){return arguments.length?(i=n,t):i},t.defined=function(n){return arguments.length?(o=n,t):o},t.interpolate=function(n){return arguments.length?(c="function"==typeof n?a=n:(a=zl.get(n)||go).key,l=a.reverse||a,s=a.closed?"M":"L",t):c
|
3 |
+
},t.tension=function(n){return arguments.length?(f=n,t):f},t}function Po(n){return n.radius}function Uo(n){return[n.x,n.y]}function jo(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]-ja;return[e*Math.cos(r),e*Math.sin(r)]}}function Fo(){return 64}function Ho(){return"circle"}function Oo(n){var t=Math.sqrt(n/Da);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Yo(n){return function(){var t,e;(t=this[n])&&(e=t[t.active])&&(--t.count?delete t[t.active]:delete this[n],t.active+=.5,e.event&&e.event.interrupt.call(this,this.__data__,e.index))}}function Io(n,t,e){return xa(n,Hl),n.namespace=t,n.id=e,n}function Zo(n,t,e,r){var u=n.id,i=n.namespace;return H(n,"function"==typeof e?function(n,o,a){n[i][u].tween.set(t,r(e.call(n,n.__data__,o,a)))}:(e=r(e),function(n){n[i][u].tween.set(t,e)}))}function Vo(n){return null==n&&(n=""),function(){this.textContent=n}}function Xo(n){return null==n?"__transition__":"__transition_"+n+"__"}function $o(n,t,e,r,u){var i=n[e]||(n[e]={active:0,count:0}),o=i[r];if(!o){var c=u.time;o=i[r]={tween:new a,time:c,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++i.count,ta.timer(function(u){function a(e){if(i.active>r)return s();var u=i[i.active];u&&(--i.count,delete i[i.active],u.event&&u.event.interrupt.call(n,n.__data__,u.index)),i.active=r,o.event&&o.event.start.call(n,n.__data__,t),o.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&v.push(r)}),h=o.ease,f=o.duration,ta.timer(function(){return p.c=l(e||1)?Ne:l,1},0,c)}function l(e){if(i.active!==r)return 1;for(var u=e/f,a=h(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,n.__data__,t),s()):void 0}function s(){return--i.count?delete i[r]:delete n[e],1}var f,h,g=o.delay,p=oc,v=[];return p.t=g+c,u>=g?a(u-g):(p.c=a,void 0)},0,c)}}function Bo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function Wo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function Jo(n){return n.toISOString()}function Go(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=ta.bisect(Wl,u);return i==Wl.length?[t.year,Vi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Wl[i-1]<Wl[i]/u?i-1:i]:[Kl,Vi(n,e)[2]]}return r.invert=function(t){return Ko(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(Ko)},r.nice=function(n,t){function e(e){return!isNaN(e)&&!n.range(e,Ko(+e+1),t).length}var i=r.domain(),o=Pi(i),a=null==n?u(o,10):"number"==typeof n&&u(o,n);return a&&(n=a[0],t=a[1]),r.domain(Fi(i,t>1?{floor:function(t){for(;e(t=n.floor(t));)t=Ko(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Ko(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Pi(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Ko(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Go(n.copy(),t,e)},Ii(r,n)}function Ko(n){return new Date(n)}function Qo(n){return JSON.parse(n.responseText)}function na(n){var t=ua.createRange();return t.selectNode(ua.body),t.createContextualFragment(n.responseText)}var ta={version:"3.5.3"};Date.now||(Date.now=function(){return+new Date});var ea=[].slice,ra=function(n){return ea.call(n)},ua=document,ia=ua.documentElement,oa=window;try{ra(ia.childNodes)[0].nodeType}catch(aa){ra=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{ua.createElement("div").style.setProperty("opacity",0,"")}catch(ca){var la=oa.Element.prototype,sa=la.setAttribute,fa=la.setAttributeNS,ha=oa.CSSStyleDeclaration.prototype,ga=ha.setProperty;la.setAttribute=function(n,t){sa.call(this,n,t+"")},la.setAttributeNS=function(n,t,e){fa.call(this,n,t,e+"")},ha.setProperty=function(n,t,e){ga.call(this,n,t+"",e)}}ta.ascending=n,ta.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ta.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i;)if(null!=(r=n[u])&&r>=r){e=r;break}for(;++u<i;)null!=(r=n[u])&&e>r&&(e=r)}else{for(;++u<i;)if(null!=(r=t.call(n,n[u],u))&&r>=r){e=r;break}for(;++u<i;)null!=(r=t.call(n,n[u],u))&&e>r&&(e=r)}return e},ta.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i;)if(null!=(r=n[u])&&r>=r){e=r;break}for(;++u<i;)null!=(r=n[u])&&r>e&&(e=r)}else{for(;++u<i;)if(null!=(r=t.call(n,n[u],u))&&r>=r){e=r;break}for(;++u<i;)null!=(r=t.call(n,n[u],u))&&r>e&&(e=r)}return e},ta.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i<o;)if(null!=(r=n[i])&&r>=r){e=u=r;break}for(;++i<o;)null!=(r=n[i])&&(e>r&&(e=r),r>u&&(u=r))}else{for(;++i<o;)if(null!=(r=t.call(n,n[i],i))&&r>=r){e=u=r;break}for(;++i<o;)null!=(r=t.call(n,n[i],i))&&(e>r&&(e=r),r>u&&(u=r))}return[e,u]},ta.sum=function(n,t){var r,u=0,i=n.length,o=-1;if(1===arguments.length)for(;++o<i;)e(r=+n[o])&&(u+=r);else for(;++o<i;)e(r=+t.call(n,n[o],o))&&(u+=r);return u},ta.mean=function(n,r){var u,i=0,o=n.length,a=-1,c=o;if(1===arguments.length)for(;++a<o;)e(u=t(n[a]))?i+=u:--c;else for(;++a<o;)e(u=t(r.call(n,n[a],a)))?i+=u:--c;return c?i/c:void 0},ta.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),u=+n[r-1],i=e-r;return i?u+i*(n[r]-u):u},ta.median=function(r,u){var i,o=[],a=r.length,c=-1;if(1===arguments.length)for(;++c<a;)e(i=t(r[c]))&&o.push(i);else for(;++c<a;)e(i=t(u.call(r,r[c],c)))&&o.push(i);return o.length?ta.quantile(o.sort(n),.5):void 0},ta.variance=function(n,r){var u,i,o=n.length,a=0,c=0,l=-1,s=0;if(1===arguments.length)for(;++l<o;)e(u=t(n[l]))&&(i=u-a,a+=i/++s,c+=i*(u-a));else for(;++l<o;)e(u=t(r.call(n,n[l],l)))&&(i=u-a,a+=i/++s,c+=i*(u-a));return s>1?c/(s-1):void 0},ta.deviation=function(){var n=ta.variance.apply(this,arguments);return n?Math.sqrt(n):n};var pa=r(n);ta.bisectLeft=pa.left,ta.bisect=ta.bisectRight=pa.right,ta.bisector=function(t){return r(1===t.length?function(e,r){return n(t(e),r)}:t)},ta.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=0|Math.random()*i--,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},ta.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ta.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},ta.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=ta.min(arguments,u),e=new Array(t);++n<t;)for(var r,i=-1,o=e[n]=new Array(r);++i<r;)o[i]=arguments[i][n];return e},ta.transpose=function(n){return ta.zip.apply(ta,n)},ta.keys=function(n){var t=[];for(var e in n)t.push(e);return t},ta.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},ta.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},ta.merge=function(n){for(var t,e,r,u=n.length,i=-1,o=0;++i<u;)o+=n[i].length;for(e=new Array(o);--u>=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var va=Math.abs;ta.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/e)throw new Error("infinite range");var r,u=[],o=i(va(e)),a=-1;if(n*=o,t*=o,e*=o,0>e)for(;(r=n+e*++a)>t;)u.push(r/o);else for(;(r=n+e*++a)<t;)u.push(r/o);return u},ta.map=function(n,t){var e=new a;if(n instanceof a)n.forEach(function(n,t){e.set(n,t)});else if(Array.isArray(n)){var r,u=-1,i=n.length;if(1===arguments.length)for(;++u<i;)e.set(u,n[u]);else for(;++u<i;)e.set(t.call(n,r=n[u],u),r)}else for(var o in n)e.set(o,n[o]);return e};var da="__proto__",ma="\x00";o(a,{has:s,get:function(n){return this._[c(n)]},set:function(n,t){return this._[c(n)]=t},remove:f,keys:h,values:function(){var n=[];for(var t in this._)n.push(this._[t]);return n},entries:function(){var n=[];for(var t in this._)n.push({key:l(t),value:this._[t]});return n},size:g,empty:p,forEach:function(n){for(var t in this._)n.call(this,l(t),this._[t])}}),ta.nest=function(){function n(t,o,c){if(c>=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var l,s,f,h,g=-1,p=o.length,v=i[c++],d=new a;++g<p;)(h=d.get(l=v(s=o[g])))?h.push(s):d.set(l,[s]);return t?(s=t(),f=function(e,r){s.set(e,n(t,r,c))}):(s={},f=function(e,r){s[e]=n(t,r,c)}),d.forEach(f),s}function t(n,e){if(e>=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ta.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ta.set=function(n){var t=new v;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},o(v,{has:s,add:function(n){return this._[c(n+="")]=!0,n},remove:f,values:h,size:g,empty:p,forEach:function(n){for(var t in this._)n.call(this,l(t))}}),ta.behavior={},ta.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r<u;)n[e=arguments[r]]=d(n,t,t[e]);return n};var ya=["webkit","ms","moz","Moz","o","O"];ta.dispatch=function(){for(var n=new M,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=x(n);return n},M.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ta.event=null,ta.requote=function(n){return n.replace(Ma,"\\$&")};var Ma=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,xa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ba=function(n,t){return t.querySelector(n)},_a=function(n,t){return t.querySelectorAll(n)},wa=ia.matches||ia[m(ia,"matchesSelector")],Sa=function(n,t){return wa.call(n,t)};"function"==typeof Sizzle&&(ba=function(n,t){return Sizzle(n,t)[0]||null},_a=Sizzle,Sa=Sizzle.matchesSelector),ta.selection=function(){return Na};var ka=ta.selection.prototype=[];ka.select=function(n){var t,e,r,u,i=[];n=k(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]),t.parentNode=(r=this[o]).parentNode;for(var c=-1,l=r.length;++c<l;)(u=r[c])?(t.push(e=n.call(u,u.__data__,c,o)),e&&"__data__"in u&&(e.__data__=u.__data__)):t.push(null)}return S(i)},ka.selectAll=function(n){var t,e,r=[];n=E(n);for(var u=-1,i=this.length;++u<i;)for(var o=this[u],a=-1,c=o.length;++a<c;)(e=o[a])&&(r.push(t=ra(n.call(e,e.__data__,a,u))),t.parentNode=e);return S(r)};var Ea={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};ta.ns={prefix:Ea,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&(e=n.slice(0,t),n=n.slice(t+1)),Ea.hasOwnProperty(e)?{space:Ea[e],local:n}:n}},ka.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ta.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(A(t,n[t]));return this}return this.each(A(n,t))},ka.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=z(n)).length,u=-1;if(t=e.classList){for(;++u<r;)if(!t.contains(n[u]))return!1}else for(t=e.getAttribute("class");++u<r;)if(!C(n[u]).test(t))return!1;return!0}for(t in n)this.each(q(t,n[t]));return this}return this.each(q(n,t))},ka.style=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(T(e,n[e],t));return this}if(2>r)return oa.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(T(n,t,e))},ka.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(R(t,n[t]));return this}return this.each(R(n,t))},ka.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},ka.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},ka.append=function(n){return n=D(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},ka.insert=function(n,t){return n=D(n),t=k(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},ka.remove=function(){return this.each(P)},ka.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new a,y=new Array(o);for(r=-1;++r<o;)m.has(d=t.call(u=n[r],u.__data__,r))?v[r]=u:m.set(d,u),y[r]=d;for(r=-1;++r<f;)(u=m.get(d=t.call(e,i=e[r],r)))?u!==!0&&(g[r]=u,u.__data__=i):p[r]=U(i),m.set(d,!0);for(r=-1;++r<o;)m.get(y[r])!==!0&&(v[r]=n[r])}else{for(r=-1;++r<h;)u=n[r],i=e[r],u?(u.__data__=i,g[r]=u):p[r]=U(i);for(;f>r;++r)p[r]=U(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),l.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++i<o;)(u=r[i])&&(n[i]=u.__data__);return n}var c=O([]),l=S([]),s=S([]);if("function"==typeof n)for(;++i<o;)e(r=this[i],n.call(r,r.parentNode.__data__,i));else for(;++i<o;)e(r=this[i],n);return l.enter=function(){return c},l.exit=function(){return s},l},ka.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},ka.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=j(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return S(u)},ka.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],u=r.length-1,i=r[u];--u>=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},ka.sort=function(n){n=F.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},ka.each=function(n){return H(this,function(t,e,r){n.call(t,t.__data__,e,r)})},ka.call=function(n){var t=ra(arguments);return n.apply(t[0]=this,t),this},ka.empty=function(){return!this.node()},ka.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},ka.size=function(){var n=0;return H(this,function(){++n}),n};var Aa=[];ta.selection.enter=O,ta.selection.enter.prototype=Aa,Aa.append=ka.append,Aa.empty=ka.empty,Aa.node=ka.node,Aa.call=ka.call,Aa.size=ka.size,Aa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++a<c;){r=(u=this[a]).update,o.push(t=[]),t.parentNode=u.parentNode;for(var l=-1,s=u.length;++l<s;)(i=u[l])?(t.push(r[l]=e=n.call(u.parentNode,i.__data__,l,a)),e.__data__=i.__data__):t.push(null)}return S(o)},Aa.insert=function(n,t){return arguments.length<2&&(t=Y(this)),ka.insert.call(this,n,t)},ta.select=function(n){var t=["string"==typeof n?ba(n,ua):n];return t.parentNode=ia,S([t])},ta.selectAll=function(n){var t=ra("string"==typeof n?_a(n,ua):n);return t.parentNode=ia,S([t])};var Na=ta.select(ia);ka.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(I(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(I(n,t,e))};var Ca=ta.map({mouseenter:"mouseover",mouseleave:"mouseout"});Ca.forEach(function(n){"on"+n in ua&&Ca.remove(n)});var za="onselectstart"in ua?null:m(ia.style,"userSelect"),qa=0;ta.mouse=function(n){return $(n,_())};var La=/WebKit/.test(oa.navigator.userAgent)?-1:0;ta.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=_().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return $(n,r)},ta.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",i)}function t(n,t,u,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+l[0],y:r[1]+l[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&ta.event.target===f),g({type:"dragend"}))}var l,s=this,f=ta.event.target,h=s.parentNode,g=e.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=ta.select(u()).on(i+d,a).on(o+d,c),y=X(),M=t(h,v);r?(l=r.apply(s,arguments),l=[l.x-M[0],l.y-M[1]]):l=[0,0],g({type:"dragstart"})}}var e=w(n,"drag","dragstart","dragend"),r=null,u=t(y,ta.mouse,J,"mousemove","mouseup"),i=t(B,ta.touch,W,"touchmove","touchend");return n.origin=function(t){return arguments.length?(r=t,n):r},ta.rebind(n,e,"on")},ta.touches=function(n,t){return arguments.length<2&&(t=_().touches),t?ra(t).map(function(t){var e=$(n,t);return e.identifier=t.identifier,e}):[]};var Ta=1e-6,Ra=Ta*Ta,Da=Math.PI,Pa=2*Da,Ua=Pa-Ta,ja=Da/2,Fa=Da/180,Ha=180/Da,Oa=Math.SQRT2,Ya=2,Ia=4;ta.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=et(v),o=i/(Ya*h)*(e*rt(Oa*t+v)-tt(v));return[r+o*l,u+o*s,i*e/et(Oa*t+v)]}return[r+n*l,u+n*s,i*Math.exp(Oa*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],l=o-r,s=a-u,f=l*l+s*s,h=Math.sqrt(f),g=(c*c-i*i+Ia*f)/(2*i*Ya*h),p=(c*c-i*i-Ia*f)/(2*c*Ya*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Oa;return e.duration=1e3*y,e},ta.behavior.zoom=function(){function n(n){n.on(z,s).on(Xa+".zoom",h).on("dblclick.zoom",g).on(T,f)}function t(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function e(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function r(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=e(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function i(t,e,i,o){t.__chart__={x:k.x,y:k.y,k:k.k},r(Math.pow(2,o)),u(v=e,i),t=ta.select(t),N>0&&(t=t.transition().duration(N)),t.call(n.event)}function o(){x&&x.domain(M.range().map(function(n){return(n-k.x)/k.k}).map(M.invert)),S&&S.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function a(n){C++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function l(n){--C||n({type:"zoomend"}),v=null}function s(){function n(){s=1,u(ta.mouse(r),h),c(o)}function e(){f.on(q,null).on(L,null),g(s&&ta.event.target===i),l(o)}var r=this,i=ta.event.target,o=R.of(r,arguments),s=0,f=ta.select(oa).on(q,n).on(L,e),h=t(ta.mouse(r)),g=X();Fl.call(r),a(o)}function f(){function n(){var n=ta.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=t(n))}),n}function e(){var t=ta.event.target;ta.select(t).on(x,o).on(_,h),w.push(t);for(var e=ta.event.changedTouches,r=0,u=e.length;u>r;++r)d[e[r].identifier]=null;var a=n(),c=Date.now();if(1===a.length){if(500>c-y){var l=a[0];i(p,l,d[l.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),b()}y=c}else if(a.length>1){var l=a[0],s=a[1],f=l[0]-s[0],g=l[1]-s[1];m=f*f+g*g}}function o(){var n,t,e,i,o=ta.touches(p);Fl.call(p);for(var a=0,l=o.length;l>a;++a,i=null)if(e=o[a],i=d[e.identifier]){if(t)break;n=e,t=i}if(i){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*g)}y=null,u(n,t),c(v)}function h(){if(ta.event.touches.length){for(var t=ta.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}ta.selectAll(w).on(M,null),S.on(z,s).on(T,f),E(),l(v)}var g,p=this,v=R.of(p,arguments),d={},m=0,M=".zoom-"+ta.event.changedTouches[0].identifier,x="touchmove"+M,_="touchend"+M,w=[],S=ta.select(p),E=X();e(),a(v),S.on(z,null).on(T,e)}function h(){var n=R.of(this,arguments);m?clearTimeout(m):(p=t(v=d||ta.mouse(this)),Fl.call(this),a(n)),m=setTimeout(function(){m=null,l(n)},50),b(),r(Math.pow(2,.002*Za())*k.k),u(v,p),c(n)}function g(){var n=ta.mouse(this),e=Math.log(k.k)/Math.LN2;i(this,n,t(n),ta.event.shiftKey?Math.ceil(e)-1:Math.floor(e)+1)}var p,v,d,m,y,M,x,_,S,k={x:0,y:0,k:1},E=[960,500],A=Va,N=250,C=0,z="mousedown.zoom",q="mousemove.zoom",L="mouseup.zoom",T="touchstart.zoom",R=w(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=R.of(this,arguments),t=k;Ul?ta.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},a(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=v?v[0]:e/2,i=v?v[1]:r/2,o=ta.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){l(n)}).each("end.zoom",function(){l(n)}):(this.__chart__=k,a(n),c(n),l(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:+t},o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Va:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(d=t&&[+t[0],+t[1]],n):d},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(N=+t,n):N},n.x=function(t){return arguments.length?(x=t,M=t.copy(),k={x:0,y:0,k:1},n):x},n.y=function(t){return arguments.length?(S=t,_=t.copy(),k={x:0,y:0,k:1},n):S},ta.rebind(n,R,"on")};var Za,Va=[0,1/0],Xa="onwheel"in ua?(Za=function(){return-ta.event.deltaY*(ta.event.deltaMode?120:1)},"wheel"):"onmousewheel"in ua?(Za=function(){return ta.event.wheelDelta},"mousewheel"):(Za=function(){return-ta.event.detail},"MozMousePixelScroll");ta.color=it,it.prototype.toString=function(){return this.rgb()+""},ta.hsl=ot;var $a=ot.prototype=new it;$a.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ot(this.h,this.s,this.l/n)},$a.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ot(this.h,this.s,n*this.l)},$a.rgb=function(){return at(this.h,this.s,this.l)},ta.hcl=ct;var Ba=ct.prototype=new it;Ba.brighter=function(n){return new ct(this.h,this.c,Math.min(100,this.l+Wa*(arguments.length?n:1)))},Ba.darker=function(n){return new ct(this.h,this.c,Math.max(0,this.l-Wa*(arguments.length?n:1)))},Ba.rgb=function(){return lt(this.h,this.c,this.l).rgb()},ta.lab=st;var Wa=18,Ja=.95047,Ga=1,Ka=1.08883,Qa=st.prototype=new it;Qa.brighter=function(n){return new st(Math.min(100,this.l+Wa*(arguments.length?n:1)),this.a,this.b)},Qa.darker=function(n){return new st(Math.max(0,this.l-Wa*(arguments.length?n:1)),this.a,this.b)},Qa.rgb=function(){return ft(this.l,this.a,this.b)},ta.rgb=dt;var nc=dt.prototype=new it;nc.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new dt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new dt(u,u,u)},nc.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new dt(n*this.r,n*this.g,n*this.b)},nc.hsl=function(){return bt(this.r,this.g,this.b)},nc.toString=function(){return"#"+Mt(this.r)+Mt(this.g)+Mt(this.b)};var tc=ta.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});tc.forEach(function(n,t){tc.set(n,mt(t))}),ta.functor=kt,ta.xhr=At(Et),ta.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=l)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++<l;)if(34===n.charCodeAt(e)){if(34!==n.charCodeAt(e+1))break;++e}s=e+2;var r=n.charCodeAt(e+1);return 13===r?(u=!0,10===n.charCodeAt(e+2)&&++s):10===r&&(u=!0),n.slice(t+1,e).replace(/""/g,'"')}for(;l>s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==c)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],l=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new v,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},ta.csv=ta.dsv(",","text/csv"),ta.tsv=ta.dsv(" ","text/tab-separated-values");var ec,rc,uc,ic,oc,ac=oa[m(oa,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ta.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};rc?rc.n=i:ec=i,rc=i,uc||(ic=clearTimeout(ic),uc=1,ac(qt))},ta.timer.flush=function(){Lt(),Tt()},ta.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var cc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Dt);ta.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ta.round(n,Rt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),cc[8+e/3]};var lc=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,sc=ta.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ta.round(n,Rt(n,t))).toFixed(Math.max(0,Math.min(20,Rt(n*(1+1e-15),t))))}}),fc=ta.time={},hc=Date;jt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){gc.setUTCDate.apply(this._,arguments)},setDay:function(){gc.setUTCDay.apply(this._,arguments)},setFullYear:function(){gc.setUTCFullYear.apply(this._,arguments)},setHours:function(){gc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){gc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){gc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){gc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){gc.setUTCSeconds.apply(this._,arguments)},setTime:function(){gc.setTime.apply(this._,arguments)}};var gc=Date.prototype;fc.year=Ft(function(n){return n=fc.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),fc.years=fc.year.range,fc.years.utc=fc.year.utc.range,fc.day=Ft(function(n){var t=new hc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),fc.days=fc.day.range,fc.days.utc=fc.day.utc.range,fc.dayOfYear=function(n){var t=fc.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=fc[n]=Ft(function(n){return(n=fc.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=fc.year(n).getDay();return Math.floor((fc.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});fc[n+"s"]=e.range,fc[n+"s"].utc=e.utc.range,fc[n+"OfYear"]=function(n){var e=fc.year(n).getDay();return Math.floor((fc.dayOfYear(n)+(e+t)%7)/7)}}),fc.week=fc.sunday,fc.weeks=fc.sunday.range,fc.weeks.utc=fc.sunday.utc.range,fc.weekOfYear=fc.sundayOfYear;var pc={"-":"",_:" ",0:"0"},vc=/^\s*\d+/,dc=/^%/;ta.locale=function(n){return{numberFormat:Pt(n),timeFormat:Ot(n)}};var mc=ta.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ta.format=mc.numberFormat,ta.geo={},ce.prototype={s:0,t:0,add:function(n){le(n,this.t,yc),le(yc.s,this.s,this),this.s?this.t+=yc.t:this.s=yc.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var yc=new ce;ta.geo.stream=function(n,t){n&&Mc.hasOwnProperty(n.type)?Mc[n.type](n,t):se(n,t)};var Mc={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++r<u;)se(e[r].geometry,t)}},xc={Sphere:function(n,t){t.sphere()},Point:function(n,t){n=n.coordinates,t.point(n[0],n[1],n[2])},MultiPoint:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)n=e[r],t.point(n[0],n[1],n[2])
|
4 |
+
},LineString:function(n,t){fe(n.coordinates,t,0)},MultiLineString:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)fe(e[r],t,0)},Polygon:function(n,t){he(n.coordinates,t)},MultiPolygon:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)he(e[r],t)},GeometryCollection:function(n,t){for(var e=n.geometries,r=-1,u=e.length;++r<u;)se(e[r],t)}};ta.geo.area=function(n){return bc=0,ta.geo.stream(n,wc),bc};var bc,_c=new ce,wc={sphere:function(){bc+=4*Da},point:y,lineStart:y,lineEnd:y,polygonStart:function(){_c.reset(),wc.lineStart=ge},polygonEnd:function(){var n=2*_c;bc+=0>n?4*Da+n:n,wc.lineStart=wc.lineEnd=wc.point=y}};ta.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=pe([t*Fa,e*Fa]);if(m){var u=de(m,r),i=[u[1],-u[0],0],o=de(i,u);Me(o),o=xe(o);var c=t-p,l=c>0?1:-1,v=o[0]*Ha*l,d=va(c)>180;if(d^(v>l*p&&l*t>v)){var y=o[1]*Ha;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>l*p&&l*t>v)){var y=-o[1]*Ha;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=va(r)>180?r+(r>0?360:-360):r}else v=n,d=e;wc.point(n,e),t(n,e)}function i(){wc.lineStart()}function o(){u(v,d),wc.lineEnd(),va(y)>Ta&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n<t[0]||t[1]<n}var s,f,h,g,p,v,d,m,y,M,x,b={point:n,lineStart:e,lineEnd:r,polygonStart:function(){b.point=u,b.lineStart=i,b.lineEnd=o,y=0,wc.polygonStart()},polygonEnd:function(){wc.polygonEnd(),b.point=n,b.lineStart=e,b.lineEnd=r,0>_c?(s=-(h=180),f=-(g=90)):y>Ta?g=90:-Ta>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ta.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],l(e[0],u)||l(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ta.geo.centroid=function(n){Sc=kc=Ec=Ac=Nc=Cc=zc=qc=Lc=Tc=Rc=0,ta.geo.stream(n,Dc);var t=Lc,e=Tc,r=Rc,u=t*t+e*e+r*r;return Ra>u&&(t=Cc,e=zc,r=qc,Ta>kc&&(t=Ec,e=Ac,r=Nc),u=t*t+e*e+r*r,Ra>u)?[0/0,0/0]:[Math.atan2(e,t)*Ha,nt(r/Math.sqrt(u))*Ha]};var Sc,kc,Ec,Ac,Nc,Cc,zc,qc,Lc,Tc,Rc,Dc={sphere:y,point:_e,lineStart:Se,lineEnd:ke,polygonStart:function(){Dc.lineStart=Ee},polygonEnd:function(){Dc.lineStart=Se}},Pc=Le(Ne,Pe,je,[-Da,-Da/2]),Uc=1e9;ta.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ye(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ta.geo.conicEqualArea=function(){return Ie(Ze)}).raw=Ze,ta.geo.albers=function(){return ta.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ta.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=ta.geo.albers(),o=ta.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ta.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var l=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=o.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Ta,f+.12*l+Ta],[s-.214*l-Ta,f+.234*l-Ta]]).stream(c).point,u=a.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Ta,f+.166*l+Ta],[s-.115*l-Ta,f+.234*l-Ta]]).stream(c).point,n},n.scale(1070)};var jc,Fc,Hc,Oc,Yc,Ic,Zc={point:y,lineStart:y,lineEnd:y,polygonStart:function(){Fc=0,Zc.lineStart=Ve},polygonEnd:function(){Zc.lineStart=Zc.lineEnd=Zc.point=y,jc+=va(Fc/2)}},Vc={point:Xe,lineStart:y,lineEnd:y,polygonStart:y,polygonEnd:y},Xc={point:We,lineStart:Je,lineEnd:Ge,polygonStart:function(){Xc.lineStart=Ke},polygonEnd:function(){Xc.point=We,Xc.lineStart=Je,Xc.lineEnd=Ge}};ta.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),ta.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return jc=0,ta.geo.stream(n,u(Zc)),jc},n.centroid=function(n){return Ec=Ac=Nc=Cc=zc=qc=Lc=Tc=Rc=0,ta.geo.stream(n,u(Xc)),Rc?[Lc/Rc,Tc/Rc]:qc?[Cc/qc,zc/qc]:Nc?[Ec/Nc,Ac/Nc]:[0/0,0/0]},n.bounds=function(n){return Yc=Ic=-(Hc=Oc=1/0),ta.geo.stream(n,u(Vc)),[[Hc,Oc],[Yc,Ic]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||tr(n):Et,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new $e:new Qe(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(ta.geo.albersUsa()).context(null)},ta.geo.transform=function(n){return{stream:function(t){var e=new er(t);for(var r in n)e[r]=n[r];return e}}},er.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ta.geo.projection=ur,ta.geo.projectionMutator=ir,(ta.geo.equirectangular=function(){return ur(ar)}).raw=ar.invert=ar,ta.geo.rotation=function(n){function t(t){return t=n(t[0]*Fa,t[1]*Fa),t[0]*=Ha,t[1]*=Ha,t}return n=lr(n[0]%360*Fa,n[1]*Fa,n.length>2?n[2]*Fa:0),t.invert=function(t){return t=n.invert(t[0]*Fa,t[1]*Fa),t[0]*=Ha,t[1]*=Ha,t},t},cr.invert=ar,ta.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=lr(-n[0]*Fa,-n[1]*Fa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ha,n[1]*=Ha}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=gr((t=+r)*Fa,u*Fa),n):t},n.precision=function(r){return arguments.length?(e=gr(t*Fa,(u=+r)*Fa),n):u},n.angle(90)},ta.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Fa,u=n[1]*Fa,i=t[1]*Fa,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),l=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=l*s-c*f*a)*e),c*s+l*f*a)},ta.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ta.range(Math.ceil(i/d)*d,u,d).map(h).concat(ta.range(Math.ceil(l/m)*m,c,m).map(g)).concat(ta.range(Math.ceil(r/p)*p,e,p).filter(function(n){return va(n%d)>Ta}).map(s)).concat(ta.range(Math.ceil(a/v)*v,o,v).filter(function(n){return va(n%m)>Ta}).map(f))}var e,r,u,i,o,a,c,l,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],l=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[i,l],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=vr(a,o,90),f=dr(r,e,y),h=vr(l,c,90),g=dr(i,u,y),n):y},n.majorExtent([[-180,-90+Ta],[180,90-Ta]]).minorExtent([[-180,-80-Ta],[180,80+Ta]])},ta.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=mr,u=yr;return n.distance=function(){return ta.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},ta.geo.interpolate=function(n,t){return Mr(n[0]*Fa,n[1]*Fa,t[0]*Fa,t[1]*Fa)},ta.geo.length=function(n){return $c=0,ta.geo.stream(n,Bc),$c};var $c,Bc={sphere:y,point:y,lineStart:xr,lineEnd:y,polygonStart:y,polygonEnd:y},Wc=br(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ta.geo.azimuthalEqualArea=function(){return ur(Wc)}).raw=Wc;var Jc=br(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},Et);(ta.geo.azimuthalEquidistant=function(){return ur(Jc)}).raw=Jc,(ta.geo.conicConformal=function(){return Ie(_r)}).raw=_r,(ta.geo.conicEquidistant=function(){return Ie(wr)}).raw=wr;var Gc=br(function(n){return 1/n},Math.atan);(ta.geo.gnomonic=function(){return ur(Gc)}).raw=Gc,Sr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-ja]},(ta.geo.mercator=function(){return kr(Sr)}).raw=Sr;var Kc=br(function(){return 1},Math.asin);(ta.geo.orthographic=function(){return ur(Kc)}).raw=Kc;var Qc=br(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ta.geo.stereographic=function(){return ur(Qc)}).raw=Qc,Er.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-ja]},(ta.geo.transverseMercator=function(){var n=kr(Er),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Er,ta.geom={},ta.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=kt(e),i=kt(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(zr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var l=Cr(a),s=Cr(c),f=s[0]===l[0],h=s[s.length-1]===l[l.length-1],g=[];for(t=l.length-1;t>=0;--t)g.push(n[a[l[t]][2]]);for(t=+f;t<s.length-h;++t)g.push(n[a[s[t]][2]]);return g}var e=Ar,r=Nr;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},ta.geom.polygon=function(n){return xa(n,nl),n};var nl=ta.geom.polygon.prototype=[];nl.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],u=0;++t<e;)n=r,r=this[t],u+=n[1]*r[0]-n[0]*r[1];return.5*u},nl.centroid=function(n){var t,e,r=-1,u=this.length,i=0,o=0,a=this[u-1];for(arguments.length||(n=-1/(6*this.area()));++r<u;)t=a,a=this[r],e=t[0]*a[1]-a[0]*t[1],i+=(t[0]+a[0])*e,o+=(t[1]+a[1])*e;return[i*n,o*n]},nl.clip=function(n){for(var t,e,r,u,i,o,a=Tr(n),c=-1,l=this.length-Tr(this),s=this[l-1];++c<l;){for(t=n.slice(),n.length=0,u=this[c],i=t[(r=t.length-a)-1],e=-1;++e<r;)o=t[e],qr(o,s,u)?(qr(i,s,u)||n.push(Lr(i,o,s,u)),n.push(o)):qr(i,s,u)&&n.push(Lr(i,o,s,u)),i=o;a&&n.push(n[0]),s=u}return n};var tl,el,rl,ul,il,ol=[],al=[];Or.prototype.prepare=function(){for(var n,t=this.edges,e=t.length;e--;)n=t[e].edge,n.b&&n.a||t.splice(e,1);return t.sort(Ir),t.length},Qr.prototype={start:function(){return this.edge.l===this.site?this.edge.a:this.edge.b},end:function(){return this.edge.l===this.site?this.edge.b:this.edge.a}},nu.prototype={insert:function(n,t){var e,r,u;if(n){if(t.P=n,t.N=n.N,n.N&&(n.N.P=t),n.N=t,n.R){for(n=n.R;n.L;)n=n.L;n.L=t}else n.R=t;e=n}else this._?(n=uu(this._),t.P=null,t.N=n,n.P=n.L=t,e=n):(t.P=t.N=null,this._=t,e=null);for(t.L=t.R=null,t.U=e,t.C=!0,n=t;e&&e.C;)r=e.U,e===r.L?(u=r.R,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.R&&(eu(this,e),n=e,e=n.U),e.C=!1,r.C=!0,ru(this,r))):(u=r.L,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.L&&(ru(this,e),n=e,e=n.U),e.C=!1,r.C=!0,eu(this,r))),e=n.U;this._.C=!1},remove:function(n){n.N&&(n.N.P=n.P),n.P&&(n.P.N=n.N),n.N=n.P=null;var t,e,r,u=n.U,i=n.L,o=n.R;if(e=i?o?uu(o):i:o,u?u.L===n?u.L=e:u.R=e:this._=e,i&&o?(r=e.C,e.C=n.C,e.L=i,i.U=e,e!==o?(u=e.U,e.U=n.U,n=e.R,u.L=n,e.R=o,o.U=e):(e.U=u,u=e,n=e.R)):(r=n.C,n=e),n&&(n.U=u),!r){if(n&&n.C)return n.C=!1,void 0;do{if(n===this._)break;if(n===u.L){if(t=u.R,t.C&&(t.C=!1,u.C=!0,eu(this,u),t=u.R),t.L&&t.L.C||t.R&&t.R.C){t.R&&t.R.C||(t.L.C=!1,t.C=!0,ru(this,t),t=u.R),t.C=u.C,u.C=t.R.C=!1,eu(this,u),n=this._;break}}else if(t=u.L,t.C&&(t.C=!1,u.C=!0,ru(this,u),t=u.L),t.L&&t.L.C||t.R&&t.R.C){t.L&&t.L.C||(t.R.C=!1,t.C=!0,eu(this,t),t=u.L),t.C=u.C,u.C=t.L.C=!1,ru(this,u),n=this._;break}t.C=!0,n=u,u=u.U}while(!n.C);n&&(n.C=!1)}}},ta.geom.voronoi=function(n){function t(n){var t=new Array(n.length),r=a[0][0],u=a[0][1],i=a[1][0],o=a[1][1];return iu(e(n),a).cells.forEach(function(e,a){var c=e.edges,l=e.site,s=t[a]=c.length?c.map(function(n){var t=n.start();return[t.x,t.y]}):l.x>=r&&l.x<=i&&l.y>=u&&l.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ta)*Ta,y:Math.round(o(n,t)/Ta)*Ta,i:t}})}var r=Ar,u=Nr,i=r,o=u,a=cl;return n?t(n):(t.links=function(n){return iu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return iu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Ir),c=-1,l=a.length,s=a[l-1].edge,f=s.l===o?s.r:s.l;++c<l;)u=s,i=f,s=a[c].edge,f=s.l===o?s.r:s.l,r<i.i&&r<f.i&&au(o,i,f)<0&&t.push([n[r],n[i.i],n[f.i]])}),t},t.x=function(n){return arguments.length?(i=kt(r=n),t):r},t.y=function(n){return arguments.length?(o=kt(u=n),t):u},t.clipExtent=function(n){return arguments.length?(a=null==n?cl:n,t):a===cl?null:a},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):a===cl?null:a&&a[1]},t)};var cl=[[-1e6,-1e6],[1e6,1e6]];ta.geom.delaunay=function(n){return ta.geom.voronoi().triangles(n)},ta.geom.quadtree=function(n,t,e,r,u){function i(n){function i(n,t,e,r,u,i,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var c=n.x,s=n.y;if(null!=c)if(va(c-e)+va(s-r)<.01)l(n,t,e,r,u,i,o,a);else{var f=n.point;n.x=n.y=n.point=null,l(n,f,c,s,u,i,o,a),l(n,t,e,r,u,i,o,a)}else n.x=e,n.y=r,n.point=t}else l(n,t,e,r,u,i,o,a)}function l(n,t,e,r,u,o,a,c){var l=.5*(u+a),s=.5*(o+c),f=e>=l,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=su()),f?u=l:a=l,h?o=s:c=s,i(n,t,e,r,u,o,a,c)}var s,f,h,g,p,v,d,m,y,M=kt(a),x=kt(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.x<v&&(v=s.x),s.y<d&&(d=s.y),s.x>m&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=su();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){fu(n,k,v,d,m,y)},k.find=function(n){return hu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g<p;)i(k,n[g],f[g],h[g],v,d,m,y);--g}else n.forEach(k.add);return f=h=n=s=null,k}var o,a=Ar,c=Nr;return(o=arguments.length)?(a=cu,c=lu,3===o&&(u=e,r=t,e=t=0),i(n)):(i.x=function(n){return arguments.length?(a=n,i):a},i.y=function(n){return arguments.length?(c=n,i):c},i.extent=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=+n[0][0],e=+n[0][1],r=+n[1][0],u=+n[1][1]),i):null==t?null:[[t,e],[r,u]]},i.size=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=e=0,r=+n[0],u=+n[1]),i):null==t?null:[r-t,u-e]},i)},ta.interpolateRgb=gu,ta.interpolateObject=pu,ta.interpolateNumber=vu,ta.interpolateString=du;var ll=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,sl=new RegExp(ll.source,"g");ta.interpolate=mu,ta.interpolators=[function(n,t){var e=typeof t;return("string"===e?tc.has(t)||/^(#|rgb\(|hsl\()/.test(t)?gu:du:t instanceof it?gu:Array.isArray(t)?yu:"object"===e&&isNaN(t)?pu:vu)(n,t)}],ta.interpolateArray=yu;var fl=function(){return Et},hl=ta.map({linear:fl,poly:ku,quad:function(){return _u},cubic:function(){return wu},sin:function(){return Eu},exp:function(){return Au},circle:function(){return Nu},elastic:Cu,back:zu,bounce:function(){return qu}}),gl=ta.map({"in":Et,out:xu,"in-out":bu,"out-in":function(n){return bu(xu(n))}});ta.ease=function(n){var t=n.indexOf("-"),e=t>=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=hl.get(e)||fl,r=gl.get(r)||Et,Mu(r(e.apply(null,ea.call(arguments,1))))},ta.interpolateHcl=Lu,ta.interpolateHsl=Tu,ta.interpolateLab=Ru,ta.interpolateRound=Du,ta.transform=function(n){var t=ua.createElementNS(ta.ns.prefix.svg,"g");return(ta.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Pu(e?e.matrix:pl)})(n)},Pu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var pl={a:1,b:0,c:0,d:1,e:0,f:0};ta.interpolateTransform=Hu,ta.layout={},ta.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e<r;)t.push(Iu(n[e]));return t}},ta.layout.chord=function(){function n(){var n,l,f,h,g,p={},v=[],d=ta.range(i),m=[];for(e=[],r=[],n=0,h=-1;++h<i;){for(l=0,g=-1;++g<i;)l+=u[h][g];v.push(l),m.push(ta.range(i)),n+=l}for(o&&d.sort(function(n,t){return o(v[n],v[t])}),a&&m.forEach(function(n,t){n.sort(function(n,e){return a(u[t][n],u[t][e])})}),n=(Pa-s*i)/n,l=0,h=-1;++h<i;){for(f=l,g=-1;++g<i;){var y=d[h],M=m[y][g],x=u[y][M],b=l,_=l+=x*n;p[y+"-"+M]={index:y,subindex:M,startAngle:b,endAngle:_,value:x}}r[y]={index:y,startAngle:f,endAngle:l,value:(l-f)/n},l+=s}for(h=-1;++h<i;)for(g=h-1;++g<i;){var w=p[h+"-"+g],S=p[g+"-"+h];(w.value||S.value)&&e.push(w.value<S.value?{source:S,target:w}:{source:w,target:S})}c&&t()}function t(){e.sort(function(n,t){return c((n.source.value+n.target.value)/2,(t.source.value+t.target.value)/2)})}var e,r,u,i,o,a,c,l={},s=0;return l.matrix=function(n){return arguments.length?(i=(u=n)&&u.length,e=r=null,l):u},l.padding=function(n){return arguments.length?(s=n,e=r=null,l):s},l.sortGroups=function(n){return arguments.length?(o=n,e=r=null,l):o},l.sortSubgroups=function(n){return arguments.length?(a=n,e=null,l):a},l.sortChords=function(n){return arguments.length?(c=n,e&&t(),l):c},l.chords=function(){return e||n(),e},l.groups=function(){return r||n(),r},l},ta.layout.force=function(){function n(n){return function(t,e,r,u){if(t.point!==n){var i=t.cx-n.x,o=t.cy-n.y,a=u-e,c=i*i+o*o;if(c>a*a/d){if(p>c){var l=t.charge/c;n.px-=i*l,n.py-=o*l}return!0}if(t.point&&c&&p>c){var l=t.pointCharge/c;n.px-=i*l,n.py-=o*l}}return!t.charge}}function t(n){n.px=ta.event.x,n.py=ta.event.y,a.resume()}var e,r,u,i,o,a={},c=ta.dispatch("start","tick","end"),l=[1,1],s=.9,f=vl,h=dl,g=-30,p=ml,v=.1,d=.64,m=[],y=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,M,x,b=m.length,_=y.length;for(e=0;_>e;++e)a=y[e],f=a.source,h=a.target,M=h.x-f.x,x=h.y-f.y,(p=M*M+x*x)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,M*=p,x*=p,h.x-=M*(d=f.weight/(h.weight+f.weight)),h.y-=x*d,f.x+=M*(d=1-d),f.y+=x*d);if((d=r*v)&&(M=l[0]/2,x=l[1]/2,e=-1,d))for(;++e<b;)a=m[e],a.x+=(M-a.x)*d,a.y+=(x-a.y)*d;if(g)for(Ju(t=ta.geom.quadtree(m),r,o),e=-1;++e<b;)(a=m[e]).fixed||t.visit(n(a));for(e=-1;++e<b;)a=m[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*s,a.y-=(a.py-(a.py=a.y))*s);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(m=n,a):m},a.links=function(n){return arguments.length?(y=n,a):y},a.size=function(n){return arguments.length?(l=n,a):l},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(s=+n,a):s},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.chargeDistance=function(n){return arguments.length?(p=n*n,a):Math.sqrt(p)},a.gravity=function(n){return arguments.length?(v=+n,a):v},a.theta=function(n){return arguments.length?(d=n*n,a):Math.sqrt(d)},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ta.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;l>a;++a){var u=y[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,l=o.length;++a<l;)if(!isNaN(i=o[a][n]))return i;return Math.random()*r}var t,e,r,c=m.length,s=y.length,p=l[0],v=l[1];for(t=0;c>t;++t)(r=m[t]).index=t,r.weight=0;for(t=0;s>t;++t)r=y[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;s>t;++t)u[t]=+f.call(this,y[t],t);else for(t=0;s>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;s>t;++t)i[t]=+h.call(this,y[t],t);else for(t=0;s>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=ta.behavior.drag().origin(Et).on("dragstart.force",Xu).on("drag.force",t).on("dragend.force",$u)),arguments.length?(this.on("mouseover.force",Bu).on("mouseout.force",Wu).call(e),void 0):e},ta.rebind(a,c,"on")};var vl=20,dl=1,ml=1/0;ta.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(l=e.call(n,i,i.depth))&&(c=l.length)){for(var c,l,s;--c>=0;)o.push(s=l[c]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=l}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Qu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=ei,e=ni,r=ti;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(Ku(t,function(n){n.children&&(n.value=0)}),Qu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ta.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,l=-1;for(r=t.value?r/t.value:0;++l<o;)n(a=i[l],e,c=a.value*r,u),e+=c}}function t(n){var e=n.children,r=0;if(e&&(u=e.length))for(var u,i=-1;++i<u;)r=Math.max(r,t(e[i]));return 1+r}function e(e,i){var o=r.call(this,e,i);return n(o[0],0,u[0],u[1]/t(o[0])),o}var r=ta.layout.hierarchy(),u=[1,1];return e.size=function(n){return arguments.length?(u=n,e):u},Gu(e,r)},ta.layout.pie=function(){function n(o){var a,c=o.length,l=o.map(function(e,r){return+t.call(n,e,r)}),s=+("function"==typeof r?r.apply(this,arguments):r),f=("function"==typeof u?u.apply(this,arguments):u)-s,h=Math.min(Math.abs(f)/c,+("function"==typeof i?i.apply(this,arguments):i)),g=h*(0>f?-1:1),p=(f-c*g)/ta.sum(l),v=ta.range(c),d=[];return null!=e&&v.sort(e===yl?function(n,t){return l[t]-l[n]}:function(n,t){return e(o[n],o[t])}),v.forEach(function(n){d[n]={data:o[n],value:a=l[n],startAngle:s,endAngle:s+=a*p+g,padAngle:h}}),d}var t=Number,e=yl,r=0,u=Pa,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var yl={};ta.layout.stack=function(){function n(a,c){if(!(h=a.length))return a;var l=a.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,c);l=ta.permute(l,f),s=ta.permute(s,f);var h,g,p,v,d=r.call(n,s,c),m=l[0].length;for(p=0;m>p;++p)for(u.call(n,l[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,l[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=Et,e=ai,r=ci,u=oi,i=ui,o=ii;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:Ml.get(t)||ai,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:xl.get(t)||ci,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var Ml=ta.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(li),i=n.map(si),o=ta.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],l.push(e)):(c+=i[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ta.range(n.length).reverse()},"default":ai}),xl=ta.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ci});ta.layout.histogram=function(){function n(n,i){for(var o,a,c=[],l=n.map(e,this),s=r.call(this,l,i),f=u.call(this,s,l,i),i=-1,h=l.length,g=f.length-1,p=t?1:1/h;++i<g;)o=c[i]=[],o.dx=f[i+1]-(o.x=f[i]),o.y=0;if(g>0)for(i=-1;++i<h;)a=l[i],a>=s[0]&&a<=s[1]&&(o=c[ta.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=pi,u=hi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=kt(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return gi(n,t)}:kt(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ta.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],l=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Qu(a,function(n){n.r=+s(n.value)}),Qu(a,Mi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/l))/2;Qu(a,function(n){n.r+=f}),Qu(a,Mi),Qu(a,function(n){n.r-=f})}return _i(a,c/2,l/2,t?1:1/Math.max(2*a.r/c,2*a.r/l)),o}var t,e=ta.layout.hierarchy().sort(vi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Gu(n,e)},ta.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(Qu(h,e),h.parent.m=-h.z,Ku(h,r),l)Ku(f,i);else{var g=f,p=f,v=f;Ku(f,function(n){n.x<g.x&&(g=n),n.x>p.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);Ku(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ni(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],l=u.m,s=i.m,f=o.m,h=c.m;o=Ei(o),u=ki(u),o&&u;)c=ki(c),i=Ei(i),i.a=n,r=o.z+f-u.z-l+a(o._,u._),r>0&&(Ai(Ci(o,n,e),n,r),l+=r,s+=r),f+=o.m,l+=u.m,h+=c.m,s+=i.m;o&&!Ei(i)&&(i.t=o,i.m+=f-s),u&&!ki(c)&&(c.t=u,c.m+=l-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=ta.layout.hierarchy().sort(null).value(null),a=Si,c=[1,1],l=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(l=null==(c=t)?i:null,n):l?null:c},n.nodeSize=function(t){return arguments.length?(l=null==(c=t)?null:i,n):l?c:null},Gu(n,o)},ta.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],l=0;Qu(c,function(n){var t=n.children;t&&t.length?(n.x=qi(t),n.y=zi(t)):(n.x=o?l+=e(n,o):0,n.y=0,o=n)});var s=Li(c),f=Ti(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Qu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=ta.layout.hierarchy().sort(null).value(null),e=Si,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Gu(n,t)},ta.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++u<i;)r=(e=n[u]).value*(0>t?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,l=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(o=h[c-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,l,!1),v=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,l,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++o<a;)(e=n[o].area)&&(i>e&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++i<o;)u=n[i],u.x=a,u.y=l,u.dy=s,a+=u.dx=Math.min(e.x+e.dx-a,s?c(u.area/s):0);u.z=!0,u.dx+=e.x+e.dx-a,e.y+=s,e.dy-=s}else{for((r||s>e.dx)&&(s=e.dx);++i<o;)u=n[i],u.x=a,u.y=l,u.dx=s,l+=u.dy=Math.min(e.y+e.dy-l,s?c(u.area/s):0);u.z=!1,u.dy+=e.y+e.dy-l,e.x+=s,e.dx-=s}}function i(r){var u=o||a(r),i=u[0];return i.x=0,i.y=0,i.dx=l[0],i.dy=l[1],o&&a.revalue(i),n([i],i.dx*i.dy/i.value),(o?e:t)(i),h&&(o=u),u}var o,a=ta.layout.hierarchy(),c=Math.round,l=[1,1],s=null,f=Ri,h=!1,g="squarify",p=.5*(1+Math.sqrt(5));return i.size=function(n){return arguments.length?(l=n,i):l},i.padding=function(n){function t(t){var e=n.call(i,t,t.depth);return null==e?Ri(t):Di(t,"number"==typeof e?[e,e,e,e]:e)}function e(t){return Di(t,n)}if(!arguments.length)return s;var r;return f=null==(s=n)?Ri:"function"==(r=typeof n)?t:"number"===r?(n=[n,n,n,n],e):e,i},i.round=function(n){return arguments.length?(c=n?Math.round:Number,i):c!=Number},i.sticky=function(n){return arguments.length?(h=n,o=null,i):h
|
5 |
+
},i.ratio=function(n){return arguments.length?(p=n,i):p},i.mode=function(n){return arguments.length?(g=n+"",i):g},Gu(i,a)},ta.random={normal:function(n,t){var e=arguments.length;return 2>e&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=ta.random.normal.apply(ta,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ta.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ta.scale={};var bl={floor:Et,ceil:Et};ta.scale.linear=function(){return Yi([0,1],[0,1],mu,!1)};var _l={s:1,g:1,p:1,r:1,e:1};ta.scale.log=function(){return Ji(ta.scale.linear().domain([0,1]),10,!0,[1,10])};var wl=ta.format(".0e"),Sl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ta.scale.pow=function(){return Gi(ta.scale.linear(),1,[0,1])},ta.scale.sqrt=function(){return ta.scale.pow().exponent(.5)},ta.scale.ordinal=function(){return Qi([],{t:"range",a:[[]]})},ta.scale.category10=function(){return ta.scale.ordinal().range(kl)},ta.scale.category20=function(){return ta.scale.ordinal().range(El)},ta.scale.category20b=function(){return ta.scale.ordinal().range(Al)},ta.scale.category20c=function(){return ta.scale.ordinal().range(Nl)};var kl=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(yt),El=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(yt),Al=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(yt),Nl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(yt);ta.scale.quantile=function(){return no([],[])},ta.scale.quantize=function(){return to(0,1,[0,1])},ta.scale.threshold=function(){return eo([.5],[0,1])},ta.scale.identity=function(){return ro([0,1])},ta.svg={},ta.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),l=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-ja,f=a.apply(this,arguments)-ja,h=Math.abs(f-s),g=s>f?0:1;if(n>l&&(p=l,l=n,n=p),h>=Ua)return t(l,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+c.apply(this,arguments)||0)/2)&&(d=i===Cl?Math.sqrt(n*n+l*l):+i.apply(this,arguments),g||(A*=-1),l&&(A=nt(d/l*Math.sin(m))),n&&(E=nt(d/n*Math.sin(m)))),l){y=l*Math.cos(s+A),M=l*Math.sin(s+A),x=l*Math.cos(f-A),b=l*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=Da?0:1;if(A&&so(y,M,x,b)===g^C){var z=(s+f)/2;y=l*Math.cos(z),M=l*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var q=Math.abs(s-f+2*E)<=Da?0:1;if(E&&so(_,w,S,k)===1-g^q){var L=(s+f)/2;_=n*Math.cos(L),w=n*Math.sin(L),S=k=null}}else _=w=0;if((p=Math.min(Math.abs(l-n)/2,+u.apply(this,arguments)))>.001){v=l>n^g?0:1;var T=null==S?[_,w]:null==x?[y,M]:Lr([y,M],[S,k],[x,b],[_,w]),R=y-T[0],D=M-T[1],P=x-T[0],U=b-T[1],j=1/Math.sin(Math.acos((R*P+D*U)/(Math.sqrt(R*R+D*D)*Math.sqrt(P*P+U*U)))/2),F=Math.sqrt(T[0]*T[0]+T[1]*T[1]);if(null!=x){var H=Math.min(p,(l-F)/(j+1)),O=fo(null==S?[_,w]:[S,k],[y,M],l,H,g),Y=fo([x,b],[_,w],l,H,g);p===H?N.push("M",O[0],"A",H,",",H," 0 0,",v," ",O[1],"A",l,",",l," 0 ",1-g^so(O[1][0],O[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",H,",",H," 0 0,",v," ",Y[0]):N.push("M",O[0],"A",H,",",H," 0 1,",v," ",Y[0])}else N.push("M",y,",",M);if(null!=S){var I=Math.min(p,(n-F)/(j-1)),Z=fo([y,M],[S,k],n,-I,g),V=fo([_,w],null==x?[y,M]:[x,b],n,-I,g);p===I?N.push("L",V[0],"A",I,",",I," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^so(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",I,",",I," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",I,",",I," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",l,",",l," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",q,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=io,r=oo,u=uo,i=Cl,o=ao,a=co,c=lo;return n.innerRadius=function(t){return arguments.length?(e=kt(t),n):e},n.outerRadius=function(t){return arguments.length?(r=kt(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=kt(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==Cl?Cl:kt(t),n):i},n.startAngle=function(t){return arguments.length?(o=kt(t),n):o},n.endAngle=function(t){return arguments.length?(a=kt(t),n):a},n.padAngle=function(t){return arguments.length?(c=kt(t),n):c},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-ja;return[Math.cos(t)*n,Math.sin(t)*n]},n};var Cl="auto";ta.svg.line=function(){return ho(Et)};var zl=ta.map({linear:go,"linear-closed":po,step:vo,"step-before":mo,"step-after":yo,basis:So,"basis-open":ko,"basis-closed":Eo,bundle:Ao,cardinal:bo,"cardinal-open":Mo,"cardinal-closed":xo,monotone:To});zl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var ql=[0,2/3,1/3,0],Ll=[0,1/3,2/3,0],Tl=[0,1/6,2/3,1/6];ta.svg.line.radial=function(){var n=ho(Ro);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},mo.reverse=yo,yo.reverse=mo,ta.svg.area=function(){return Do(Et)},ta.svg.area.radial=function(){var n=Do(Ro);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ta.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),l=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+u(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)-ja,s=l.call(n,u,r)-ja;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Da)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=mr,o=yr,a=Po,c=ao,l=co;return n.radius=function(t){return arguments.length?(a=kt(t),n):a},n.source=function(t){return arguments.length?(i=kt(t),n):i},n.target=function(t){return arguments.length?(o=kt(t),n):o},n.startAngle=function(t){return arguments.length?(c=kt(t),n):c},n.endAngle=function(t){return arguments.length?(l=kt(t),n):l},n},ta.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=mr,e=yr,r=Uo;return n.source=function(e){return arguments.length?(t=kt(e),n):t},n.target=function(t){return arguments.length?(e=kt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ta.svg.diagonal.radial=function(){var n=ta.svg.diagonal(),t=Uo,e=n.projection;return n.projection=function(n){return arguments.length?e(jo(t=n)):t},n},ta.svg.symbol=function(){function n(n,r){return(Rl.get(t.call(this,n,r))||Oo)(e.call(this,n,r))}var t=Ho,e=Fo;return n.type=function(e){return arguments.length?(t=kt(e),n):t},n.size=function(t){return arguments.length?(e=kt(t),n):e},n};var Rl=ta.map({circle:Oo,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Pl)),e=t*Pl;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Dl),e=t*Dl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Dl),e=t*Dl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ta.svg.symbolTypes=Rl.keys();var Dl=Math.sqrt(3),Pl=Math.tan(30*Fa);ka.transition=function(n){for(var t,e,r=Ul||++Ol,u=Xo(n),i=[],o=jl||{time:Date.now(),ease:Su,delay:0,duration:250},a=-1,c=this.length;++a<c;){i.push(t=[]);for(var l=this[a],s=-1,f=l.length;++s<f;)(e=l[s])&&$o(e,s,u,r,o),t.push(e)}return Io(i,u,r)},ka.interrupt=function(n){return this.each(null==n?Fl:Yo(Xo(n)))};var Ul,jl,Fl=Yo(Xo()),Hl=[],Ol=0;Hl.call=ka.call,Hl.empty=ka.empty,Hl.node=ka.node,Hl.size=ka.size,ta.transition=function(n,t){return n&&n.transition?Ul?n.transition(t):n:Na.transition(n)},ta.transition.prototype=Hl,Hl.select=function(n){var t,e,r,u=this.id,i=this.namespace,o=[];n=k(n);for(var a=-1,c=this.length;++a<c;){o.push(t=[]);for(var l=this[a],s=-1,f=l.length;++s<f;)(r=l[s])&&(e=n.call(r,r.__data__,s,a))?("__data__"in r&&(e.__data__=r.__data__),$o(e,s,i,u,r[i][u]),t.push(e)):t.push(null)}return Io(o,i,u)},Hl.selectAll=function(n){var t,e,r,u,i,o=this.id,a=this.namespace,c=[];n=E(n);for(var l=-1,s=this.length;++l<s;)for(var f=this[l],h=-1,g=f.length;++h<g;)if(r=f[h]){i=r[a][o],e=n.call(r,r.__data__,h,l),c.push(t=[]);for(var p=-1,v=e.length;++p<v;)(u=e[p])&&$o(u,p,a,o,i),t.push(u)}return Io(c,a,o)},Hl.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=j(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Io(u,this.namespace,this.id)},Hl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):H(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Hl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Hu:mu,a=ta.ns.qualify(n);return Zo(this,"attr."+n,t,a.local?i:u)},Hl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=ta.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Hl.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=oa.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=mu(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return Zo(this,"style."+n,t,u)},Hl.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,oa.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Hl.text=function(n){return Zo(this,"text",n,Vo)},Hl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Hl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ta.ease.apply(ta,arguments)),H(this,function(r){r[e][t].ease=n}))},Hl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:H(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Hl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:H(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Hl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=jl,i=Ul;try{Ul=e,H(this,function(t,u,i){jl=t[r][e],n.call(t,t.__data__,u,i)})}finally{jl=u,Ul=i}}else H(this,function(u){var i=u[r][e];(i.event||(i.event=ta.dispatch("start","end","interrupt"))).on(n,t)});return this},Hl.transition=function(){for(var n,t,e,r,u=this.id,i=++Ol,o=this.namespace,a=[],c=0,l=this.length;l>c;c++){a.push(n=[]);for(var t=this[c],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],$o(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Io(a,o,i)},ta.svg.axis=function(){function n(n){n.each(function(){var n,l=ta.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):Et:t,p=l.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ta),d=ta.transition(p.exit()).style("opacity",Ta).remove(),m=ta.transition(p.order()).style("opacity",1),y=Math.max(u,0)+o,M=Ui(f),x=l.selectAll(".domain").data([0]),b=(x.enter().append("path").attr("class","domain"),ta.transition(x));v.append("line"),v.append("text");var _,w,S,k,E=v.select("line"),A=m.select("line"),N=p.select("text").text(g),C=v.select("text"),z=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=Bo,_="x",S="y",w="x2",k="y2",N.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),b.attr("d","M"+M[0]+","+q*i+"V0H"+M[1]+"V"+q*i)):(n=Wo,_="y",S="x",w="y2",k="x2",N.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),b.attr("d","M"+q*i+","+M[0]+"H0V"+M[1]+"H"+q*i)),E.attr(k,q*u),C.attr(S,q*y),A.attr(w,0).attr(k,q*u),z.attr(_,0).attr(S,q*y),f.rangeBand){var L=f,T=L.rangeBand()/2;s=f=function(n){return L(n)+T}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=ta.scale.linear(),r=Yl,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Il?t+"":Yl,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Yl="bottom",Il={top:1,right:1,bottom:1,left:1};ta.svg.brush=function(){function n(i){i.each(function(){var i=ta.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(p,Et);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Zl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var s,f=ta.transition(i),h=ta.transition(o);c&&(s=Ui(c),h.attr("x",s[0]).attr("width",s[1]-s[0]),e(f)),l&&(s=Ui(l),h.attr("y",s[0]).attr("height",s[1]-s[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+f[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function r(n){n.select(".extent").attr("y",f[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1]-f[0])}function u(){function u(){32==ta.event.keyCode&&(N||(y=null,z[0]-=s[1],z[1]-=f[1],N=2),b())}function p(){32==ta.event.keyCode&&2==N&&(z[0]+=s[1],z[1]+=f[1],N=0,b())}function v(){var n=ta.mouse(x),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),N||(ta.event.altKey?(y||(y=[(s[0]+s[1])/2,(f[0]+f[1])/2]),z[0]=s[+(n[0]<y[0])],z[1]=f[+(n[1]<y[1])]):y=null),E&&d(n,c,0)&&(e(S),u=!0),A&&d(n,l,1)&&(r(S),u=!0),u&&(t(S),w({type:"brush",mode:N?"move":"resize"}))}function d(n,t,e){var r,u,a=Ui(t),c=a[0],l=a[1],p=z[e],v=e?f:s,d=v[1]-v[0];return N&&(c-=p,l-=d+p),r=(e?g:h)?Math.max(c,Math.min(l,n[e])):n[e],N?u=(r+=p)+d:(y&&(p=Math.max(c,Math.min(l,2*y[e]-r))),r>p?(u=r,r=p):u=p),v[0]!=r||v[1]!=u?(e?o=null:i=null,v[0]=r,v[1]=u,!0):void 0}function m(){v(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ta.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),C(),w({type:"brushend"})}var y,M,x=this,_=ta.select(ta.event.target),w=a.of(x,arguments),S=ta.select(x),k=_.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&l,N=_.classed("extent"),C=X(),z=ta.mouse(x),q=ta.select(oa).on("keydown.brush",u).on("keyup.brush",p);if(ta.event.changedTouches?q.on("touchmove.brush",v).on("touchend.brush",m):q.on("mousemove.brush",v).on("mouseup.brush",m),S.interrupt().selectAll("*").interrupt(),N)z[0]=s[0]-z[0],z[1]=f[0]-z[1];else if(k){var L=+/w$/.test(k),T=+/^n/.test(k);M=[s[1-L]-z[0],f[1-T]-z[1]],z[0]=s[L],z[1]=f[T]}else ta.event.altKey&&(y=z.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),ta.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),v()}var i,o,a=w(n,"brushstart","brush","brushend"),c=null,l=null,s=[0,0],f=[0,0],h=!0,g=!0,p=Vl[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:s,y:f,i:i,j:o},e=this.__chart__||t;this.__chart__=t,Ul?ta.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,s=e.x,f=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=yu(s,t.x),r=yu(f,t.y);return i=o=null,function(u){s=t.x=e(u),f=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,p=Vl[!c<<1|!l],n):c},n.y=function(t){return arguments.length?(l=t,p=Vl[!c<<1|!l],n):l},n.clamp=function(t){return arguments.length?(c&&l?(h=!!t[0],g=!!t[1]):c?h=!!t:l&&(g=!!t),n):c&&l?[h,g]:c?h:l?g:null},n.extent=function(t){var e,r,u,a,h;return arguments.length?(c&&(e=t[0],r=t[1],l&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(h=e,e=r,r=h),(e!=s[0]||r!=s[1])&&(s=[e,r])),l&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],l.invert&&(u=l(u),a=l(a)),u>a&&(h=u,u=a,a=h),(u!=f[0]||a!=f[1])&&(f=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(h=e,e=r,r=h))),l&&(o?(u=o[0],a=o[1]):(u=f[0],a=f[1],l.invert&&(u=l.invert(u),a=l.invert(a)),u>a&&(h=u,u=a,a=h))),c&&l?[[e,u],[r,a]]:c?[e,r]:l&&[u,a])},n.clear=function(){return n.empty()||(s=[0,0],f=[0,0],i=o=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!l&&f[0]==f[1]},ta.rebind(n,a,"on")};var Zl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Vl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Xl=fc.format=mc.timeFormat,$l=Xl.utc,Bl=$l("%Y-%m-%dT%H:%M:%S.%LZ");Xl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Jo:Bl,Jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Jo.toString=Bl.toString,fc.second=Ft(function(n){return new hc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),fc.seconds=fc.second.range,fc.seconds.utc=fc.second.utc.range,fc.minute=Ft(function(n){return new hc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),fc.minutes=fc.minute.range,fc.minutes.utc=fc.minute.utc.range,fc.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new hc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),fc.hours=fc.hour.range,fc.hours.utc=fc.hour.utc.range,fc.month=Ft(function(n){return n=fc.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),fc.months=fc.month.range,fc.months.utc=fc.month.utc.range;var Wl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Jl=[[fc.second,1],[fc.second,5],[fc.second,15],[fc.second,30],[fc.minute,1],[fc.minute,5],[fc.minute,15],[fc.minute,30],[fc.hour,1],[fc.hour,3],[fc.hour,6],[fc.hour,12],[fc.day,1],[fc.day,2],[fc.week,1],[fc.month,1],[fc.month,3],[fc.year,1]],Gl=Xl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ne]]),Kl={range:function(n,t,e){return ta.range(Math.ceil(n/e)*e,+t,e).map(Ko)},floor:Et,ceil:Et};Jl.year=fc.year,fc.scale=function(){return Go(ta.scale.linear(),Jl,Gl)};var Ql=Jl.map(function(n){return[n[0].utc,n[1]]}),ns=$l.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ne]]);Ql.year=fc.year.utc,fc.scale.utc=function(){return Go(ta.scale.linear(),Ql,ns)},ta.text=At(function(n){return n.responseText}),ta.json=function(n,t){return Nt(n,"application/json",Qo,t)},ta.html=function(n,t){return Nt(n,"text/html",na,t)},ta.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(ta):"object"==typeof module&&module.exports&&(module.exports=ta),this.d3=ta}();
|
inc/tpl/base.html.tpl
CHANGED
@@ -12,17 +12,20 @@
|
|
12 |
|
13 |
<h2 class="nav-tab-wrapper sucuriscan-navbar">
|
14 |
%%SUCURI.Navbar%%
|
|
|
|
|
|
|
15 |
</h2>
|
16 |
|
17 |
<div class="sucuriscan-maincontent sucuriscan-clearfix">
|
18 |
|
19 |
-
<div class="sucuriscan-leftside sucuriscan-%%SUCURI.PageStyleClass%%">
|
20 |
|
21 |
%%SUCURI.PageContent%%
|
22 |
|
23 |
</div>
|
24 |
|
25 |
-
<div class="sucuriscan-sidebar">
|
26 |
|
27 |
<div class="sucuriscan-ad">
|
28 |
<h2>Is your website infected with malware? Blacklisted by Google?</h2>
|
12 |
|
13 |
<h2 class="nav-tab-wrapper sucuriscan-navbar">
|
14 |
%%SUCURI.Navbar%%
|
15 |
+
|
16 |
+
<a class="button button-hero button-primary sucuriscan-review-hero"
|
17 |
+
href="http://goo.gl/aByqP5" target="_blank">Review Plugin</a>
|
18 |
</h2>
|
19 |
|
20 |
<div class="sucuriscan-maincontent sucuriscan-clearfix">
|
21 |
|
22 |
+
<div class="sucuriscan-leftside sucuriscan-%%SUCURI.LayoutType%% sucuriscan-%%SUCURI.PageStyleClass%%">
|
23 |
|
24 |
%%SUCURI.PageContent%%
|
25 |
|
26 |
</div>
|
27 |
|
28 |
+
<div class="sucuriscan-sidebar sucuriscan-%%SUCURI.AdsVisibility%%">
|
29 |
|
30 |
<div class="sucuriscan-ad">
|
31 |
<h2>Is your website infected with malware? Blacklisted by Google?</h2>
|
inc/tpl/infosys-errorlogs.html.tpl
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
<h3>Error Logs</h3>
|
5 |
|
6 |
<div class="inside">
|
|
|
7 |
<p>
|
8 |
Web servers like Apache, Nginx and others use files to record errors encountered
|
9 |
during the execution of a dynamic language or the server processes. Depending on
|
@@ -16,10 +17,10 @@
|
|
16 |
<p>
|
17 |
If you are a developer, you may want to check the latest errors encountered by
|
18 |
the server before delete the log file, that way you can see where the
|
19 |
-
application is failing and fix the errors. Note that
|
20 |
-
|
21 |
-
interpreter
|
22 |
-
|
23 |
</p>
|
24 |
</div>
|
25 |
|
@@ -29,6 +30,18 @@
|
|
29 |
panel in the <em>Settings</em> page to enable it.
|
30 |
</p>
|
31 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
</div>
|
33 |
</div>
|
34 |
</div>
|
@@ -60,6 +73,12 @@
|
|
60 |
<tbody>
|
61 |
%%SUCURI.ErrorLog.List%%
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
<tr class="sucuriscan-%%SUCURI.ErrorLog.NoItemsVisibility%%">
|
64 |
<td colspan="5">
|
65 |
<em>No logs so far.</em>
|
4 |
<h3>Error Logs</h3>
|
5 |
|
6 |
<div class="inside">
|
7 |
+
|
8 |
<p>
|
9 |
Web servers like Apache, Nginx and others use files to record errors encountered
|
10 |
during the execution of a dynamic language or the server processes. Depending on
|
17 |
<p>
|
18 |
If you are a developer, you may want to check the latest errors encountered by
|
19 |
the server before delete the log file, that way you can see where the
|
20 |
+
application is failing and fix the errors. Note that a log file may have
|
21 |
+
thousand of lines, so to prevent an overflow in the memory of the PHP
|
22 |
+
interpreter the plugin limits the process to the <strong>latest
|
23 |
+
%%SUCURI.ErrorLog.LogsLimit%% lines</strong> inserted in the log file.
|
24 |
</p>
|
25 |
</div>
|
26 |
|
30 |
panel in the <em>Settings</em> page to enable it.
|
31 |
</p>
|
32 |
</div>
|
33 |
+
|
34 |
+
<div class="sucuriscan-inline-alert-warning sucuriscan-%%SUCURI.ErrorLog.InvalidFormatVisibility%%">
|
35 |
+
<p>
|
36 |
+
Note that if the log file is not empty but the table is, it means that the
|
37 |
+
format of the logs used by the web server is not supported by the scanner,
|
38 |
+
you can try to increase the number of lines processed though from
|
39 |
+
<a href="%%SUCURI.URL.Settings%%#settings-scanner">here</a> in case that
|
40 |
+
other lines have a different format which is very common on servers with
|
41 |
+
mixed configurations.
|
42 |
+
</p>
|
43 |
+
</div>
|
44 |
+
|
45 |
</div>
|
46 |
</div>
|
47 |
</div>
|
73 |
<tbody>
|
74 |
%%SUCURI.ErrorLog.List%%
|
75 |
|
76 |
+
<tr class="sucuriscan-%%SUCURI.ErrorLog.InvalidFormatVisibility%%">
|
77 |
+
<td colspan="5">
|
78 |
+
<em>No valid logs in the last %%SUCURI.ErrorLog.LogsLimit%% lines of the error log file.</em>
|
79 |
+
</td>
|
80 |
+
</tr>
|
81 |
+
|
82 |
<tr class="sucuriscan-%%SUCURI.ErrorLog.NoItemsVisibility%%">
|
83 |
<td colspan="5">
|
84 |
<em>No logs so far.</em>
|
inc/tpl/infosys-errorlogs.snippet.tpl
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
<tr class="%%SUCURI.ErrorLog.CssClass%%">
|
3 |
<td>%%SUCURI.ErrorLog.DateTime%%</td>
|
4 |
<td><a href="#" title="%%SUCURI.ErrorLog.ErrorType%%" class="sucuriscan-label-%%SUCURI.ErrorLog.ErrorCode%%">%%SUCURI.ErrorLog.ErrorAbbr%%</a></td>
|
5 |
-
<td>%%SUCURI.ErrorLog.ErrorMessage%%</td>
|
6 |
<td><span class="sucuriscan-monospace sucuriscan-wraptext">%%SUCURI.ErrorLog.FilePath%%</span></td>
|
7 |
<td><span class="sucuriscan-monospace">%%SUCURI.ErrorLog.LineNumber%%</span></td>
|
8 |
</tr>
|
2 |
<tr class="%%SUCURI.ErrorLog.CssClass%%">
|
3 |
<td>%%SUCURI.ErrorLog.DateTime%%</td>
|
4 |
<td><a href="#" title="%%SUCURI.ErrorLog.ErrorType%%" class="sucuriscan-label-%%SUCURI.ErrorLog.ErrorCode%%">%%SUCURI.ErrorLog.ErrorAbbr%%</a></td>
|
5 |
+
<td><span class="sucuriscan-wraptext">%%SUCURI.ErrorLog.ErrorMessage%%</span></td>
|
6 |
<td><span class="sucuriscan-monospace sucuriscan-wraptext">%%SUCURI.ErrorLog.FilePath%%</span></td>
|
7 |
<td><span class="sucuriscan-monospace">%%SUCURI.ErrorLog.LineNumber%%</span></td>
|
8 |
</tr>
|
inc/tpl/infosys-wpconfig.snippet.tpl
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
|
2 |
<tr class="%%SUCURI.WordpressConfig.CssClass%%">
|
3 |
<td><span class="sucuriscan-monospace">%%SUCURI.WordpressConfig.VariableName%%</span></td>
|
4 |
-
<td><span class="%%SUCURI.WordpressConfig.VariableCssClass%%">%%SUCURI.WordpressConfig.VariableValue%%</span></td>
|
5 |
</tr>
|
1 |
|
2 |
<tr class="%%SUCURI.WordpressConfig.CssClass%%">
|
3 |
<td><span class="sucuriscan-monospace">%%SUCURI.WordpressConfig.VariableName%%</span></td>
|
4 |
+
<td><span class="sucuriscan-wraptext %%SUCURI.WordpressConfig.VariableCssClass%%">%%SUCURI.WordpressConfig.VariableValue%%</span></td>
|
5 |
</tr>
|
inc/tpl/integrity-auditlogs.html.tpl
CHANGED
@@ -2,11 +2,13 @@
|
|
2 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-auditlogs">
|
3 |
<thead>
|
4 |
<tr>
|
5 |
-
<th colspan="
|
6 |
</tr>
|
7 |
<tr>
|
8 |
-
<th
|
9 |
-
<th>
|
|
|
|
|
10 |
</tr>
|
11 |
</thead>
|
12 |
|
@@ -14,13 +16,13 @@
|
|
14 |
%%SUCURI.AuditLogs.List%%
|
15 |
|
16 |
<tr class="sucuriscan-%%SUCURI.AuditLogs.NoItemsVisibility%%">
|
17 |
-
<td colspan="
|
18 |
<em>No logs so far.</em>
|
19 |
</td>
|
20 |
</tr>
|
21 |
|
22 |
<tr class="sucuriscan-%%SUCURI.AuditLogs.PaginationVisibility%%">
|
23 |
-
<td colspan="
|
24 |
<ul class="sucuriscan-pagination">
|
25 |
%%SUCURI.AuditLogs.PaginationLinks%%
|
26 |
</ul>
|
2 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-auditlogs">
|
3 |
<thead>
|
4 |
<tr>
|
5 |
+
<th colspan="4">Audit Logs (%%SUCURI.AuditLogs.Count%% latest logs)</th>
|
6 |
</tr>
|
7 |
<tr>
|
8 |
+
<th> </th>
|
9 |
+
<th>Username</th>
|
10 |
+
<th>IP Address</th>
|
11 |
+
<th>Event Message</th>
|
12 |
</tr>
|
13 |
</thead>
|
14 |
|
16 |
%%SUCURI.AuditLogs.List%%
|
17 |
|
18 |
<tr class="sucuriscan-%%SUCURI.AuditLogs.NoItemsVisibility%%">
|
19 |
+
<td colspan="4">
|
20 |
<em>No logs so far.</em>
|
21 |
</td>
|
22 |
</tr>
|
23 |
|
24 |
<tr class="sucuriscan-%%SUCURI.AuditLogs.PaginationVisibility%%">
|
25 |
+
<td colspan="4">
|
26 |
<ul class="sucuriscan-pagination">
|
27 |
%%SUCURI.AuditLogs.PaginationLinks%%
|
28 |
</ul>
|
inc/tpl/integrity-auditlogs.snippet.tpl
CHANGED
@@ -1,9 +1,15 @@
|
|
1 |
|
2 |
<tr class="%%SUCURI.AuditLog.CssClass%%">
|
3 |
-
<td>%%SUCURI.AuditLog.DateTime%%</td>
|
4 |
<td>
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
|
8 |
</td>
|
9 |
</tr>
|
1 |
|
2 |
<tr class="%%SUCURI.AuditLog.CssClass%%">
|
|
|
3 |
<td>
|
4 |
+
<span href="#" title="%%SUCURI.AuditLog.DateTime%%"
|
5 |
+
class="sucuriscan-label sucuriscan-auditlog-%%SUCURI.AuditLog.Event%%">
|
6 |
+
%%SUCURI.AuditLog.EventTitle%%</span>
|
7 |
+
</td>
|
8 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.AuditLog.Username%%</span></td>
|
9 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.AuditLog.RemoteAddress%%</span></td>
|
10 |
+
<td>
|
11 |
+
<span>%%SUCURI.AuditLog.Message%%</span>
|
12 |
|
13 |
+
%%SUCURI.AuditLog.Extra%%
|
14 |
</td>
|
15 |
</tr>
|
inc/tpl/integrity-auditreport.html.tpl
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
<div class="postbox sucuriscan-audit-report sucuriscan-border">
|
3 |
+
<h3>Audit Report</h3>
|
4 |
+
|
5 |
+
<div class="inside">
|
6 |
+
|
7 |
+
<div class="sucuriscan-inline-alert-info">
|
8 |
+
<p>
|
9 |
+
The data used to generate these charts come from the last <strong>%%SUCURI.AuditReport.Logs4Report%%
|
10 |
+
audit logs</strong> of your site, you can configure this number from the plugin
|
11 |
+
settings. The categorization of each event may change at anytime but will be
|
12 |
+
associated to the severity of the performed action, this means that an event
|
13 |
+
generated after an user interaction <em>(authentications, database modifications,
|
14 |
+
website options)</em> will always be more severe than a simple notification or a
|
15 |
+
change in the plugin settings.
|
16 |
+
</p>
|
17 |
+
</div>
|
18 |
+
|
19 |
+
<div class="sucuriscan-clearfix sucuriscan-report-row">
|
20 |
+
|
21 |
+
<div class="sucuriscan-pull-left sucuriscan-report-chart">
|
22 |
+
<h4>Audit Logs per Event</h4>
|
23 |
+
<h5>source http://sucuri.net/</h5>
|
24 |
+
<div id="sucuriscan-report-events-per-type"></div>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
<div class="sucuriscan-pull-right sucuriscan-report-chart">
|
28 |
+
<h4>Successful/Failed Logins</h4>
|
29 |
+
<h5>source http://sucuri.net/</h5>
|
30 |
+
<div id="sucuriscan-report-events-per-login"></div>
|
31 |
+
</div>
|
32 |
+
|
33 |
+
</div>
|
34 |
+
|
35 |
+
<div class="sucuriscan-clearfix sucuriscan-report-row">
|
36 |
+
|
37 |
+
<div class="sucuriscan-pull-left sucuriscan-report-chart">
|
38 |
+
<h4>Audit Logs per User</h4>
|
39 |
+
<h5>source http://sucuri.net/</h5>
|
40 |
+
<div id="sucuriscan-report-events-per-user"></div>
|
41 |
+
</div>
|
42 |
+
|
43 |
+
<div class="sucuriscan-pull-right sucuriscan-report-chart">
|
44 |
+
<h4>Audit Logs per IP Address</h4>
|
45 |
+
<h5>source http://sucuri.net/</h5>
|
46 |
+
<div id="sucuriscan-report-events-per-ipaddress"></div>
|
47 |
+
</div>
|
48 |
+
|
49 |
+
</div>
|
50 |
+
|
51 |
+
</div>
|
52 |
+
</div>
|
53 |
+
|
54 |
+
<script type="text/javascript">
|
55 |
+
jQuery(document).ready(function($){
|
56 |
+
|
57 |
+
var sucuriscan_pie_chart = function( element, series, colors ){
|
58 |
+
c3.generate({
|
59 |
+
bindto: element,
|
60 |
+
size: { height: 250 },
|
61 |
+
padding: { top: 10, bottom: 10 },
|
62 |
+
color: { pattern: colors },
|
63 |
+
legend: { position: 'right' },
|
64 |
+
data: { type: 'pie', labels: true, columns: series },
|
65 |
+
});
|
66 |
+
};
|
67 |
+
|
68 |
+
var sucuriscan_bar_chart = function( element, categories, series ){
|
69 |
+
c3.generate({
|
70 |
+
bindto: element,
|
71 |
+
size: { height: 320 },
|
72 |
+
padding: { top: 10, bottom: 0 },
|
73 |
+
tooltip: { show: false },
|
74 |
+
legend: { show: false },
|
75 |
+
data: { type: 'bar', labels: true, columns: [ series ] },
|
76 |
+
axis: {
|
77 |
+
rotated: true,
|
78 |
+
x: { type: 'category', categories: categories },
|
79 |
+
},
|
80 |
+
});
|
81 |
+
};
|
82 |
+
|
83 |
+
/* Pie-chart with number of audit logs per event type. */
|
84 |
+
sucuriscan_pie_chart(
|
85 |
+
'#sucuriscan-report-events-per-type',
|
86 |
+
[ %%SUCURI.AuditReport.EventsPerType%% ],
|
87 |
+
[ %%SUCURI.AuditReport.EventColors%% ]
|
88 |
+
);
|
89 |
+
|
90 |
+
/* Column-chart with number of audit logs per event login. */
|
91 |
+
sucuriscan_pie_chart(
|
92 |
+
'#sucuriscan-report-events-per-login',
|
93 |
+
[ %%SUCURI.AuditReport.EventsPerLogin%% ],
|
94 |
+
[ '#5cb85c', '#f27d7d' ]
|
95 |
+
);
|
96 |
+
|
97 |
+
/* Bar-chart with number of audit logs per user account. */
|
98 |
+
sucuriscan_bar_chart(
|
99 |
+
'#sucuriscan-report-events-per-user',
|
100 |
+
[ %%SUCURI.AuditReport.EventsPerUserCategories%% ],
|
101 |
+
[ 'data', %%SUCURI.AuditReport.EventsPerUserSeries%% ]
|
102 |
+
);
|
103 |
+
|
104 |
+
/* Bar-chart with number of audit logs per remote address. */
|
105 |
+
sucuriscan_bar_chart(
|
106 |
+
'#sucuriscan-report-events-per-ipaddress',
|
107 |
+
[ %%SUCURI.AuditReport.EventsPerIPAddressCategories%% ],
|
108 |
+
[ 'data', %%SUCURI.AuditReport.EventsPerIPAddressSeries%% ]
|
109 |
+
);
|
110 |
+
|
111 |
+
});
|
112 |
+
</script>
|
inc/tpl/integrity-corefiles.html.tpl
CHANGED
@@ -14,26 +14,20 @@
|
|
14 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-corefiles sucuriscan-%%SUCURI.CoreFiles.BadVisibility%%">
|
15 |
<thead>
|
16 |
<tr>
|
17 |
-
<th colspan="
|
18 |
<span>Core integrity (%%SUCURI.CoreFiles.ListCount%% files)</span>
|
19 |
-
<
|
20 |
-
<span class="sucuriscan-status-type sucuriscan-status-added">Added</span>
|
21 |
-
<span class="sucuriscan-status-type sucuriscan-status-modified">Modified</span>
|
22 |
-
<span class="sucuriscan-status-type sucuriscan-status-removed">Removed</span>
|
23 |
-
<button id="sucuriscan-corefiles-show" class="button button-primary thead-topright-action" data-action="show">Show files</button>
|
24 |
-
</div>
|
25 |
</th>
|
26 |
</tr>
|
27 |
|
28 |
<tr>
|
29 |
-
<td colspan="
|
30 |
<div>
|
31 |
<p>
|
32 |
Changes in the integrity of your core files were detected. There are files that
|
33 |
-
were added, modified, and/or removed in the core directories
|
34 |
-
<code
|
35 |
-
|
36 |
-
infected with malicious code.
|
37 |
</p>
|
38 |
</div>
|
39 |
</td>
|
@@ -44,8 +38,9 @@
|
|
44 |
<label class="screen-reader-text" for="cb-select-all-1">Select All</label>
|
45 |
<input id="cb-select-all-1" type="checkbox">
|
46 |
</th>
|
47 |
-
<th width="
|
48 |
-
<th class="manage-column">
|
|
|
49 |
</tr>
|
50 |
</thead>
|
51 |
|
@@ -55,7 +50,7 @@
|
|
55 |
|
56 |
<tfoot>
|
57 |
<tr>
|
58 |
-
<td colspan="
|
59 |
<p>
|
60 |
The action to restore the content of a file will only work with files that were
|
61 |
<b>modified</b> or <b>removed</b>, for files that were <b>added</b> you must
|
@@ -68,7 +63,7 @@
|
|
68 |
<select name="sucuriscan_integrity_action">
|
69 |
<option value="">Choose action</option>
|
70 |
<option value="restore">Restore file(s) content</option>
|
71 |
-
<option value="
|
72 |
<option value="fixed">Mark as fixed</option>
|
73 |
</select>
|
74 |
</label>
|
14 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-corefiles sucuriscan-%%SUCURI.CoreFiles.BadVisibility%%">
|
15 |
<thead>
|
16 |
<tr>
|
17 |
+
<th colspan="4" class="sucuriscan-clearfix thead-with-button">
|
18 |
<span>Core integrity (%%SUCURI.CoreFiles.ListCount%% files)</span>
|
19 |
+
<button id="sucuriscan-corefiles-show" class="button button-primary thead-topright-action" data-action="show">Show files</button>
|
|
|
|
|
|
|
|
|
|
|
20 |
</th>
|
21 |
</tr>
|
22 |
|
23 |
<tr>
|
24 |
+
<td colspan="4" class="sucuriscan-corefiles-warning">
|
25 |
<div>
|
26 |
<p>
|
27 |
Changes in the integrity of your core files were detected. There are files that
|
28 |
+
were added, modified, and/or removed in the core directories <code>/<root></code>,
|
29 |
+
<code>/wp-admin</code> and/or <code>/wp-includes</code>. You may want to check
|
30 |
+
each file to determine if they were infected with malicious code.
|
|
|
31 |
</p>
|
32 |
</div>
|
33 |
</td>
|
38 |
<label class="screen-reader-text" for="cb-select-all-1">Select All</label>
|
39 |
<input id="cb-select-all-1" type="checkbox">
|
40 |
</th>
|
41 |
+
<th width="80" class="manage-column">Status</th>
|
42 |
+
<th width="100" class="manage-column">File Size</th>
|
43 |
+
<th class="manage-column">File Path</th>
|
44 |
</tr>
|
45 |
</thead>
|
46 |
|
50 |
|
51 |
<tfoot>
|
52 |
<tr>
|
53 |
+
<td colspan="4">
|
54 |
<p>
|
55 |
The action to restore the content of a file will only work with files that were
|
56 |
<b>modified</b> or <b>removed</b>, for files that were <b>added</b> you must
|
63 |
<select name="sucuriscan_integrity_action">
|
64 |
<option value="">Choose action</option>
|
65 |
<option value="restore">Restore file(s) content</option>
|
66 |
+
<option value="delete">Delete file(s)</option>
|
67 |
<option value="fixed">Mark as fixed</option>
|
68 |
</select>
|
69 |
</label>
|
inc/tpl/integrity-corefiles.snippet.tpl
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
<input type="checkbox" name="sucuriscan_integrity_files[]" value="%%SUCURI.CoreFiles.FilePath%%" />
|
5 |
<input type="hidden" name="sucuriscan_integrity_types[]" value="%%SUCURI.CoreFiles.StatusType%%" />
|
6 |
</td>
|
7 |
-
<td><span class="sucuriscan-
|
|
|
8 |
<td><span class="sucuriscan-monospace sucuriscan-wraptext">%%SUCURI.CoreFiles.FilePath%%</span></td>
|
9 |
</tr>
|
4 |
<input type="checkbox" name="sucuriscan_integrity_files[]" value="%%SUCURI.CoreFiles.FilePath%%" />
|
5 |
<input type="hidden" name="sucuriscan_integrity_types[]" value="%%SUCURI.CoreFiles.StatusType%%" />
|
6 |
</td>
|
7 |
+
<td><span class="sucuriscan-label sucuriscan-label-%%SUCURI.CoreFiles.StatusType%%">%%SUCURI.CoreFiles.StatusType%%</span></td>
|
8 |
+
<td><em title="%%SUCURI.CoreFiles.FileSizeNumber%% bytes">~%%SUCURI.CoreFiles.FileSizeHuman%%</em></td>
|
9 |
<td><span class="sucuriscan-monospace sucuriscan-wraptext">%%SUCURI.CoreFiles.FilePath%%</span></td>
|
10 |
</tr>
|
inc/tpl/integrity-modifiedfiles.html.tpl
CHANGED
@@ -1,4 +1,33 @@
|
|
1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-modifiedfiles">
|
3 |
<thead>
|
4 |
<tr>
|
@@ -22,9 +51,9 @@
|
|
22 |
</tr>
|
23 |
|
24 |
<tr>
|
25 |
-
<th>
|
26 |
-
<th width="
|
27 |
-
<th width="
|
28 |
</tr>
|
29 |
</thead>
|
30 |
|
1 |
|
2 |
+
<div id="poststuff">
|
3 |
+
<div class="postbox sucuriscan-border sucuriscan-table-description">
|
4 |
+
<h3>Modified Files</h3>
|
5 |
+
|
6 |
+
<div class="inside">
|
7 |
+
|
8 |
+
<p>
|
9 |
+
If your site was recently attacked, you can see which files were modified to
|
10 |
+
assist with any investigation. Note that in most Unix file systems, a file is
|
11 |
+
considered modified when its inode data is changed; that is, when the
|
12 |
+
permissions, owner, group, or other metadata from the inode is updated.
|
13 |
+
</p>
|
14 |
+
|
15 |
+
<div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.ModifiedFiles.DisabledVisibility%%">
|
16 |
+
<p>
|
17 |
+
The scanner that searches for modified files under the content directory is
|
18 |
+
disabled. This tool is disabled by default to prevent an overflow in the memory
|
19 |
+
of the PHP interpreter in the majority of websites that have too many files in
|
20 |
+
their projects, but you can enable this scanner from
|
21 |
+
<a href="%%SUCURI.URL.Settings%%#settings-scanner">here</a> though, and if you
|
22 |
+
experience issues like <em>"Internal Server Error"</em> messages or blank pages
|
23 |
+
just disable the scanner again.
|
24 |
+
</p>
|
25 |
+
</div>
|
26 |
+
|
27 |
+
</div>
|
28 |
+
</div>
|
29 |
+
</div>
|
30 |
+
|
31 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-table-double-title sucuriscan-modifiedfiles">
|
32 |
<thead>
|
33 |
<tr>
|
51 |
</tr>
|
52 |
|
53 |
<tr>
|
54 |
+
<th>File Path</th>
|
55 |
+
<th width="100">File Size</th>
|
56 |
+
<th width="190">Modified at</th>
|
57 |
</tr>
|
58 |
</thead>
|
59 |
|
inc/tpl/integrity-modifiedfiles.snippet.tpl
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
|
2 |
<tr class="sucuriscan-wraptext %%SUCURI.ModifiedFiles.CssClass%%">
|
3 |
<td><span class="sucuriscan-monospace">%%SUCURI.ModifiedFiles.FilePath%%</span></td>
|
4 |
-
<td><
|
5 |
<td>%%SUCURI.ModifiedFiles.DateTime%%</td>
|
6 |
</tr>
|
1 |
|
2 |
<tr class="sucuriscan-wraptext %%SUCURI.ModifiedFiles.CssClass%%">
|
3 |
<td><span class="sucuriscan-monospace">%%SUCURI.ModifiedFiles.FilePath%%</span></td>
|
4 |
+
<td><em title="%%SUCURI.ModifiedFiles.FileSizeNumber%% bytes">~%%SUCURI.ModifiedFiles.FileSizeHuman%%</em></td>
|
5 |
<td>%%SUCURI.ModifiedFiles.DateTime%%</td>
|
6 |
</tr>
|
inc/tpl/integrity.html.tpl
CHANGED
@@ -4,5 +4,7 @@
|
|
4 |
|
5 |
%%SUCURI.CoreFiles%%
|
6 |
|
|
|
|
|
7 |
%%SUCURI.AuditLogs%%
|
8 |
</div>
|
4 |
|
5 |
%%SUCURI.CoreFiles%%
|
6 |
|
7 |
+
%%SUCURI.AuditReports%%
|
8 |
+
|
9 |
%%SUCURI.AuditLogs%%
|
10 |
</div>
|
inc/tpl/lastlogins-admins.snippet.tpl
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
<td>%%SUCURI.AdminUsers.RegisteredAt%%</td>
|
5 |
<td class="adminusers-lastlogin">
|
6 |
<div class="sucuriscan-%%SUCURI.AdminUsers.NoLastLogins%%">
|
7 |
-
<i>
|
8 |
</div>
|
9 |
|
10 |
<table class="widefat sucuriscan-admins-lastlogins sucuriscan-%%SUCURI.AdminUsers.NoLastLoginsTable%%">
|
4 |
<td>%%SUCURI.AdminUsers.RegisteredAt%%</td>
|
5 |
<td class="adminusers-lastlogin">
|
6 |
<div class="sucuriscan-%%SUCURI.AdminUsers.NoLastLogins%%">
|
7 |
+
<i>No data available for this account.</i>
|
8 |
</div>
|
9 |
|
10 |
<table class="widefat sucuriscan-admins-lastlogins sucuriscan-%%SUCURI.AdminUsers.NoLastLoginsTable%%">
|
inc/tpl/lastlogins-failedlogins.html.tpl
CHANGED
@@ -5,30 +5,35 @@
|
|
5 |
|
6 |
<div class="inside">
|
7 |
<p>
|
8 |
-
This information will be used to determine if your site is being victim of
|
9 |
-
<a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
|
10 |
-
|
11 |
-
|
12 |
-
<
|
13 |
-
|
|
|
|
|
|
|
|
|
14 |
</p>
|
15 |
|
16 |
-
<div class="sucuriscan-inline-alert-
|
17 |
<p>
|
18 |
-
The option to
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
22 |
</p>
|
23 |
</div>
|
24 |
|
25 |
<div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.FailedLogins.CollectPasswordsVisibility%%">
|
26 |
<p>
|
27 |
-
If you
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
attacker may get your credentials and invade your site. Change this from the <a
|
32 |
href="%%SUCURI.URL.Settings%%#settings-general">general settings</a>
|
33 |
</p>
|
34 |
</div>
|
5 |
|
6 |
<div class="inside">
|
7 |
<p>
|
8 |
+
This information will be used to determine if your site is being victim of
|
9 |
+
<a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
|
10 |
+
target="_blank">Password Guessing Brute Force Attacks</a>. These logs will be
|
11 |
+
accumulated and the plugin will send a report via email if there are more than
|
12 |
+
<code>%%SUCURI.FailedLogins.MaxFailedLogins%%</code> failed login attempts during
|
13 |
+
the same hour, you can change this number from <a href="%%SUCURI.URL.Settings%%#settings-general">here</a>.
|
14 |
+
<strong>Note.</strong> Some <em>"Two-Factor Authentication"</em> plugins do not
|
15 |
+
follow the same rules that WordPress have to report failed login attempts, so
|
16 |
+
you may not see all the attempts in this panel if you have one of these plugins
|
17 |
+
installed.
|
18 |
</p>
|
19 |
|
20 |
+
<div class="sucuriscan-inline-alert-warning sucuriscan-%%SUCURI.FailedLogins.WarningVisibility%%">
|
21 |
<p>
|
22 |
+
The option to alert possible <a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing"
|
23 |
+
target="_blank">Password Guessing Brute Force Attacks</a> is disabled, you will
|
24 |
+
not receive email reports with the attempts collected during the attacks, but
|
25 |
+
you will continue receiving the alerts of failed logins if you have enabled that
|
26 |
+
option. Go to the <a href="%%SUCURI.URL.Settings%%#settings-notifications">alert
|
27 |
+
settings</a> panel to change this configuration.
|
28 |
</p>
|
29 |
</div>
|
30 |
|
31 |
<div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.FailedLogins.CollectPasswordsVisibility%%">
|
32 |
<p>
|
33 |
+
If you mistype your password the plugin will log the username and password in
|
34 |
+
the security logs. If someone get access to your API key, or your server fails
|
35 |
+
to process the PHP files (which is not usual but may happen) then an attacker
|
36 |
+
may steal your credentials and get access to your site. Change this from the <a
|
|
|
37 |
href="%%SUCURI.URL.Settings%%#settings-general">general settings</a>
|
38 |
</p>
|
39 |
</div>
|
inc/tpl/posthack-resetpassword.html.tpl
CHANGED
@@ -37,6 +37,14 @@
|
|
37 |
|
38 |
<tbody>
|
39 |
%%SUCURI.ResetPassword.UserList%%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
</tbody>
|
41 |
</table>
|
42 |
|
37 |
|
38 |
<tbody>
|
39 |
%%SUCURI.ResetPassword.UserList%%
|
40 |
+
|
41 |
+
<tr class="sucuriscan-%%SUCURI.ResetPassword.PaginationVisibility%%">
|
42 |
+
<td colspan="4">
|
43 |
+
<ul class="sucuriscan-pagination">
|
44 |
+
%%SUCURI.ResetPassword.PaginationLinks%%
|
45 |
+
</ul>
|
46 |
+
</td>
|
47 |
+
</tr>
|
48 |
</tbody>
|
49 |
</table>
|
50 |
|
inc/tpl/posthack-resetpassword.snippet.tpl
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
<td class="check-column">
|
4 |
<input type="checkbox" name="user_ids[]" value="%%SUCURI.ResetPassword.UserId%%" />
|
5 |
</td>
|
6 |
-
<td>%%SUCURI.ResetPassword.
|
7 |
<td><a href="mailto:%%SUCURI.ResetPassword.Email%%">%%SUCURI.ResetPassword.Email%%</a></td>
|
8 |
<td>%%SUCURI.ResetPassword.Registered%%</td>
|
9 |
<td>%%SUCURI.ResetPassword.Roles%%</td>
|
3 |
<td class="check-column">
|
4 |
<input type="checkbox" name="user_ids[]" value="%%SUCURI.ResetPassword.UserId%%" />
|
5 |
</td>
|
6 |
+
<td>%%SUCURI.ResetPassword.DisplayUsername%%</td>
|
7 |
<td><a href="mailto:%%SUCURI.ResetPassword.Email%%">%%SUCURI.ResetPassword.Email%%</a></td>
|
8 |
<td>%%SUCURI.ResetPassword.Registered%%</td>
|
9 |
<td>%%SUCURI.ResetPassword.Roles%%</td>
|
inc/tpl/settings-general.html.tpl
CHANGED
@@ -38,7 +38,7 @@
|
|
38 |
</tr>
|
39 |
|
40 |
<tr class="alternate">
|
41 |
-
<td width="200">API
|
42 |
<td>
|
43 |
<span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
|
44 |
</td>
|
@@ -62,19 +62,31 @@
|
|
62 |
</tr>
|
63 |
|
64 |
<tr>
|
65 |
-
<td>
|
66 |
-
<td>%%SUCURI.
|
67 |
<td class="td-with-button">
|
68 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
69 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
70 |
-
<input type="text" name="
|
71 |
<button type="submit" class="button-primary">Change</button>
|
72 |
</form>
|
73 |
</td>
|
74 |
</tr>
|
75 |
|
76 |
<tr class="alternate">
|
77 |
-
<td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
<td>%%SUCURI.EmailsPerHour%%</td>
|
79 |
<td class="td-with-button">
|
80 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
@@ -87,7 +99,7 @@
|
|
87 |
</td>
|
88 |
</tr>
|
89 |
|
90 |
-
<tr>
|
91 |
<td>Consider brute-force after</td>
|
92 |
<td>%%SUCURI.MaximumFailedLogins%%</td>
|
93 |
<td class="td-with-button">
|
@@ -101,8 +113,8 @@
|
|
101 |
</td>
|
102 |
</tr>
|
103 |
|
104 |
-
<tr
|
105 |
-
<td>
|
106 |
<td>%%SUCURI.VerifySSLCert%%</td>
|
107 |
<td class="td-with-button">
|
108 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
@@ -115,7 +127,7 @@
|
|
115 |
</td>
|
116 |
</tr>
|
117 |
|
118 |
-
<tr>
|
119 |
<td>API request timeout</td>
|
120 |
<td>%%SUCURI.RequestTimeout%%</td>
|
121 |
<td class="td-with-button">
|
@@ -139,17 +151,53 @@
|
|
139 |
</td>
|
140 |
</tr>
|
141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
<tr>
|
143 |
-
<td>
|
144 |
-
<td
|
145 |
<td class="td-with-button">
|
146 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
147 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
148 |
-
<input type="text" name="
|
149 |
<button type="submit" class="button-primary">Change</button>
|
150 |
</form>
|
151 |
</td>
|
152 |
</tr>
|
153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
</tbody>
|
155 |
</table>
|
38 |
</tr>
|
39 |
|
40 |
<tr class="alternate">
|
41 |
+
<td width="200">Sucuri API key</td>
|
42 |
<td>
|
43 |
<span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
|
44 |
</td>
|
62 |
</tr>
|
63 |
|
64 |
<tr>
|
65 |
+
<td>Log storage path</td>
|
66 |
+
<td class="sucuriscan-wraptext"><span class="sucuriscan-monospace" title="%%SUCURI.DatastorePath%%">%%SUCURI.DatastorePath%%</span></td>
|
67 |
<td class="td-with-button">
|
68 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
69 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
70 |
+
<input type="text" name="sucuriscan_datastore_path" class="input-text" placeholder="Directory to save logs..." />
|
71 |
<button type="submit" class="button-primary">Change</button>
|
72 |
</form>
|
73 |
</td>
|
74 |
</tr>
|
75 |
|
76 |
<tr class="alternate">
|
77 |
+
<td>Send plugin alerts to</td>
|
78 |
+
<td>%%SUCURI.NotifyTo%%</td>
|
79 |
+
<td class="td-with-button">
|
80 |
+
<form action="%%SUCURI.URL.Settings%%" method="post">
|
81 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
82 |
+
<input type="text" name="sucuriscan_notify_to" class="input-text" placeholder="Separated by commas" />
|
83 |
+
<button type="submit" class="button-primary">Change</button>
|
84 |
+
</form>
|
85 |
+
</td>
|
86 |
+
</tr>
|
87 |
+
|
88 |
+
<tr>
|
89 |
+
<td>Maximum alerts per hour</td>
|
90 |
<td>%%SUCURI.EmailsPerHour%%</td>
|
91 |
<td class="td-with-button">
|
92 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
99 |
</td>
|
100 |
</tr>
|
101 |
|
102 |
+
<tr class="alternate">
|
103 |
<td>Consider brute-force after</td>
|
104 |
<td>%%SUCURI.MaximumFailedLogins%%</td>
|
105 |
<td class="td-with-button">
|
113 |
</td>
|
114 |
</tr>
|
115 |
|
116 |
+
<tr>
|
117 |
+
<td>API request with SSL</td>
|
118 |
<td>%%SUCURI.VerifySSLCert%%</td>
|
119 |
<td class="td-with-button">
|
120 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
127 |
</td>
|
128 |
</tr>
|
129 |
|
130 |
+
<tr class="alternate">
|
131 |
<td>API request timeout</td>
|
132 |
<td>%%SUCURI.RequestTimeout%%</td>
|
133 |
<td class="td-with-button">
|
151 |
</td>
|
152 |
</tr>
|
153 |
|
154 |
+
<tr class="alternate">
|
155 |
+
<td>Display audit report</td>
|
156 |
+
<td>%%SUCURI.AuditReportStatus%%</td>
|
157 |
+
<td class="td-with-button">
|
158 |
+
<form action="%%SUCURI.URL.Settings%%" method="post">
|
159 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
160 |
+
<input type="hidden" name="sucuriscan_audit_report" value="%%SUCURI.AuditReportSwitchValue%%" />
|
161 |
+
<button type="submit" class="button-primary %%SUCURI.AuditReportSwitchCssClass%%">%%SUCURI.AuditReportSwitchText%%</button>
|
162 |
+
</form>
|
163 |
+
</td>
|
164 |
+
</tr>
|
165 |
+
|
166 |
<tr>
|
167 |
+
<td>Audit report limit</td>
|
168 |
+
<td>Process latest %%SUCURI.AuditReportLimit%% logs</td>
|
169 |
<td class="td-with-button">
|
170 |
<form action="%%SUCURI.URL.Settings%%" method="post">
|
171 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
172 |
+
<input type="text" name="sucuriscan_logs4report" class="input-text" placeholder="e.g. 500" />
|
173 |
<button type="submit" class="button-primary">Change</button>
|
174 |
</form>
|
175 |
</td>
|
176 |
</tr>
|
177 |
|
178 |
+
<tr class="alternate">
|
179 |
+
<td>Plugin advertisement</td>
|
180 |
+
<td>%%SUCURI.AdsVisibility%%</td>
|
181 |
+
<td class="td-with-button">
|
182 |
+
<form action="%%SUCURI.URL.Settings%%" method="post">
|
183 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
184 |
+
<input type="text" name="sucuriscan_ads_visibility" class="input-text" placeholder="Type: SHOW or HIDE" />
|
185 |
+
<button type="submit" class="button-primary">Change</button>
|
186 |
+
</form>
|
187 |
+
</td>
|
188 |
+
</tr>
|
189 |
+
|
190 |
+
<tr>
|
191 |
+
<td>Support reverse proxy</td>
|
192 |
+
<td>%%SUCURI.ReverseProxyStatus%%</td>
|
193 |
+
<td class="td-with-button">
|
194 |
+
<form action="%%SUCURI.URL.Settings%%" method="post">
|
195 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
196 |
+
<input type="hidden" name="sucuriscan_revproxy" value="%%SUCURI.ReverseProxySwitchValue%%" />
|
197 |
+
<button type="submit" class="button-primary %%SUCURI.ReverseProxySwitchCssClass%%">%%SUCURI.ReverseProxySwitchText%%</button>
|
198 |
+
</form>
|
199 |
+
</td>
|
200 |
+
</tr>
|
201 |
+
|
202 |
</tbody>
|
203 |
</table>
|
inc/tpl/settings-ignorerules.html.tpl
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
|
2 |
<div id="poststuff">
|
3 |
<div class="postbox sucuriscan-border sucuriscan-border-bad sucuriscan-%%SUCURI.IgnoreRules.MessageVisibility%%">
|
4 |
-
<h3>Ignore
|
5 |
|
6 |
<div class="inside">
|
7 |
<p>
|
@@ -17,7 +17,7 @@
|
|
17 |
|
18 |
<div id="poststuff">
|
19 |
<div class="postbox sucuriscan-border sucuriscan-table-description sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
|
20 |
-
<h3>Ignore
|
21 |
|
22 |
<div class="inside">
|
23 |
<p>
|
@@ -34,7 +34,7 @@
|
|
34 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-settings-ignorerules sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
|
35 |
<thead>
|
36 |
<tr>
|
37 |
-
<th
|
38 |
<th>Post Type</th>
|
39 |
<th width="50">Ignored</th>
|
40 |
<th>Ignored At</th>
|
1 |
|
2 |
<div id="poststuff">
|
3 |
<div class="postbox sucuriscan-border sucuriscan-border-bad sucuriscan-%%SUCURI.IgnoreRules.MessageVisibility%%">
|
4 |
+
<h3>Ignore Alerts</h3>
|
5 |
|
6 |
<div class="inside">
|
7 |
<p>
|
17 |
|
18 |
<div id="poststuff">
|
19 |
<div class="postbox sucuriscan-border sucuriscan-table-description sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
|
20 |
+
<h3>Ignore Alerts</h3>
|
21 |
|
22 |
<div class="inside">
|
23 |
<p>
|
34 |
<table class="wp-list-table widefat sucuriscan-table sucuriscan-settings-ignorerules sucuriscan-%%SUCURI.IgnoreRules.TableVisibility%%">
|
35 |
<thead>
|
36 |
<tr>
|
37 |
+
<th> </th>
|
38 |
<th>Post Type</th>
|
39 |
<th width="50">Ignored</th>
|
40 |
<th>Ignored At</th>
|
inc/tpl/settings-ignorescanning.html.tpl
CHANGED
@@ -33,7 +33,7 @@
|
|
33 |
<label class="screen-reader-text" for="cb-select-all-1">Select All</label>
|
34 |
<input id="cb-select-all-1" type="checkbox">
|
35 |
</th>
|
36 |
-
<th class="manage-column"
|
37 |
<th class="manage-column">Directory</th>
|
38 |
<th class="manage-column" width="180">Ignored At</th>
|
39 |
</thead>
|
33 |
<label class="screen-reader-text" for="cb-select-all-1">Select All</label>
|
34 |
<input id="cb-select-all-1" type="checkbox">
|
35 |
</th>
|
36 |
+
<th class="manage-column"> </th>
|
37 |
<th class="manage-column">Directory</th>
|
38 |
<th class="manage-column" width="180">Ignored At</th>
|
39 |
</thead>
|
inc/tpl/settings-notifications.snippet.tpl
CHANGED
@@ -5,7 +5,7 @@
|
|
5 |
<label>
|
6 |
<input type="hidden" name="%%SUCURI.Notification.Name%%" value="0" />
|
7 |
<input type="checkbox" name="%%SUCURI.Notification.Name%%" value="1" %%SUCURI.Notification.Checked%% />
|
8 |
-
<span>%%SUCURI.Notification.Label%%</span>
|
9 |
</label>
|
10 |
</div>
|
11 |
</td>
|
5 |
<label>
|
6 |
<input type="hidden" name="%%SUCURI.Notification.Name%%" value="0" />
|
7 |
<input type="checkbox" name="%%SUCURI.Notification.Name%%" value="1" %%SUCURI.Notification.Checked%% />
|
8 |
+
<span class="%%SUCURI.Notification.LabelIcon%%">%%SUCURI.Notification.Label%%</span>
|
9 |
</label>
|
10 |
</div>
|
11 |
</td>
|
inc/tpl/settings-scanner.html.tpl
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
<h3>Scanner Settings</h3>
|
5 |
|
6 |
<div class="inside">
|
|
|
7 |
<p>
|
8 |
There are multiple scanners implemented in the code of the plugin, all of them
|
9 |
are enabled by default and you can deactivate them separately without affect the
|
@@ -15,14 +16,15 @@
|
|
15 |
|
16 |
<div class="sucuriscan-inline-alert-info">
|
17 |
<p>
|
18 |
-
The <em>Scanning
|
19 |
-
|
20 |
-
scanners are executed.
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
</p>
|
25 |
</div>
|
|
|
26 |
</div>
|
27 |
</div>
|
28 |
</div>
|
@@ -37,8 +39,48 @@
|
|
37 |
</thead>
|
38 |
|
39 |
<tbody>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
<tr class="alternate">
|
41 |
-
<td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
<td>%%SUCURI.FsScannerStatus%%</td>
|
43 |
<td class="td-with-button">
|
44 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
@@ -50,7 +92,7 @@
|
|
50 |
</tr>
|
51 |
|
52 |
<tr>
|
53 |
-
<td>
|
54 |
<td>%%SUCURI.ScanModfilesStatus%%</td>
|
55 |
<td class="td-with-button">
|
56 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
@@ -62,7 +104,7 @@
|
|
62 |
</tr>
|
63 |
|
64 |
<tr class="alternate">
|
65 |
-
<td>
|
66 |
<td>%%SUCURI.ScanChecksumsStatus%%</td>
|
67 |
<td class="td-with-button">
|
68 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
@@ -74,7 +116,7 @@
|
|
74 |
</tr>
|
75 |
|
76 |
<tr>
|
77 |
-
<td>Ignore
|
78 |
<td>%%SUCURI.IgnoreScanningStatus%%</td>
|
79 |
<td class="td-with-button">
|
80 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
@@ -86,7 +128,7 @@
|
|
86 |
</tr>
|
87 |
|
88 |
<tr class="alternate">
|
89 |
-
<td>
|
90 |
<td>%%SUCURI.ScanErrorlogsStatus%%</td>
|
91 |
<td class="td-with-button">
|
92 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
@@ -98,88 +140,97 @@
|
|
98 |
</tr>
|
99 |
|
100 |
<tr>
|
101 |
-
<td>
|
102 |
-
<td>%%SUCURI.
|
103 |
<td class="td-with-button">
|
104 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
105 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
106 |
-
<input type="hidden" name="
|
107 |
-
<button type="submit" class="button-primary %%SUCURI.
|
108 |
</form>
|
109 |
</td>
|
110 |
</tr>
|
111 |
|
112 |
<tr class="alternate">
|
113 |
-
<td>SiteCheck
|
114 |
-
<td>%%SUCURI.
|
115 |
<td class="td-with-button">
|
116 |
-
<form action="%%SUCURI.URL.
|
117 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
118 |
-
<input type="hidden" name="
|
119 |
-
<button type="submit" class="button-primary
|
120 |
</form>
|
121 |
</td>
|
122 |
</tr>
|
123 |
|
124 |
<tr>
|
125 |
-
<td>
|
126 |
-
<td
|
127 |
<td class="td-with-button">
|
128 |
-
<form action="%%SUCURI.URL.
|
129 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
130 |
-
<input type="hidden" name="
|
131 |
-
<button type="submit" class="button-primary"
|
132 |
</form>
|
133 |
</td>
|
134 |
</tr>
|
135 |
|
136 |
<tr class="alternate">
|
137 |
-
<td>
|
138 |
-
<td
|
139 |
<td class="td-with-button">
|
140 |
-
<form action="%%SUCURI.URL.
|
141 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
142 |
-
<
|
|
|
143 |
</form>
|
144 |
</td>
|
145 |
</tr>
|
146 |
|
147 |
<tr>
|
148 |
-
<td>
|
149 |
-
<td>%%SUCURI.
|
150 |
<td class="td-with-button">
|
151 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
152 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
153 |
-
<
|
154 |
-
|
155 |
-
</select>
|
156 |
-
<button type="submit" class="button-primary">Change</button>
|
157 |
</form>
|
158 |
</td>
|
159 |
</tr>
|
160 |
|
161 |
<tr class="alternate">
|
162 |
-
<td>
|
163 |
-
<td>%%SUCURI.
|
164 |
<td class="td-with-button">
|
165 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
166 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
167 |
-
<
|
168 |
-
|
169 |
-
</select>
|
170 |
-
<button type="submit" class="button-primary">Change</button>
|
171 |
</form>
|
172 |
</td>
|
173 |
</tr>
|
174 |
|
175 |
<tr>
|
176 |
-
<td>
|
177 |
-
<td>%%SUCURI.
|
178 |
<td class="td-with-button">
|
179 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
180 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
181 |
-
<input type="
|
182 |
-
<button type="submit" class="button-primary">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
</form>
|
184 |
</td>
|
185 |
</tr>
|
4 |
<h3>Scanner Settings</h3>
|
5 |
|
6 |
<div class="inside">
|
7 |
+
|
8 |
<p>
|
9 |
There are multiple scanners implemented in the code of the plugin, all of them
|
10 |
are enabled by default and you can deactivate them separately without affect the
|
16 |
|
17 |
<div class="sucuriscan-inline-alert-info">
|
18 |
<p>
|
19 |
+
The <em>"Scanning Algorithm"</em> is the method that will be used to read the
|
20 |
+
diretories and files contained in the project when any of the file system
|
21 |
+
scanners are executed. The best option is SPL <em>(Standard PHP Library)</em>
|
22 |
+
but it is not available in all versions of the PHP interpreter. We recommend to
|
23 |
+
upgrade the version of PHP installed in the server, but if you can not do this
|
24 |
+
then choose a different algorithm.
|
25 |
</p>
|
26 |
</div>
|
27 |
+
|
28 |
</div>
|
29 |
</div>
|
30 |
</div>
|
39 |
</thead>
|
40 |
|
41 |
<tbody>
|
42 |
+
|
43 |
+
<tr>
|
44 |
+
<td>Last background scan</td>
|
45 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
|
46 |
+
<td class="td-with-button">
|
47 |
+
<form action="%%SUCURI.URL.Home%%" method="post">
|
48 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
49 |
+
<button type="submit" name="sucuriscan_force_scan" class="button-primary">Force scan</button>
|
50 |
+
</form>
|
51 |
+
</td>
|
52 |
+
</tr>
|
53 |
+
|
54 |
<tr class="alternate">
|
55 |
+
<td>Scanning algorithm</td>
|
56 |
+
<td>%%SUCURI.ScanningInterface%%</td>
|
57 |
+
<td class="td-with-button">
|
58 |
+
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
59 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
60 |
+
<select name="sucuriscan_scan_interface">
|
61 |
+
%%SUCURI.ScanningInterfaceOptions%%
|
62 |
+
</select>
|
63 |
+
<button type="submit" class="button-primary">Change</button>
|
64 |
+
</form>
|
65 |
+
</td>
|
66 |
+
</tr>
|
67 |
+
|
68 |
+
<tr>
|
69 |
+
<td>Scanning frequency</td>
|
70 |
+
<td>%%SUCURI.ScanningFrequency%%</td>
|
71 |
+
<td class="td-with-button">
|
72 |
+
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
73 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
74 |
+
<select name="sucuriscan_scan_frequency">
|
75 |
+
%%SUCURI.ScanningFrequencyOptions%%
|
76 |
+
</select>
|
77 |
+
<button type="submit" class="button-primary">Change</button>
|
78 |
+
</form>
|
79 |
+
</td>
|
80 |
+
</tr>
|
81 |
+
|
82 |
+
<tr class="alternate">
|
83 |
+
<td>Main <abbr title="File System Scanner">FS Scanner</abbr></td>
|
84 |
<td>%%SUCURI.FsScannerStatus%%</td>
|
85 |
<td class="td-with-button">
|
86 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
92 |
</tr>
|
93 |
|
94 |
<tr>
|
95 |
+
<td>FS Scanner, Modified files</td>
|
96 |
<td>%%SUCURI.ScanModfilesStatus%%</td>
|
97 |
<td class="td-with-button">
|
98 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
104 |
</tr>
|
105 |
|
106 |
<tr class="alternate">
|
107 |
+
<td>FS Scanner, Core integrity checks</td>
|
108 |
<td>%%SUCURI.ScanChecksumsStatus%%</td>
|
109 |
<td class="td-with-button">
|
110 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
116 |
</tr>
|
117 |
|
118 |
<tr>
|
119 |
+
<td>FS Scanner, Ignore scanning</td>
|
120 |
<td>%%SUCURI.IgnoreScanningStatus%%</td>
|
121 |
<td class="td-with-button">
|
122 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
128 |
</tr>
|
129 |
|
130 |
<tr class="alternate">
|
131 |
+
<td>FS Scanner, Error log files</td>
|
132 |
<td>%%SUCURI.ScanErrorlogsStatus%%</td>
|
133 |
<td class="td-with-button">
|
134 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
140 |
</tr>
|
141 |
|
142 |
<tr>
|
143 |
+
<td>SiteCheck scanner</td>
|
144 |
+
<td>%%SUCURI.SiteCheckScannerStatus%%</td>
|
145 |
<td class="td-with-button">
|
146 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
147 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
148 |
+
<input type="hidden" name="sucuriscan_sitecheck_scanner" value="%%SUCURI.SiteCheckScannerSwitchValue%%" />
|
149 |
+
<button type="submit" class="button-primary %%SUCURI.SiteCheckScannerSwitchCssClass%%">%%SUCURI.SiteCheckScannerSwitchText%%</button>
|
150 |
</form>
|
151 |
</td>
|
152 |
</tr>
|
153 |
|
154 |
<tr class="alternate">
|
155 |
+
<td>SiteCheck counter</td>
|
156 |
+
<td>%%SUCURI.SiteCheckCounter%% scans so far</td>
|
157 |
<td class="td-with-button">
|
158 |
+
<form action="%%SUCURI.URL.Scanner%%" method="post">
|
159 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
160 |
+
<input type="hidden" name="sucuriscan_malware_scan" value="1" />
|
161 |
+
<button type="submit" class="button-primary">Force scan</button>
|
162 |
</form>
|
163 |
</td>
|
164 |
</tr>
|
165 |
|
166 |
<tr>
|
167 |
+
<td>Analyze error logs</td>
|
168 |
+
<td>%%SUCURI.ParseErrorLogsStatus%%</td>
|
169 |
<td class="td-with-button">
|
170 |
+
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
171 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
172 |
+
<input type="hidden" name="sucuriscan_parse_errorlogs" value="%%SUCURI.ParseErrorLogsSwitchValue%%" />
|
173 |
+
<button type="submit" class="button-primary %%SUCURI.ParseErrorLogsSwitchCssClass%%">%%SUCURI.ParseErrorLogsSwitchText%%</button>
|
174 |
</form>
|
175 |
</td>
|
176 |
</tr>
|
177 |
|
178 |
<tr class="alternate">
|
179 |
+
<td>Error logs limit</td>
|
180 |
+
<td>Analyze last %%SUCURI.ErrorLogsLimit%% logs</td>
|
181 |
<td class="td-with-button">
|
182 |
+
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
183 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
184 |
+
<input type="text" name="sucuriscan_errorlogs_limit" placeholder="Number of lines to analyze" class="input-text" />
|
185 |
+
<button type="submit" class="button-primary">Change</button>
|
186 |
</form>
|
187 |
</td>
|
188 |
</tr>
|
189 |
|
190 |
<tr>
|
191 |
+
<td>Reset core integrity logs</td>
|
192 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.IntegrityLogLife%% of data</span></td>
|
193 |
<td class="td-with-button">
|
194 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
195 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
196 |
+
<input type="hidden" name="sucuriscan_reset_logfile" value="integrity" />
|
197 |
+
<button type="submit" class="button-primary">Reset logs</button>
|
|
|
|
|
198 |
</form>
|
199 |
</td>
|
200 |
</tr>
|
201 |
|
202 |
<tr class="alternate">
|
203 |
+
<td>Reset last login logs</td>
|
204 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.LastLoginLogLife%% of data</span></td>
|
205 |
<td class="td-with-button">
|
206 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
207 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
208 |
+
<input type="hidden" name="sucuriscan_reset_logfile" value="lastlogins" />
|
209 |
+
<button type="submit" class="button-primary">Reset logs</button>
|
|
|
|
|
210 |
</form>
|
211 |
</td>
|
212 |
</tr>
|
213 |
|
214 |
<tr>
|
215 |
+
<td>Reset failed login logs</td>
|
216 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.FailedLoginLogLife%% of data</span></td>
|
217 |
<td class="td-with-button">
|
218 |
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
219 |
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
220 |
+
<input type="hidden" name="sucuriscan_reset_logfile" value="failedlogins" />
|
221 |
+
<button type="submit" class="button-primary">Reset logs</button>
|
222 |
+
</form>
|
223 |
+
</td>
|
224 |
+
</tr>
|
225 |
+
|
226 |
+
<tr class="alternate">
|
227 |
+
<td>Reset sitecheck logs</td>
|
228 |
+
<td><span class="sucuriscan-monospace">%%SUCURI.SiteCheckLogLife%% of data</span></td>
|
229 |
+
<td class="td-with-button">
|
230 |
+
<form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
|
231 |
+
<input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
|
232 |
+
<input type="hidden" name="sucuriscan_reset_logfile" value="sitecheck" />
|
233 |
+
<button type="submit" class="button-primary">Reset logs</button>
|
234 |
</form>
|
235 |
</td>
|
236 |
</tr>
|
readme.txt
CHANGED
@@ -3,8 +3,8 @@ Contributors: dd@sucuri.net
|
|
3 |
Donate Link: http://sucuri.net/
|
4 |
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
|
5 |
Requires at least:3.2
|
6 |
-
Stable tag:1.7.
|
7 |
-
Tested up to: 4.
|
8 |
|
9 |
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
|
10 |
|
@@ -352,6 +352,14 @@ service from the WordPress dashboard.
|
|
352 |
|
353 |
== Changelog ==
|
354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
= 1.7.5 =
|
356 |
* Added better handling of API responses of remote scanner.
|
357 |
|
3 |
Donate Link: http://sucuri.net/
|
4 |
Tags: malware, security, firewall, scan, spam, virus, sucuri, protection,WordPress Security, Login Security,Security Auditing,File Integrity,htaccess,phishing,backdoors,SQL Injection, RFI, LFI, XSS, CSRF, website firewall, Website Security, Performance Optimization, Zero Day, Software Vulnerability, Exploits, Hacks, Attackers, Bad Actors, Reverse Proxy, Two Factor Security, Two Factor Authentication, Security Logs, HeatBleed Vulnerability, Website Protection, Bash Vulnerability, RevSlider Vulnerability, MailPoet Vulnerability, Malware Prevention, Website Firewall, Website AntiVirus, Security Response, Security Detection, Security Prevention
|
5 |
Requires at least:3.2
|
6 |
+
Stable tag:1.7.6
|
7 |
+
Tested up to: 4.1
|
8 |
|
9 |
The Sucuri WordPress Security plugin is a security toolset for security integrity monitoring, malware detection and security hardening.
|
10 |
|
352 |
|
353 |
== Changelog ==
|
354 |
|
355 |
+
= 1.7.6 =
|
356 |
+
* Added audit log reporting.
|
357 |
+
* Added more settings for better control.
|
358 |
+
* Added support for more actions.
|
359 |
+
* Improved multisite support.
|
360 |
+
* Added support for reverse proxies.
|
361 |
+
* Various bugfixes and improvements.
|
362 |
+
|
363 |
= 1.7.5 =
|
364 |
* Added better handling of API responses of remote scanner.
|
365 |
|
sucuri.php
CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
|
|
4 |
Plugin URI: http://wordpress.sucuri.net/
|
5 |
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
|
6 |
Author: Sucuri, INC
|
7 |
-
Version: 1.7.
|
8 |
Author URI: http://sucuri.net
|
9 |
*/
|
10 |
|
@@ -42,8 +42,8 @@ $sucuriscan_dependencies = array(
|
|
42 |
);
|
43 |
|
44 |
// Terminate execution if any of the functions mentioned above is not defined.
|
45 |
-
foreach( $sucuriscan_dependencies as $dependency ){
|
46 |
-
if( !function_exists($dependency) ){
|
47 |
exit(0);
|
48 |
}
|
49 |
}
|
@@ -61,92 +61,92 @@ foreach( $sucuriscan_dependencies as $dependency ){
|
|
61 |
/**
|
62 |
* Unique name of the plugin through out all the code.
|
63 |
*/
|
64 |
-
define('SUCURISCAN', 'sucuriscan');
|
65 |
|
66 |
/**
|
67 |
* Current version of the plugin's code.
|
68 |
*/
|
69 |
-
define('SUCURISCAN_VERSION', '1.7.
|
70 |
|
71 |
/**
|
72 |
* The name of the Sucuri plugin main file.
|
73 |
*/
|
74 |
-
define('SUCURISCAN_PLUGIN_FILE', 'sucuri.php');
|
75 |
|
76 |
/**
|
77 |
* The name of the folder where the plugin's files will be located.
|
78 |
*/
|
79 |
-
define('SUCURISCAN_PLUGIN_FOLDER', 'sucuri-scanner');
|
80 |
|
81 |
/**
|
82 |
* The fullpath where the plugin's files will be located.
|
83 |
*/
|
84 |
-
define('SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER);
|
85 |
|
86 |
/**
|
87 |
* The fullpath of the main plugin file.
|
88 |
*/
|
89 |
-
define('SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE);
|
90 |
|
91 |
/**
|
92 |
* The local URL where the plugin's files and assets are served.
|
93 |
*/
|
94 |
-
define('SUCURISCAN_URL', rtrim(plugin_dir_url(SUCURISCAN_PLUGIN_FILEPATH), '/') );
|
95 |
|
96 |
/**
|
97 |
* Checksum of this file to check the integrity of the plugin.
|
98 |
*/
|
99 |
-
define('SUCURISCAN_PLUGIN_CHECKSUM', @md5_file(SUCURISCAN_PLUGIN_FILEPATH));
|
100 |
|
101 |
/**
|
102 |
* Remote URL where the public Sucuri API service is running.
|
103 |
*/
|
104 |
-
define('SUCURISCAN_API', 'https://wordpress.sucuri.net/api/');
|
105 |
|
106 |
/**
|
107 |
* Latest version of the public Sucuri API.
|
108 |
*/
|
109 |
-
define('SUCURISCAN_API_VERSION', 'v1');
|
110 |
|
111 |
/**
|
112 |
* Remote URL where the CloudProxy API service is running.
|
113 |
*/
|
114 |
-
define('SUCURISCAN_CLOUDPROXY_API', 'https://waf.sucuri.net/api');
|
115 |
|
116 |
/**
|
117 |
* Latest version of the CloudProxy API.
|
118 |
*/
|
119 |
-
define('SUCURISCAN_CLOUDPROXY_API_VERSION', 'v2');
|
120 |
|
121 |
/**
|
122 |
* The maximum quantity of entries that will be displayed in the last login page.
|
123 |
*/
|
124 |
-
define('SUCURISCAN_LASTLOGINS_USERSLIMIT', 25);
|
125 |
|
126 |
/**
|
127 |
* The maximum quantity of entries that will be displayed in the audit logs page.
|
128 |
*/
|
129 |
-
define('SUCURISCAN_AUDITLOGS_PER_PAGE', 50);
|
130 |
|
131 |
/**
|
132 |
* The maximum quantity of buttons in the paginations.
|
133 |
*/
|
134 |
-
define('SUCURISCAN_MAX_PAGINATION_BUTTONS', 20);
|
135 |
|
136 |
/**
|
137 |
* The minimum quantity of seconds to wait before each filesystem scan.
|
138 |
*/
|
139 |
-
define('SUCURISCAN_MINIMUM_RUNTIME', 10800);
|
140 |
|
141 |
/**
|
142 |
* The life time of the cache for the results of the SiteCheck scans.
|
143 |
*/
|
144 |
-
define('SUCURISCAN_SITECHECK_LIFETIME', 1200);
|
145 |
|
146 |
/**
|
147 |
* The life time of the cache for the results of the get_plugins function.
|
148 |
*/
|
149 |
-
define('SUCURISCAN_GET_PLUGINS_LIFETIME', 1800);
|
150 |
|
151 |
/**
|
152 |
* Plugin's global variables.
|
@@ -156,7 +156,7 @@ define('SUCURISCAN_GET_PLUGINS_LIFETIME', 1800);
|
|
156 |
* conditional will act as a container helping in the readability of the code
|
157 |
* considering the total number of lines that this file will have.
|
158 |
*/
|
159 |
-
if( defined('SUCURISCAN') ){
|
160 |
|
161 |
/**
|
162 |
* List an associative array with the sub-pages of this plugin.
|
@@ -187,26 +187,28 @@ if( defined('SUCURISCAN') ){
|
|
187 |
*/
|
188 |
|
189 |
$sucuriscan_notify_options = array(
|
190 |
-
'
|
|
|
191 |
'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
|
192 |
-
'sucuriscan_notify_user_registration' => '
|
193 |
-
'sucuriscan_notify_success_login' => '
|
194 |
-
'sucuriscan_notify_failed_login' => '
|
195 |
-
'sucuriscan_notify_bruteforce_attack' => '
|
196 |
-
'sucuriscan_notify_post_publication' => '
|
197 |
-
'
|
198 |
-
'
|
199 |
-
'
|
200 |
-
'
|
201 |
-
'
|
202 |
-
'
|
203 |
-
'
|
204 |
-
'
|
205 |
-
'
|
206 |
-
'
|
207 |
-
'
|
208 |
-
'
|
209 |
-
'
|
|
|
210 |
);
|
211 |
|
212 |
$sucuriscan_schedule_allowed = array(
|
@@ -246,6 +248,7 @@ if( defined('SUCURISCAN') ){
|
|
246 |
);
|
247 |
|
248 |
$sucuriscan_no_notices_in = array(
|
|
|
249 |
);
|
250 |
|
251 |
$sucuriscan_email_subjects = array(
|
@@ -266,7 +269,7 @@ if( defined('SUCURISCAN') ){
|
|
266 |
* information to the Sucuri API service where a security and integrity scan
|
267 |
* will be performed against the hashes provided and the official versions.
|
268 |
*/
|
269 |
-
add_action('sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan');
|
270 |
|
271 |
/**
|
272 |
* Initialize the execute of the main plugin's functions.
|
@@ -290,7 +293,7 @@ if( defined('SUCURISCAN') ){
|
|
290 |
*
|
291 |
* @see Class SucuriScanHook
|
292 |
*/
|
293 |
-
if( class_exists('SucuriScanHook') ){
|
294 |
$sucuriscan_hooks = array(
|
295 |
// Passes.
|
296 |
'add_attachment',
|
@@ -309,9 +312,10 @@ if( defined('SUCURISCAN') ){
|
|
309 |
'user_register',
|
310 |
'wp_login',
|
311 |
'wp_login_failed',
|
|
|
312 |
);
|
313 |
|
314 |
-
foreach( $sucuriscan_hooks as $hook_name ){
|
315 |
$hook_func = 'SucuriScanHook::hook_' . $hook_name;
|
316 |
add_action( $hook_name, $hook_func, 50 );
|
317 |
}
|
@@ -331,8 +335,7 @@ if( defined('SUCURISCAN') ){
|
|
331 |
* the plugin to execute the filesystem scans, the project integrity, and the
|
332 |
* email notifications.
|
333 |
*/
|
334 |
-
|
335 |
-
add_action( $sucuriscan_admin_notice_name, 'SucuriScanInterface::setup_notice' );
|
336 |
|
337 |
/**
|
338 |
* Heartbeat API
|
@@ -378,8 +381,8 @@ class SucuriScan {
|
|
378 |
* @param string $var_name Name of a variable with an optional colon at the beginning.
|
379 |
* @return string Full name of the variable with the extra characters (if needed).
|
380 |
*/
|
381 |
-
public static function variable_prefix( $var_name='' ){
|
382 |
-
if( preg_match('/^:(.*)/', $var_name, $match) ){
|
383 |
$var_name = sprintf( '%s_%s', SUCURISCAN, $match[1] );
|
384 |
}
|
385 |
|
@@ -392,11 +395,11 @@ class SucuriScan {
|
|
392 |
* @param string $property The configuration option name.
|
393 |
* @return string Value of the configuration option as a string on success.
|
394 |
*/
|
395 |
-
public static function ini_get( $property='' ){
|
396 |
-
$ini_value = ini_get($property);
|
397 |
|
398 |
-
if( empty($ini_value) || is_null($ini_value) ){
|
399 |
-
switch( $property ){
|
400 |
case 'error_log': $ini_value = 'error_log'; break;
|
401 |
case 'safe_mode': $ini_value = 'Off'; break;
|
402 |
case 'allow_url_fopen': $ini_value = '1'; break;
|
@@ -418,12 +421,12 @@ class SucuriScan {
|
|
418 |
* @param string $text The text which is to be encoded.
|
419 |
* @return string The encoded text with HTML entities.
|
420 |
*/
|
421 |
-
public static function escape( $text='' ){
|
422 |
// Escape the value of the variable using a built-in function if possible.
|
423 |
-
if( function_exists('esc_attr') ){
|
424 |
-
$text = esc_attr($text);
|
425 |
} else {
|
426 |
-
$text = htmlspecialchars($text);
|
427 |
}
|
428 |
|
429 |
return $text;
|
@@ -435,12 +438,12 @@ class SucuriScan {
|
|
435 |
* @param integer $length Length of the string that will be generated.
|
436 |
* @return string The random string generated.
|
437 |
*/
|
438 |
-
public static function random_char( $length=4 ){
|
439 |
$string = '';
|
440 |
-
$chars = range('a','z');
|
441 |
|
442 |
-
for( $i=0; $i
|
443 |
-
$string .= $chars[ rand(0, count($chars)-1) ];
|
444 |
}
|
445 |
|
446 |
return $string;
|
@@ -455,10 +458,10 @@ class SucuriScan {
|
|
455 |
* @param integer $decimals How many decimals should be returned after the translation.
|
456 |
* @return string Human readable representation of the given number in Kylo, Mega, Giga, etc.
|
457 |
*/
|
458 |
-
public static function human_filesize( $bytes=0, $decimals=2 ){
|
459 |
$sz = 'BKMGTP';
|
460 |
-
$factor = floor((strlen($bytes) - 1) / 3);
|
461 |
-
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
462 |
}
|
463 |
|
464 |
/**
|
@@ -468,25 +471,36 @@ class SucuriScan {
|
|
468 |
* @param string $path The relative path that needs to be completed to get the absolute path.
|
469 |
* @return string The full filesystem path including the directory specified.
|
470 |
*/
|
471 |
-
public static function datastore_folder_path( $path='' ){
|
472 |
-
$datastore_path = SucuriScanOption::get_option(':datastore_path');
|
|
|
473 |
|
474 |
// Use the uploads folder by default.
|
475 |
if ( empty($datastore_path) ) {
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
|
|
|
|
|
|
|
|
|
|
480 |
}
|
481 |
|
482 |
-
|
483 |
-
|
|
|
|
|
|
|
|
|
484 |
}
|
485 |
|
|
|
486 |
SucuriScanOption::update_option( ':datastore_path', $datastore_path );
|
487 |
}
|
488 |
|
489 |
-
$wp_filepath = rtrim($datastore_path, '/') . '/' . $path;
|
490 |
|
491 |
return $wp_filepath;
|
492 |
}
|
@@ -497,14 +511,14 @@ class SucuriScan {
|
|
497 |
* @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
|
498 |
*/
|
499 |
public static function is_multisite(){
|
500 |
-
if(
|
501 |
-
function_exists('is_multisite')
|
502 |
&& is_multisite()
|
503 |
){
|
504 |
-
return
|
505 |
}
|
506 |
|
507 |
-
return
|
508 |
}
|
509 |
|
510 |
/**
|
@@ -513,19 +527,23 @@ class SucuriScan {
|
|
513 |
* @return string The version number of Wordpress installed.
|
514 |
*/
|
515 |
public static function site_version(){
|
516 |
-
|
517 |
-
|
518 |
-
if( $
|
|
|
|
|
|
|
|
|
|
|
|
|
519 |
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
if( isset($wp_version) ){ return $wp_version; }
|
525 |
}
|
526 |
|
527 |
-
|
528 |
-
return md5_file(ABSPATH . WPINC . '/class-wp.php');
|
529 |
}
|
530 |
|
531 |
/**
|
@@ -534,23 +552,23 @@ class SucuriScan {
|
|
534 |
* @return string Absolute path of the WordPress configuration file.
|
535 |
*/
|
536 |
public static function get_wpconfig_path(){
|
537 |
-
if( defined('ABSPATH') ){
|
538 |
$file_path = ABSPATH . '/wp-config.php';
|
539 |
|
540 |
// if wp-config.php doesn't exist, or is not readable check one directory up.
|
541 |
-
if( !file_exists($file_path) ){
|
542 |
$file_path = ABSPATH . '/../wp-config.php';
|
543 |
}
|
544 |
|
545 |
// Remove duplicated double slashes.
|
546 |
-
$file_path = realpath($file_path);
|
547 |
|
548 |
-
if( $file_path ){
|
549 |
return $file_path;
|
550 |
}
|
551 |
}
|
552 |
|
553 |
-
return
|
554 |
}
|
555 |
|
556 |
/**
|
@@ -559,23 +577,23 @@ class SucuriScan {
|
|
559 |
* @return string Absolute path of the main WordPress htaccess file.
|
560 |
*/
|
561 |
public static function get_htaccess_path(){
|
562 |
-
if( defined('ABSPATH') ){
|
563 |
$base_dirs = array(
|
564 |
-
rtrim(ABSPATH, '/'),
|
565 |
-
dirname(ABSPATH),
|
566 |
-
dirname(dirname(ABSPATH))
|
567 |
);
|
568 |
|
569 |
-
foreach( $base_dirs as $base_dir ){
|
570 |
-
$htaccess_path = sprintf('%s/.htaccess', $base_dir);
|
571 |
|
572 |
-
if( file_exists($htaccess_path) ){
|
573 |
return $htaccess_path;
|
574 |
}
|
575 |
}
|
576 |
}
|
577 |
|
578 |
-
return
|
579 |
}
|
580 |
|
581 |
/**
|
@@ -584,7 +602,7 @@ class SucuriScan {
|
|
584 |
* @return string Secret key definition pattern.
|
585 |
*/
|
586 |
public static function secret_key_pattern(){
|
587 |
-
return '/define\(\'([A-Z_]+)\',(
|
588 |
}
|
589 |
|
590 |
/**
|
@@ -593,11 +611,14 @@ class SucuriScan {
|
|
593 |
* @param boolean $return_header Whether the header name where the address was found must be returned.
|
594 |
* @return string The real ip address of the user in the current request.
|
595 |
*/
|
596 |
-
public static function get_remote_addr( $return_header=
|
597 |
$remote_addr = '';
|
598 |
$header_used = 'unknown';
|
599 |
|
600 |
-
if
|
|
|
|
|
|
|
601 |
$alternatives = array(
|
602 |
'HTTP_X_SUCURI_CLIENTIP',
|
603 |
'HTTP_X_REAL_IP',
|
@@ -610,28 +631,28 @@ class SucuriScan {
|
|
610 |
'REMOTE_ADDR',
|
611 |
);
|
612 |
|
613 |
-
foreach( $alternatives as $alternative ){
|
614 |
-
if(
|
615 |
-
isset($_SERVER[$alternative])
|
616 |
-
&& self::is_valid_ip($_SERVER[$alternative])
|
617 |
){
|
618 |
-
$remote_addr = $_SERVER[$alternative];
|
619 |
$header_used = $alternative;
|
620 |
break;
|
621 |
}
|
622 |
}
|
623 |
}
|
624 |
|
625 |
-
elseif( isset($_SERVER['REMOTE_ADDR']) ) {
|
626 |
$remote_addr = $_SERVER['REMOTE_ADDR'];
|
627 |
$header_used = 'REMOTE_ADDR';
|
628 |
}
|
629 |
|
630 |
-
if( $remote_addr == '::1' ){
|
631 |
$remote_addr = '127.0.0.1';
|
632 |
}
|
633 |
|
634 |
-
if( $return_header ){
|
635 |
return $header_used;
|
636 |
}
|
637 |
|
@@ -644,7 +665,7 @@ class SucuriScan {
|
|
644 |
* @return string The HTTP header used to retrieve the remote address.
|
645 |
*/
|
646 |
public static function get_remote_addr_header(){
|
647 |
-
return self::get_remote_addr(
|
648 |
}
|
649 |
|
650 |
/**
|
@@ -653,11 +674,11 @@ class SucuriScan {
|
|
653 |
* @return string The user-agent from the current request.
|
654 |
*/
|
655 |
public static function get_user_agent(){
|
656 |
-
if( isset($_SERVER['HTTP_USER_AGENT']) ){
|
657 |
-
return self::escape($_SERVER['HTTP_USER_AGENT']);
|
658 |
}
|
659 |
|
660 |
-
return
|
661 |
}
|
662 |
|
663 |
/**
|
@@ -665,21 +686,35 @@ class SucuriScan {
|
|
665 |
*
|
666 |
* @return string The domain of the current site.
|
667 |
*/
|
668 |
-
public static function get_domain(){
|
669 |
-
if( function_exists('get_site_url') ){
|
670 |
$site_url = get_site_url();
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
}
|
675 |
|
676 |
-
|
677 |
}
|
678 |
|
679 |
-
|
680 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
681 |
|
682 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
683 |
}
|
684 |
|
685 |
/**
|
@@ -688,11 +723,11 @@ class SucuriScan {
|
|
688 |
* @param boolean $verbose Return an array with the hostname, address, and status, or not.
|
689 |
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
|
690 |
*/
|
691 |
-
public static function is_behind_cloudproxy( $verbose=
|
692 |
-
$http_host = self::
|
693 |
-
$host_by_addr = @gethostbyname($http_host);
|
694 |
-
$host_by_name = @gethostbyaddr($host_by_addr);
|
695 |
-
$status = (bool) preg_match('/^cloudproxy[0-9]+\.sucuri\.net$/', $host_by_name);
|
696 |
|
697 |
/*
|
698 |
* If the DNS reversion failed but the CloudProxy API key is set, then consider
|
@@ -700,13 +735,13 @@ class SucuriScan {
|
|
700 |
* checking, but that is not something that will affect us, only the client.
|
701 |
*/
|
702 |
if (
|
703 |
-
$status ===
|
704 |
&& SucuriScanAPI::get_cloudproxy_key()
|
705 |
) {
|
706 |
-
$status =
|
707 |
}
|
708 |
|
709 |
-
if( $verbose ){
|
710 |
return array(
|
711 |
'http_host' => $http_host,
|
712 |
'host_name' => $host_by_name,
|
@@ -726,13 +761,13 @@ class SucuriScan {
|
|
726 |
* @return string The administrator email address.
|
727 |
*/
|
728 |
public static function get_site_email(){
|
729 |
-
$email = get_option('admin_email');
|
730 |
|
731 |
-
if( self::is_valid_email($email) ){
|
732 |
return $email;
|
733 |
}
|
734 |
|
735 |
-
return
|
736 |
}
|
737 |
|
738 |
/**
|
@@ -741,8 +776,8 @@ class SucuriScan {
|
|
741 |
* @return integer Return current Unix timestamp.
|
742 |
*/
|
743 |
public static function local_time(){
|
744 |
-
if( function_exists('current_time') ){
|
745 |
-
return current_time('timestamp');
|
746 |
} else {
|
747 |
return time();
|
748 |
}
|
@@ -758,16 +793,16 @@ class SucuriScan {
|
|
758 |
* @param integer $timestamp Unix timestamp.
|
759 |
* @return string The date, translated if locale specifies it.
|
760 |
*/
|
761 |
-
public static function datetime( $timestamp=0 ){
|
762 |
-
if( is_numeric($timestamp) && $timestamp > 0 ){
|
763 |
-
$date_format = get_option('date_format');
|
764 |
-
$time_format = get_option('time_format');
|
765 |
$timezone_format = sprintf( '%s %s', $date_format, $time_format );
|
766 |
|
767 |
return date_i18n( $timezone_format, $timestamp );
|
768 |
}
|
769 |
|
770 |
-
return
|
771 |
}
|
772 |
|
773 |
/**
|
@@ -778,7 +813,7 @@ class SucuriScan {
|
|
778 |
public static function current_datetime(){
|
779 |
$local_time = self::local_time();
|
780 |
|
781 |
-
return self::datetime($local_time);
|
782 |
}
|
783 |
|
784 |
/**
|
@@ -787,27 +822,27 @@ class SucuriScan {
|
|
787 |
* @param integer $timestamp The Unix time number of the date/time before now.
|
788 |
* @return string The time passed since the timestamp specified.
|
789 |
*/
|
790 |
-
public static function time_ago( $timestamp=0 ){
|
791 |
-
if( !is_numeric($timestamp) ){
|
792 |
-
$timestamp = strtotime($timestamp);
|
793 |
}
|
794 |
|
795 |
$local_time = self::local_time();
|
796 |
-
$diff = abs( $local_time - intval($timestamp) );
|
797 |
|
798 |
-
if( $diff == 0 ){ return 'just now'; }
|
799 |
|
800 |
$intervals = array(
|
801 |
-
1 => array('year', 31556926),
|
802 |
-
$diff < 31556926 => array('month', 2628000),
|
803 |
-
$diff < 2629744 => array('week', 604800),
|
804 |
-
$diff < 604800 => array('day', 86400),
|
805 |
-
$diff < 86400 => array('hour', 3600),
|
806 |
-
$diff < 3600 => array('minute', 60),
|
807 |
-
$diff < 60 => array('second', 1)
|
808 |
);
|
809 |
|
810 |
-
$value = floor($diff
|
811 |
$time_ago = sprintf(
|
812 |
'%s %s%s ago',
|
813 |
$value,
|
@@ -826,10 +861,10 @@ class SucuriScan {
|
|
826 |
* @param string $text A text containing alpha-numeric and special characters.
|
827 |
* @return string A valid variable name.
|
828 |
*/
|
829 |
-
public static function human2var( $text='' ){
|
830 |
-
$text = strtolower($text);
|
831 |
$pattern = '/[^a-z0-9_]/';
|
832 |
-
$var_name = preg_replace($pattern, '_', $text);
|
833 |
|
834 |
return $var_name;
|
835 |
}
|
@@ -840,8 +875,8 @@ class SucuriScan {
|
|
840 |
* @param string $data The data that will be checked.
|
841 |
* @return boolean TRUE if the data was serialized, FALSE otherwise.
|
842 |
*/
|
843 |
-
public static function is_serialized( $data='' ){
|
844 |
-
return ( is_string($data) && preg_match('/^(a|O):[0-9]+:.+/', $data) );
|
845 |
}
|
846 |
|
847 |
/**
|
@@ -850,26 +885,26 @@ class SucuriScan {
|
|
850 |
* @param string $remote_addr The host IP address.
|
851 |
* @return boolean Whether the IP address specified is valid or not.
|
852 |
*/
|
853 |
-
public static function is_valid_ip( $remote_addr='' ){
|
854 |
// Check for IPv4 and IPv6.
|
855 |
-
if( function_exists('filter_var') ){
|
856 |
return (bool) filter_var( $remote_addr, FILTER_VALIDATE_IP );
|
857 |
}
|
858 |
|
859 |
// Assuming older version of PHP and server, so only will check for IPv4.
|
860 |
-
elseif( strlen($remote_addr) >= 7 ) {
|
861 |
$pattern = '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/';
|
862 |
|
863 |
-
if( preg_match($pattern, $remote_addr, $match) ){
|
864 |
-
for( $i=0; $i<4; $i++ ){
|
865 |
-
if( $match[$i] > 255 ){ return
|
866 |
}
|
867 |
|
868 |
-
return
|
869 |
}
|
870 |
}
|
871 |
|
872 |
-
return
|
873 |
}
|
874 |
|
875 |
|
@@ -879,14 +914,14 @@ class SucuriScan {
|
|
879 |
* @param string $remote_addr The supposed ip address that will be checked.
|
880 |
* @return boolean Either TRUE or FALSE if the ip address specified is valid or not.
|
881 |
*/
|
882 |
-
public static function is_valid_cidr( $remote_addr='' ){
|
883 |
-
if ( preg_match('/^([0-9\.]{7,15})\/(8|16|24)$/', $remote_addr, $match) ) {
|
884 |
-
if ( self::is_valid_ip($match[1]) ) {
|
885 |
-
return
|
886 |
}
|
887 |
}
|
888 |
|
889 |
-
return
|
890 |
}
|
891 |
|
892 |
/**
|
@@ -895,7 +930,7 @@ class SucuriScan {
|
|
895 |
* @param string $remote_addr The supposed ip address that will be formatted.
|
896 |
* @return array Clean address, CIDR range, and CIDR format; FALSE otherwise.
|
897 |
*/
|
898 |
-
public static function get_ip_info( $remote_addr='' ){
|
899 |
if ( $remote_addr ) {
|
900 |
$addr_info = array();
|
901 |
$ip_parts = explode( '/', $remote_addr );
|
@@ -903,11 +938,10 @@ class SucuriScan {
|
|
903 |
$addr_info['cidr_range'] = isset($ip_parts[1]) ? $ip_parts[1] : '32';
|
904 |
$addr_info['cidr_format'] = $addr_info['remote_addr'] . '/' . $addr_info['cidr_range'];
|
905 |
|
906 |
-
|
907 |
return $addr_info;
|
908 |
}
|
909 |
|
910 |
-
return
|
911 |
}
|
912 |
|
913 |
/**
|
@@ -922,12 +956,12 @@ class SucuriScan {
|
|
922 |
* @param string $email The string that will be validated as an email address.
|
923 |
* @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
|
924 |
*/
|
925 |
-
public static function is_valid_email( $email='' ){
|
926 |
-
if( function_exists('filter_var') ){
|
927 |
-
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
|
928 |
} else {
|
929 |
$pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
|
930 |
-
return (bool) preg_match($pattern, $email);
|
931 |
}
|
932 |
}
|
933 |
|
@@ -938,34 +972,34 @@ class SucuriScan {
|
|
938 |
* @param boolean $as_array TRUE to return the list of valid email addresses as an array.
|
939 |
* @return string All the valid email addresses separated by a comma.
|
940 |
*/
|
941 |
-
public static function get_valid_email( $email='', $as_array=
|
942 |
$valid_emails = array();
|
943 |
|
944 |
-
if( strpos($email, ',') !==
|
945 |
-
$addresses = explode(',', $email);
|
946 |
|
947 |
-
foreach( $addresses as $address ){
|
948 |
-
$address = trim($address);
|
949 |
|
950 |
-
if( self::is_valid_email($address) ){
|
951 |
$valid_emails[] = $address;
|
952 |
}
|
953 |
}
|
954 |
}
|
955 |
|
956 |
-
elseif( self::is_valid_email($email) ){
|
957 |
$valid_emails[] = $email;
|
958 |
}
|
959 |
|
960 |
-
if( !empty($valid_emails) ){
|
961 |
-
if( $as_array ===
|
962 |
return $valid_emails;
|
963 |
}
|
964 |
|
965 |
-
return self::implode(', ', $valid_emails);
|
966 |
}
|
967 |
|
968 |
-
return
|
969 |
}
|
970 |
|
971 |
/**
|
@@ -975,10 +1009,10 @@ class SucuriScan {
|
|
975 |
* @param integer $length Maximum length of the returned string, default is 10.
|
976 |
* @return string Short version of the text specified.
|
977 |
*/
|
978 |
-
public static function excerpt( $text='', $length=10 ){
|
979 |
-
$text_length = strlen($text);
|
980 |
|
981 |
-
if( $text_length > $length ){
|
982 |
return substr( $text, 0, $length ) . '...';
|
983 |
}
|
984 |
|
@@ -992,10 +1026,10 @@ class SucuriScan {
|
|
992 |
* @param integer $length Maximum length of the returned string, default is 10.
|
993 |
* @return string Short version of the text specified.
|
994 |
*/
|
995 |
-
public static function excerpt_rev( $text='', $length=10 ){
|
996 |
-
$str_reversed = strrev($text);
|
997 |
$str_excerpt = self::excerpt( $str_reversed, $length );
|
998 |
-
$text_transformed = strrev($str_excerpt);
|
999 |
|
1000 |
return $text_transformed;
|
1001 |
}
|
@@ -1006,16 +1040,16 @@ class SucuriScan {
|
|
1006 |
* @param array $list An array or multidimensional array of different values.
|
1007 |
* @return boolean TRUE if the list is multidimensional, FALSE otherwise.
|
1008 |
*/
|
1009 |
-
public static function is_multi_list( $list=array() ){
|
1010 |
-
if( !empty($list) ){
|
1011 |
-
foreach( $list as $item ){
|
1012 |
-
if( is_array($item) ){
|
1013 |
-
return
|
1014 |
}
|
1015 |
}
|
1016 |
}
|
1017 |
|
1018 |
-
return
|
1019 |
}
|
1020 |
|
1021 |
/**
|
@@ -1025,11 +1059,11 @@ class SucuriScan {
|
|
1025 |
* @param array $list The array of strings to implode.
|
1026 |
* @return string String of all the items in the list, with the separator between them.
|
1027 |
*/
|
1028 |
-
public static function implode( $separator='', $list=array() ){
|
1029 |
-
if( self::is_multi_list($list) ){
|
1030 |
$pieces = array();
|
1031 |
|
1032 |
-
foreach( $list as $items ){
|
1033 |
$pieces[] = @implode( $separator, $items );
|
1034 |
}
|
1035 |
|
@@ -1047,24 +1081,42 @@ class SucuriScan {
|
|
1047 |
* @param string $current_page Identifier of the current page.
|
1048 |
* @return boolean TRUE if the current page must not have noticies.
|
1049 |
*/
|
1050 |
-
public static function no_notices_here( $current_page=false ){
|
1051 |
global $sucuriscan_no_notices_in;
|
1052 |
|
1053 |
if ( $current_page === false ) {
|
1054 |
-
$current_page = SucuriScanRequest::get('page');
|
1055 |
}
|
1056 |
|
1057 |
if (
|
1058 |
isset($sucuriscan_no_notices_in)
|
1059 |
-
&& is_array($sucuriscan_no_notices_in)
|
1060 |
-
&& !empty($sucuriscan_no_notices_in)
|
1061 |
) {
|
1062 |
-
return (bool) in_array($current_page, $sucuriscan_no_notices_in);
|
1063 |
}
|
1064 |
|
1065 |
return false;
|
1066 |
}
|
1067 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1068 |
}
|
1069 |
|
1070 |
/**
|
@@ -1087,22 +1139,22 @@ class SucuriScanRequest extends SucuriScan {
|
|
1087 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1088 |
* @return string The value stored in the specified key inside the global _GET variable.
|
1089 |
*/
|
1090 |
-
public static function request( $list=array(), $key='', $pattern='' ){
|
1091 |
-
$key = self::variable_prefix($key);
|
1092 |
|
1093 |
-
if(
|
1094 |
-
is_array($list)
|
1095 |
-
&& is_string($key)
|
1096 |
-
&& isset($list[$key])
|
1097 |
){
|
1098 |
// Select the key from the list and escape its content.
|
1099 |
-
$key_value = $list[$key];
|
1100 |
|
1101 |
// Define regular expressions for specific value types.
|
1102 |
-
if( $pattern === '' ){
|
1103 |
$pattern = '/.*/';
|
1104 |
} else {
|
1105 |
-
switch( $pattern ){
|
1106 |
case '_nonce': $pattern = '/^[a-z0-9]{10}$/'; break;
|
1107 |
case '_page': $pattern = '/^[a-z_]+$/'; break;
|
1108 |
case '_array': $pattern = '_array'; break;
|
@@ -1112,17 +1164,17 @@ class SucuriScanRequest extends SucuriScan {
|
|
1112 |
}
|
1113 |
|
1114 |
// If the request data is an array, then only cast the value.
|
1115 |
-
if( $pattern == '_array' && is_array($key_value) ){
|
1116 |
return (array) $key_value;
|
1117 |
}
|
1118 |
|
1119 |
// Check the format of the request data with a regex defined above.
|
1120 |
-
if( @preg_match($pattern, $key_value) ){
|
1121 |
-
return self::escape($key_value);
|
1122 |
}
|
1123 |
}
|
1124 |
|
1125 |
-
return
|
1126 |
}
|
1127 |
|
1128 |
/**
|
@@ -1133,7 +1185,7 @@ class SucuriScanRequest extends SucuriScan {
|
|
1133 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1134 |
* @return string The value stored in the specified key inside the global _GET variable.
|
1135 |
*/
|
1136 |
-
public static function get( $key='', $pattern='' ){
|
1137 |
return self::request( $_GET, $key, $pattern );
|
1138 |
}
|
1139 |
|
@@ -1145,7 +1197,7 @@ class SucuriScanRequest extends SucuriScan {
|
|
1145 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1146 |
* @return string The value stored in the specified key inside the global _POST variable.
|
1147 |
*/
|
1148 |
-
public static function post( $key='', $pattern='' ){
|
1149 |
return self::request( $_POST, $key, $pattern );
|
1150 |
}
|
1151 |
|
@@ -1157,7 +1209,7 @@ class SucuriScanRequest extends SucuriScan {
|
|
1157 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1158 |
* @return string The value stored in the specified key inside the global _POST variable.
|
1159 |
*/
|
1160 |
-
public static function get_or_post( $key='', $pattern='' ){
|
1161 |
return self::request( $_REQUEST, $key, $pattern );
|
1162 |
}
|
1163 |
|
@@ -1189,7 +1241,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1189 |
*
|
1190 |
* @var boolean
|
1191 |
*/
|
1192 |
-
public $ignore_files =
|
1193 |
|
1194 |
/**
|
1195 |
* Whether the list of folders that can be ignored from the filesystem scan will
|
@@ -1198,7 +1250,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1198 |
*
|
1199 |
* @var boolean
|
1200 |
*/
|
1201 |
-
public $ignore_directories =
|
1202 |
|
1203 |
/**
|
1204 |
* A list of ignored directory paths, these folders will be skipped during the
|
@@ -1215,7 +1267,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1215 |
*
|
1216 |
* @var boolean
|
1217 |
*/
|
1218 |
-
public $run_recursively =
|
1219 |
|
1220 |
/**
|
1221 |
* Class constructor.
|
@@ -1233,30 +1285,30 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1233 |
* @param boolean $as_array Whether the result of the operation will be returned as an array or string.
|
1234 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1235 |
*/
|
1236 |
-
public function get_directory_tree_md5( $directory='', $as_array=
|
1237 |
$project_signatures = '';
|
1238 |
$abs_path = rtrim( ABSPATH, '/' );
|
1239 |
-
$files = $this->get_directory_tree($directory);
|
1240 |
|
1241 |
-
if( $as_array ){
|
1242 |
$project_signatures = array();
|
1243 |
}
|
1244 |
|
1245 |
-
if( $files ){
|
1246 |
-
sort($files);
|
1247 |
|
1248 |
-
foreach( $files as $filepath){
|
1249 |
-
$file_checksum = @md5_file($filepath);
|
1250 |
-
$filesize = @filesize($filepath);
|
1251 |
|
1252 |
-
if( $as_array ){
|
1253 |
$basename = str_replace( $abs_path . '/', '', $filepath );
|
1254 |
-
$project_signatures[$basename] = array(
|
1255 |
'filepath' => $filepath,
|
1256 |
'checksum' => $file_checksum,
|
1257 |
'filesize' => $filesize,
|
1258 |
-
'created_at' => filectime($filepath),
|
1259 |
-
'modified_at' => filemtime($filepath),
|
1260 |
);
|
1261 |
} else {
|
1262 |
$filepath = str_replace( $abs_path, $abs_path . '/', $filepath );
|
@@ -1264,7 +1316,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1264 |
"%s%s%s%s\n",
|
1265 |
$file_checksum,
|
1266 |
$filesize,
|
1267 |
-
chr(32),
|
1268 |
$filepath
|
1269 |
);
|
1270 |
}
|
@@ -1282,43 +1334,43 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1282 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1283 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1284 |
*/
|
1285 |
-
public function get_directory_tree($directory=''){
|
1286 |
-
if( file_exists($directory) && is_dir($directory) ){
|
1287 |
$tree = array();
|
1288 |
|
1289 |
// Check whether the ignore scanning feature is enabled or not.
|
1290 |
-
if( SucuriScanFSScanner::will_ignore_scanning() ){
|
1291 |
$this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
|
1292 |
}
|
1293 |
|
1294 |
-
switch( $this->scan_interface ){
|
1295 |
case 'spl':
|
1296 |
-
if( $this->is_spl_available() ){
|
1297 |
-
$tree = $this->get_directory_tree_with_spl($directory);
|
1298 |
} else {
|
1299 |
$this->scan_interface = 'opendir';
|
1300 |
-
$tree = $this->get_directory_tree($directory);
|
1301 |
}
|
1302 |
break;
|
1303 |
|
1304 |
case 'glob':
|
1305 |
-
$tree = $this->get_directory_tree_with_glob($directory);
|
1306 |
break;
|
1307 |
|
1308 |
case 'opendir':
|
1309 |
-
$tree = $this->get_directory_tree_with_opendir($directory);
|
1310 |
break;
|
1311 |
|
1312 |
default:
|
1313 |
$this->scan_interface = 'spl';
|
1314 |
-
$tree = $this->get_directory_tree($directory);
|
1315 |
break;
|
1316 |
}
|
1317 |
|
1318 |
return $tree;
|
1319 |
}
|
1320 |
|
1321 |
-
return
|
1322 |
}
|
1323 |
|
1324 |
/**
|
@@ -1328,21 +1380,21 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1328 |
* @param string $directory Directory where the scanner is located at the moment.
|
1329 |
* @return array List of file paths where the file was found.
|
1330 |
*/
|
1331 |
-
public function find_file( $filename='', $directory=
|
1332 |
$file_paths = array();
|
1333 |
|
1334 |
-
if(
|
1335 |
-
is_null($directory)
|
1336 |
-
&& defined('ABSPATH')
|
1337 |
){
|
1338 |
$directory = ABSPATH;
|
1339 |
}
|
1340 |
|
1341 |
-
if( is_dir($directory) ){
|
1342 |
$dir_tree = $this->get_directory_tree( $directory );
|
1343 |
|
1344 |
-
foreach( $dir_tree as $filepath ){
|
1345 |
-
if( stripos($filepath, $filename) !==
|
1346 |
$file_paths[] = $filepath;
|
1347 |
}
|
1348 |
}
|
@@ -1361,7 +1413,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1361 |
* @return boolean Whether the PHP class "SplFileObject" is available or not.
|
1362 |
*/
|
1363 |
public static function is_spl_available(){
|
1364 |
-
return (bool) class_exists('SplFileObject');
|
1365 |
}
|
1366 |
|
1367 |
/**
|
@@ -1378,42 +1430,42 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1378 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1379 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1380 |
*/
|
1381 |
-
private function get_directory_tree_with_spl($directory=''){
|
1382 |
$files = array();
|
1383 |
-
$filepath = realpath($directory);
|
1384 |
|
1385 |
-
if( !class_exists('FilesystemIterator') ){
|
1386 |
-
return $this->get_directory_tree($directory, 'opendir');
|
1387 |
}
|
1388 |
|
1389 |
-
if( $this->run_recursively ){
|
1390 |
$flags = FilesystemIterator::KEY_AS_PATHNAME
|
1391 |
| FilesystemIterator::CURRENT_AS_FILEINFO
|
1392 |
| FilesystemIterator::SKIP_DOTS
|
1393 |
| FilesystemIterator::UNIX_PATHS;
|
1394 |
$objects = new RecursiveIteratorIterator(
|
1395 |
-
new RecursiveDirectoryIterator($filepath, $flags),
|
1396 |
RecursiveIteratorIterator::SELF_FIRST,
|
1397 |
RecursiveIteratorIterator::CATCH_GET_CHILD
|
1398 |
);
|
1399 |
} else {
|
1400 |
-
$objects = new DirectoryIterator($filepath);
|
1401 |
}
|
1402 |
|
1403 |
-
foreach( $objects as $filepath => $fileinfo ){
|
1404 |
-
if( $this->run_recursively ){
|
1405 |
-
$directory = dirname($filepath);
|
1406 |
$filename = $fileinfo->getFilename();
|
1407 |
} else {
|
1408 |
-
if( $fileinfo->isDot() || $fileinfo->isDir() ){ continue; }
|
1409 |
|
1410 |
$directory = $fileinfo->getPath();
|
1411 |
$filename = $fileinfo->getFilename();
|
1412 |
$filepath = $directory . '/' . $filename;
|
1413 |
}
|
1414 |
|
1415 |
-
if( $this->ignore_folderpath($directory, $filename) ){ continue; }
|
1416 |
-
if( $this->ignore_filepath($filename) ){ continue; }
|
1417 |
|
1418 |
$files[] = $filepath;
|
1419 |
}
|
@@ -1429,31 +1481,31 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1429 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1430 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1431 |
*/
|
1432 |
-
private function get_directory_tree_with_glob($directory=''){
|
1433 |
$files = array();
|
1434 |
|
1435 |
-
$directory_pattern = sprintf( '%s/*', rtrim($directory,'/') );
|
1436 |
-
$files_found = glob($directory_pattern);
|
1437 |
|
1438 |
-
if( is_array($files_found) ){
|
1439 |
-
foreach( $files_found as $filepath ){
|
1440 |
-
$filepath = realpath($filepath);
|
1441 |
-
$directory = dirname($filepath);
|
1442 |
-
$filepath_parts = explode('/', $filepath);
|
1443 |
-
$filename = array_pop($filepath_parts);
|
1444 |
|
1445 |
-
if( is_dir($filepath) ){
|
1446 |
-
if( $this->ignore_folderpath($directory, $filename) ){ continue; }
|
1447 |
|
1448 |
-
if( $this->run_recursively ){
|
1449 |
-
$sub_files = $this->get_directory_tree_with_glob($filepath);
|
1450 |
|
1451 |
-
if( $sub_files ){
|
1452 |
-
$files = array_merge($files, $sub_files);
|
1453 |
}
|
1454 |
}
|
1455 |
} else {
|
1456 |
-
if( $this->ignore_filepath($filename) ){ continue; }
|
1457 |
$files[] = $filepath;
|
1458 |
}
|
1459 |
}
|
@@ -1470,31 +1522,31 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1470 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1471 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1472 |
*/
|
1473 |
-
private function get_directory_tree_with_opendir($directory=''){
|
1474 |
-
$dh = @opendir($directory);
|
1475 |
-
if(
|
1476 |
|
1477 |
$files = array();
|
1478 |
-
while( ($filename = readdir($dh)) !==
|
1479 |
-
$filepath = realpath($directory.'/'.$filename);
|
1480 |
|
1481 |
-
if( is_dir($filepath) ){
|
1482 |
-
if( $this->ignore_folderpath($directory, $filename) ){ continue; }
|
1483 |
|
1484 |
-
if( $this->run_recursively ){
|
1485 |
-
$sub_files = $this->get_directory_tree_with_opendir($filepath);
|
1486 |
|
1487 |
-
if( $sub_files ){
|
1488 |
-
$files = array_merge($files, $sub_files);
|
1489 |
}
|
1490 |
}
|
1491 |
} else {
|
1492 |
-
if( $this->ignore_filepath($filename) ){ continue; }
|
1493 |
$files[] = $filepath;
|
1494 |
}
|
1495 |
}
|
1496 |
|
1497 |
-
closedir($dh);
|
1498 |
return $files;
|
1499 |
}
|
1500 |
|
@@ -1505,30 +1557,30 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1505 |
* @param string $filename Name of the folder or file being scanned at the moment.
|
1506 |
* @return boolean Either TRUE or FALSE representing that the scan should ignore this folder or not.
|
1507 |
*/
|
1508 |
-
private function ignore_folderpath( $directory='', $filename='' ){
|
1509 |
// Ignoring current and parent folders.
|
1510 |
-
if( $filename == '.' || $filename == '..' ){ return
|
1511 |
|
1512 |
-
if( $this->ignore_directories ){
|
1513 |
// Ignore directories based on a common regular expression.
|
1514 |
$filepath = realpath( $directory . '/' . $filename );
|
1515 |
$pattern = '/\/wp-content\/(uploads|cache|backup|w3tc)/';
|
1516 |
|
1517 |
-
if( preg_match($pattern, $filepath) ){
|
1518 |
-
return
|
1519 |
}
|
1520 |
|
1521 |
// Ignore directories specified by the administrator.
|
1522 |
-
if( !empty($this->ignored_directories) ){
|
1523 |
-
foreach( $this->ignored_directories['directories'] as $ignored_dir ){
|
1524 |
-
if( strpos($directory, $ignored_dir) !==
|
1525 |
-
return
|
1526 |
}
|
1527 |
}
|
1528 |
}
|
1529 |
}
|
1530 |
|
1531 |
-
return
|
1532 |
}
|
1533 |
|
1534 |
/**
|
@@ -1537,22 +1589,22 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1537 |
* @param string $filename Name of the folder or file being scanned at the moment.
|
1538 |
* @return boolean Either TRUE or FALSE representing that the scan should ignore this filename or not.
|
1539 |
*/
|
1540 |
-
private function ignore_filepath( $filename='' ){
|
1541 |
-
if(
|
1542 |
|
1543 |
// Ignoring backup files from our clean ups.
|
1544 |
-
if( strpos($filename, '_sucuribackup.') !==
|
1545 |
|
1546 |
// Any file maching one of these rules WILL NOT be ignored.
|
1547 |
-
if(
|
1548 |
-
( strpos($filename, '.php')
|
1549 |
-
( strpos($filename, '.htm')
|
1550 |
-
( strpos($filename, '.js')
|
1551 |
-
( strcmp($filename, '.htaccess') == 0 ) ||
|
1552 |
-
( strcmp($filename, 'php.ini')
|
1553 |
-
){ return
|
1554 |
|
1555 |
-
return
|
1556 |
}
|
1557 |
|
1558 |
/**
|
@@ -1561,20 +1613,20 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1561 |
* @param array $dir_tree A list of files under a directory.
|
1562 |
* @return array A list of unique directory paths.
|
1563 |
*/
|
1564 |
-
public function get_diretories_only( $dir_tree=array() ){
|
1565 |
$dirs = array();
|
1566 |
|
1567 |
-
if( is_string($dir_tree) ){
|
1568 |
-
$dir_tree = $this->get_directory_tree($dir_tree);
|
1569 |
}
|
1570 |
|
1571 |
-
if( is_array($dir_tree) && !empty($dir_tree) ){
|
1572 |
-
foreach( $dir_tree as $filepath ){
|
1573 |
-
$dir_path = dirname($filepath);
|
1574 |
|
1575 |
-
if(
|
1576 |
-
!in_array($dir_path, $dirs)
|
1577 |
-
&& !in_array($dir_path, $this->ignored_directories['directories'])
|
1578 |
){
|
1579 |
$dirs[] = $dir_path;
|
1580 |
}
|
@@ -1590,28 +1642,28 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1590 |
* @param string $directory Path of the existing directory that will be removed.
|
1591 |
* @return boolean TRUE if all the files and folder inside the directory were removed.
|
1592 |
*/
|
1593 |
-
public function remove_directory_tree( $directory='' ){
|
1594 |
-
$all_removed =
|
1595 |
-
$dir_tree = $this->get_directory_tree($directory);
|
1596 |
|
1597 |
-
if( $dir_tree ){
|
1598 |
$dirs_only = array();
|
1599 |
|
1600 |
-
foreach( $dir_tree as $filepath ){
|
1601 |
-
if( is_file($filepath) ){
|
1602 |
-
$removed = @unlink($filepath);
|
1603 |
|
1604 |
-
if(
|
1605 |
-
$all_removed =
|
1606 |
}
|
1607 |
}
|
1608 |
|
1609 |
-
elseif( is_dir($filepath) ){
|
1610 |
$dirs_only[] = $filepath;
|
1611 |
}
|
1612 |
}
|
1613 |
|
1614 |
-
if( !function_exists('sucuriscan_strlen_diff') ){
|
1615 |
/**
|
1616 |
* Evaluates the difference between the length of two strings.
|
1617 |
*
|
@@ -1619,15 +1671,15 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1619 |
* @param string $b Second string of characters that will be measured.
|
1620 |
* @return integer The difference in length between the two strings.
|
1621 |
*/
|
1622 |
-
function sucuriscan_strlen_diff( $a='', $b='' ){
|
1623 |
-
return strlen($b) - strlen($a);
|
1624 |
}
|
1625 |
}
|
1626 |
|
1627 |
-
usort($dirs_only, 'sucuriscan_strlen_diff');
|
1628 |
|
1629 |
-
foreach( $dirs_only as $dir_path ){
|
1630 |
-
@rmdir($dir_path);
|
1631 |
}
|
1632 |
}
|
1633 |
|
@@ -1642,7 +1694,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1642 |
* @param string $filepath Path to the file.
|
1643 |
* @return array An array where each element is a line in the file.
|
1644 |
*/
|
1645 |
-
public static function file_lines( $filepath='' ){
|
1646 |
return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
|
1647 |
}
|
1648 |
|
@@ -1656,7 +1708,7 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1656 |
* @param boolean $adaptive Whether the buffer will adapt to a specific number of bytes or not.
|
1657 |
* @return string Text contained at the end of the file.
|
1658 |
*/
|
1659 |
-
public static function tail_file( $file_path='', $lines=1, $adaptive=true ) {
|
1660 |
$file = @fopen( $file_path, 'rb' );
|
1661 |
$limit = $lines;
|
1662 |
|
@@ -1667,30 +1719,60 @@ class SucuriScanFileInfo extends SucuriScan {
|
|
1667 |
elseif ( $adaptive && $lines < 10 ) { $buffer = 512; }
|
1668 |
else { $buffer = 4096; }
|
1669 |
|
1670 |
-
if ( fread($file, 1) != "\n" ) { $lines -= 1; }
|
1671 |
|
1672 |
$output = '';
|
1673 |
$chunk = '';
|
1674 |
|
1675 |
-
while ( ftell($file) > 0 && $lines >= 0 ) {
|
1676 |
-
$seek = min( ftell($file), $buffer );
|
1677 |
fseek( $file, -$seek, SEEK_CUR );
|
1678 |
$chunk = fread( $file, $seek );
|
1679 |
$output = $chunk . $output;
|
1680 |
-
fseek( $file, -mb_strlen($chunk, '8bit'), SEEK_CUR );
|
1681 |
$lines -= substr_count( $chunk, "\n" );
|
1682 |
}
|
1683 |
|
1684 |
-
fclose($file);
|
1685 |
|
1686 |
$lines_arr = explode( "\n", $output );
|
1687 |
-
$lines_count = count($lines_arr);
|
1688 |
$result = array_slice( $lines_arr, ($lines_count - $limit) );
|
1689 |
|
1690 |
return $result;
|
1691 |
}
|
1692 |
|
1693 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1694 |
}
|
1695 |
|
1696 |
}
|
@@ -1723,7 +1805,7 @@ class SucuriScanCache extends SucuriScan {
|
|
1723 |
*
|
1724 |
* @var null|string
|
1725 |
*/
|
1726 |
-
private $datastore =
|
1727 |
|
1728 |
/**
|
1729 |
* The full path of the datastore file.
|
@@ -1740,7 +1822,7 @@ class SucuriScanCache extends SucuriScan {
|
|
1740 |
*
|
1741 |
* @var boolean
|
1742 |
*/
|
1743 |
-
private $usable_datastore =
|
1744 |
|
1745 |
/**
|
1746 |
* Class constructor.
|
@@ -1748,7 +1830,7 @@ class SucuriScanCache extends SucuriScan {
|
|
1748 |
* @param string $datastore Unique name (or identifier) of the file with the data.
|
1749 |
* @return void
|
1750 |
*/
|
1751 |
-
public function __construct( $datastore='' ){
|
1752 |
$this->datastore = $datastore;
|
1753 |
$this->datastore_path = $this->datastore_file_path();
|
1754 |
$this->usable_datastore = (bool) $this->datastore_path;
|
@@ -1775,18 +1857,18 @@ class SucuriScanCache extends SucuriScan {
|
|
1775 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1776 |
* @return string Default content of every datastore file.
|
1777 |
*/
|
1778 |
-
private function datastore_info( $finfo=array() ){
|
1779 |
$attrs = $this->datastore_default_info();
|
1780 |
$info_is_available = (bool) isset($finfo['info']);
|
1781 |
$info = "<?php\n";
|
1782 |
|
1783 |
-
foreach( $attrs as $attr_name => $attr_value ){
|
1784 |
-
if(
|
1785 |
$info_is_available
|
1786 |
&& $attr_name != 'updated_on'
|
1787 |
-
&& isset($finfo['info'][$attr_name])
|
1788 |
){
|
1789 |
-
$attr_value = $finfo['info'][$attr_name];
|
1790 |
}
|
1791 |
|
1792 |
$info .= sprintf( "// %s=%s;\n", $attr_name, $attr_value );
|
@@ -1806,29 +1888,29 @@ class SucuriScanCache extends SucuriScan {
|
|
1806 |
* @return string The full path where the datastore file is located, FALSE otherwise.
|
1807 |
*/
|
1808 |
private function datastore_file_path(){
|
1809 |
-
if( !is_null($this->datastore) ){
|
1810 |
$folder_path = $this->datastore_folder_path();
|
1811 |
$file_path = $folder_path . 'sucuri-' . $this->datastore . '.php';
|
1812 |
|
1813 |
// Create the datastore file is it does not exists and the folder is writable.
|
1814 |
-
if(
|
1815 |
-
!file_exists($file_path)
|
1816 |
-
&& is_writable($folder_path)
|
1817 |
){
|
1818 |
@file_put_contents( $file_path, $this->datastore_info(), LOCK_EX );
|
1819 |
}
|
1820 |
|
1821 |
// Continue the operation after an attemp to create the datastore file.
|
1822 |
-
if(
|
1823 |
-
file_exists($file_path)
|
1824 |
-
&& is_writable($file_path)
|
1825 |
-
&& is_readable($file_path)
|
1826 |
){
|
1827 |
return $file_path;
|
1828 |
}
|
1829 |
}
|
1830 |
|
1831 |
-
return
|
1832 |
}
|
1833 |
|
1834 |
/**
|
@@ -1839,20 +1921,20 @@ class SucuriScanCache extends SucuriScan {
|
|
1839 |
* @param string $action Either "valid", "content", or "header".
|
1840 |
* @return string Cache key pattern.
|
1841 |
*/
|
1842 |
-
private function key_pattern( $action='valid' ){
|
1843 |
-
if( $action == 'valid' ){
|
1844 |
return '/^([0-9a-zA-Z_]+)$/';
|
1845 |
}
|
1846 |
|
1847 |
-
if( $action == 'content' ){
|
1848 |
return '/^([0-9a-zA-Z_]+):(.+)/';
|
1849 |
}
|
1850 |
|
1851 |
-
if( $action == 'header' ){
|
1852 |
return '/^\/\/ ([a-z_]+)=(.*);$/';
|
1853 |
}
|
1854 |
|
1855 |
-
return
|
1856 |
}
|
1857 |
|
1858 |
/**
|
@@ -1861,8 +1943,8 @@ class SucuriScanCache extends SucuriScan {
|
|
1861 |
* @param string $key Unique name to identify the data in the datastore file.
|
1862 |
* @return boolean TRUE if the format of the key name is valid, FALSE otherwise.
|
1863 |
*/
|
1864 |
-
private function valid_key_name( $key='' ){
|
1865 |
-
return (bool) preg_match( $this->key_pattern('valid'), $key );
|
1866 |
}
|
1867 |
|
1868 |
/**
|
@@ -1871,13 +1953,13 @@ class SucuriScanCache extends SucuriScan {
|
|
1871 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1872 |
* @return boolean TRUE if the operation finished successfully, FALSE otherwise.
|
1873 |
*/
|
1874 |
-
private function save_new_entries( $finfo=array() ){
|
1875 |
-
$data_string = $this->datastore_info($finfo);
|
1876 |
|
1877 |
-
if( !empty($finfo) ){
|
1878 |
-
foreach( $finfo['entries'] as $key => $data ){
|
1879 |
-
if( $this->valid_key_name($key) ){
|
1880 |
-
$data = json_encode($data);
|
1881 |
$data_string .= sprintf( "%s:%s\n", $key, $data );
|
1882 |
}
|
1883 |
}
|
@@ -1897,27 +1979,27 @@ class SucuriScanCache extends SucuriScan {
|
|
1897 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
1898 |
* @return array Rainbow table with the key names and decoded values.
|
1899 |
*/
|
1900 |
-
private function get_datastore_content( $assoc=
|
1901 |
$data_object = array(
|
1902 |
'info' => array(),
|
1903 |
'entries' => array(),
|
1904 |
);
|
1905 |
|
1906 |
-
if( $this->usable_datastore ){
|
1907 |
-
$data_lines = SucuriScanFileInfo::file_lines($this->datastore_path);
|
1908 |
|
1909 |
-
if( !empty($data_lines) ){
|
1910 |
-
foreach( $data_lines as $line ){
|
1911 |
-
if( preg_match( $this->key_pattern('header'), $line, $match ) ){
|
1912 |
-
$data_object['info'][$match[1]] = $match[2];
|
1913 |
}
|
1914 |
|
1915 |
-
elseif( preg_match( $this->key_pattern('content'), $line, $match ) ){
|
1916 |
-
if(
|
1917 |
-
$this->valid_key_name($match[1])
|
1918 |
-
&& !array_key_exists($match[1], $data_object)
|
1919 |
){
|
1920 |
-
$data_object['entries'][$match[1]] = @json_decode( $match[2], $assoc );
|
1921 |
}
|
1922 |
}
|
1923 |
}
|
@@ -1942,11 +2024,11 @@ class SucuriScanCache extends SucuriScan {
|
|
1942 |
public function get_datastore_info(){
|
1943 |
$finfo = $this->get_datastore_content();
|
1944 |
|
1945 |
-
if( !empty($finfo['info']) ){
|
1946 |
return $finfo['info'];
|
1947 |
}
|
1948 |
|
1949 |
-
return
|
1950 |
}
|
1951 |
|
1952 |
/**
|
@@ -1955,12 +2037,12 @@ class SucuriScanCache extends SucuriScan {
|
|
1955 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1956 |
* @return integer Total number of unique entries found in the datastore file.
|
1957 |
*/
|
1958 |
-
public function get_count( $finfo=
|
1959 |
-
if( !is_array($finfo) ){
|
1960 |
$finfo = $this->get_datastore_content();
|
1961 |
}
|
1962 |
|
1963 |
-
return count($finfo['entries']);
|
1964 |
}
|
1965 |
|
1966 |
/**
|
@@ -1973,20 +2055,20 @@ class SucuriScanCache extends SucuriScan {
|
|
1973 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1974 |
* @return boolean TRUE if the life time of the data has expired, FALSE otherwise.
|
1975 |
*/
|
1976 |
-
public function data_has_expired( $lifetime=0, $finfo=
|
1977 |
-
if( is_null($finfo) ){
|
1978 |
$finfo = $this->get_datastore_content();
|
1979 |
}
|
1980 |
|
1981 |
-
if( $lifetime > 0 && !empty($finfo['info']) ){
|
1982 |
-
$diff_time = time() - intval($finfo['info']['updated_on']);
|
1983 |
|
1984 |
-
if( $diff_time >= $lifetime ){
|
1985 |
-
return
|
1986 |
}
|
1987 |
}
|
1988 |
|
1989 |
-
return
|
1990 |
}
|
1991 |
|
1992 |
/**
|
@@ -1999,49 +2081,49 @@ class SucuriScanCache extends SucuriScan {
|
|
1999 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2000 |
* @return boolean TRUE if the operation finished successfully, FALSE otherwise.
|
2001 |
*/
|
2002 |
-
private function handle_key_data( $key='', $data=
|
2003 |
-
if( preg_match('/^(add|set|get|get_all|exists|delete)$/', $action) ){
|
2004 |
-
if(
|
2005 |
-
$this->valid_key_name($key)
|
2006 |
&& $this->usable_datastore
|
2007 |
){
|
2008 |
-
$finfo = $this->get_datastore_content($assoc);
|
2009 |
|
2010 |
-
switch( $action ){
|
2011 |
case 'add': /* no_break */
|
2012 |
case 'set':
|
2013 |
-
$finfo['entries'][$key] = $data;
|
2014 |
-
return $this->save_new_entries($finfo);
|
2015 |
break;
|
2016 |
case 'get':
|
2017 |
-
if(
|
2018 |
-
|
2019 |
-
&& array_key_exists($key, $finfo['entries'])
|
2020 |
){
|
2021 |
-
return $finfo['entries'][$key];
|
2022 |
}
|
2023 |
break;
|
2024 |
case 'get_all': /* no_break */
|
2025 |
-
if(
|
2026 |
return $finfo['entries'];
|
2027 |
}
|
2028 |
case 'exists':
|
2029 |
-
if(
|
2030 |
-
|
2031 |
-
&& array_key_exists($key, $finfo['entries'])
|
2032 |
){
|
2033 |
-
return
|
2034 |
}
|
2035 |
break;
|
2036 |
case 'delete':
|
2037 |
-
unset($finfo['entries'][$key]);
|
2038 |
-
return $this->save_new_entries($finfo);
|
2039 |
break;
|
2040 |
}
|
2041 |
}
|
2042 |
}
|
2043 |
|
2044 |
-
return
|
2045 |
}
|
2046 |
|
2047 |
/**
|
@@ -2054,7 +2136,7 @@ class SucuriScanCache extends SucuriScan {
|
|
2054 |
* @param string $data Mixed data stored in the datastore file following the unique key name.
|
2055 |
* @return boolean TRUE if the data was stored successfully, FALSE otherwise.
|
2056 |
*/
|
2057 |
-
public function add( $key='', $data='' ){
|
2058 |
return $this->handle_key_data( $key, $data, 'add' );
|
2059 |
}
|
2060 |
|
@@ -2065,7 +2147,7 @@ class SucuriScanCache extends SucuriScan {
|
|
2065 |
* @param string $data Mixed data stored in the datastore file following the unique key name.
|
2066 |
* @return boolean TRUE if the data was stored successfully, FALSE otherwise.
|
2067 |
*/
|
2068 |
-
public function set( $key='', $data='' ){
|
2069 |
return $this->handle_key_data( $key, $data, 'set' );
|
2070 |
}
|
2071 |
|
@@ -2077,10 +2159,10 @@ class SucuriScanCache extends SucuriScan {
|
|
2077 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2078 |
* @return string Mixed data stored in the datastore file following the unique key name.
|
2079 |
*/
|
2080 |
-
public function get( $key='', $lifetime=0, $assoc=
|
2081 |
-
$assoc = ( $assoc == 'array' ?
|
2082 |
|
2083 |
-
return $this->handle_key_data( $key,
|
2084 |
}
|
2085 |
|
2086 |
/**
|
@@ -2090,10 +2172,10 @@ class SucuriScanCache extends SucuriScan {
|
|
2090 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2091 |
* @return string Mixed data stored in the datastore file following the unique key name.
|
2092 |
*/
|
2093 |
-
public function get_all( $lifetime=0, $assoc=
|
2094 |
-
$assoc = ( $assoc == 'array' ?
|
2095 |
|
2096 |
-
return $this->handle_key_data( 'temp',
|
2097 |
}
|
2098 |
|
2099 |
/**
|
@@ -2102,8 +2184,8 @@ class SucuriScanCache extends SucuriScan {
|
|
2102 |
* @param string $key Unique name to identify the data in the datastore file.
|
2103 |
* @return boolean TRUE if the key exists in the datastore file, FALSE otherwise.
|
2104 |
*/
|
2105 |
-
public function exists( $key='' ){
|
2106 |
-
return $this->handle_key_data( $key,
|
2107 |
}
|
2108 |
|
2109 |
/**
|
@@ -2112,8 +2194,8 @@ class SucuriScanCache extends SucuriScan {
|
|
2112 |
* @param string $key Unique name to identify the data in the datastore file.
|
2113 |
* @return boolean TRUE if the entries were removed, FALSE otherwise.
|
2114 |
*/
|
2115 |
-
public function delete( $key='' ){
|
2116 |
-
return $this->handle_key_data( $key,
|
2117 |
}
|
2118 |
|
2119 |
/**
|
@@ -2124,7 +2206,7 @@ class SucuriScanCache extends SucuriScan {
|
|
2124 |
public function flush(){
|
2125 |
$finfo = $this->get_datastore_content();
|
2126 |
|
2127 |
-
return $this->save_new_entries($finfo);
|
2128 |
}
|
2129 |
|
2130 |
}
|
@@ -2159,17 +2241,17 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2159 |
*
|
2160 |
* @return array Default plugin option values.
|
2161 |
*/
|
2162 |
-
|
2163 |
$defaults = array(
|
2164 |
-
'sucuriscan_api_key' =>
|
2165 |
'sucuriscan_account' => '',
|
2166 |
'sucuriscan_datastore_path' => '',
|
2167 |
'sucuriscan_fs_scanner' => 'enabled',
|
2168 |
'sucuriscan_scan_frequency' => 'hourly',
|
2169 |
'sucuriscan_scan_interface' => 'spl',
|
2170 |
-
'sucuriscan_scan_modfiles' => '
|
2171 |
'sucuriscan_scan_checksums' => 'enabled',
|
2172 |
-
'sucuriscan_scan_errorlogs' => '
|
2173 |
'sucuriscan_sitecheck_scanner' => 'enabled',
|
2174 |
'sucuriscan_sitecheck_counter' => 0,
|
2175 |
'sucuriscan_parse_errorlogs' => 'enabled',
|
@@ -2196,6 +2278,10 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2196 |
'sucuriscan_heartbeat_pulse' => 15,
|
2197 |
'sucuriscan_heartbeat_interval' => 'standard',
|
2198 |
'sucuriscan_heartbeat_autostart' => 'enabled',
|
|
|
|
|
|
|
|
|
2199 |
);
|
2200 |
|
2201 |
return $defaults;
|
@@ -2207,71 +2293,35 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2207 |
* @param string|array $settings Either an array that will be complemented or a string with the name of the option.
|
2208 |
* @return string|array The default values for the specified options.
|
2209 |
*/
|
2210 |
-
|
2211 |
$default_options = self::get_default_option_values();
|
2212 |
|
2213 |
// Use framework built-in function.
|
2214 |
-
if( function_exists('get_option') ){
|
2215 |
-
$admin_email = get_option('admin_email');
|
2216 |
$default_options['sucuriscan_account'] = $admin_email;
|
2217 |
$default_options['sucuriscan_notify_to'] = $admin_email;
|
2218 |
}
|
2219 |
|
2220 |
-
if( is_array($settings) ){
|
2221 |
-
foreach( $default_options as $option_name => $option_value ){
|
2222 |
-
if( !isset($settings[$option_name]) ){
|
2223 |
-
$settings[$option_name] = $option_value;
|
2224 |
}
|
2225 |
}
|
2226 |
|
2227 |
return $settings;
|
2228 |
}
|
2229 |
|
2230 |
-
if
|
2231 |
-
|
2232 |
-
|
2233 |
-
|
2234 |
-
|
2235 |
-
|
2236 |
-
return FALSE;
|
2237 |
-
}
|
2238 |
-
|
2239 |
-
/**
|
2240 |
-
* Retrieve specific options from the database.
|
2241 |
-
*
|
2242 |
-
* Considering the case in which this plugin is installed in a multisite instance
|
2243 |
-
* of Wordpress, the allowed values for the first parameter of this function will
|
2244 |
-
* be treated like this:
|
2245 |
-
*
|
2246 |
-
* <ul>
|
2247 |
-
* <li>all_plugin_options: Will retrieve all the option values created by this plugin in the main site (aka. network),</li>
|
2248 |
-
* <li>site_options: Will retrieve all the option values stored in the current site visited by the user (aka. sub-site) excluding the transient options,</li>
|
2249 |
-
* <li>plugin_option: Will retrieve one specific option from the network site only if the option starts with the prefix <i>sucuri_<i>.</li>
|
2250 |
-
* </ul>
|
2251 |
-
*
|
2252 |
-
* @param string $filter_by Criteria to filter the results, valid values: all_plugin_options, site_options, sucuri_option.
|
2253 |
-
* @param string $option_name Optional parameter with the name of the option that will be filtered.
|
2254 |
-
* @return array List of options retrieved from the query in the database.
|
2255 |
-
*/
|
2256 |
-
public static function get_options_from_db( $filter_by='', $option_name='' ){
|
2257 |
-
global $wpdb;
|
2258 |
-
|
2259 |
-
$output = FALSE;
|
2260 |
-
|
2261 |
-
switch($filter_by){
|
2262 |
-
case 'all_plugin_options':
|
2263 |
-
$output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE 'sucuriscan%' ORDER BY option_id ASC");
|
2264 |
-
break;
|
2265 |
-
case 'site_options':
|
2266 |
-
$output = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name NOT LIKE '%_transient_%' ORDER BY option_id ASC");
|
2267 |
-
break;
|
2268 |
-
case 'plugin_option':
|
2269 |
-
$row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM {$wpdb->base_prefix}options WHERE option_name = %s LIMIT 1", $option_name) );
|
2270 |
-
if( $row ){ $output = $row->option_value; }
|
2271 |
-
break;
|
2272 |
}
|
2273 |
|
2274 |
-
return
|
2275 |
}
|
2276 |
|
2277 |
/**
|
@@ -2291,10 +2341,19 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2291 |
* @param string $option_name Optional parameter that you can use to filter the results to one option.
|
2292 |
* @return string The value (or default value) of the option specified.
|
2293 |
*/
|
2294 |
-
public static function get_option( $option_name='' ){
|
2295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2296 |
|
2297 |
-
return
|
2298 |
}
|
2299 |
|
2300 |
/**
|
@@ -2311,14 +2370,14 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2311 |
* @param string $option_value The new value for the option, can be an integer, string, array, or object.
|
2312 |
* @return boolean True if option value has changed, false if not or if update failed.
|
2313 |
*/
|
2314 |
-
public static function update_option( $option_name='', $option_value='' ){
|
2315 |
-
if( function_exists('update_option') ){
|
2316 |
-
$option_name = self::variable_prefix($option_name);
|
2317 |
|
2318 |
return update_option( $option_name, $option_value );
|
2319 |
}
|
2320 |
|
2321 |
-
return
|
2322 |
}
|
2323 |
|
2324 |
/**
|
@@ -2331,64 +2390,33 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2331 |
* @param string $option_name Name of the option to be deleted.
|
2332 |
* @return boolean True, if option is successfully deleted. False on failure, or option does not exist.
|
2333 |
*/
|
2334 |
-
public static function delete_option( $option_name='' ){
|
2335 |
-
if( function_exists('delete_option') ){
|
2336 |
-
$option_name = self::variable_prefix($option_name);
|
2337 |
|
2338 |
return delete_option( $option_name );
|
2339 |
}
|
2340 |
|
2341 |
-
return
|
2342 |
-
}
|
2343 |
-
|
2344 |
-
/**
|
2345 |
-
* Retrieve all the options created by this Plugin from the Wordpress database.
|
2346 |
-
*
|
2347 |
-
* The function acts as an alias of WP::get_option() and if the returned value
|
2348 |
-
* is FALSE it tries to search for a default value to complement the information.
|
2349 |
-
*
|
2350 |
-
* @param string $option_name Optional parameter that you can use to filter the results to one option.
|
2351 |
-
* @return array Either FALSE or an Array containing all the sucuri options in the database.
|
2352 |
-
*/
|
2353 |
-
private static function get_options( $option_name='' ){
|
2354 |
-
if( !empty($option_name) ){
|
2355 |
-
return self::get_single_option($option_name);
|
2356 |
-
}
|
2357 |
-
|
2358 |
-
$settings = array();
|
2359 |
-
$results = self::get_options_from_db('all_plugin_options');
|
2360 |
-
|
2361 |
-
foreach( $results as $row ){
|
2362 |
-
$settings[$row->option_name] = $row->option_value;
|
2363 |
-
}
|
2364 |
-
|
2365 |
-
return self::get_default_options($settings);
|
2366 |
}
|
2367 |
|
2368 |
/**
|
2369 |
-
*
|
2370 |
*
|
2371 |
-
* @
|
2372 |
-
* @return string Value of the option stored in the database, FALSE if not found.
|
2373 |
*/
|
2374 |
-
|
2375 |
-
$
|
2376 |
-
$is_plugin_option = preg_match('/^sucuriscan_/', $option_name) ? TRUE : FALSE;
|
2377 |
-
|
2378 |
-
if( self::is_multisite() && $is_plugin_option ){
|
2379 |
-
$option_value = self::get_options_from_db('plugin_option', $option_name);
|
2380 |
-
}
|
2381 |
|
2382 |
-
|
2383 |
-
|
2384 |
-
|
2385 |
-
|
|
|
2386 |
|
2387 |
-
|
2388 |
-
|
2389 |
}
|
2390 |
-
|
2391 |
-
return $option_value;
|
2392 |
}
|
2393 |
|
2394 |
/**
|
@@ -2399,11 +2427,17 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2399 |
* @return array All the options stored by Wordpress in the database, except the transient options.
|
2400 |
*/
|
2401 |
public static function get_site_options(){
|
|
|
|
|
2402 |
$settings = array();
|
2403 |
-
$results =
|
|
|
|
|
|
|
|
|
2404 |
|
2405 |
-
foreach( $results as $row ){
|
2406 |
-
$settings[$row->option_name] = $row->option_value;
|
2407 |
}
|
2408 |
|
2409 |
return $settings;
|
@@ -2416,7 +2450,7 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2416 |
* @param array $request The content of the global variable GET or POST considering SERVER[REQUEST_METHOD].
|
2417 |
* @return array A list of all the options that were changes through this request.
|
2418 |
*/
|
2419 |
-
public static function what_options_were_changed( $request=array() ){
|
2420 |
$options_changed = array(
|
2421 |
'original' => array(),
|
2422 |
'changed' => array()
|
@@ -2424,13 +2458,13 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2424 |
|
2425 |
$site_options = self::get_site_options();
|
2426 |
|
2427 |
-
foreach( $request as $req_name => $req_value ){
|
2428 |
-
if(
|
2429 |
-
array_key_exists($req_name, $site_options)
|
2430 |
-
&& $site_options[$req_name] != $req_value
|
2431 |
){
|
2432 |
-
$options_changed['original'][$req_name] = $site_options[$req_name];
|
2433 |
-
$options_changed['changed'][$req_name] = $req_value;
|
2434 |
}
|
2435 |
}
|
2436 |
|
@@ -2444,19 +2478,19 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2444 |
*/
|
2445 |
public static function check_options_nonce(){
|
2446 |
// Create the option_page value if permalink submission.
|
2447 |
-
if(
|
2448 |
-
!isset($_POST['option_page'])
|
2449 |
&& isset($_POST['permalink_structure'])
|
2450 |
){
|
2451 |
$_POST['option_page'] = 'permalink';
|
2452 |
}
|
2453 |
|
2454 |
// Check if the option_page has an allowed value.
|
2455 |
-
if( $option_page = SucuriScanRequest::post('option_page') ){
|
2456 |
-
$nonce='_wpnonce';
|
2457 |
$action = '';
|
2458 |
|
2459 |
-
switch( $option_page ){
|
2460 |
case 'general': /* no_break */
|
2461 |
case 'writing': /* no_break */
|
2462 |
case 'reading': /* no_break */
|
@@ -2471,16 +2505,16 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2471 |
}
|
2472 |
|
2473 |
// Check the nonce validity.
|
2474 |
-
if(
|
2475 |
-
!empty($action)
|
2476 |
-
&& isset($_REQUEST[$nonce])
|
2477 |
-
&& wp_verify_nonce($_REQUEST[$nonce], $action)
|
2478 |
){
|
2479 |
-
return
|
2480 |
}
|
2481 |
}
|
2482 |
|
2483 |
-
return
|
2484 |
}
|
2485 |
|
2486 |
/**
|
@@ -2490,24 +2524,24 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2490 |
* @return array List of ignored posts-types to send notifications.
|
2491 |
*/
|
2492 |
public static function get_ignored_events(){
|
2493 |
-
$post_types = self::get_option(':ignored_events');
|
2494 |
-
$post_types_arr =
|
2495 |
|
2496 |
// Encode (old) serialized data into JSON.
|
2497 |
-
if( self::is_serialized($post_types) ){
|
2498 |
-
$post_types_arr = @unserialize($post_types);
|
2499 |
-
$post_types_fix = json_encode($post_types_arr);
|
2500 |
self::update_option( ':ignored_events', $post_types_fix );
|
2501 |
|
2502 |
return $post_types_arr;
|
2503 |
}
|
2504 |
|
2505 |
// Decode JSON-encoded data as an array.
|
2506 |
-
elseif( preg_match('/^\{.+\}$/', $post_types) ){
|
2507 |
-
$post_types_arr = @json_decode( $post_types,
|
2508 |
}
|
2509 |
|
2510 |
-
if( !is_array($post_types_arr) ){
|
2511 |
$post_types_arr = array();
|
2512 |
}
|
2513 |
|
@@ -2520,25 +2554,25 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2520 |
* @param string $event_name Unique post-type name.
|
2521 |
* @return boolean Whether the event was ignored or not.
|
2522 |
*/
|
2523 |
-
public static function add_ignored_event( $event_name='' ){
|
2524 |
-
if( function_exists('get_post_types') ){
|
2525 |
$post_types = get_post_types();
|
2526 |
|
2527 |
// Check if the event is a registered post-type.
|
2528 |
-
if( array_key_exists($event_name, $post_types) ){
|
2529 |
$ignored_events = self::get_ignored_events();
|
2530 |
|
2531 |
// Check if the event is not ignored already.
|
2532 |
-
if( !array_key_exists($event_name, $ignored_events) ){
|
2533 |
-
$ignored_events[$event_name] = time();
|
2534 |
-
$saved = self::update_option( ':ignored_events', json_encode($ignored_events) );
|
2535 |
|
2536 |
return $saved;
|
2537 |
}
|
2538 |
}
|
2539 |
}
|
2540 |
|
2541 |
-
return
|
2542 |
}
|
2543 |
|
2544 |
/**
|
@@ -2547,17 +2581,17 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2547 |
* @param string $event_name Unique post-type name.
|
2548 |
* @return boolean Whether the event was removed from the list or not.
|
2549 |
*/
|
2550 |
-
public static function remove_ignored_event( $event_name='' ){
|
2551 |
$ignored_events = self::get_ignored_events();
|
2552 |
|
2553 |
-
if( array_key_exists($event_name, $ignored_events) ){
|
2554 |
-
unset( $ignored_events[$event_name] );
|
2555 |
-
$saved = self::update_option( ':ignored_events', json_encode($ignored_events) );
|
2556 |
|
2557 |
return $saved;
|
2558 |
}
|
2559 |
|
2560 |
-
return
|
2561 |
}
|
2562 |
|
2563 |
/**
|
@@ -2566,15 +2600,15 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2566 |
* @param string $event_name Unique post-type name.
|
2567 |
* @return boolean Whether an event is being ignored or not.
|
2568 |
*/
|
2569 |
-
public static function is_ignored_event( $event_name='' ){
|
2570 |
-
$event_name = strtolower($event_name);
|
2571 |
$ignored_events = self::get_ignored_events();
|
2572 |
|
2573 |
-
if( array_key_exists($event_name, $ignored_events) ){
|
2574 |
-
return
|
2575 |
}
|
2576 |
|
2577 |
-
return
|
2578 |
}
|
2579 |
|
2580 |
/**
|
@@ -2600,17 +2634,17 @@ class SucuriScanOption extends SucuriScanRequest {
|
|
2600 |
'SECURE_AUTH_SALT',
|
2601 |
);
|
2602 |
|
2603 |
-
foreach( $key_names as $key_name ){
|
2604 |
-
if( defined($key_name) ){
|
2605 |
-
$key_value = constant($key_name);
|
2606 |
|
2607 |
-
if( stripos( $key_value, 'unique phrase' ) !==
|
2608 |
-
$response['bad'][$key_name] = $key_value;
|
2609 |
} else {
|
2610 |
-
$response['good'][$key_name] = $key_value;
|
2611 |
}
|
2612 |
} else {
|
2613 |
-
$response['missing'][$key_name] =
|
2614 |
}
|
2615 |
}
|
2616 |
|
@@ -2644,7 +2678,7 @@ class SucuriScanEvent extends SucuriScan {
|
|
2644 |
public static function schedule_task(){
|
2645 |
$task_name = 'sucuriscan_scheduled_scan';
|
2646 |
|
2647 |
-
if( !wp_next_scheduled($task_name) ){
|
2648 |
wp_schedule_event( time() + 10, 'twicedaily', $task_name );
|
2649 |
}
|
2650 |
|
@@ -2658,31 +2692,31 @@ class SucuriScanEvent extends SucuriScan {
|
|
2658 |
* @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
|
2659 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation respectively.
|
2660 |
*/
|
2661 |
-
private static function verify_run( $runtime=0, $force_scan=
|
2662 |
$option_name = ':runtime';
|
2663 |
-
$last_run = SucuriScanOption::get_option($option_name);
|
2664 |
$current_time = time();
|
2665 |
|
2666 |
// The filesystem scanner can be disabled from the settings page.
|
2667 |
-
if(
|
2668 |
-
SucuriScanOption::get_option(':fs_scanner') == 'disabled'
|
2669 |
-
&& $force_scan ===
|
2670 |
){
|
2671 |
-
return
|
2672 |
}
|
2673 |
|
2674 |
// Check if the last runtime is too near the current time.
|
2675 |
-
if( $last_run &&
|
2676 |
$runtime_diff = $current_time - $runtime;
|
2677 |
|
2678 |
-
if( $last_run >= $runtime_diff ){
|
2679 |
-
return
|
2680 |
}
|
2681 |
}
|
2682 |
|
2683 |
SucuriScanOption::update_option( $option_name, $current_time );
|
2684 |
|
2685 |
-
return
|
2686 |
}
|
2687 |
|
2688 |
/**
|
@@ -2693,17 +2727,17 @@ class SucuriScanEvent extends SucuriScan {
|
|
2693 |
*/
|
2694 |
private static function report_site_version(){
|
2695 |
$option_name = ':site_version';
|
2696 |
-
$reported_version = SucuriScanOption::get_option($option_name);
|
2697 |
$wp_version = self::site_version();
|
2698 |
|
2699 |
-
if( $reported_version != $wp_version ){
|
2700 |
-
|
2701 |
SucuriScanOption::update_option( $option_name, $wp_version );
|
2702 |
|
2703 |
-
return
|
2704 |
}
|
2705 |
|
2706 |
-
return
|
2707 |
}
|
2708 |
|
2709 |
/**
|
@@ -2714,27 +2748,27 @@ class SucuriScanEvent extends SucuriScan {
|
|
2714 |
* @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
|
2715 |
* @return boolean TRUE if the filesystem scan was successful, FALSE otherwise.
|
2716 |
*/
|
2717 |
-
public static function filesystem_scan( $force_scan=
|
2718 |
$minimum_runtime = SUCURISCAN_MINIMUM_RUNTIME;
|
2719 |
|
2720 |
-
if(
|
2721 |
self::verify_run( $minimum_runtime, $force_scan )
|
2722 |
-
&& class_exists('SucuriScanFileInfo')
|
2723 |
&& SucuriScanAPI::get_plugin_key()
|
2724 |
){
|
2725 |
self::report_site_version();
|
2726 |
|
2727 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
2728 |
-
$sucuri_fileinfo->scan_interface = SucuriScanOption::get_option(':scan_interface');
|
2729 |
-
$signatures = $sucuri_fileinfo->get_directory_tree_md5(ABSPATH);
|
2730 |
|
2731 |
-
if( $signatures ){
|
2732 |
$hashes_sent = SucuriScanAPI::send_hashes( $signatures );
|
2733 |
|
2734 |
-
if( $hashes_sent ){
|
2735 |
SucuriScanInterface::info( 'Successful filesystem scan' );
|
2736 |
SucuriScanOption::update_option( ':runtime', time() );
|
2737 |
-
return
|
2738 |
} else {
|
2739 |
SucuriScanInterface::error( 'The file hashes could not be stored.' );
|
2740 |
}
|
@@ -2743,7 +2777,7 @@ class SucuriScanEvent extends SucuriScan {
|
|
2743 |
}
|
2744 |
}
|
2745 |
|
2746 |
-
return
|
2747 |
}
|
2748 |
|
2749 |
/**
|
@@ -2754,32 +2788,30 @@ class SucuriScanEvent extends SucuriScan {
|
|
2754 |
* @param string $message The explanation of the event.
|
2755 |
* @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
|
2756 |
*/
|
2757 |
-
|
2758 |
$user = wp_get_current_user();
|
2759 |
-
$username =
|
2760 |
$current_time = date( 'Y-m-d H:i:s' );
|
2761 |
$remote_ip = self::get_remote_addr();
|
2762 |
|
2763 |
-
// Fixing severity value.
|
2764 |
-
$severity = (int) $severity;
|
2765 |
-
if( $severity > 0 ){ $severity = 1; }
|
2766 |
-
elseif( $severity > 5 ){ $severity = 5; }
|
2767 |
-
|
2768 |
// Identify current user in session.
|
2769 |
-
if(
|
2770 |
$user instanceof WP_User
|
2771 |
&& isset($user->user_login)
|
2772 |
-
&& !empty($user->user_login)
|
2773 |
){
|
2774 |
-
if( $user->user_login != $user->display_name ){
|
2775 |
-
$username = sprintf(
|
2776 |
} else {
|
2777 |
-
$username = sprintf(
|
2778 |
}
|
2779 |
}
|
2780 |
|
|
|
|
|
|
|
2781 |
// Convert the severity number into a readable string.
|
2782 |
-
switch( $severity ){
|
2783 |
case 0: $severity_name = 'Debug'; break;
|
2784 |
case 1: $severity_name = 'Notice'; break;
|
2785 |
case 2: $severity_name = 'Info'; break;
|
@@ -2789,7 +2821,12 @@ class SucuriScanEvent extends SucuriScan {
|
|
2789 |
default: $severity_name = 'Info'; break;
|
2790 |
}
|
2791 |
|
2792 |
-
|
|
|
|
|
|
|
|
|
|
|
2793 |
$event_message = sprintf(
|
2794 |
'%s:%s %s; %s',
|
2795 |
$severity_name,
|
@@ -2798,7 +2835,92 @@ class SucuriScanEvent extends SucuriScan {
|
|
2798 |
$message
|
2799 |
);
|
2800 |
|
2801 |
-
return SucuriScanAPI::send_log($event_message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2802 |
}
|
2803 |
|
2804 |
/**
|
@@ -2809,41 +2931,41 @@ class SucuriScanEvent extends SucuriScan {
|
|
2809 |
* @param string $content Body of the email that will be sent to the administrator.
|
2810 |
* @return void
|
2811 |
*/
|
2812 |
-
public static function notify_event( $event='', $content='' ){
|
2813 |
-
$notify = SucuriScanOption::get_option(':notify_' . $event);
|
2814 |
-
$email = SucuriScanOption::get_option(':notify_to');
|
2815 |
$email_params = array();
|
2816 |
|
2817 |
if ( self::is_trusted_ip() ) {
|
2818 |
$notify = 'disabled';
|
2819 |
}
|
2820 |
|
2821 |
-
if( $notify == 'enabled' ){
|
2822 |
-
if( $event == 'post_publication' ){
|
2823 |
$event = 'post_update';
|
2824 |
}
|
2825 |
|
2826 |
-
elseif( $event == 'failed_login' ){
|
2827 |
$content .= "<br>\n<br>\n<em>Explanation: Someone failed to login to your site. If you";
|
2828 |
-
$content .=
|
2829 |
-
$content .=
|
2830 |
$content .= " More details at Password Guessing Brute Force Attacks [2].</em><br>\n<br>\n";
|
2831 |
-
$content .=
|
2832 |
$content .= "[2] http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing <br>\n";
|
2833 |
}
|
2834 |
|
2835 |
// Send a notification even if the limit of emails per hour was reached.
|
2836 |
-
elseif( $event == 'bruteforce_attack' ){
|
2837 |
-
$email_params['Force'] =
|
2838 |
}
|
2839 |
|
2840 |
-
$title = str_replace('_', chr(32), $event);
|
2841 |
$mail_sent = SucuriScanMail::send_mail( $email, $title, $content, $email_params );
|
2842 |
|
2843 |
return $mail_sent;
|
2844 |
}
|
2845 |
|
2846 |
-
return
|
2847 |
}
|
2848 |
|
2849 |
/**
|
@@ -2852,29 +2974,29 @@ class SucuriScanEvent extends SucuriScan {
|
|
2852 |
* @param string $remote_addr The supposed ip address that will be checked.
|
2853 |
* @return boolean TRUE if the IP address of the user is trusted, FALSE otherwise.
|
2854 |
*/
|
2855 |
-
private static function is_trusted_ip( $remote_addr='' ){
|
2856 |
-
$cache = new SucuriScanCache('trustip');
|
2857 |
$trusted_ips = $cache->get_all();
|
2858 |
|
2859 |
-
if (
|
2860 |
$remote_addr = SucuriScan::get_remote_addr();
|
2861 |
}
|
2862 |
|
2863 |
-
$addr_md5 = md5($remote_addr);
|
2864 |
|
2865 |
// Check if the CIDR in range 32 of this IP is trusted.
|
2866 |
if (
|
2867 |
-
is_array($trusted_ips)
|
2868 |
-
&& !empty($trusted_ips)
|
2869 |
-
&& array_key_exists($addr_md5, $trusted_ips)
|
2870 |
) {
|
2871 |
-
return
|
2872 |
}
|
2873 |
|
2874 |
if ( $trusted_ips ) {
|
2875 |
foreach ( $trusted_ips as $cache_key => $ip_info ) {
|
2876 |
$ip_parts = explode( '.', $ip_info->remote_addr );
|
2877 |
-
$ip_pattern =
|
2878 |
|
2879 |
// Generate the regular expression for CIDR range 24.
|
2880 |
if ( $ip_info->cidr_range == 24 ) {
|
@@ -2891,13 +3013,13 @@ class SucuriScanEvent extends SucuriScan {
|
|
2891 |
$ip_pattern = sprintf( '/^%d(\.[0-9]{1,3}){3}$/', $ip_parts[0] );
|
2892 |
}
|
2893 |
|
2894 |
-
if ( $ip_pattern && preg_match($ip_pattern, $remote_addr) ) {
|
2895 |
-
return
|
2896 |
}
|
2897 |
}
|
2898 |
}
|
2899 |
|
2900 |
-
return
|
2901 |
}
|
2902 |
|
2903 |
/**
|
@@ -2906,31 +3028,31 @@ class SucuriScanEvent extends SucuriScan {
|
|
2906 |
* @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
|
2907 |
* @return boolean Either TRUE or FALSE in case of success or error respectively.
|
2908 |
*/
|
2909 |
-
public static function set_new_password( $user_id=0 ){
|
2910 |
-
$user_id = intval($user_id);
|
2911 |
|
2912 |
-
if( $user_id > 0 && function_exists('wp_generate_password') ){
|
2913 |
-
$user = get_userdata($user_id);
|
2914 |
|
2915 |
-
if( $user instanceof WP_User ){
|
2916 |
-
$new_password = wp_generate_password(15,
|
2917 |
|
2918 |
-
$message
|
2919 |
. 'in the website specified above was changed, this is the new password generated automatically '
|
2920 |
. 'by the system, please update as soon as possible.<br><div style="display:inline-block;'
|
2921 |
. 'background:#ddd;font-family:monaco,monospace,courier;font-size:30px;margin:0;padding:15px;'
|
2922 |
. 'border:1px solid #999">'. $new_password .'</div>';
|
2923 |
|
2924 |
-
$data_set = array( 'Force' =>
|
2925 |
SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
|
2926 |
|
2927 |
-
wp_set_password($new_password, $user_id);
|
2928 |
|
2929 |
-
return
|
2930 |
}
|
2931 |
}
|
2932 |
|
2933 |
-
return
|
2934 |
}
|
2935 |
|
2936 |
/**
|
@@ -2946,26 +3068,26 @@ class SucuriScanEvent extends SucuriScan {
|
|
2946 |
$new_wpconfig = '';
|
2947 |
$config_path = self::get_wpconfig_path();
|
2948 |
|
2949 |
-
if( $config_path ){
|
2950 |
$pattern = self::secret_key_pattern();
|
2951 |
$define_tpl = "define('%s',%s'%s');";
|
2952 |
-
$config_lines = SucuriScanFileInfo::file_lines($config_path);
|
2953 |
$new_keys = SucuriScanAPI::get_new_secret_keys();
|
2954 |
$old_keys = array();
|
2955 |
$old_keys_string = '';
|
2956 |
$new_keys_string = '';
|
2957 |
|
2958 |
-
foreach( (array) $config_lines as $config_line ){
|
2959 |
-
$config_line = str_replace("\n", '', $config_line);
|
2960 |
|
2961 |
-
if( preg_match($pattern, $config_line, $match) ){
|
2962 |
$key_name = $match[1];
|
2963 |
|
2964 |
-
if( array_key_exists($key_name, $new_keys) ){
|
2965 |
$white_spaces = $match[2];
|
2966 |
-
$old_keys[$key_name] = $match[3];
|
2967 |
-
$config_line = sprintf( $define_tpl, $key_name, $white_spaces, $new_keys[$key_name] );
|
2968 |
-
$old_keys_string .= sprintf( $define_tpl, $key_name, $white_spaces, $old_keys[$key_name] ) . "\n";
|
2969 |
$new_keys_string .= $config_line . "\n";
|
2970 |
}
|
2971 |
}
|
@@ -2974,7 +3096,7 @@ class SucuriScanEvent extends SucuriScan {
|
|
2974 |
}
|
2975 |
|
2976 |
$response = array(
|
2977 |
-
'updated' => is_writable($config_path),
|
2978 |
'old_keys' => $old_keys,
|
2979 |
'old_keys_string' => $old_keys_string,
|
2980 |
'new_keys' => $new_keys,
|
@@ -2982,14 +3104,14 @@ class SucuriScanEvent extends SucuriScan {
|
|
2982 |
'new_wpconfig' => $new_wpconfig,
|
2983 |
);
|
2984 |
|
2985 |
-
if( $response['updated'] ){
|
2986 |
-
file_put_contents($config_path, $new_wpconfig, LOCK_EX);
|
2987 |
}
|
2988 |
|
2989 |
return $response;
|
2990 |
}
|
2991 |
|
2992 |
-
return
|
2993 |
}
|
2994 |
|
2995 |
}
|
@@ -3012,127 +3134,158 @@ class SucuriScanEvent extends SucuriScan {
|
|
3012 |
class SucuriScanHook extends SucuriScanEvent {
|
3013 |
|
3014 |
/**
|
3015 |
-
* Send to Sucuri servers an alert
|
3016 |
*
|
3017 |
* @param integer $id The post identifier.
|
3018 |
* @return void
|
3019 |
*/
|
3020 |
-
public static function hook_add_attachment( $id=0 ){
|
3021 |
-
$data = (
|
3022 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
3023 |
|
3024 |
-
$message = 'Media file added
|
3025 |
-
self::
|
3026 |
self::notify_event( 'post_publication', $message );
|
3027 |
}
|
3028 |
|
3029 |
/**
|
3030 |
-
* Send an alert
|
3031 |
*
|
3032 |
* @param integer $id Identifier of the new link created;
|
3033 |
* @return void
|
3034 |
*/
|
3035 |
-
public static function hook_add_link( $id=0 ){
|
3036 |
-
$data = (
|
3037 |
-
|
3038 |
-
if( $data ){
|
3039 |
$title = $data->link_name;
|
3040 |
$url = $data->link_url;
|
|
|
3041 |
} else {
|
3042 |
-
$title = '
|
3043 |
$url = 'undefined/url';
|
|
|
3044 |
}
|
3045 |
|
3046 |
-
$message =
|
3047 |
-
|
|
|
|
|
|
|
3048 |
self::notify_event( 'post_publication', $message );
|
3049 |
}
|
3050 |
|
3051 |
/**
|
3052 |
-
* Send an alert
|
3053 |
*
|
3054 |
* @param integer $id The identifier of the category created.
|
3055 |
* @return void
|
3056 |
*/
|
3057 |
-
public static function hook_create_category( $id=0 ){
|
3058 |
-
$title = ( is_int($id) ? get_cat_name($id) : 'Unknown' );
|
3059 |
|
3060 |
-
$message = 'Category created
|
3061 |
-
self::
|
3062 |
self::notify_event( 'post_publication', $message );
|
3063 |
}
|
3064 |
|
3065 |
/**
|
3066 |
-
* Send an alert
|
3067 |
*
|
3068 |
* @param integer $id The identifier of the post deleted.
|
3069 |
* @return void
|
3070 |
*/
|
3071 |
-
public static function hook_delete_post( $id=0 ){
|
3072 |
-
self::
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3073 |
}
|
3074 |
|
3075 |
/**
|
3076 |
-
* Send an alert
|
3077 |
*
|
3078 |
* @param integer $id The identifier of the user account deleted.
|
3079 |
* @return void
|
3080 |
*/
|
3081 |
-
public static function hook_delete_user( $id=0 ){
|
3082 |
-
self::
|
3083 |
}
|
3084 |
|
3085 |
/**
|
3086 |
-
* Send an alert
|
3087 |
* of an user account was executed.
|
3088 |
*
|
3089 |
* @return void
|
3090 |
*/
|
3091 |
public static function hook_login_form_resetpass(){
|
3092 |
// Detecting WordPress 2.8.3 vulnerability - $key is array.
|
3093 |
-
if( isset($_GET['key']) && is_array($_GET['key']) ){
|
3094 |
-
self::
|
3095 |
}
|
3096 |
}
|
3097 |
|
3098 |
/**
|
3099 |
-
* Send an alert
|
3100 |
* from private to published. This will only applies for posts not pages.
|
3101 |
*
|
3102 |
* @param integer $id The identifier of the post changed.
|
3103 |
* @return void
|
3104 |
*/
|
3105 |
-
public static function hook_private_to_published( $id=0 ){
|
3106 |
-
$data = (
|
3107 |
-
|
3108 |
-
if( $data ){
|
3109 |
$title = $data->post_title;
|
3110 |
-
$p_type = ucwords($data->post_type);
|
3111 |
} else {
|
3112 |
$title = 'Unknown';
|
3113 |
$p_type = 'Publication';
|
3114 |
}
|
3115 |
|
3116 |
// Check whether the post-type is being ignored to send notifications.
|
3117 |
-
if( !SucuriScanOption::is_ignored_event($p_type) ){
|
3118 |
-
$message =
|
3119 |
-
|
|
|
|
|
|
|
3120 |
self::notify_event( 'post_publication', $message );
|
3121 |
}
|
3122 |
}
|
3123 |
|
3124 |
/**
|
3125 |
-
* Send an alert
|
3126 |
*
|
3127 |
* @param integer $id The identifier of the post or page published.
|
3128 |
* @return void
|
3129 |
*/
|
3130 |
-
public static function hook_publish( $id=0 ){
|
3131 |
-
$data = (
|
3132 |
-
|
3133 |
-
if( $data ){
|
3134 |
$title = $data->post_title;
|
3135 |
-
$p_type = ucwords($data->post_type);
|
3136 |
$action = ( $data->post_date == $data->post_modified ? 'created' : 'updated' );
|
3137 |
} else {
|
3138 |
$title = 'Unknown';
|
@@ -3140,8 +3293,11 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3140 |
$action = 'published';
|
3141 |
}
|
3142 |
|
3143 |
-
$message =
|
3144 |
-
|
|
|
|
|
|
|
3145 |
self::notify_event( 'post_publication', $message );
|
3146 |
}
|
3147 |
|
@@ -3151,8 +3307,8 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3151 |
* @param integer $id The identifier of the post or page published.
|
3152 |
* @return void
|
3153 |
*/
|
3154 |
-
public static function hook_publish_page( $id=0 ){
|
3155 |
-
self::hook_publish($id);
|
3156 |
}
|
3157 |
|
3158 |
/**
|
@@ -3161,8 +3317,8 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3161 |
* @param integer $id The identifier of the post or page published.
|
3162 |
* @return void
|
3163 |
*/
|
3164 |
-
public static function hook_publish_post( $id=0 ){
|
3165 |
-
self::hook_publish($id);
|
3166 |
}
|
3167 |
|
3168 |
/**
|
@@ -3171,8 +3327,8 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3171 |
* @param integer $id The identifier of the post or page published.
|
3172 |
* @return void
|
3173 |
*/
|
3174 |
-
public static function hook_publish_phone( $id=0 ){
|
3175 |
-
self::hook_publish($id);
|
3176 |
}
|
3177 |
|
3178 |
/**
|
@@ -3181,97 +3337,109 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3181 |
* @param integer $id The identifier of the post or page published.
|
3182 |
* @return void
|
3183 |
*/
|
3184 |
-
public static function hook_xmlrpc_publish_post( $id=0 ){
|
3185 |
-
self::hook_publish($id);
|
3186 |
}
|
3187 |
|
3188 |
/**
|
3189 |
-
* Send an alert
|
3190 |
* of an user account was tried.
|
3191 |
*
|
3192 |
* @param string $title The name of the user account involved in the trasaction.
|
3193 |
* @return void
|
3194 |
*/
|
3195 |
-
public static function hook_retrieve_password( $title='' ){
|
3196 |
-
if( empty($title) ){ $title = '
|
3197 |
|
3198 |
-
self::
|
3199 |
}
|
3200 |
|
3201 |
/**
|
3202 |
-
* Send an alert
|
3203 |
*
|
3204 |
* @param string $title The name of the new theme selected to used through out the site.
|
3205 |
* @return void
|
3206 |
*/
|
3207 |
-
public static function hook_switch_theme( $title='' ){
|
3208 |
-
if( empty($title) ){ $title = '
|
3209 |
|
3210 |
-
$message = 'Theme
|
3211 |
-
self::
|
3212 |
-
self::notify_event( '
|
3213 |
}
|
3214 |
|
3215 |
/**
|
3216 |
-
* Send an alert
|
3217 |
*
|
3218 |
* @param integer $id The identifier of the new user account created.
|
3219 |
* @return void
|
3220 |
*/
|
3221 |
-
public static function hook_user_register( $id=0 ){
|
3222 |
-
$data = (
|
3223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3224 |
|
3225 |
-
$message =
|
3226 |
-
|
|
|
|
|
|
|
3227 |
self::notify_event( 'user_registration', $message );
|
3228 |
}
|
3229 |
|
3230 |
/**
|
3231 |
-
* Send an alert
|
3232 |
* administration panel was successful.
|
3233 |
*
|
3234 |
* @param string $title The name of the user account involved in the transaction.
|
3235 |
* @return void
|
3236 |
*/
|
3237 |
-
public static function hook_wp_login( $title='' ){
|
3238 |
-
if( empty($title) ){ $title = 'Unknown'; }
|
3239 |
|
3240 |
-
$message = 'User
|
3241 |
-
self::
|
3242 |
self::notify_event( 'success_login', $message );
|
3243 |
}
|
3244 |
|
3245 |
/**
|
3246 |
-
* Send an alert
|
3247 |
* administration panel failed.
|
3248 |
*
|
3249 |
* @param string $title The name of the user account involved in the transaction.
|
3250 |
* @return void
|
3251 |
*/
|
3252 |
-
public static function hook_wp_login_failed( $title='' ){
|
3253 |
-
if( empty($title) ){ $title = 'Unknown'; }
|
3254 |
|
3255 |
-
$
|
|
|
3256 |
$message = 'User authentication failed: ' . $title;
|
3257 |
|
|
|
|
|
3258 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
3259 |
$message .= "<br>\nUser wrong password: " . $password;
|
3260 |
}
|
3261 |
|
3262 |
-
self::report_event( 2, 'core', $message );
|
3263 |
self::notify_event( 'failed_login', $message );
|
3264 |
|
3265 |
// Log the failed login in the internal datastore for future reports.
|
3266 |
$logged = sucuriscan_log_failed_login( $title, $password );
|
3267 |
|
3268 |
// Check if the quantity of failed logins will be considered as a brute-force attack.
|
3269 |
-
if( $logged ){
|
3270 |
$failed_logins = sucuriscan_get_failed_logins();
|
3271 |
|
3272 |
-
if( $failed_logins ){
|
3273 |
$max_time = 3600;
|
3274 |
-
$maximum_failed_logins = SucuriScanOption::get_option('sucuriscan_maximum_failed_logins');
|
3275 |
|
3276 |
/**
|
3277 |
* If the time passed is within the hour, and the quantity of failed logins
|
@@ -3280,11 +3448,11 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3280 |
* settings page), then send an email notification reporting the event and
|
3281 |
* specifying that it may be a brute-force attack against the login page.
|
3282 |
*/
|
3283 |
-
if(
|
3284 |
$failed_logins['diff_time'] <= $max_time
|
3285 |
&& $failed_logins['count'] >= $maximum_failed_logins
|
3286 |
){
|
3287 |
-
sucuriscan_report_failed_logins($failed_logins);
|
3288 |
}
|
3289 |
|
3290 |
/**
|
@@ -3296,9 +3464,9 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3296 |
* first entry of that file in case of future attempts during the next sixty
|
3297 |
* minutes.
|
3298 |
*/
|
3299 |
-
elseif( $failed_logins['diff_time'] > $max_time ){
|
3300 |
sucuriscan_reset_failed_logins();
|
3301 |
-
sucuriscan_log_failed_login($title);
|
3302 |
}
|
3303 |
}
|
3304 |
}
|
@@ -3315,224 +3483,303 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3315 |
*/
|
3316 |
public static function hook_undefined_actions(){
|
3317 |
|
|
|
|
|
|
|
3318 |
// Plugin activation and/or deactivation.
|
3319 |
-
if(
|
3320 |
-
current_user_can('activate_plugins')
|
3321 |
&& (
|
3322 |
-
SucuriScanRequest::
|
3323 |
-
SucuriScanRequest::
|
3324 |
)
|
3325 |
){
|
3326 |
$plugin_list = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
3327 |
|
3328 |
-
if(
|
3329 |
-
SucuriScanRequest::get('plugin', '.+')
|
3330 |
-
&& strpos($_SERVER['REQUEST_URI'], 'plugins.php') !==
|
3331 |
){
|
3332 |
-
$
|
3333 |
-
$plugin_list[] = $_GET['plugin'];
|
3334 |
}
|
3335 |
|
3336 |
-
elseif(
|
3337 |
isset($_POST['checked'])
|
3338 |
-
&& is_array($_POST['checked'])
|
3339 |
-
&& !empty($_POST['checked'])
|
3340 |
){
|
3341 |
-
$
|
3342 |
-
$
|
3343 |
}
|
3344 |
|
3345 |
-
foreach( $plugin_list as $plugin ){
|
3346 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3347 |
|
3348 |
-
if(
|
3349 |
-
!empty($plugin_info['Name'])
|
3350 |
-
&& !empty($plugin_info['Version'])
|
3351 |
){
|
3352 |
-
$
|
3353 |
-
'
|
3354 |
-
$
|
3355 |
-
self::escape($plugin_info['
|
3356 |
-
self::escape($
|
3357 |
-
self::escape($plugin)
|
3358 |
);
|
3359 |
-
|
3360 |
-
self::report_event( 3, 'core', $message );
|
3361 |
-
self::notify_event( 'plugin_' . $action_d, $message );
|
3362 |
}
|
3363 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3364 |
}
|
3365 |
|
3366 |
// Plugin update request.
|
3367 |
-
elseif(
|
3368 |
-
current_user_can('update_plugins')
|
3369 |
&& (
|
3370 |
-
SucuriScanRequest::
|
3371 |
-
|| SucuriScanRequest::
|
3372 |
)
|
3373 |
){
|
3374 |
$plugin_list = array();
|
|
|
3375 |
|
3376 |
-
if(
|
3377 |
-
SucuriScanRequest::get('plugin', '.+')
|
3378 |
-
&& strpos($_SERVER['REQUEST_URI'], 'wp-admin/update.php') !==
|
3379 |
){
|
3380 |
-
$plugin_list[] = SucuriScanRequest::get('plugin', '.+');
|
3381 |
}
|
3382 |
|
3383 |
-
elseif(
|
3384 |
isset($_POST['checked'])
|
3385 |
-
&& is_array($_POST['checked'])
|
3386 |
-
&& !empty($_POST['checked'])
|
3387 |
){
|
3388 |
-
$plugin_list =
|
3389 |
}
|
3390 |
|
3391 |
-
foreach( $plugin_list as $plugin ){
|
3392 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3393 |
|
3394 |
-
if(
|
3395 |
-
!empty($plugin_info['Name'])
|
3396 |
-
&& !empty($plugin_info['Version'])
|
3397 |
){
|
3398 |
-
$
|
3399 |
-
'
|
3400 |
-
self::escape($plugin_info['Name']),
|
3401 |
-
self::escape($plugin_info['Version']),
|
3402 |
-
self::escape($plugin)
|
3403 |
);
|
3404 |
-
|
3405 |
-
self::report_event( 3, 'core', $message );
|
3406 |
-
self::notify_event( 'plugin_updated', $message );
|
3407 |
}
|
3408 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3409 |
}
|
3410 |
|
3411 |
// Plugin installation request.
|
3412 |
-
elseif(
|
3413 |
-
current_user_can('install_plugins')
|
3414 |
-
&& SucuriScanRequest::get('action', '(install|upload)-plugin')
|
3415 |
){
|
3416 |
-
if( isset($_FILES['pluginzip']) ){
|
3417 |
-
$plugin = self::escape($_FILES['pluginzip']['name']);
|
3418 |
} else {
|
3419 |
-
$plugin = SucuriScanRequest::get('plugin', '.+');
|
3420 |
|
3421 |
-
if(
|
3422 |
}
|
3423 |
|
3424 |
-
$message = 'Plugin
|
3425 |
-
|
3426 |
self::notify_event( 'plugin_installed', $message );
|
3427 |
}
|
3428 |
|
3429 |
// Plugin deletion request.
|
3430 |
-
elseif(
|
3431 |
-
current_user_can('delete_plugins')
|
3432 |
-
&& SucuriScanRequest::post('action', 'delete-selected')
|
3433 |
-
&& SucuriScanRequest::post('verify-delete', '1')
|
3434 |
){
|
3435 |
-
$plugin_list = (
|
|
|
3436 |
|
3437 |
-
foreach( $plugin_list as $plugin ){
|
3438 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3439 |
|
3440 |
-
if(
|
3441 |
-
!empty($plugin_info['Name'])
|
3442 |
-
&& !empty($plugin_info['Version'])
|
3443 |
){
|
3444 |
-
$
|
3445 |
-
'
|
3446 |
-
self::escape($plugin_info['Name']),
|
3447 |
-
self::escape($plugin_info['Version']),
|
3448 |
-
self::escape($plugin)
|
3449 |
);
|
3450 |
-
|
3451 |
-
self::report_event( 3, 'core', $message );
|
3452 |
-
self::notify_event( 'plugin_deleted', $message );
|
3453 |
}
|
3454 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3455 |
}
|
3456 |
|
3457 |
// Plugin editor request.
|
3458 |
-
elseif(
|
3459 |
-
current_user_can('edit_plugins')
|
3460 |
-
&& SucuriScanRequest::post('action', 'update')
|
3461 |
-
&& SucuriScanRequest::post('plugin', '.+')
|
3462 |
-
&& SucuriScanRequest::post('file', '.+')
|
3463 |
-
&& strpos($_SERVER['REQUEST_URI'], 'plugin-editor.php') !==
|
3464 |
){
|
3465 |
-
$
|
3466 |
-
|
|
|
3467 |
self::notify_event( 'theme_editor', $message );
|
3468 |
}
|
3469 |
|
3470 |
// Theme editor request.
|
3471 |
-
elseif(
|
3472 |
-
current_user_can('edit_themes')
|
3473 |
-
&& SucuriScanRequest::post('action', 'update')
|
3474 |
-
&& SucuriScanRequest::post('theme', '.+')
|
3475 |
-
&& SucuriScanRequest::post('file', '.+')
|
3476 |
-
&& strpos($_SERVER['REQUEST_URI'], 'theme-editor.php') !==
|
3477 |
){
|
3478 |
-
$
|
3479 |
-
|
|
|
|
|
3480 |
self::notify_event( 'theme_editor', $message );
|
3481 |
}
|
3482 |
|
3483 |
-
// Theme
|
3484 |
-
|
3485 |
-
|
3486 |
-
|
3487 |
-
// Theme update request.
|
3488 |
-
elseif(
|
3489 |
-
current_user_can('update_themes')
|
3490 |
-
&& SucuriScanRequest::get('action', '(upgrade-theme|do-theme-upgrade)')
|
3491 |
-
&& SucuriScanRequest::post('checked', '_array')
|
3492 |
){
|
3493 |
-
$
|
3494 |
|
3495 |
-
|
3496 |
-
$theme_info = wp_get_theme($theme);
|
3497 |
-
$theme_name = ucwords($theme);
|
3498 |
-
$theme_version = '0.0';
|
3499 |
|
3500 |
-
|
3501 |
-
|
3502 |
-
|
3503 |
-
|
3504 |
|
3505 |
-
|
3506 |
-
|
3507 |
-
|
3508 |
-
|
3509 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3510 |
);
|
|
|
3511 |
|
3512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3513 |
self::notify_event( 'theme_updated', $message );
|
3514 |
}
|
3515 |
}
|
3516 |
|
3517 |
// WordPress update request.
|
3518 |
-
elseif(
|
3519 |
-
current_user_can('update_core')
|
3520 |
-
&& SucuriScanRequest::get('action', '(do-core-upgrade|do-core-reinstall)')
|
3521 |
-
&& SucuriScanRequest::post('upgrade')
|
3522 |
){
|
3523 |
-
$message = 'WordPress updated to version: ' . SucuriScanRequest::post('version');
|
3524 |
-
self::
|
3525 |
self::notify_event( 'website_updated', $message );
|
3526 |
}
|
3527 |
|
3528 |
// Widget addition or deletion.
|
3529 |
-
elseif(
|
3530 |
-
current_user_can('edit_theme_options')
|
3531 |
-
&& SucuriScanRequest::post('action', 'save-widget')
|
3532 |
-
&& SucuriScanRequest::post('id_base') !==
|
3533 |
-
&& SucuriScanRequest::post('sidebar') !==
|
3534 |
){
|
3535 |
-
if( SucuriScanRequest::post('delete_widget', '1') ){
|
3536 |
$action_d = 'deleted';
|
3537 |
$action_text = 'deleted from';
|
3538 |
} else {
|
@@ -3542,47 +3789,53 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3542 |
|
3543 |
$message = sprintf(
|
3544 |
'Widget %s (%s) %s %s (#%d; size %dx%d)',
|
3545 |
-
SucuriScanRequest::post('id_base'),
|
3546 |
-
SucuriScanRequest::post('widget-id'),
|
3547 |
$action_text,
|
3548 |
-
SucuriScanRequest::post('sidebar'),
|
3549 |
-
SucuriScanRequest::post('widget_number'),
|
3550 |
-
SucuriScanRequest::post('widget-width'),
|
3551 |
-
SucuriScanRequest::post('widget-height')
|
3552 |
);
|
3553 |
|
3554 |
-
self::
|
3555 |
self::notify_event( 'widget_' . $action_d, $message );
|
3556 |
}
|
3557 |
|
3558 |
// Detect any Wordpress settings modification.
|
3559 |
-
elseif(
|
3560 |
-
current_user_can('manage_options')
|
3561 |
&& SucuriScanOption::check_options_nonce()
|
3562 |
){
|
3563 |
// Get the settings available in the database and compare them with the submission.
|
3564 |
-
$
|
3565 |
-
$options_changed = SucuriScanOption::what_options_were_changed($_POST);
|
3566 |
$options_changed_str = '';
|
|
|
3567 |
$options_changed_count = 0;
|
3568 |
|
3569 |
// Generate the list of options changed.
|
3570 |
-
foreach( $options_changed['original'] as $option_name => $option_value ){
|
3571 |
$options_changed_count += 1;
|
3572 |
$options_changed_str .= sprintf(
|
3573 |
"The value of the option <b>%s</b> was changed from <b>'%s'</b> to <b>'%s'</b>.<br>\n",
|
3574 |
-
self::escape($option_name),
|
3575 |
-
self::escape($option_value),
|
3576 |
-
self::escape($options_changed['changed'][$option_name])
|
|
|
|
|
|
|
|
|
|
|
|
|
3577 |
);
|
3578 |
}
|
3579 |
|
3580 |
// Get the option group (name of the page where the request was originated).
|
3581 |
$option_page = isset($_POST['option_page']) ? $_POST['option_page'] : 'options';
|
3582 |
-
$page_referer =
|
3583 |
|
3584 |
// Check which of these option groups where modified.
|
3585 |
-
switch( $option_page ){
|
3586 |
case 'options':
|
3587 |
$page_referer = 'Global';
|
3588 |
break;
|
@@ -3592,16 +3845,20 @@ class SucuriScanHook extends SucuriScanEvent {
|
|
3592 |
case 'discussion': /* no_break */
|
3593 |
case 'media': /* no_break */
|
3594 |
case 'permalink':
|
3595 |
-
$page_referer = ucwords($option_page);
|
3596 |
break;
|
3597 |
default:
|
3598 |
$page_referer = 'Common';
|
3599 |
break;
|
3600 |
}
|
3601 |
|
3602 |
-
if( $page_referer && $options_changed_count > 0 ){
|
3603 |
-
$message = $page_referer.' settings changed';
|
3604 |
-
|
|
|
|
|
|
|
|
|
3605 |
self::notify_event( 'settings_updated', $message . "<br>\n" . $options_changed_str );
|
3606 |
}
|
3607 |
}
|
@@ -3639,7 +3896,7 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3639 |
* @return boolean Whether the SSL certs will be verified while sending a request.
|
3640 |
*/
|
3641 |
public static function verify_ssl_cert(){
|
3642 |
-
return ( self::get_option(':verify_ssl_cert') === 'true' );
|
3643 |
}
|
3644 |
|
3645 |
/**
|
@@ -3648,7 +3905,7 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3648 |
* @return integer Seconds to consider a HTTP request timeout.
|
3649 |
*/
|
3650 |
public static function request_timeout(){
|
3651 |
-
return intval( self::get_option(':request_timeout') );
|
3652 |
}
|
3653 |
|
3654 |
/**
|
@@ -3679,8 +3936,8 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3679 |
* @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
|
3680 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
3681 |
*/
|
3682 |
-
private static function api_call( $url='', $method='GET', $params=array(), $args=array() ){
|
3683 |
-
if(
|
3684 |
|
3685 |
$req_args = array(
|
3686 |
'method' => $method,
|
@@ -3688,36 +3945,36 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3688 |
'redirection' => 2,
|
3689 |
'httpversion' => '1.0',
|
3690 |
'user-agent' => self::user_agent(),
|
3691 |
-
'blocking' =>
|
3692 |
'headers' => array(),
|
3693 |
'cookies' => array(),
|
3694 |
-
'compress' =>
|
3695 |
-
'decompress' =>
|
3696 |
'sslverify' => self::verify_ssl_cert(),
|
3697 |
);
|
3698 |
|
3699 |
// Update the request arguments with the values passed tot he function.
|
3700 |
-
foreach( $args as $arg_name => $arg_value ){
|
3701 |
-
if( array_key_exists($arg_name, $req_args) ){
|
3702 |
-
$req_args[$arg_name] = $arg_value;
|
3703 |
}
|
3704 |
}
|
3705 |
|
3706 |
-
if( $method == 'GET' ){
|
3707 |
-
if( !empty($params) ){
|
3708 |
-
$url = sprintf( '%s?%s', $url, http_build_query($params) );
|
3709 |
}
|
3710 |
|
3711 |
$response = wp_remote_get( $url, $req_args );
|
3712 |
}
|
3713 |
|
3714 |
-
elseif( $method == 'POST' ){
|
3715 |
$req_args['body'] = $params;
|
3716 |
$response = wp_remote_post( $url, $req_args );
|
3717 |
}
|
3718 |
|
3719 |
-
if( isset($response) ){
|
3720 |
-
if( is_wp_error($response) ){
|
3721 |
SucuriScanInterface::error(sprintf(
|
3722 |
'Something went wrong with an API call (%s action): %s',
|
3723 |
( isset($params['a']) ? $params['a'] : 'unknown' ),
|
@@ -3727,17 +3984,17 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3727 |
$response['body_raw'] = $response['body'];
|
3728 |
|
3729 |
// Check if the response data is JSON-encoded, then decode it.
|
3730 |
-
if(
|
3731 |
isset($response['headers']['content-type'])
|
3732 |
&& $response['headers']['content-type'] == 'application/json'
|
3733 |
-
){
|
3734 |
-
$assoc = ( isset($args['assoc']) && $args['assoc'] ===
|
3735 |
-
$response['body'] = @json_decode($response['body_raw'], $assoc);
|
3736 |
}
|
3737 |
|
3738 |
// Check if the response data is serialized (which we will consider as insecure).
|
3739 |
-
elseif( self::is_serialized($response['body']) ){
|
3740 |
-
$response['body_raw'] =
|
3741 |
$response['body'] = 'ERROR:Serialized data is not supported.';
|
3742 |
}
|
3743 |
|
@@ -3747,7 +4004,7 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3747 |
SucuriScanInterface::error( 'HTTP method not allowed: ' . $method );
|
3748 |
}
|
3749 |
|
3750 |
-
return
|
3751 |
}
|
3752 |
|
3753 |
/**
|
@@ -3755,17 +4012,17 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3755 |
*
|
3756 |
* @param string $api_key An unique string of characters to identify this installation.
|
3757 |
* @param boolean $validate Whether the format of the key should be validated before store it.
|
3758 |
-
* @return boolean Either
|
3759 |
*/
|
3760 |
-
public static function set_plugin_key( $api_key='', $validate=
|
3761 |
-
if( $validate ){
|
3762 |
-
if( !preg_match('/^[a-z0-9]{32}$/', $api_key) ){
|
3763 |
SucuriScanInterface::error( 'Invalid API key format' );
|
3764 |
-
return
|
3765 |
}
|
3766 |
}
|
3767 |
|
3768 |
-
if( !empty($api_key) ){
|
3769 |
SucuriScanEvent::notify_event( 'plugin_change', 'API key updated successfully: ' . $api_key );
|
3770 |
}
|
3771 |
|
@@ -3775,16 +4032,16 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3775 |
/**
|
3776 |
* Retrieve the API key from the local storage.
|
3777 |
*
|
3778 |
-
* @return string|boolean The API key or
|
3779 |
*/
|
3780 |
public static function get_plugin_key(){
|
3781 |
-
$api_key = self::get_option(':api_key');
|
3782 |
|
3783 |
-
if( $api_key && strlen($api_key) > 10 ){
|
3784 |
return $api_key;
|
3785 |
}
|
3786 |
|
3787 |
-
return
|
3788 |
}
|
3789 |
|
3790 |
/**
|
@@ -3794,34 +4051,34 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3794 |
* slash, the first part of it is in fact the key and the second part is the
|
3795 |
* unique identifier of the site in the remote server.
|
3796 |
*
|
3797 |
-
* @return array|boolean
|
3798 |
*/
|
3799 |
public static function get_cloudproxy_key(){
|
3800 |
$option_name = ':cloudproxy_apikey';
|
3801 |
-
$api_key = self::get_option($option_name);
|
3802 |
|
3803 |
// Check if the cloudproxy-waf plugin was previously installed.
|
3804 |
-
if(
|
3805 |
-
$api_key = self::get_option('sucuriwaf_apikey');
|
3806 |
|
3807 |
-
if( $api_key ){
|
3808 |
self::update_option( $option_name, $api_key );
|
3809 |
-
self::delete_option('sucuriwaf_apikey');
|
3810 |
}
|
3811 |
}
|
3812 |
|
3813 |
// Check the validity of the API key.
|
3814 |
-
$match = self::is_valid_cloudproxy_key( $api_key,
|
3815 |
|
3816 |
-
if( $match ){
|
3817 |
return array(
|
3818 |
'string' => $match[1].'/'.$match[2],
|
3819 |
'k' => $match[1],
|
3820 |
-
's' => $match[2]
|
3821 |
);
|
3822 |
}
|
3823 |
|
3824 |
-
return
|
3825 |
}
|
3826 |
|
3827 |
/**
|
@@ -3829,18 +4086,18 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3829 |
*
|
3830 |
* @param string $api_key The CloudProxy API key.
|
3831 |
* @param boolean $return_match Whether the parts of the API key must be returned or not.
|
3832 |
-
* @return boolean
|
3833 |
*/
|
3834 |
-
public static function is_valid_cloudproxy_key( $api_key='', $return_match=
|
3835 |
$pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
|
3836 |
|
3837 |
-
if( $api_key && preg_match($pattern, $api_key, $match) ){
|
3838 |
-
if( $return_match ){ return $match; }
|
3839 |
|
3840 |
-
return
|
3841 |
}
|
3842 |
|
3843 |
-
return
|
3844 |
}
|
3845 |
|
3846 |
/**
|
@@ -3852,15 +4109,15 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3852 |
* @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
|
3853 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
3854 |
*/
|
3855 |
-
public static function api_call_wordpress( $method='GET', $params=array(), $send_api_key=
|
3856 |
$url = SUCURISCAN_API;
|
3857 |
-
$params[SUCURISCAN_API_VERSION] = 1;
|
3858 |
$params['p'] = 'wordpress';
|
3859 |
|
3860 |
-
if( $send_api_key ){
|
3861 |
$api_key = self::get_plugin_key();
|
3862 |
|
3863 |
-
if(
|
3864 |
|
3865 |
$params['k'] = $api_key;
|
3866 |
}
|
@@ -3877,31 +4134,31 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3877 |
* @param array $params Parameters for the request defined in an associative array of key-value.
|
3878 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
3879 |
*/
|
3880 |
-
public static function api_call_cloudproxy( $method='GET', $params=array() ){
|
3881 |
-
$send_request =
|
3882 |
|
3883 |
-
if( isset($params['k']) && isset($params['s']) ){
|
3884 |
-
$send_request =
|
3885 |
} else {
|
3886 |
$api_key = self::get_cloudproxy_key();
|
3887 |
|
3888 |
-
if( $api_key ){
|
3889 |
-
$send_request =
|
3890 |
$params['k'] = $api_key['k'];
|
3891 |
$params['s'] = $api_key['s'];
|
3892 |
}
|
3893 |
}
|
3894 |
|
3895 |
-
if( $send_request ){
|
3896 |
$url = SUCURISCAN_CLOUDPROXY_API;
|
3897 |
-
$params[SUCURISCAN_CLOUDPROXY_API_VERSION] = 1;
|
3898 |
|
3899 |
$response = self::api_call( $url, $method, $params );
|
3900 |
|
3901 |
return $response;
|
3902 |
}
|
3903 |
|
3904 |
-
return
|
3905 |
}
|
3906 |
|
3907 |
/**
|
@@ -3910,14 +4167,14 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3910 |
* in the administrator panel explaining the result of the operation.
|
3911 |
*
|
3912 |
* @param array $response Array of results including HTTP headers or WP_Error if the request failed.
|
3913 |
-
* @return boolean Either
|
3914 |
-
*/
|
3915 |
-
private static function handle_response( $response=array() ){
|
3916 |
-
if( $response ){
|
3917 |
-
if( $response['body'] instanceof stdClass ){
|
3918 |
-
if( isset($response['body']->status) ){
|
3919 |
-
if( $response['body']->status == 1 ){
|
3920 |
-
return
|
3921 |
} else {
|
3922 |
$action_message = 'Unknown error, there is no more information.';
|
3923 |
|
@@ -3925,47 +4182,62 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3925 |
$action_message = $response['body']->messages[0];
|
3926 |
}
|
3927 |
|
3928 |
-
SucuriScanInterface::error( ucwords($response['body']->action) . ': ' . $action_message );
|
3929 |
}
|
3930 |
} else {
|
3931 |
SucuriScanInterface::error( 'Could not determine the status of an API call.' );
|
3932 |
}
|
3933 |
} else {
|
3934 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3935 |
}
|
3936 |
}
|
3937 |
|
3938 |
-
return
|
3939 |
}
|
3940 |
|
3941 |
/**
|
3942 |
* Send a request to the API to register this site.
|
3943 |
*
|
3944 |
-
* @return boolean
|
3945 |
*/
|
3946 |
public static function register_site(){
|
3947 |
$response = self::api_call_wordpress( 'POST', array(
|
3948 |
'e' => self::get_site_email(),
|
3949 |
's' => self::get_domain(),
|
3950 |
'a' => 'register_site',
|
3951 |
-
),
|
3952 |
|
3953 |
-
if( self::handle_response($response) ){
|
3954 |
self::set_plugin_key( $response['body']->output->api_key );
|
3955 |
SucuriScanEvent::schedule_task();
|
3956 |
SucuriScanEvent::notify_event( 'plugin_change', 'Site registered and API key generated' );
|
3957 |
-
SucuriScanInterface::info( 'The API key for your site was successfully generated and saved.');
|
3958 |
|
3959 |
-
return
|
3960 |
}
|
3961 |
|
3962 |
-
return
|
3963 |
}
|
3964 |
|
3965 |
/**
|
3966 |
* Send a request to recover a previously registered API key.
|
3967 |
*
|
3968 |
-
* @return boolean
|
3969 |
*/
|
3970 |
public static function recover_key(){
|
3971 |
$clean_domain = self::get_domain();
|
@@ -3974,16 +4246,16 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3974 |
'e' => self::get_site_email(),
|
3975 |
's' => $clean_domain,
|
3976 |
'a' => 'recover_key',
|
3977 |
-
),
|
3978 |
|
3979 |
-
if( self::handle_response($response) ){
|
3980 |
SucuriScanEvent::notify_event( 'plugin_change', 'API key recovered for domain: ' . $clean_domain );
|
3981 |
SucuriScanInterface::info( $response['body']->output->message );
|
3982 |
|
3983 |
-
return
|
3984 |
}
|
3985 |
|
3986 |
-
return
|
3987 |
}
|
3988 |
|
3989 |
/**
|
@@ -3993,21 +4265,21 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
3993 |
* this plugin.
|
3994 |
*
|
3995 |
* @param string $event The information gathered through out the normal functioning of the site.
|
3996 |
-
* @return boolean
|
3997 |
*/
|
3998 |
-
public static function send_log( $event='' ){
|
3999 |
-
if( !empty($event) ){
|
4000 |
$response = self::api_call_wordpress( 'POST', array(
|
4001 |
'a' => 'send_log',
|
4002 |
'm' => $event,
|
4003 |
-
),
|
4004 |
|
4005 |
-
if( self::handle_response($response) ){
|
4006 |
-
return
|
4007 |
}
|
4008 |
}
|
4009 |
|
4010 |
-
return
|
4011 |
}
|
4012 |
|
4013 |
/**
|
@@ -4016,34 +4288,65 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4016 |
* @param integer $lines How many lines from the log file will be retrieved.
|
4017 |
* @return string The response of the API service.
|
4018 |
*/
|
4019 |
-
public static function get_logs( $lines=50 ){
|
4020 |
$response = self::api_call_wordpress( 'GET', array(
|
4021 |
'a' => 'get_logs',
|
4022 |
'l' => $lines,
|
4023 |
) );
|
4024 |
|
4025 |
-
if( self::handle_response($response) ){
|
4026 |
$response['body']->output_data = array();
|
4027 |
$log_pattern = '/^([0-9-: ]+) (.*) : (.*)/';
|
4028 |
$extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
|
|
|
|
|
4029 |
|
4030 |
-
foreach( $response['body']->output as $log ){
|
4031 |
-
if( preg_match($log_pattern, $log, $log_match) ){
|
4032 |
$log_data = array(
|
|
|
4033 |
'datetime' => $log_match[1],
|
4034 |
-
'timestamp' => strtotime($log_match[1]),
|
4035 |
'account' => $log_match[2],
|
|
|
|
|
4036 |
'message' => $log_match[3],
|
4037 |
-
'
|
4038 |
-
'
|
4039 |
);
|
4040 |
|
4041 |
$log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
|
|
|
|
|
|
|
|
|
|
|
|
|
4042 |
|
4043 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4044 |
$log_data['message'] = $log_extra[1];
|
4045 |
-
$log_data['
|
4046 |
-
$log_data['
|
4047 |
}
|
4048 |
|
4049 |
$response['body']->output_data[] = $log_data;
|
@@ -4053,7 +4356,141 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4053 |
return $response['body'];
|
4054 |
}
|
4055 |
|
4056 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4057 |
}
|
4058 |
|
4059 |
/**
|
@@ -4063,21 +4500,21 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4063 |
* changes in the system.
|
4064 |
*
|
4065 |
* @param string $hashes The information gathered after the scanning of the site's files.
|
4066 |
-
* @return boolean
|
4067 |
*/
|
4068 |
-
public static function send_hashes( $hashes='' ){
|
4069 |
-
if( !empty($hashes) ){
|
4070 |
$response = self::api_call_wordpress( 'POST', array(
|
4071 |
'a' => 'send_hashes',
|
4072 |
'h' => $hashes,
|
4073 |
) );
|
4074 |
|
4075 |
-
if( self::handle_response($response) ){
|
4076 |
-
return
|
4077 |
}
|
4078 |
}
|
4079 |
|
4080 |
-
return
|
4081 |
}
|
4082 |
|
4083 |
/**
|
@@ -4089,20 +4526,20 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4089 |
* @param boolean $api_key The CloudProxy API key.
|
4090 |
* @return array A hash with the settings of a CloudProxy account.
|
4091 |
*/
|
4092 |
-
public static function get_cloudproxy_settings( $api_key=
|
4093 |
$params = array( 'a' => 'show_settings' );
|
4094 |
|
4095 |
-
if( $api_key ){
|
4096 |
$params = array_merge( $params, $api_key );
|
4097 |
}
|
4098 |
|
4099 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4100 |
|
4101 |
-
if( self::handle_response($response) ){
|
4102 |
return $response['body']->output;
|
4103 |
}
|
4104 |
|
4105 |
-
return
|
4106 |
}
|
4107 |
|
4108 |
/**
|
@@ -4111,20 +4548,20 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4111 |
* @param boolean $api_key The CloudProxy API key.
|
4112 |
* @return string Message explaining the result of the operation.
|
4113 |
*/
|
4114 |
-
public static function clear_cloudproxy_cache( $api_key=
|
4115 |
$params = array( 'a' => 'clear_cache' );
|
4116 |
|
4117 |
-
if( $api_key ){
|
4118 |
$params = array_merge( $params, $api_key );
|
4119 |
}
|
4120 |
|
4121 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4122 |
|
4123 |
-
if( self::handle_response($response) ){
|
4124 |
return $response['body'];
|
4125 |
}
|
4126 |
|
4127 |
-
return
|
4128 |
}
|
4129 |
|
4130 |
/**
|
@@ -4142,27 +4579,27 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4142 |
* @param string $date An optional date to filter the result to a specific timespan: yyyy-mm-dd.
|
4143 |
* @return array A list of objects with the detailed version of each request blocked by our service.
|
4144 |
*/
|
4145 |
-
public static function get_cloudproxy_logs( $api_key=
|
4146 |
$params = array(
|
4147 |
'a' => 'audit_trails',
|
4148 |
-
'date' => date('Y-m-d'),
|
4149 |
);
|
4150 |
|
4151 |
-
if( preg_match('/^[0-9]{4}(\-[0-9]{2}){2}$/', $date) ){
|
4152 |
$params['date'] = $date;
|
4153 |
}
|
4154 |
|
4155 |
-
if( $api_key ){
|
4156 |
$params = array_merge( $params, $api_key );
|
4157 |
}
|
4158 |
|
4159 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4160 |
|
4161 |
-
if( self::handle_response($response) ){
|
4162 |
return $response['body']->output;
|
4163 |
}
|
4164 |
|
4165 |
-
return
|
4166 |
}
|
4167 |
|
4168 |
/**
|
@@ -4174,8 +4611,8 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4174 |
* @param string $domain The clean version of the website's domain.
|
4175 |
* @return object Serialized data of the scanning results for the site specified.
|
4176 |
*/
|
4177 |
-
public static function get_sitecheck_results( $domain='' ){
|
4178 |
-
if( !empty($domain) ){
|
4179 |
$url = 'http://sitecheck.sucuri.net/';
|
4180 |
$response = self::api_call( $url, 'GET', array(
|
4181 |
'scan' => $domain,
|
@@ -4183,15 +4620,62 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4183 |
'clear' => 1,
|
4184 |
'json' => 1,
|
4185 |
), array(
|
4186 |
-
'assoc' =>
|
4187 |
-
));
|
4188 |
|
4189 |
-
if( $response ){
|
4190 |
return $response['body'];
|
4191 |
}
|
4192 |
}
|
4193 |
|
4194 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4195 |
}
|
4196 |
|
4197 |
/**
|
@@ -4204,17 +4688,17 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4204 |
$pattern = self::secret_key_pattern();
|
4205 |
$response = self::api_call( 'https://api.wordpress.org/secret-key/1.1/salt/', 'GET' );
|
4206 |
|
4207 |
-
if( $response && preg_match_all($pattern, $response['body'], $match) ){
|
4208 |
$new_keys = array();
|
4209 |
|
4210 |
-
foreach( $match[1] as $i => $value ){
|
4211 |
-
$new_keys[$value] = $match[3][$i];
|
4212 |
}
|
4213 |
|
4214 |
return $new_keys;
|
4215 |
}
|
4216 |
|
4217 |
-
return
|
4218 |
}
|
4219 |
|
4220 |
/**
|
@@ -4225,7 +4709,7 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4225 |
* @param integer $version Valid version number of the WordPress project.
|
4226 |
* @return object Associative object with the relative filepath and the checksums of the project files.
|
4227 |
*/
|
4228 |
-
public static function get_official_checksums( $version=0 ){
|
4229 |
$url = 'http://api.wordpress.org/core/checksums/1.0/';
|
4230 |
$language = 'en_US'; /* WPLANG does not works. */
|
4231 |
$response = self::api_call( $url, 'GET', array(
|
@@ -4233,21 +4717,21 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4233 |
'locale' => $language,
|
4234 |
));
|
4235 |
|
4236 |
-
if( $response ){
|
4237 |
-
if( $response['body'] instanceof stdClass ){
|
4238 |
$json_data = $response['body'];
|
4239 |
} else {
|
4240 |
-
$json_data = @json_decode($response['body']);
|
4241 |
}
|
4242 |
|
4243 |
-
if(
|
4244 |
isset($json_data->checksums)
|
4245 |
-
&& !empty($json_data->checksums)
|
4246 |
-
){
|
4247 |
$checksums = $json_data->checksums;
|
4248 |
|
4249 |
// Convert the object list to an array for better handle of the data.
|
4250 |
-
if( $checksums instanceof stdClass ){
|
4251 |
$checksums = (array) $checksums;
|
4252 |
}
|
4253 |
|
@@ -4255,7 +4739,7 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4255 |
}
|
4256 |
}
|
4257 |
|
4258 |
-
return
|
4259 |
}
|
4260 |
|
4261 |
/**
|
@@ -4267,14 +4751,14 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4267 |
*/
|
4268 |
public static function get_plugins(){
|
4269 |
// Check if the cache library was loaded.
|
4270 |
-
$can_cache = class_exists('SucuriScanCache');
|
4271 |
|
4272 |
-
if( $can_cache ){
|
4273 |
-
$cache = new SucuriScanCache('plugindata');
|
4274 |
$cached_data = $cache->get( 'plugins', SUCURISCAN_GET_PLUGINS_LIFETIME, 'array' );
|
4275 |
|
4276 |
// Return the previously cached results of this function.
|
4277 |
-
if( $cached_data !==
|
4278 |
return $cached_data;
|
4279 |
}
|
4280 |
}
|
@@ -4285,58 +4769,58 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4285 |
$wp_market = 'https://wordpress.org/plugins/%s/';
|
4286 |
|
4287 |
// Loop through each plugin data and complement its information with more attributes.
|
4288 |
-
foreach( $plugins as $plugin_path => $plugin_data ){
|
4289 |
// Default values for the plugin extra attributes.
|
4290 |
$repository = '';
|
4291 |
$repository_name = '';
|
4292 |
-
$is_free_plugin =
|
4293 |
|
4294 |
// If the plugin's info object has already a plugin_uri.
|
4295 |
-
if(
|
4296 |
isset($plugin_data['PluginURI'])
|
4297 |
-
&& preg_match($pattern, $plugin_data['PluginURI'], $match)
|
4298 |
-
){
|
4299 |
$repository = $match[0];
|
4300 |
$repository_name = $match[2];
|
4301 |
-
$is_free_plugin =
|
4302 |
}
|
4303 |
|
4304 |
// Retrieve the WordPress plugin page from the plugin's filename.
|
4305 |
else {
|
4306 |
-
if( strpos($plugin_path, '/') !==
|
4307 |
-
$plugin_path_parts = explode('/', $plugin_path, 2);
|
4308 |
} else {
|
4309 |
-
$plugin_path_parts = explode('.', $plugin_path, 2);
|
4310 |
}
|
4311 |
|
4312 |
-
if( isset($plugin_path_parts[0]) ){
|
4313 |
-
$possible_repository = sprintf($wp_market, $plugin_path_parts[0]);
|
4314 |
-
$resp = wp_remote_head($possible_repository);
|
4315 |
|
4316 |
-
if(
|
4317 |
-
!is_wp_error($resp)
|
4318 |
&& $resp['response']['code'] == 200
|
4319 |
-
){
|
4320 |
$repository = $possible_repository;
|
4321 |
$repository_name = $plugin_path_parts[0];
|
4322 |
-
$is_free_plugin =
|
4323 |
}
|
4324 |
}
|
4325 |
}
|
4326 |
|
4327 |
// Complement the plugin's information with these attributes.
|
4328 |
-
$plugins[$plugin_path]['Repository'] = $repository;
|
4329 |
-
$plugins[$plugin_path]['RepositoryName'] = $repository_name;
|
4330 |
-
$plugins[$plugin_path]['IsFreePlugin'] = $is_free_plugin;
|
4331 |
-
$plugins[$plugin_path]['PluginType'] = ( $is_free_plugin ? 'free' : 'premium' );
|
4332 |
-
$plugins[$plugin_path]['IsPluginActive'] =
|
4333 |
|
4334 |
-
if( is_plugin_active($plugin_path) ){
|
4335 |
-
$plugins[$plugin_path]['IsPluginActive'] =
|
4336 |
}
|
4337 |
}
|
4338 |
|
4339 |
-
if( $can_cache ){
|
4340 |
// Add the information of the plugins to the file-based cache.
|
4341 |
$cache->add( 'plugins', $plugins );
|
4342 |
}
|
@@ -4361,19 +4845,19 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4361 |
* @param string $plugin Frienly name of the plugin.
|
4362 |
* @return object Object on success, WP_Error on failure.
|
4363 |
*/
|
4364 |
-
public static function get_remote_plugin_data( $plugin='' ){
|
4365 |
-
if( !empty($plugin) ){
|
4366 |
-
$url = sprintf( 'http://api.wordpress.org/plugins/info/1.0/%s
|
4367 |
$response = self::api_call( $url, 'GET' );
|
4368 |
|
4369 |
-
if( $response ){
|
4370 |
-
if( $response['body'] instanceof stdClass ){
|
4371 |
return $response['body'];
|
4372 |
}
|
4373 |
}
|
4374 |
}
|
4375 |
|
4376 |
-
return
|
4377 |
}
|
4378 |
|
4379 |
/**
|
@@ -4387,29 +4871,29 @@ class SucuriScanAPI extends SucuriScanOption {
|
|
4387 |
*
|
4388 |
* @param string $filepath Relative file path of a project core file.
|
4389 |
* @param string $version Optional site version, default will be the global version number.
|
4390 |
-
* @return string Full content of the official file retrieved,
|
4391 |
*/
|
4392 |
-
public static function get_original_core_file( $filepath='', $version=0 ){
|
4393 |
-
if( !empty($filepath) ){
|
4394 |
-
if( $version == 0 ){
|
4395 |
$version = self::site_version();
|
4396 |
}
|
4397 |
|
4398 |
$url = sprintf( 'http://core.svn.wordpress.org/tags/%s/%s', $version, $filepath );
|
4399 |
$response = self::api_call( $url, 'GET' );
|
4400 |
|
4401 |
-
if( $response ){
|
4402 |
-
if(
|
4403 |
isset($response['headers']['content-length'])
|
4404 |
&& $response['headers']['content-length'] > 0
|
4405 |
-
&& is_string($response['body'])
|
4406 |
-
){
|
4407 |
return $response['body'];
|
4408 |
}
|
4409 |
}
|
4410 |
}
|
4411 |
|
4412 |
-
return
|
4413 |
}
|
4414 |
|
4415 |
}
|
@@ -4430,7 +4914,7 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4430 |
* @return boolean Whether the emails will be in HTML or Plain/Text.
|
4431 |
*/
|
4432 |
public static function prettify_mails(){
|
4433 |
-
return ( self::get_option(':prettify_mails') === 'enabled' );
|
4434 |
}
|
4435 |
|
4436 |
/**
|
@@ -4442,56 +4926,56 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4442 |
* @param array $data_set Optional parameter to add more information to the notification.
|
4443 |
* @return boolean Whether the email contents were sent successfully.
|
4444 |
*/
|
4445 |
-
public static function send_mail( $email='', $subject='', $message='', $data_set=array() ){
|
4446 |
$headers = array();
|
4447 |
-
$subject = ucwords(strtolower($subject));
|
4448 |
-
$force =
|
4449 |
-
$debug =
|
4450 |
|
4451 |
// Check whether the mail will be printed in the site instead of sent.
|
4452 |
-
if(
|
4453 |
isset($data_set['Debug'])
|
4454 |
-
&& $data_set['Debug'] ==
|
4455 |
){
|
4456 |
-
$debug =
|
4457 |
unset($data_set['Debug']);
|
4458 |
}
|
4459 |
|
4460 |
// Check whether the mail will be even if the limit per hour was reached or not.
|
4461 |
-
if(
|
4462 |
isset($data_set['Force'])
|
4463 |
-
&& $data_set['Force'] ==
|
4464 |
){
|
4465 |
-
$force =
|
4466 |
unset($data_set['Force']);
|
4467 |
}
|
4468 |
|
4469 |
// Check whether the email notifications will be sent in HTML or Plain/Text.
|
4470 |
-
if( self::prettify_mails() ){
|
4471 |
$headers = array( 'content-type: text/html' );
|
4472 |
$data_set['PrettifyType'] = 'pretty';
|
4473 |
} else {
|
4474 |
-
$message = strip_tags($message);
|
4475 |
}
|
4476 |
|
4477 |
-
if( !self::emails_per_hour_reached() || $force || $debug ){
|
4478 |
-
$message = self::prettify_mail($subject, $message, $data_set);
|
4479 |
|
4480 |
-
if( $debug ){ die($message); }
|
4481 |
|
4482 |
-
$subject = self::get_email_subject($subject);
|
4483 |
$mail_sent = wp_mail( $email, $subject, $message, $headers );
|
4484 |
|
4485 |
-
if( $mail_sent ){
|
4486 |
-
$emails_sent_num = (int) self::get_option(':emails_sent');
|
4487 |
self::update_option( ':emails_sent', $emails_sent_num + 1 );
|
4488 |
self::update_option( ':last_email_at', time() );
|
4489 |
|
4490 |
-
return
|
4491 |
}
|
4492 |
}
|
4493 |
|
4494 |
-
return
|
4495 |
}
|
4496 |
|
4497 |
/**
|
@@ -4500,16 +4984,16 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4500 |
* @param string $event The reason of the message that will be sent.
|
4501 |
* @return string A text with the subject for the email alert.
|
4502 |
*/
|
4503 |
-
private static function get_email_subject( $event='' ){
|
4504 |
$domain_name = self::get_domain();
|
4505 |
$remote_addr = self::get_remote_addr();
|
4506 |
-
$email_subject = self::get_option(':email_subject');
|
4507 |
|
4508 |
if ( $email_subject ) {
|
4509 |
$email_subject = str_replace(
|
4510 |
array( ':domain', ':event', ':remoteaddr' ),
|
4511 |
array( $domain_name, $event, $remote_addr ),
|
4512 |
-
strip_tags($email_subject)
|
4513 |
);
|
4514 |
|
4515 |
return $email_subject;
|
@@ -4521,8 +5005,8 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4521 |
* loop, but this is the easiest way to control this procedure.
|
4522 |
*/
|
4523 |
else {
|
4524 |
-
self::delete_option(':email_subject');
|
4525 |
-
return self::get_email_subject($event);
|
4526 |
}
|
4527 |
}
|
4528 |
|
@@ -4534,32 +5018,52 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4534 |
* @param array $data_set Optional parameter to add more information to the notification.
|
4535 |
* @return string The message formatted in a HTML template.
|
4536 |
*/
|
4537 |
-
private static function prettify_mail( $subject='', $message='', $data_set=array() ){
|
4538 |
$prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'simple';
|
4539 |
$template_name = 'notification-' . $prettify_type;
|
4540 |
$user = wp_get_current_user();
|
4541 |
$display_name = '';
|
4542 |
|
4543 |
-
if(
|
4544 |
$user instanceof WP_User
|
4545 |
&& isset($user->user_login)
|
4546 |
-
&& !empty($user->user_login)
|
4547 |
){
|
4548 |
$display_name = sprintf( 'User: %s (%s)', $user->display_name, $user->user_login );
|
4549 |
}
|
4550 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4551 |
$mail_variables = array(
|
4552 |
'TemplateTitle' => 'Sucuri Alert',
|
4553 |
'Subject' => $subject,
|
4554 |
-
'Website' => self::get_option('siteurl'),
|
4555 |
'RemoteAddress' => self::get_remote_addr(),
|
4556 |
'Message' => $message,
|
4557 |
'User' => $display_name,
|
4558 |
'Time' => SucuriScan::current_datetime(),
|
4559 |
);
|
4560 |
|
4561 |
-
foreach( $data_set as $var_key => $var_value ){
|
4562 |
-
$mail_variables[$var_key] = $var_value;
|
4563 |
}
|
4564 |
|
4565 |
return SucuriScanTemplate::get_section( $template_name, $mail_variables );
|
@@ -4571,21 +5075,21 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4571 |
* @return boolean Whether the quota emails per hour was reached.
|
4572 |
*/
|
4573 |
private static function emails_per_hour_reached(){
|
4574 |
-
$max_per_hour = self::get_option(':emails_per_hour');
|
4575 |
|
4576 |
-
if( $max_per_hour != 'unlimited' ){
|
4577 |
// Check if we are still in that sixty minutes.
|
4578 |
$current_time = time();
|
4579 |
-
$last_email_at = self::get_option(':last_email_at');
|
4580 |
$diff_time = abs( $current_time - $last_email_at );
|
4581 |
|
4582 |
-
if( $diff_time <= 3600 ){
|
4583 |
// Check if the quantity of emails sent is bigger than the configured.
|
4584 |
-
$emails_sent = (int) self::get_option(':emails_sent');
|
4585 |
-
$max_per_hour = intval($max_per_hour);
|
4586 |
|
4587 |
-
if( $emails_sent >= $max_per_hour ){
|
4588 |
-
return
|
4589 |
}
|
4590 |
} else {
|
4591 |
// Reset the counter of emails sent.
|
@@ -4593,7 +5097,7 @@ class SucuriScanMail extends SucuriScanOption {
|
|
4593 |
}
|
4594 |
}
|
4595 |
|
4596 |
-
return
|
4597 |
}
|
4598 |
|
4599 |
}
|
@@ -4620,9 +5124,9 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4620 |
* @param array $params List of pseudo-variables that will be replaced in the template.
|
4621 |
* @return string The content of the template with the pseudo-variables replated.
|
4622 |
*/
|
4623 |
-
private static function replace_pseudovars( $content='', $params=array() ){
|
4624 |
-
if( is_array($params) ){
|
4625 |
-
foreach( $params as $tpl_key => $tpl_value ){
|
4626 |
$tpl_key = '%%SUCURI.' . $tpl_key . '%%';
|
4627 |
$content = str_replace( $tpl_key, $tpl_value, $content );
|
4628 |
}
|
@@ -4630,7 +5134,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4630 |
return $content;
|
4631 |
}
|
4632 |
|
4633 |
-
return
|
4634 |
}
|
4635 |
|
4636 |
/**
|
@@ -4639,19 +5143,32 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4639 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4640 |
* @return array A complementary list of pseudo-variables for the template files.
|
4641 |
*/
|
4642 |
-
private static function shared_params( $params=array() ){
|
4643 |
-
$params = is_array($params) ? $params : array();
|
4644 |
|
4645 |
// Base parameters, required to render all the pages.
|
4646 |
-
$params = self::links_and_navbar($params);
|
4647 |
|
4648 |
// Global parameters, used through out all the pages.
|
4649 |
$params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
|
4650 |
-
$params['PageNonce'] = wp_create_nonce('sucuriscan_page_nonce');
|
4651 |
$params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
|
4652 |
$params['CleanDomain'] = self::get_domain();
|
4653 |
$params['AdminEmail'] = self::get_site_email();
|
4654 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4655 |
return $params;
|
4656 |
}
|
4657 |
|
@@ -4661,8 +5178,8 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4661 |
* @param boolean $visible Whether the condition executed returned a positive value or not.
|
4662 |
* @return string A string indicating the visibility of a HTML component.
|
4663 |
*/
|
4664 |
-
public static function visibility( $visible=
|
4665 |
-
return ( $visible ===
|
4666 |
}
|
4667 |
|
4668 |
/**
|
@@ -4672,11 +5189,11 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4672 |
* @param string $page Short name of the page that will be generated.
|
4673 |
* @return string Full string containing the link of the page.
|
4674 |
*/
|
4675 |
-
public static function get_url( $page='' ){
|
4676 |
-
$url_path = admin_url('admin.php?page=sucuriscan');
|
4677 |
|
4678 |
-
if( !empty($page) ){
|
4679 |
-
$url_path .= '_' . strtolower($page);
|
4680 |
}
|
4681 |
|
4682 |
return $url_path;
|
@@ -4690,20 +5207,20 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4690 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4691 |
* @return array A complementary list of pseudo-variables for the template files.
|
4692 |
*/
|
4693 |
-
private static function links_and_navbar( $params=array() ){
|
4694 |
global $sucuriscan_pages;
|
4695 |
|
4696 |
-
$params = is_array($params) ? $params : array();
|
4697 |
-
$sub_pages = is_array($sucuriscan_pages) ? $sucuriscan_pages : array();
|
4698 |
|
4699 |
$params['Navbar'] = '';
|
4700 |
$params['CurrentPageFunc'] = '';
|
4701 |
|
4702 |
-
if( $_page = self::get('page', '_page') ){
|
4703 |
$params['CurrentPageFunc'] = $_page;
|
4704 |
}
|
4705 |
|
4706 |
-
foreach( $sub_pages as $sub_page_func => $sub_page_title ){
|
4707 |
if (
|
4708 |
$sub_page_func == 'sucuriscan_scanner'
|
4709 |
&& self::is_sitecheck_disabled()
|
@@ -4713,26 +5230,26 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4713 |
|
4714 |
$func_parts = explode( '_', $sub_page_func, 2 );
|
4715 |
|
4716 |
-
if( isset($func_parts[1]) ){
|
4717 |
$unique_name = $func_parts[1];
|
4718 |
-
$pseudo_var = 'URL.' . ucwords($unique_name);
|
4719 |
} else {
|
4720 |
$unique_name = '';
|
4721 |
$pseudo_var = 'URL.Home';
|
4722 |
}
|
4723 |
|
4724 |
-
$params[$pseudo_var] = self::get_url($unique_name);
|
4725 |
|
4726 |
$navbar_item_css_class = 'nav-tab';
|
4727 |
|
4728 |
-
if( $params['CurrentPageFunc'] == $sub_page_func ){
|
4729 |
-
$navbar_item_css_class .= chr(32) . 'nav-tab-active';
|
4730 |
}
|
4731 |
|
4732 |
$params['Navbar'] .= sprintf(
|
4733 |
'<a class="%s" href="%s">%s</a>' . "\n",
|
4734 |
$navbar_item_css_class,
|
4735 |
-
$params[$pseudo_var],
|
4736 |
$sub_page_title
|
4737 |
);
|
4738 |
}
|
@@ -4749,10 +5266,10 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4749 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4750 |
* @return string The formatted HTML content of the base template.
|
4751 |
*/
|
4752 |
-
public static function get_base_template( $html='', $params=array() ){
|
4753 |
-
$params = is_array($params) ? $params : array();
|
4754 |
|
4755 |
-
$params = self::shared_params($params);
|
4756 |
$params['PageContent'] = $html;
|
4757 |
|
4758 |
return self::get_template( 'base', $params );
|
@@ -4768,8 +5285,8 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4768 |
* @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
|
4769 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
4770 |
*/
|
4771 |
-
public static function get_template( $template='', $params=array(), $type='page' ){
|
4772 |
-
switch( $type ){
|
4773 |
case 'page': /* no_break */
|
4774 |
case 'section':
|
4775 |
$template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
|
@@ -4780,26 +5297,26 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4780 |
}
|
4781 |
|
4782 |
$template_content = '';
|
4783 |
-
$template_path =
|
4784 |
-
$params = is_array($params) ? $params : array();
|
4785 |
|
4786 |
-
if( file_exists($template_path) && is_readable($template_path) ){
|
4787 |
-
$template_content = @file_get_contents($template_path);
|
4788 |
|
4789 |
$params['SucuriURL'] = SUCURISCAN_URL;
|
4790 |
|
4791 |
// Detect the current page URL.
|
4792 |
-
if( $_page = self::get('page', '_page') ){
|
4793 |
-
$params['CurrentURL'] = admin_url('admin.php?page=' . $_page);
|
4794 |
} else {
|
4795 |
$params['CurrentURL'] = admin_url();
|
4796 |
}
|
4797 |
|
4798 |
// Replace the global pseudo-variables in the section/snippets templates.
|
4799 |
-
if(
|
4800 |
$template == 'base'
|
4801 |
&& isset($params['PageContent'])
|
4802 |
-
&& preg_match('/%%SUCURI\.(.+)%%/', $params['PageContent'])
|
4803 |
){
|
4804 |
$params['PageContent'] = self::replace_pseudovars( $params['PageContent'], $params );
|
4805 |
}
|
@@ -4807,7 +5324,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4807 |
$template_content = self::replace_pseudovars( $template_content, $params );
|
4808 |
}
|
4809 |
|
4810 |
-
if( $template == 'base' || $type != 'page' ){
|
4811 |
return $template_content;
|
4812 |
}
|
4813 |
|
@@ -4823,8 +5340,8 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4823 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4824 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
4825 |
*/
|
4826 |
-
public static function get_section($template='', $params=array()){
|
4827 |
-
$params = self::shared_params($params);
|
4828 |
|
4829 |
return self::get_template( $template, $params, 'section' );
|
4830 |
}
|
@@ -4838,7 +5355,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4838 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4839 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
4840 |
*/
|
4841 |
-
public static function get_modal($template='', $params=array()){
|
4842 |
$required = array(
|
4843 |
'Title' => 'Lorem ipsum dolor sit amet',
|
4844 |
'CssClass' => '',
|
@@ -4850,17 +5367,17 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4850 |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>',
|
4851 |
);
|
4852 |
|
4853 |
-
if( !empty($template) && $template != 'none' ){
|
4854 |
-
$params['Content'] = self::get_section($template);
|
4855 |
}
|
4856 |
|
4857 |
-
foreach( $required as $param_name => $param_value ){
|
4858 |
-
if( !isset($params[$param_name]) ){
|
4859 |
-
$params[$param_name] = $param_value;
|
4860 |
}
|
4861 |
}
|
4862 |
|
4863 |
-
$params = self::shared_params($params);
|
4864 |
|
4865 |
return self::get_template( 'modalwindow', $params, 'section' );
|
4866 |
}
|
@@ -4874,7 +5391,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4874 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
4875 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
4876 |
*/
|
4877 |
-
public static function get_snippet($template='', $params=array()){
|
4878 |
return self::get_template( $template, $params, 'snippet' );
|
4879 |
}
|
4880 |
|
@@ -4885,13 +5402,13 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4885 |
* @param string $selected_val Value of the option that will be selected by default.
|
4886 |
* @return string Option list for a select form field.
|
4887 |
*/
|
4888 |
-
public static function get_select_options( $allowed_values=array(), $selected_val='' ){
|
4889 |
$options = '';
|
4890 |
|
4891 |
-
foreach( $allowed_values as $option_name => $option_label ){
|
4892 |
$selected_str = '';
|
4893 |
|
4894 |
-
if( $option_name == $selected_val ){
|
4895 |
$selected_str = 'selected="selected"';
|
4896 |
}
|
4897 |
|
@@ -4910,9 +5427,9 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4910 |
* @return integer Page number of the link clicked in a pagination.
|
4911 |
*/
|
4912 |
public static function get_page_number(){
|
4913 |
-
$
|
4914 |
|
4915 |
-
return ( $
|
4916 |
}
|
4917 |
|
4918 |
/**
|
@@ -4923,23 +5440,30 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4923 |
* @param integer $max_per_page Maximum number of items that will be shown per page.
|
4924 |
* @return string HTML code for a pagination generated using the provided data.
|
4925 |
*/
|
4926 |
-
public static function get_pagination( $base_url='', $total_items=0, $max_per_page=1 ){
|
4927 |
// Calculate the number of links for the pagination.
|
4928 |
$html_links = '';
|
4929 |
$page_number = self::get_page_number();
|
4930 |
-
$max_pages = ceil($total_items / $max_per_page);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4931 |
|
4932 |
// Generate the HTML links for the pagination.
|
4933 |
-
for( $j=1; $j
|
4934 |
$link_class = 'sucuriscan-pagination-link';
|
4935 |
|
4936 |
-
if( $page_number == $j ){
|
4937 |
-
$link_class .= chr(32) . 'sucuriscan-pagination-active';
|
4938 |
}
|
4939 |
|
4940 |
$html_links .= sprintf(
|
4941 |
-
'<li><a href="%s&
|
4942 |
-
$base_url, $j, $link_class, $j
|
4943 |
);
|
4944 |
}
|
4945 |
|
@@ -4952,7 +5476,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4952 |
* @return boolean TRUE if the SiteCheck scanner and malware scan page are disabled.
|
4953 |
*/
|
4954 |
public static function is_sitecheck_disabled(){
|
4955 |
-
return (bool) ( SucuriScanOption::get_option(':sitecheck_scanner') === 'disabled' );
|
4956 |
}
|
4957 |
|
4958 |
/**
|
@@ -4961,7 +5485,7 @@ class SucuriScanTemplate extends SucuriScanRequest {
|
|
4961 |
* @return boolean TRUE if the SiteCheck scanner and malware scan page are enabled.
|
4962 |
*/
|
4963 |
public static function is_sitecheck_enabled(){
|
4964 |
-
return (bool) ( SucuriScanOption::get_option(':sitecheck_scanner') !== 'disabled' );
|
4965 |
}
|
4966 |
|
4967 |
}
|
@@ -4983,22 +5507,22 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
4983 |
* @param boolean $format Whether the timestamp must be formatted as date/time or not.
|
4984 |
* @return string The timestamp of the runtime, or an string with the date/time.
|
4985 |
*/
|
4986 |
-
public static function get_filesystem_runtime( $format=
|
4987 |
-
$runtime = SucuriScanOption::get_option(':runtime');
|
4988 |
|
4989 |
-
if( $runtime > 0 ){
|
4990 |
-
if( $format ){
|
4991 |
-
return SucuriScan::datetime($runtime);
|
4992 |
}
|
4993 |
|
4994 |
return $runtime;
|
4995 |
}
|
4996 |
|
4997 |
-
if( $format ){
|
4998 |
return '<em>Unknown</em>';
|
4999 |
}
|
5000 |
|
5001 |
-
return
|
5002 |
}
|
5003 |
|
5004 |
/**
|
@@ -5010,7 +5534,7 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5010 |
* @return boolean Whether the feature to ignore files is enabled or not.
|
5011 |
*/
|
5012 |
public static function will_ignore_scanning(){
|
5013 |
-
return ( SucuriScanOption::get_option(':ignore_scanning') === 'enabled' );
|
5014 |
}
|
5015 |
|
5016 |
/**
|
@@ -5019,11 +5543,11 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5019 |
* @param string $directory_path The (full) absolute path of a directory.
|
5020 |
* @return boolean TRUE if the directory path was added to the list, FALSE otherwise.
|
5021 |
*/
|
5022 |
-
public static function ignore_directory( $directory_path='' ){
|
5023 |
-
$cache = new SucuriScanCache('ignorescanning');
|
5024 |
|
5025 |
// Use the checksum of the directory path as the cache key.
|
5026 |
-
$cache_key = md5($directory_path);
|
5027 |
$cache_value = array(
|
5028 |
'directory_path' => $directory_path,
|
5029 |
'ignored_at' => self::local_time(),
|
@@ -5039,12 +5563,12 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5039 |
* @param string $directory_path The (full) absolute path of a directory.
|
5040 |
* @return boolean TRUE if the directory path was removed to the list, FALSE otherwise.
|
5041 |
*/
|
5042 |
-
public static function unignore_directory( $directory_path='' ){
|
5043 |
-
$cache = new SucuriScanCache('ignorescanning');
|
5044 |
|
5045 |
// Use the checksum of the directory path as the cache key.
|
5046 |
-
$cache_key = md5($directory_path);
|
5047 |
-
$removed = $cache->delete($cache_key);
|
5048 |
|
5049 |
return $removed;
|
5050 |
}
|
@@ -5078,14 +5602,14 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5078 |
'ignored_at_list' => array(),
|
5079 |
);
|
5080 |
|
5081 |
-
$cache = new SucuriScanCache('ignorescanning');
|
5082 |
$cache_lifetime = 0; // It is not necessary to expire this cache.
|
5083 |
$ignored_directories = $cache->get_all( $cache_lifetime, 'array' );
|
5084 |
|
5085 |
-
if( $ignored_directories ){
|
5086 |
$response['raw'] = $ignored_directories;
|
5087 |
|
5088 |
-
foreach( $ignored_directories as $checksum => $data ){
|
5089 |
$response['checksums'][] = $checksum;
|
5090 |
$response['directories'][] = $data['directory_path'];
|
5091 |
$response['ignored_at_list'][] = $data['ignored_at'];
|
@@ -5114,18 +5638,18 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5114 |
// Get the ignored directories from the cache.
|
5115 |
$ignored_directories = self::get_ignored_directories();
|
5116 |
|
5117 |
-
if( $ignored_directories ){
|
5118 |
$response['is_ignored'] = $ignored_directories['raw'];
|
5119 |
}
|
5120 |
|
5121 |
// Scan the project and file all directories.
|
5122 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
5123 |
-
$sucuri_fileinfo->ignore_files =
|
5124 |
-
$sucuri_fileinfo->ignore_directories =
|
5125 |
-
$sucuri_fileinfo->scan_interface = SucuriScanOption::get_option(':scan_interface');
|
5126 |
-
$directory_list = $sucuri_fileinfo->get_diretories_only(ABSPATH);
|
5127 |
|
5128 |
-
if( $directory_list ){
|
5129 |
$response['is_not_ignored'] = $directory_list;
|
5130 |
}
|
5131 |
|
@@ -5138,23 +5662,23 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5138 |
* @param array $error_logs The content of an error log file, or an array with the lines.
|
5139 |
* @return array List of valid error logs with their attributes separated.
|
5140 |
*/
|
5141 |
-
public static function parse_error_logs( $error_logs=array() ){
|
5142 |
$logs_arr = array();
|
5143 |
$pattern = '/^'
|
5144 |
-
. '(\[(\S+) ([0-9:]{5,8})( \S+)?\] )?'
|
5145 |
-
. '(PHP )?([a-zA-Z ]+):\s'
|
5146 |
-
. '(.+) in (.+)'
|
5147 |
-
. '(:| on line )([0-9]+)'
|
5148 |
. '$/';
|
5149 |
|
5150 |
-
if ( is_string($error_logs) ) {
|
5151 |
$error_logs = explode( "\n", $error_logs );
|
5152 |
}
|
5153 |
|
5154 |
foreach ( (array) $error_logs as $line ) {
|
5155 |
-
if ( !is_string($line) || empty($line) ) { continue; }
|
5156 |
|
5157 |
-
if ( preg_match($pattern, $line, $match) ) {
|
5158 |
$data_set = array(
|
5159 |
'date' => '',
|
5160 |
'time' => '',
|
@@ -5171,10 +5695,10 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5171 |
// Basic attributes from the scrapping.
|
5172 |
$data_set['date'] = $match[2];
|
5173 |
$data_set['time'] = $match[3];
|
5174 |
-
$data_set['time_zone'] = trim($match[4]);
|
5175 |
-
$data_set['error_type'] = trim($match[6]);
|
5176 |
-
$data_set['error_message'] = trim($match[7]);
|
5177 |
-
$data_set['file_path'] = trim($match[8]);
|
5178 |
$data_set['line_number'] = (int) $match[10];
|
5179 |
|
5180 |
// Additional data from the attributes.
|
@@ -5189,7 +5713,7 @@ class SucuriScanFSScanner extends SucuriScan {
|
|
5189 |
$valid_types = array( 'warning', 'notice', 'error' );
|
5190 |
|
5191 |
foreach ( $valid_types as $valid_type ) {
|
5192 |
-
if ( stripos($data_set['error_type'], $valid_type) !==
|
5193 |
$data_set['error_code'] = $valid_type;
|
5194 |
break;
|
5195 |
}
|
@@ -5227,31 +5751,31 @@ class SucuriScanHeartbeat extends SucuriScanOption {
|
|
5227 |
public static function register_script(){
|
5228 |
global $pagenow;
|
5229 |
|
5230 |
-
$status = SucuriScanOption::get_option(':heartbeat');
|
5231 |
|
5232 |
// Enable heartbeat everywhere.
|
5233 |
-
if( $status == 'enabled' ){ /* do_nothing */ }
|
5234 |
|
5235 |
// Disable heartbeat everywhere.
|
5236 |
-
elseif( $status == 'disabled' ){
|
5237 |
-
wp_deregister_script('heartbeat');
|
5238 |
}
|
5239 |
|
5240 |
// Disable heartbeat only on the dashboard and home pages.
|
5241 |
-
elseif(
|
5242 |
$status == 'dashboard'
|
5243 |
&& $pagenow == 'index.php'
|
5244 |
){
|
5245 |
-
wp_deregister_script('heartbeat');
|
5246 |
}
|
5247 |
|
5248 |
// Disable heartbeat everywhere except in post edition.
|
5249 |
-
elseif(
|
5250 |
$status == 'addpost'
|
5251 |
&& $pagenow != 'post.php'
|
5252 |
&& $pagenow != 'post-new.php'
|
5253 |
){
|
5254 |
-
wp_deregister_script('heartbeat');
|
5255 |
}
|
5256 |
}
|
5257 |
|
@@ -5265,17 +5789,17 @@ class SucuriScanHeartbeat extends SucuriScanOption {
|
|
5265 |
* @param array $settings Heartbeat settings.
|
5266 |
* @return array Updated version of the heartbeat settings.
|
5267 |
*/
|
5268 |
-
public static function update_settings( $settings=array() ){
|
5269 |
-
$pulse = SucuriScanOption::get_option(':heartbeat_pulse');
|
5270 |
-
$autostart = SucuriScanOption::get_option(':heartbeat_autostart');
|
5271 |
|
5272 |
-
if( $pulse < 15 || $pulse > 60 ){
|
5273 |
-
SucuriScanOption::delete_option(':heartbeat_pulse');
|
5274 |
$pulse = 15;
|
5275 |
}
|
5276 |
|
5277 |
$settings['interval'] = $pulse;
|
5278 |
-
$settings['autostart'] = ( $autostart == 'disabled' ?
|
5279 |
|
5280 |
return $settings;
|
5281 |
}
|
@@ -5288,17 +5812,17 @@ class SucuriScanHeartbeat extends SucuriScanOption {
|
|
5288 |
* @param string $screen_id Identifier of the screen the heartbeat occurred on.
|
5289 |
* @return array Response with new data.
|
5290 |
*/
|
5291 |
-
public static function respond_to_received( $response=array(), $data=array(), $screen_id='' ) {
|
5292 |
-
$interval = SucuriScanOption::get_option(':heartbeat_interval');
|
5293 |
|
5294 |
-
if(
|
5295 |
$interval == 'slow'
|
5296 |
|| $interval == 'fast'
|
5297 |
|| $interval == 'standard'
|
5298 |
){
|
5299 |
$response['heartbeat_interval'] = $interval;
|
5300 |
} else {
|
5301 |
-
SucuriScanOption::delete_option(':heartbeat_interval');
|
5302 |
}
|
5303 |
|
5304 |
return $response;
|
@@ -5311,7 +5835,7 @@ class SucuriScanHeartbeat extends SucuriScanOption {
|
|
5311 |
* @param string $screen_id Identifier of the screen the heartbeat occurred on.
|
5312 |
* @return array Response with new data.
|
5313 |
*/
|
5314 |
-
public static function respond_to_send( $response=array(), $screen_id='' ) {
|
5315 |
return $response;
|
5316 |
}
|
5317 |
|
@@ -5350,8 +5874,8 @@ class SucuriScanHeartbeat extends SucuriScanOption {
|
|
5350 |
public static function pulses_allowed(){
|
5351 |
$pulses = array();
|
5352 |
|
5353 |
-
for( $i=15; $i<=60; $i++ ){
|
5354 |
-
$pulses[$i] = sprintf( 'Run every %d seconds', $i );
|
5355 |
}
|
5356 |
|
5357 |
return $pulses;
|
@@ -5396,15 +5920,24 @@ class SucuriScanInterface {
|
|
5396 |
public static function enqueue_scripts(){
|
5397 |
$asset_version = '';
|
5398 |
|
5399 |
-
if( strlen(SUCURISCAN_PLUGIN_CHECKSUM) >= 7 ){
|
5400 |
-
$asset_version = substr(SUCURISCAN_PLUGIN_CHECKSUM, 0, 7);
|
5401 |
}
|
5402 |
|
5403 |
wp_register_style( 'sucuriscan', SUCURISCAN_URL . '/inc/css/sucuriscan-default-css.css', array(), $asset_version );
|
5404 |
wp_register_script( 'sucuriscan', SUCURISCAN_URL . '/inc/js/sucuriscan-scripts.js', array(), $asset_version );
|
5405 |
-
|
5406 |
wp_enqueue_style( 'sucuriscan' );
|
5407 |
wp_enqueue_script( 'sucuriscan' );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5408 |
}
|
5409 |
|
5410 |
/**
|
@@ -5415,8 +5948,8 @@ class SucuriScanInterface {
|
|
5415 |
public static function add_interface_menu(){
|
5416 |
global $sucuriscan_pages;
|
5417 |
|
5418 |
-
if(
|
5419 |
-
function_exists('add_menu_page')
|
5420 |
&& $sucuriscan_pages
|
5421 |
){
|
5422 |
// Add main menu link.
|
@@ -5429,9 +5962,9 @@ class SucuriScanInterface {
|
|
5429 |
SUCURISCAN_URL . '/inc/images/menu-icon.png'
|
5430 |
);
|
5431 |
|
5432 |
-
$sub_pages = is_array($sucuriscan_pages) ? $sucuriscan_pages : array();
|
5433 |
|
5434 |
-
foreach( $sub_pages as $sub_page_func => $sub_page_title ){
|
5435 |
if (
|
5436 |
$sub_page_func == 'sucuriscan_scanner'
|
5437 |
&& SucuriScanTemplate::is_sitecheck_disabled()
|
@@ -5462,25 +5995,25 @@ class SucuriScanInterface {
|
|
5462 |
* @return void
|
5463 |
*/
|
5464 |
public static function handle_old_plugins(){
|
5465 |
-
if( class_exists('SucuriScanFileInfo') ){
|
5466 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
5467 |
-
$sucuri_fileinfo->ignore_files =
|
5468 |
-
$sucuri_fileinfo->ignore_directories =
|
5469 |
|
5470 |
$plugins = array(
|
5471 |
'sucuri-wp-plugin/sucuri.php',
|
5472 |
'sucuri-cloudproxy-waf/cloudproxy.php',
|
5473 |
);
|
5474 |
|
5475 |
-
foreach( $plugins as $plugin ){
|
5476 |
$plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin );
|
5477 |
|
5478 |
-
if( file_exists($plugin_directory) ){
|
5479 |
-
if( is_plugin_active($plugin) ){
|
5480 |
-
deactivate_plugins($plugin);
|
5481 |
}
|
5482 |
|
5483 |
-
$plugin_removed = $sucuri_fileinfo->remove_directory_tree($plugin_directory);
|
5484 |
}
|
5485 |
}
|
5486 |
}
|
@@ -5495,8 +6028,10 @@ class SucuriScanInterface {
|
|
5495 |
public static function create_datastore_folder(){
|
5496 |
$plugin_upload_folder = SucuriScan::datastore_folder_path();
|
5497 |
|
5498 |
-
if( !file_exists($plugin_upload_folder) ){
|
5499 |
-
|
|
|
|
|
5500 |
// Create last-logins datastore file.
|
5501 |
sucuriscan_lastlogins_datastore_exists();
|
5502 |
|
@@ -5510,14 +6045,15 @@ class SucuriScanInterface {
|
|
5510 |
// Create an index.html to avoid directory listing.
|
5511 |
@file_put_contents(
|
5512 |
$plugin_upload_folder . '/index.html',
|
5513 |
-
'<!--
|
5514 |
LOCK_EX
|
5515 |
);
|
5516 |
} else {
|
5517 |
SucuriScanInterface::error(
|
5518 |
-
'Data folder does not exists and could not be created. You will need to
|
5519 |
-
|
5520 |
-
|
|
|
5521 |
);
|
5522 |
}
|
5523 |
}
|
@@ -5529,12 +6065,12 @@ class SucuriScanInterface {
|
|
5529 |
* @return void
|
5530 |
*/
|
5531 |
public static function check_permissions(){
|
5532 |
-
if(
|
5533 |
-
!function_exists('current_user_can')
|
5534 |
-
|| !current_user_can('manage_options')
|
5535 |
){
|
5536 |
-
$page = SucuriScanRequest::get('page', '_page');
|
5537 |
-
wp_die(__('Access denied by <b>Sucuri</b> to see <code>' . $page . '</code>') );
|
5538 |
}
|
5539 |
}
|
5540 |
|
@@ -5546,18 +6082,18 @@ class SucuriScanInterface {
|
|
5546 |
* @return boolean Either TRUE or FALSE if the nonce is valid or not respectively.
|
5547 |
*/
|
5548 |
public static function check_nonce(){
|
5549 |
-
if( !empty($_POST) ){
|
5550 |
$nonce_name = 'sucuriscan_page_nonce';
|
5551 |
-
$nonce_value = SucuriScanRequest::post($nonce_name, '_nonce');
|
5552 |
|
5553 |
-
if(
|
5554 |
-
wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
|
5555 |
|
5556 |
-
return
|
5557 |
}
|
5558 |
}
|
5559 |
|
5560 |
-
return
|
5561 |
}
|
5562 |
|
5563 |
/**
|
@@ -5567,12 +6103,12 @@ class SucuriScanInterface {
|
|
5567 |
* @param string $message The message that will be printed in the alert.
|
5568 |
* @return void
|
5569 |
*/
|
5570 |
-
private static function admin_notice( $type='updated', $message='' ){
|
5571 |
-
$alert_id = rand(100, 999);
|
5572 |
-
if( !empty($message) ): ?>
|
5573 |
<div id="sucuriscan-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuriscan-alert sucuriscan-alert-<?php echo $type; ?>">
|
5574 |
<a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">×</a>
|
5575 |
-
<p><?php _e($message); ?></p>
|
5576 |
</div>
|
5577 |
<?php endif;
|
5578 |
}
|
@@ -5583,7 +6119,7 @@ class SucuriScanInterface {
|
|
5583 |
* @param string $error_msg The message that will be printed in the alert.
|
5584 |
* @return void
|
5585 |
*/
|
5586 |
-
public static function error( $error_msg='' ){
|
5587 |
self::admin_notice( 'error', '<b>Sucuri:</b> ' . $error_msg );
|
5588 |
}
|
5589 |
|
@@ -5593,7 +6129,7 @@ class SucuriScanInterface {
|
|
5593 |
* @param string $info_msg The message that will be printed in the alert.
|
5594 |
* @return void
|
5595 |
*/
|
5596 |
-
public static function info( $info_msg='' ){
|
5597 |
self::admin_notice( 'updated', '<b>Sucuri:</b> ' . $info_msg );
|
5598 |
}
|
5599 |
|
@@ -5605,15 +6141,15 @@ class SucuriScanInterface {
|
|
5605 |
* @return void
|
5606 |
*/
|
5607 |
public static function setup_notice(){
|
5608 |
-
if(
|
5609 |
-
current_user_can('manage_options')
|
5610 |
&& SucuriScan::no_notices_here() === false
|
5611 |
-
&& !SucuriScanAPI::get_plugin_key()
|
5612 |
-
&& SucuriScanRequest::post(':plugin_api_key') ===
|
5613 |
-
&& SucuriScanRequest::post(':recover_key') ===
|
5614 |
-
&& !SucuriScanRequest::post(':manual_api_key')
|
5615 |
-
){
|
5616 |
-
echo SucuriScanTemplate::get_section('setup-notice');
|
5617 |
}
|
5618 |
}
|
5619 |
|
@@ -5630,24 +6166,24 @@ function sucuriscan_scanner_page(){
|
|
5630 |
SucuriScanInterface::check_permissions();
|
5631 |
|
5632 |
// Check if the information is already cached.
|
5633 |
-
$cache = new SucuriScanCache('sitecheck');
|
5634 |
$scan_results = $cache->get( 'scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array' );
|
5635 |
|
5636 |
-
if(
|
5637 |
(
|
5638 |
$scan_results
|
5639 |
-
&& !empty($scan_results)
|
5640 |
) || (
|
5641 |
SucuriScanInterface::check_nonce()
|
5642 |
-
&& SucuriScanRequest::post(':malware_scan', '1')
|
5643 |
)
|
5644 |
){
|
5645 |
-
sucuriscan_sitecheck_info($scan_results);
|
5646 |
} else {
|
5647 |
-
echo SucuriScanTemplate::get_template('malwarescan', array(
|
5648 |
'PageTitle' => 'Malware Scan',
|
5649 |
'PageStyleClass' => 'scanner-loading',
|
5650 |
-
));
|
5651 |
}
|
5652 |
}
|
5653 |
|
@@ -5657,18 +6193,18 @@ function sucuriscan_scanner_page(){
|
|
5657 |
* @param array $res Array with information of the scanning.
|
5658 |
* @return void
|
5659 |
*/
|
5660 |
-
function sucuriscan_sitecheck_info( $res=array() ){
|
5661 |
// Will be TRUE only if the scanning results were retrieved from the cache.
|
5662 |
$display_results = (bool) $res;
|
5663 |
$clean_domain = SucuriScan::get_domain();
|
5664 |
|
5665 |
// If the results are not cached, then request a new scanning.
|
5666 |
-
if( $res ===
|
5667 |
-
$res = SucuriScanAPI::get_sitecheck_results($clean_domain);
|
5668 |
|
5669 |
// Check for error messages in the request's response.
|
5670 |
-
if( is_string($res) ){
|
5671 |
-
if( preg_match('/^ERROR:(.*)/', $res, $error_m) ){
|
5672 |
SucuriScanInterface::error( 'The site <code>' . $clean_domain . '</code> was not scanned: ' . $error_m[1] );
|
5673 |
} else {
|
5674 |
SucuriScanInterface::error( 'SiteCheck error: ' . $res );
|
@@ -5676,11 +6212,11 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5676 |
}
|
5677 |
|
5678 |
else {
|
5679 |
-
$cache = new SucuriScanCache('sitecheck');
|
5680 |
-
$display_results =
|
5681 |
|
5682 |
// Cache the scanning results to reduce memory lose.
|
5683 |
-
if(
|
5684 |
SucuriScanInterface::error( 'Could not cache the results of the SiteCheck scanning.' );
|
5685 |
}
|
5686 |
}
|
@@ -5688,7 +6224,7 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5688 |
|
5689 |
// Count the number of scans.
|
5690 |
if ( $display_results === true ) {
|
5691 |
-
$sitecheck_counter = (int) SucuriScanOption::get_option(':sitecheck_counter');
|
5692 |
SucuriScanOption::update_option( ':sitecheck_counter', $sitecheck_counter + 1 );
|
5693 |
}
|
5694 |
|
@@ -5696,46 +6232,51 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5696 |
?>
|
5697 |
|
5698 |
|
5699 |
-
<?php if( $display_results ): ?>
|
5700 |
|
5701 |
<?php
|
5702 |
// Check for general warnings, and return the information for Infected/Clean site.
|
5703 |
-
$malware_warns_exist = isset($res['MALWARE']['WARN']) ?
|
5704 |
-
$blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ?
|
5705 |
-
$outdated_warns_exist = isset($res['OUTDATEDSCAN']) ?
|
5706 |
-
$recommendations_exist = isset($res['RECOMMENDATIONS']) ?
|
5707 |
|
5708 |
// Check whether this WordPress installation needs an update.
|
5709 |
global $wp_version;
|
5710 |
-
$wordpress_updated =
|
5711 |
-
$updates = function_exists('get_core_updates') ? get_core_updates() : array();
|
5712 |
|
5713 |
-
if
|
5714 |
-
$
|
|
|
|
|
|
|
|
|
5715 |
}
|
5716 |
|
5717 |
-
if(
|
|
|
|
|
|
|
5718 |
// Initialize the CSS classes with default values.
|
5719 |
-
$sucuriscan_css_blacklist = 'sucuriscan-border-good';
|
5720 |
$sucuriscan_css_malware = 'sucuriscan-border-good';
|
5721 |
$sitecheck_results_tab = '';
|
5722 |
$blacklist_status_tab = '';
|
5723 |
$website_details_tab = '';
|
5724 |
|
5725 |
// Generate the CSS classes for the blacklist status.
|
5726 |
-
if( $blacklist_warns_exist ){
|
5727 |
-
$sucuriscan_css_blacklist = 'sucuriscan-border-bad';
|
5728 |
$blacklist_status_tab = 'sucuriscan-red-tab';
|
5729 |
}
|
5730 |
|
5731 |
// Generate the CSS classes for the SiteCheck scanning results.
|
5732 |
-
if( $malware_warns_exist ){
|
5733 |
$sucuriscan_css_malware = 'sucuriscan-border-bad';
|
5734 |
$sitecheck_results_tab = 'sucuriscan-red-tab';
|
5735 |
}
|
5736 |
|
5737 |
// Generate the CSS classes for the outdated/recommendations panel.
|
5738 |
-
if( $outdated_warns_exist || $recommendations_exist ){
|
5739 |
$website_details_tab = 'sucuriscan-red-tab';
|
5740 |
}
|
5741 |
|
@@ -5743,34 +6284,21 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5743 |
}
|
5744 |
?>
|
5745 |
|
5746 |
-
<div id="poststuff">
|
5747 |
-
<div class="postbox sucuriscan-border sucuriscan-border-info sucuriscan-malwarescan-message">
|
5748 |
-
<h3>SiteCheck Scanner</h3>
|
5749 |
-
|
5750 |
-
<div class="inside">
|
5751 |
-
<p>
|
5752 |
-
If your site was recently hacked, you can see which files were modified to
|
5753 |
-
assist with any investigation.
|
5754 |
-
</p>
|
5755 |
-
</div>
|
5756 |
-
</div>
|
5757 |
-
</div>
|
5758 |
-
|
5759 |
|
5760 |
<div class="sucuriscan-tabs">
|
5761 |
|
5762 |
|
5763 |
<ul>
|
5764 |
-
<li class="<?php _e($sitecheck_results_tab) ?>">
|
5765 |
<a href="#" data-tabname="sitecheck-results">Remote Scanner Results</a>
|
5766 |
</li>
|
5767 |
-
<li class="<?php _e($website_details_tab) ?>">
|
5768 |
<a href="#" data-tabname="website-details">Website Details</a>
|
5769 |
</li>
|
5770 |
<li>
|
5771 |
<a href="#" data-tabname="website-links">IFrames / Links / Scripts</a>
|
5772 |
</li>
|
5773 |
-
<li class="<?php _e($blacklist_status_tab) ?>">
|
5774 |
<a href="#" data-tabname="blacklist-status">Blacklist Status</a>
|
5775 |
</li>
|
5776 |
<li>
|
@@ -5783,72 +6311,144 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5783 |
|
5784 |
|
5785 |
<div id="sucuriscan-sitecheck-results">
|
5786 |
-
<
|
5787 |
-
<
|
5788 |
-
<
|
5789 |
-
|
5790 |
-
|
5791 |
-
|
5792 |
-
|
5793 |
-
|
5794 |
-
|
5795 |
-
|
5796 |
-
|
5797 |
-
|
5798 |
-
|
5799 |
-
|
5800 |
-
|
5801 |
-
|
5802 |
-
|
5803 |
-
|
5804 |
-
|
5805 |
-
|
5806 |
-
|
5807 |
-
|
5808 |
-
|
5809 |
-
|
5810 |
-
|
5811 |
-
|
5812 |
-
|
5813 |
-
|
5814 |
-
|
5815 |
-
|
5816 |
-
|
5817 |
-
|
5818 |
-
|
5819 |
-
|
5820 |
-
|
5821 |
-
);
|
5822 |
-
|
5823 |
-
|
5824 |
-
|
5825 |
-
|
5826 |
-
|
5827 |
-
|
5828 |
-
|
5829 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5830 |
|
5831 |
-
<
|
5832 |
-
<
|
5833 |
-
|
5834 |
-
|
5835 |
-
|
5836 |
-
|
5837 |
-
|
5838 |
-
|
5839 |
-
|
5840 |
-
|
5841 |
-
|
5842 |
-
|
5843 |
-
|
5844 |
-
|
5845 |
-
|
5846 |
-
|
5847 |
-
|
5848 |
-
|
5849 |
-
|
5850 |
-
|
5851 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5852 |
</div>
|
5853 |
|
5854 |
|
@@ -5857,10 +6457,10 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5857 |
<thead>
|
5858 |
<tr>
|
5859 |
<th colspan="2" class="thead-with-button">
|
5860 |
-
<span>System
|
5861 |
-
<?php if(
|
5862 |
-
<a href="<?php echo admin_url('update-core.php'); ?>" class="button button-primary thead-topright-action">
|
5863 |
-
Update to <?php _e($updates[0]->version) ?>
|
5864 |
</a>
|
5865 |
<?php endif; ?>
|
5866 |
</th>
|
@@ -5884,77 +6484,83 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5884 |
);
|
5885 |
?>
|
5886 |
|
5887 |
-
<?php foreach( $possible_keys as $result_key
|
5888 |
-
<?php if( isset($res['SCAN'][$result_key]) ): ?>
|
5889 |
-
<?php $result_value = implode(', ', $res['SCAN'][$result_key]); ?>
|
5890 |
<tr>
|
5891 |
-
<td><?php _e($result_title) ?></td>
|
5892 |
-
<td><span class="sucuriscan-monospace"><?php _e($result_value) ?></span></td>
|
5893 |
</tr>
|
5894 |
<?php endif; ?>
|
5895 |
<?php endforeach; ?>
|
5896 |
|
5897 |
<tr>
|
5898 |
<td>WordPress Version</td>
|
5899 |
-
<td><span class="sucuriscan-monospace"><?php _e($wp_version) ?></span></td>
|
5900 |
</tr>
|
5901 |
<tr>
|
5902 |
<td>PHP Version</td>
|
5903 |
-
<td><span class="sucuriscan-monospace"><?php _e(phpversion()) ?></span></td>
|
5904 |
</tr>
|
5905 |
|
5906 |
<!-- List of application details from the site. -->
|
5907 |
<tr>
|
5908 |
<th colspan="2">Web application details</th>
|
5909 |
</tr>
|
5910 |
-
<?php if( isset($res['WEBAPP']) ): ?>
|
5911 |
-
<?php foreach( $res['WEBAPP'] as $webapp_key
|
5912 |
-
<?php if( is_array($webapp_details) ): ?>
|
5913 |
-
<?php foreach( $webapp_details as $i
|
5914 |
-
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5915 |
<tr>
|
5916 |
-
<td
|
5917 |
-
|
5918 |
-
</td>
|
5919 |
</tr>
|
5920 |
<?php endforeach; ?>
|
5921 |
<?php endif; ?>
|
5922 |
<?php endforeach; ?>
|
5923 |
<?php endif; ?>
|
5924 |
|
5925 |
-
<?php if( isset($res['SYSTEM']['NOTICE']) ): ?>
|
5926 |
-
<?php foreach( $res['SYSTEM']['NOTICE'] as $j
|
5927 |
-
<?php if( is_array($notice) ){ $notice = implode(', ', $notice); } ?>
|
5928 |
<tr>
|
5929 |
<td colspan="2">
|
5930 |
-
<span class="sucuriscan-monospace"><?php _e($notice) ?></span>
|
5931 |
</td>
|
5932 |
</tr>
|
5933 |
<?php endforeach; ?>
|
5934 |
<?php endif; ?>
|
5935 |
|
5936 |
-
<?php if ( !isset($res['WEBAPP']) && !isset($res['SYSTEM']['NOTICE']) ): ?>
|
5937 |
<tr>
|
5938 |
<td colspan="2"><em>No more information was found.</em></td>
|
5939 |
</tr>
|
5940 |
<?php endif; ?>
|
5941 |
|
5942 |
<!-- Possible recommendations or outdated software on the site. -->
|
5943 |
-
<?php if( $outdated_warns_exist || $recommendations_exist ): ?>
|
5944 |
<tr>
|
5945 |
<th colspan="2">Recommendations for the site</th>
|
5946 |
</tr>
|
5947 |
<?php endif; ?>
|
5948 |
|
5949 |
<!-- Possible outdated software on the site. -->
|
5950 |
-
<?php if( $outdated_warns_exist ): ?>
|
5951 |
-
<?php foreach( $res['OUTDATEDSCAN'] as $outdated ): ?>
|
5952 |
-
<?php if( count($outdated) >= 3 ): ?>
|
5953 |
<tr>
|
5954 |
<td colspan="2" class="sucuriscan-border-bad">
|
5955 |
-
<strong><?php _e($outdated[0]) ?></strong>
|
5956 |
-
<em>(<?php _e($outdated[2]) ?>)</em>
|
5957 |
-
<span><?php _e($outdated[1]) ?></span>
|
5958 |
</td>
|
5959 |
</tr>
|
5960 |
<?php endif; ?>
|
@@ -5962,18 +6568,20 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5962 |
<?php endif; ?>
|
5963 |
|
5964 |
<!-- Possible recommendations for the site. -->
|
5965 |
-
<?php if( $recommendations_exist ): ?>
|
5966 |
-
<?php foreach( $res['RECOMMENDATIONS'] as $recommendation ): ?>
|
5967 |
-
<?php if( count($recommendation) >= 3 ): ?>
|
5968 |
<tr>
|
5969 |
<td colspan="2" class="sucuriscan-border-bad">
|
5970 |
-
<?php
|
|
|
5971 |
'<strong>%s</strong><br><span>%s</span><br><a href="%s" target="_blank">%s</a>',
|
5972 |
-
SucuriScan::escape($recommendation[0]),
|
5973 |
-
SucuriScan::escape($recommendation[1]),
|
5974 |
-
SucuriScan::escape($recommendation[2]),
|
5975 |
-
SucuriScan::escape($recommendation[2])
|
5976 |
-
);
|
|
|
5977 |
</td>
|
5978 |
</tr>
|
5979 |
<?php endif; ?>
|
@@ -5989,23 +6597,25 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
5989 |
<tbody>
|
5990 |
<?php if ( isset($res['LINKS']) ): ?>
|
5991 |
|
5992 |
-
<?php foreach( $possible_url_keys as $result_url_key
|
5993 |
|
5994 |
-
<?php if( isset($res['LINKS'][$result_url_key]) ): ?>
|
5995 |
<tr>
|
5996 |
<th colspan="2">
|
5997 |
-
<?php
|
|
|
5998 |
'%s (%d found)',
|
5999 |
-
__($result_url_title),
|
6000 |
-
count($res['LINKS'][$result_url_key])
|
6001 |
-
)
|
|
|
6002 |
</th>
|
6003 |
</tr>
|
6004 |
|
6005 |
-
<?php foreach( $res['LINKS'][$result_url_key] as $url_path ): ?>
|
6006 |
<tr>
|
6007 |
<td colspan="2">
|
6008 |
-
<span class="sucuriscan-monospace sucuriscan-wraptext"><?php _e($url_path) ?></span>
|
6009 |
</td>
|
6010 |
</tr>
|
6011 |
<?php endforeach; ?>
|
@@ -6026,39 +6636,47 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
6026 |
|
6027 |
|
6028 |
<div id="sucuriscan-blacklist-status">
|
6029 |
-
<
|
6030 |
-
<
|
6031 |
-
<
|
6032 |
-
|
6033 |
-
Site blacklisted
|
6034 |
-
|
6035 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6036 |
<?php endif; ?>
|
6037 |
-
|
6038 |
-
|
6039 |
-
|
6040 |
-
|
6041 |
-
|
6042 |
-
foreach(array(
|
6043 |
-
'INFO' => 'CLEAN',
|
6044 |
-
'WARN' => 'WARNING'
|
6045 |
-
) as $type => $group_title){
|
6046 |
-
if( isset($res['BLACKLIST'][$type]) ){
|
6047 |
-
foreach( $res['BLACKLIST'][$type] as $blres ){
|
6048 |
-
$report_site = SucuriScan::escape($blres[0]);
|
6049 |
-
$report_url = SucuriScan::escape($blres[1]);
|
6050 |
-
printf(
|
6051 |
-
'<li><b>%s:</b> %s.<br>Details at <a href="%s" target="_blank">%s</a></li>',
|
6052 |
-
$group_title, $report_site, $report_url, $report_url
|
6053 |
-
);
|
6054 |
-
}
|
6055 |
-
}
|
6056 |
-
}
|
6057 |
-
?>
|
6058 |
-
</ul>
|
6059 |
-
</div>
|
6060 |
-
</div>
|
6061 |
-
</div>
|
6062 |
</div>
|
6063 |
|
6064 |
|
@@ -6070,7 +6688,7 @@ function sucuriscan_sitecheck_info( $res=array() ){
|
|
6070 |
</div>
|
6071 |
</div>
|
6072 |
|
6073 |
-
<?php if( $malware_warns_exist || $blacklist_warns_exist ): ?>
|
6074 |
<a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
|
6075 |
Get your site protected with Sucuri
|
6076 |
</a>
|
@@ -6112,8 +6730,8 @@ function sucuriscan_monitoring_page(){
|
|
6112 |
$template_variables = array(
|
6113 |
'PageTitle' => 'Firewall WAF',
|
6114 |
'Monitoring.InstructionsVisibility' => 'visible',
|
6115 |
-
'Monitoring.Settings' => sucuriscan_monitoring_settings($api_key),
|
6116 |
-
'Monitoring.Logs' => sucuriscan_monitoring_logs($api_key),
|
6117 |
|
6118 |
/* Pseudo-variables for the monitoring logs. */
|
6119 |
'AuditLogs.List' => '',
|
@@ -6124,11 +6742,11 @@ function sucuriscan_monitoring_page(){
|
|
6124 |
'AuditLogs.AuditPagination' => '',
|
6125 |
);
|
6126 |
|
6127 |
-
if( $api_key ){
|
6128 |
$template_variables['Monitoring.InstructionsVisibility'] = 'hidden';
|
6129 |
}
|
6130 |
|
6131 |
-
echo SucuriScanTemplate::get_template('monitoring', $template_variables);
|
6132 |
}
|
6133 |
|
6134 |
/**
|
@@ -6140,18 +6758,20 @@ function sucuriscan_monitoring_page(){
|
|
6140 |
*/
|
6141 |
function sucuriscan_monitoring_form_submissions(){
|
6142 |
|
6143 |
-
if( SucuriScanInterface::check_nonce() ){
|
6144 |
|
6145 |
// Add and/or Update the Sucuri WAF API Key (do it before anything else).
|
6146 |
$option_name = ':cloudproxy_apikey';
|
6147 |
-
$api_key = SucuriScanRequest::post($option_name);
|
6148 |
|
6149 |
-
if( $api_key !==
|
6150 |
-
if( SucuriScanAPI::is_valid_cloudproxy_key($api_key) ){
|
6151 |
-
SucuriScanOption::update_option($option_name, $api_key);
|
|
|
6152 |
SucuriScanInterface::info( 'CloudProxy API key saved successfully' );
|
6153 |
-
} elseif( empty($api_key) ){
|
6154 |
-
SucuriScanOption::delete_option($option_name);
|
|
|
6155 |
SucuriScanInterface::info( 'CloudProxy API key removed successfully' );
|
6156 |
} else {
|
6157 |
SucuriScanInterface::error( 'Invalid CloudProxy API key, check your settings and try again.' );
|
@@ -6159,23 +6779,22 @@ function sucuriscan_monitoring_form_submissions(){
|
|
6159 |
}
|
6160 |
|
6161 |
// Flush the cache of the site(s) associated with the API key.
|
6162 |
-
if( SucuriScanRequest::post(':clear_cache', '1') ){
|
6163 |
$clear_cache_resp = SucuriScanAPI::clear_cloudproxy_cache();
|
6164 |
|
6165 |
-
if( $clear_cache_resp ){
|
6166 |
-
if( isset($clear_cache_resp->messages[0]) ){
|
6167 |
// Clear W3 Total Cache if it is installed.
|
6168 |
-
if( function_exists('w3tc_flush_all') ){ w3tc_flush_all(); }
|
6169 |
|
6170 |
-
SucuriScanInterface::info($clear_cache_resp->messages[0]);
|
6171 |
} else {
|
6172 |
-
SucuriScanInterface::error('Could not clear the cache of your site, try later again.');
|
6173 |
}
|
6174 |
} else {
|
6175 |
SucuriScanInterface::error( 'CloudProxy is not enabled on your site, or your API key is invalid.' );
|
6176 |
}
|
6177 |
}
|
6178 |
-
|
6179 |
}
|
6180 |
|
6181 |
}
|
@@ -6186,38 +6805,38 @@ function sucuriscan_monitoring_form_submissions(){
|
|
6186 |
* @param string $api_key The CloudProxy API key.
|
6187 |
* @return string The parsed-content of the monitoring settings panel.
|
6188 |
*/
|
6189 |
-
function sucuriscan_monitoring_settings( $api_key='' ){
|
6190 |
$template_variables = array(
|
6191 |
'Monitoring.APIKey' => '',
|
6192 |
'Monitoring.SettingsVisibility' => 'hidden',
|
6193 |
'Monitoring.SettingOptions' => '',
|
6194 |
);
|
6195 |
|
6196 |
-
if( $api_key ){
|
6197 |
-
$settings = SucuriScanAPI::get_cloudproxy_settings($api_key);
|
6198 |
|
6199 |
$template_variables['Monitoring.APIKey'] = $api_key['string'];
|
6200 |
|
6201 |
-
if( $settings ){
|
6202 |
$counter = 0;
|
6203 |
$template_variables['Monitoring.SettingsVisibility'] = 'visible';
|
6204 |
-
$settings = sucuriscan_explain_monitoring_settings($settings);
|
6205 |
|
6206 |
-
foreach( $settings as $option_name => $option_value ){
|
6207 |
// Change the name of some options.
|
6208 |
-
if( $option_name == 'internal_ip' ){
|
6209 |
$option_name = 'hosting_ip';
|
6210 |
}
|
6211 |
|
6212 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
6213 |
-
$option_title = ucwords(str_replace('_', chr(32), $option_name));
|
6214 |
|
6215 |
// Generate a HTML list when the option's value is an array.
|
6216 |
-
if( is_array($option_value) ){
|
6217 |
-
$css_scrollable = count($option_value) > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
|
6218 |
$html_list = '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
|
6219 |
|
6220 |
-
foreach( $option_value as $single_value ){
|
6221 |
$html_list .= '<li>' . $single_value . '</li>';
|
6222 |
}
|
6223 |
|
@@ -6247,22 +6866,22 @@ function sucuriscan_monitoring_settings( $api_key='' ){
|
|
6247 |
* @param array $settings A hash with the settings of a CloudProxy account.
|
6248 |
* @return array The explained version of the CloudProxy settings.
|
6249 |
*/
|
6250 |
-
function sucuriscan_explain_monitoring_settings( $settings=array() ){
|
6251 |
-
if( $settings ){
|
6252 |
-
foreach( $settings as $option_name => $option_value ){
|
6253 |
-
switch( $option_name ){
|
6254 |
case 'security_level':
|
6255 |
-
$new_value = ucwords($option_value);
|
6256 |
break;
|
6257 |
case 'proxy_active':
|
6258 |
$new_value = ( $option_value == 1 ) ? 'Active' : 'not active';
|
6259 |
break;
|
6260 |
case 'cache_mode':
|
6261 |
-
$new_value = sucuriscan_cache_mode_title($option_value);
|
6262 |
break;
|
6263 |
}
|
6264 |
|
6265 |
-
if( isset($new_value) ){
|
6266 |
$settings->{$option_name} = $new_value;
|
6267 |
}
|
6268 |
}
|
@@ -6270,7 +6889,7 @@ function sucuriscan_explain_monitoring_settings( $settings=array() ){
|
|
6270 |
return $settings;
|
6271 |
}
|
6272 |
|
6273 |
-
return
|
6274 |
}
|
6275 |
|
6276 |
/**
|
@@ -6279,10 +6898,10 @@ function sucuriscan_explain_monitoring_settings( $settings=array() ){
|
|
6279 |
* @param string $mode The value set for the cache settings of the site.
|
6280 |
* @return string Explanation of the meaning of the cache_mode value.
|
6281 |
*/
|
6282 |
-
function sucuriscan_cache_mode_title( $mode='' ){
|
6283 |
$title = '';
|
6284 |
|
6285 |
-
switch( $mode ){
|
6286 |
case 'docache': $title = 'Enabled (recommended)'; break;
|
6287 |
case 'sitecache': $title = 'Site caching (using your site headers)'; break;
|
6288 |
case 'nocache': $title = 'Minimal (only for a few minutes)'; break;
|
@@ -6299,7 +6918,7 @@ function sucuriscan_cache_mode_title( $mode='' ){
|
|
6299 |
* @param string $api_key The CloudProxy API key.
|
6300 |
* @return string The parsed-content of the monitoring logs panel.
|
6301 |
*/
|
6302 |
-
function sucuriscan_monitoring_logs( $api_key='' ){
|
6303 |
$template_variables = array(
|
6304 |
'AuditLogs.List' => '',
|
6305 |
'AuditLogs.CountText' => 0,
|
@@ -6313,38 +6932,38 @@ function sucuriscan_monitoring_logs( $api_key='' ){
|
|
6313 |
'AuditLogs.DateDays' => '',
|
6314 |
);
|
6315 |
|
6316 |
-
$date = date('Y-m-d');
|
6317 |
|
6318 |
-
if( $api_key ){
|
6319 |
// Retrieve the date filter from the GET request (if any).
|
6320 |
-
if( $date_by_get = SucuriScanRequest::get('date', '_yyyymmdd') ){
|
6321 |
$date = $date_by_get;
|
6322 |
}
|
6323 |
|
6324 |
// Retrieve the date filter from the POST request (if any).
|
6325 |
-
$year = SucuriScanRequest::post(':year');
|
6326 |
-
$month = SucuriScanRequest::post(':month');
|
6327 |
-
$day = SucuriScanRequest::post(':day');
|
6328 |
|
6329 |
-
if( $year && $month && $day ){
|
6330 |
$date = sprintf( '%s-%s-%s', $year, $month, $day );
|
6331 |
}
|
6332 |
|
6333 |
$logs_data = SucuriScanAPI::get_cloudproxy_logs( $api_key, $date );
|
6334 |
|
6335 |
-
if( $logs_data ){
|
6336 |
add_thickbox(); /* Include the Thickbox library. */
|
6337 |
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
|
6338 |
$template_variables['AuditLogs.CountText'] = $logs_data->limit . '/' . $logs_data->total_lines;
|
6339 |
-
$template_variables['AuditLogs.List'] = sucuriscan_monitoring_access_logs($logs_data->access_logs);
|
6340 |
-
$template_variables['AuditLogs.DenialTypeOptions'] = sucuriscan_monitoring_denial_types($logs_data->access_logs);
|
6341 |
}
|
6342 |
}
|
6343 |
|
6344 |
-
$template_variables['AuditLogs.TargetDate'] = SucuriScan::escape($date);
|
6345 |
-
$template_variables['AuditLogs.DateYears'] = sucuriscan_monitoring_dates('years', $date);
|
6346 |
-
$template_variables['AuditLogs.DateMonths'] = sucuriscan_monitoring_dates('months', $date);
|
6347 |
-
$template_variables['AuditLogs.DateDays'] = sucuriscan_monitoring_dates('days', $date);
|
6348 |
|
6349 |
return SucuriScanTemplate::get_section( 'monitoring-logs', $template_variables );
|
6350 |
}
|
@@ -6355,10 +6974,10 @@ function sucuriscan_monitoring_logs( $api_key='' ){
|
|
6355 |
* @param array $access_logs The logs retrieved from the remote API service.
|
6356 |
* @return string The HTML code to show the access-logs in the page as a table.
|
6357 |
*/
|
6358 |
-
function sucuriscan_monitoring_access_logs( $access_logs=array() ){
|
6359 |
$logs_html = '';
|
6360 |
|
6361 |
-
if( $access_logs && !empty($access_logs) ){
|
6362 |
$counter = 0;
|
6363 |
$needed_attrs = array(
|
6364 |
'request_date',
|
@@ -6378,21 +6997,21 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
|
|
6378 |
'http_user_agent',
|
6379 |
);
|
6380 |
|
6381 |
-
$filter_by_denial_type =
|
6382 |
-
$filter_by_keyword =
|
6383 |
-
$filter_query =
|
6384 |
|
6385 |
-
if( $q = SucuriScanRequest::post(':monitoring_denial_type') ){
|
6386 |
-
$filter_by_denial_type =
|
6387 |
$filter_query = $q;
|
6388 |
}
|
6389 |
|
6390 |
-
if( $q = SucuriScanRequest::post(':monitoring_log_filter') ){
|
6391 |
-
$filter_by_keyword =
|
6392 |
$filter_query = $q;
|
6393 |
}
|
6394 |
|
6395 |
-
foreach( $access_logs as $access_log ){
|
6396 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
6397 |
$audit_log_snippet = array(
|
6398 |
'AuditLog.Id' => $counter,
|
@@ -6400,35 +7019,35 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
|
|
6400 |
);
|
6401 |
|
6402 |
// If there is a filter, check the access_log data and break the operation if needed.
|
6403 |
-
if( $filter_query ){
|
6404 |
-
if( $filter_by_denial_type ){
|
6405 |
-
$denial_type_slug = SucuriScan::human2var($access_log->sucuri_block_reason);
|
6406 |
|
6407 |
-
if( $denial_type_slug != $filter_query ){ continue; }
|
6408 |
}
|
6409 |
|
6410 |
-
if(
|
6411 |
$filter_by_keyword
|
6412 |
-
&& strpos($access_log->remote_addr, $filter_query) ===
|
6413 |
-
&& strpos($access_log->resource_path, $filter_query) ===
|
6414 |
){
|
6415 |
continue;
|
6416 |
}
|
6417 |
}
|
6418 |
|
6419 |
// Generate (dynamically) the pseudo-variables for the template.
|
6420 |
-
foreach( $needed_attrs as $attr_name ){
|
6421 |
$attr_value = '';
|
6422 |
|
6423 |
-
$attr_title = str_replace('_', chr(32), $attr_name);
|
6424 |
-
$attr_title = ucwords($attr_title);
|
6425 |
-
$attr_title = str_replace(chr(32), '', $attr_title);
|
6426 |
$attr_title = 'AuditLog.' . $attr_title;
|
6427 |
|
6428 |
-
if( isset($access_log->{$attr_name}) ){
|
6429 |
$attr_value = $access_log->{$attr_name};
|
6430 |
|
6431 |
-
if(
|
6432 |
empty($attr_value)
|
6433 |
&& $attr_name == 'sucuri_block_reason'
|
6434 |
){
|
@@ -6436,14 +7055,14 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
|
|
6436 |
}
|
6437 |
}
|
6438 |
|
6439 |
-
elseif( $attr_name == 'local_request_time' ){
|
6440 |
-
$attr_value = SucuriScan::datetime($access_log->request_timestamp);
|
6441 |
}
|
6442 |
|
6443 |
-
$audit_log_snippet[$attr_title] = SucuriScan::escape($attr_value);
|
6444 |
}
|
6445 |
|
6446 |
-
$logs_html .= SucuriScanTemplate::get_snippet('monitoring-logs', $audit_log_snippet);
|
6447 |
$counter += 1;
|
6448 |
}
|
6449 |
}
|
@@ -6460,31 +7079,31 @@ function sucuriscan_monitoring_access_logs( $access_logs=array() ){
|
|
6460 |
* @param boolean $in_html Whether the list should be converted to a HTML select options or not.
|
6461 |
* @return array Either a list of unique blocking types, or a HTML code.
|
6462 |
*/
|
6463 |
-
function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=
|
6464 |
$types = array();
|
6465 |
|
6466 |
-
if( $access_logs && !empty($access_logs) ){
|
6467 |
-
foreach( $access_logs as $access_log ){
|
6468 |
-
if( !array_key_exists($access_log->sucuri_block_reason, $types) ){
|
6469 |
-
$denial_type_k = SucuriScan::human2var($access_log->sucuri_block_reason);
|
6470 |
$denial_type_v = $access_log->sucuri_block_reason;
|
6471 |
-
if( empty($denial_type_v) ){ $denial_type_v = 'Unknown'; }
|
6472 |
-
$types[$denial_type_k] = $denial_type_v;
|
6473 |
}
|
6474 |
}
|
6475 |
}
|
6476 |
|
6477 |
-
if( $in_html ){
|
6478 |
$html_types = '<option value="">Filter</option>';
|
6479 |
-
$selected = SucuriScanRequest::post(':monitoring_denial_type', '.+');
|
6480 |
|
6481 |
-
foreach( $types as $type_key => $type_value ){
|
6482 |
$selected_tag = ( $type_key === $selected ) ? 'selected="selected"' : '';
|
6483 |
$html_types .= sprintf(
|
6484 |
'<option value="%s" %s>%s</option>',
|
6485 |
-
SucuriScan::escape($type_key),
|
6486 |
$selected_tag,
|
6487 |
-
SucuriScan::escape($type_value)
|
6488 |
);
|
6489 |
}
|
6490 |
|
@@ -6502,11 +7121,11 @@ function sucuriscan_monitoring_denial_types( $access_logs=array(), $in_html=TRUE
|
|
6502 |
* @param boolean $in_html Whether the list should be converted to a HTML select options or not.
|
6503 |
* @return array Either an array with the expected values, or a HTML code.
|
6504 |
*/
|
6505 |
-
function sucuriscan_monitoring_dates( $type='', $date='', $in_html=
|
6506 |
$options = array();
|
6507 |
$selected = '';
|
6508 |
|
6509 |
-
if( preg_match('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date, $date_m) ){
|
6510 |
$s_year = $date_m[1];
|
6511 |
$s_month = $date_m[2];
|
6512 |
$s_day = $date_m[3];
|
@@ -6516,10 +7135,10 @@ function sucuriscan_monitoring_dates( $type='', $date='', $in_html=TRUE ){
|
|
6516 |
$s_day = '';
|
6517 |
}
|
6518 |
|
6519 |
-
switch( $type ){
|
6520 |
case 'years':
|
6521 |
$selected = $s_year;
|
6522 |
-
$current_year = (int) date('Y');
|
6523 |
$max_years = 5; /* Maximum number of years to keep the logs. */
|
6524 |
$options = range( ($current_year - $max_years), $current_year );
|
6525 |
break;
|
@@ -6537,22 +7156,22 @@ function sucuriscan_monitoring_dates( $type='', $date='', $in_html=TRUE ){
|
|
6537 |
'09' => 'September',
|
6538 |
'10' => 'October',
|
6539 |
'11' => 'November',
|
6540 |
-
'12' => 'December'
|
6541 |
);
|
6542 |
break;
|
6543 |
case 'days':
|
6544 |
-
$options = range(1, 31);
|
6545 |
$selected = $s_day;
|
6546 |
break;
|
6547 |
}
|
6548 |
|
6549 |
-
if( $in_html ){
|
6550 |
$html_options = '';
|
6551 |
|
6552 |
-
foreach( $options as $key => $value ){
|
6553 |
-
if( is_numeric($value) ){ $value = str_pad($value, 2, 0, STR_PAD_LEFT); }
|
6554 |
|
6555 |
-
if( $type != 'months' ){ $key = $value; }
|
6556 |
|
6557 |
$selected_tag = ( $key == $selected ) ? 'selected="selected"' : '';
|
6558 |
$html_options .= sprintf( '<option value="%s" %s>%s</option>', $key, $selected_tag, $value );
|
@@ -6575,9 +7194,9 @@ function sucuriscan_monitoring_dates( $type='', $date='', $in_html=TRUE ){
|
|
6575 |
function sucuriscan_hardening_page(){
|
6576 |
SucuriScanInterface::check_permissions();
|
6577 |
|
6578 |
-
if(
|
6579 |
-
SucuriScanRequest::post(':run_hardening')
|
6580 |
-
&& !SucuriScanInterface::check_nonce()
|
6581 |
){
|
6582 |
unset($_POST['sucuriscan_run_hardening']);
|
6583 |
}
|
@@ -6594,9 +7213,17 @@ function sucuriscan_hardening_page(){
|
|
6594 |
sucuriscan_harden_version();
|
6595 |
sucuriscan_cloudproxy_enabled();
|
6596 |
sucuriscan_harden_removegenerator();
|
6597 |
-
|
6598 |
-
|
6599 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6600 |
sucuriscan_harden_phpversion();
|
6601 |
sucuriscan_harden_secretkeys();
|
6602 |
sucuriscan_harden_readme();
|
@@ -6614,7 +7241,7 @@ function sucuriscan_hardening_page(){
|
|
6614 |
echo SucuriScanTemplate::get_base_template($_html, array(
|
6615 |
'PageTitle' => 'Hardening',
|
6616 |
'PageContent' => $_html,
|
6617 |
-
'PageStyleClass' => 'hardening'
|
6618 |
));
|
6619 |
return;
|
6620 |
}
|
@@ -6633,35 +7260,39 @@ function sucuriscan_hardening_page(){
|
|
6633 |
* @param string $updatemsg Optional explanation of the hardening after the submission of the form.
|
6634 |
* @return void
|
6635 |
*/
|
6636 |
-
function sucuriscan_harden_status( $title='', $status=0, $type='', $messageok='', $messagewarn='', $desc=
|
6637 |
<div class="postbox">
|
6638 |
-
<h3><?php _e($title) ?></h3>
|
6639 |
|
6640 |
<div class="inside">
|
6641 |
-
<?php if( $desc !=
|
6642 |
-
<p><?php _e($desc) ?></p>
|
6643 |
<?php endif; ?>
|
6644 |
|
6645 |
-
|
6646 |
-
|
6647 |
-
<?php if( $
|
6648 |
-
|
6649 |
-
|
6650 |
-
|
|
|
|
|
6651 |
<?php endif; ?>
|
6652 |
-
<?php endif; ?>
|
6653 |
|
6654 |
-
|
6655 |
-
|
6656 |
-
|
6657 |
-
|
6658 |
-
|
6659 |
-
|
6660 |
-
|
6661 |
-
|
|
|
|
|
|
|
6662 |
|
6663 |
-
<?php if( $updatemsg !=
|
6664 |
-
<p><?php _e($updatemsg) ?></p>
|
6665 |
<?php endif; ?>
|
6666 |
</div>
|
6667 |
</div>
|
@@ -6677,10 +7308,10 @@ function sucuriscan_harden_status( $title='', $status=0, $type='', $messageok=''
|
|
6677 |
function sucuriscan_harden_version(){
|
6678 |
$site_version = SucuriScan::site_version();
|
6679 |
$updates = get_core_updates();
|
6680 |
-
$cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
|
6681 |
|
6682 |
-
if( isset($updates[0]) && $updates[0] instanceof stdClass ){
|
6683 |
-
if(
|
6684 |
$updates[0]->response == 'latest'
|
6685 |
|| $updates[0]->response == 'development'
|
6686 |
){
|
@@ -6688,7 +7319,7 @@ function sucuriscan_harden_version(){
|
|
6688 |
}
|
6689 |
}
|
6690 |
|
6691 |
-
if( strcmp($site_version, '3.7') < 0 ){
|
6692 |
$cp = 0;
|
6693 |
}
|
6694 |
|
@@ -6697,14 +7328,14 @@ function sucuriscan_harden_version(){
|
|
6697 |
to the source code are made public, if there were security fixes then
|
6698 |
someone with malicious intent can use this information to attack any site
|
6699 |
that has not been upgraded.';
|
6700 |
-
$messageok = sprintf('Your WordPress installation (%s) is current.', $site_version);
|
6701 |
$messagewarn = sprintf(
|
6702 |
'Your current version (%s) is not current.<br>
|
6703 |
<a href="update-core.php" class="button-primary">Update now!</a>',
|
6704 |
$site_version
|
6705 |
);
|
6706 |
|
6707 |
-
sucuriscan_harden_status( 'Verify WordPress version', $cp,
|
6708 |
}
|
6709 |
|
6710 |
/**
|
@@ -6718,14 +7349,46 @@ function sucuriscan_harden_removegenerator(){
|
|
6718 |
sucuriscan_harden_status(
|
6719 |
'Remove WordPress version',
|
6720 |
1,
|
6721 |
-
|
6722 |
'WordPress version properly hidden',
|
6723 |
-
|
6724 |
'It checks if your WordPress version is being hidden from being displayed '
|
6725 |
.'in the generator tag (enabled by default with this plugin).'
|
6726 |
);
|
6727 |
}
|
6728 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6729 |
/**
|
6730 |
* Check whether the WordPress upload folder is protected or not.
|
6731 |
*
|
@@ -6737,47 +7400,50 @@ function sucuriscan_harden_removegenerator(){
|
|
6737 |
*/
|
6738 |
function sucuriscan_harden_upload(){
|
6739 |
$cp = 1;
|
6740 |
-
$upmsg = NULL;
|
6741 |
$datastore_path = SucuriScan::datastore_folder_path();
|
6742 |
-
$htaccess_upload = dirname($datastore_path) . '/.htaccess';
|
6743 |
|
6744 |
-
if( !is_readable($htaccess_upload) ){
|
6745 |
$cp = 0;
|
6746 |
} else {
|
6747 |
$cp = 0;
|
6748 |
-
$fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
|
6749 |
|
6750 |
-
foreach( $fcontent as $fline ){
|
6751 |
-
if( stripos($fline, 'deny from all') !==
|
6752 |
$cp = 1;
|
6753 |
break;
|
6754 |
}
|
6755 |
}
|
6756 |
}
|
6757 |
|
6758 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
6759 |
-
if( SucuriScanRequest::post(':harden_upload') && $cp == 0 ){
|
6760 |
-
if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") ===
|
6761 |
-
|
6762 |
} else {
|
6763 |
-
$upmsg = SucuriScanInterface::info('Hardening applied successfully to upload directory');
|
6764 |
$cp = 1;
|
|
|
|
|
|
|
6765 |
}
|
6766 |
}
|
6767 |
|
6768 |
-
elseif( SucuriScanRequest::post(':harden_upload_unharden') ){
|
6769 |
-
$htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ?
|
6770 |
-
$htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
|
6771 |
|
6772 |
-
if( $htaccess_upload_writable ){
|
6773 |
$cp = 0;
|
6774 |
|
6775 |
-
if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
|
6776 |
-
$htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
|
6777 |
-
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
|
6778 |
}
|
6779 |
|
6780 |
-
|
|
|
|
|
6781 |
} else {
|
6782 |
SucuriScanInterface::error(
|
6783 |
'File <code>/wp-content/uploads/.htaccess</code> does not exists or
|
@@ -6795,7 +7461,7 @@ function sucuriscan_harden_upload(){
|
|
6795 |
'Upload directory properly hardened',
|
6796 |
'Upload directory not hardened',
|
6797 |
'It checks if your upload directory allows PHP execution or if it is browsable.',
|
6798 |
-
|
6799 |
);
|
6800 |
}
|
6801 |
|
@@ -6810,46 +7476,49 @@ function sucuriscan_harden_upload(){
|
|
6810 |
*/
|
6811 |
function sucuriscan_harden_wpcontent(){
|
6812 |
$cp = 1;
|
6813 |
-
$upmsg = NULL;
|
6814 |
$htaccess_upload = WP_CONTENT_DIR . '/.htaccess';
|
6815 |
|
6816 |
-
if( !is_readable($htaccess_upload) ){
|
6817 |
$cp = 0;
|
6818 |
} else {
|
6819 |
$cp = 0;
|
6820 |
-
$fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
|
6821 |
|
6822 |
-
foreach( $fcontent as $fline ){
|
6823 |
-
if( stripos($fline, 'deny from all') !==
|
6824 |
$cp = 1;
|
6825 |
break;
|
6826 |
}
|
6827 |
}
|
6828 |
}
|
6829 |
|
6830 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
6831 |
-
if( SucuriScanRequest::post(':harden_wpcontent') && $cp == 0 ){
|
6832 |
-
if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>") ===
|
6833 |
-
|
6834 |
} else {
|
6835 |
-
$upmsg = SucuriScanInterface::info('Hardening applied successfully to content directory.');
|
6836 |
$cp = 1;
|
|
|
|
|
|
|
6837 |
}
|
6838 |
}
|
6839 |
|
6840 |
-
elseif( SucuriScanRequest::post(':harden_wpcontent_unharden') ){
|
6841 |
-
$htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ?
|
6842 |
-
$htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
|
6843 |
|
6844 |
-
if( $htaccess_upload_writable ){
|
6845 |
$cp = 0;
|
6846 |
|
6847 |
-
if( preg_match('/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match) ){
|
6848 |
-
$htaccess_content = str_replace("<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content);
|
6849 |
-
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
|
6850 |
}
|
6851 |
|
6852 |
-
|
|
|
|
|
6853 |
} else {
|
6854 |
SucuriScanInterface::info(
|
6855 |
'File <code>' . WP_CONTENT_DIR . '/.htaccess</code> does not exists or is not
|
@@ -6874,7 +7543,7 @@ function sucuriscan_harden_wpcontent(){
|
|
6874 |
'WP-content directory properly hardened',
|
6875 |
'WP-content directory not hardened',
|
6876 |
$description,
|
6877 |
-
|
6878 |
);
|
6879 |
}
|
6880 |
|
@@ -6890,47 +7559,63 @@ function sucuriscan_harden_wpcontent(){
|
|
6890 |
*/
|
6891 |
function sucuriscan_harden_wpincludes(){
|
6892 |
$cp = 1;
|
6893 |
-
$upmsg = NULL;
|
6894 |
$htaccess_upload = ABSPATH . '/wp-includes/.htaccess';
|
6895 |
|
6896 |
-
if( !is_readable($htaccess_upload) ){
|
6897 |
$cp = 0;
|
6898 |
} else {
|
6899 |
$cp = 0;
|
6900 |
-
$fcontent = SucuriScanFileInfo::file_lines($htaccess_upload);
|
6901 |
|
6902 |
-
foreach( $fcontent as $fline ){
|
6903 |
-
if( stripos($fline, 'deny from all') !==
|
6904 |
$cp = 1;
|
6905 |
break;
|
6906 |
}
|
6907 |
}
|
6908 |
}
|
6909 |
|
6910 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
6911 |
-
if( SucuriScanRequest::post(':harden_wpincludes') && $cp == 0 ){
|
6912 |
-
|
6913 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6914 |
} else {
|
6915 |
-
$upmsg = SucuriScanInterface::info('Hardening applied successfully to library\'s directory.');
|
6916 |
$cp = 1;
|
|
|
|
|
|
|
6917 |
}
|
6918 |
}
|
6919 |
|
6920 |
-
elseif( SucuriScanRequest::post(':harden_wpincludes_unharden') ){
|
6921 |
-
$htaccess_upload_writable = ( file_exists($htaccess_upload) && is_writable($htaccess_upload) ) ?
|
6922 |
-
$htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
|
6923 |
|
6924 |
-
if( $htaccess_upload_writable ){
|
6925 |
$cp = 0;
|
6926 |
-
|
6927 |
-
|
6928 |
-
|
|
|
6929 |
}
|
6930 |
|
6931 |
-
@file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
|
6932 |
}
|
6933 |
-
|
|
|
|
|
|
|
6934 |
} else {
|
6935 |
SucuriScanInterface::error(
|
6936 |
'File <code>wp-includes/.htaccess</code> does not exists or is not
|
@@ -6948,7 +7633,7 @@ function sucuriscan_harden_wpincludes(){
|
|
6948 |
'WP-Includes directory properly hardened',
|
6949 |
'WP-Includes directory not hardened',
|
6950 |
'This option blocks direct PHP access to any file inside <code>wp-includes</code>.',
|
6951 |
-
|
6952 |
);
|
6953 |
}
|
6954 |
|
@@ -6960,16 +7645,16 @@ function sucuriscan_harden_wpincludes(){
|
|
6960 |
*/
|
6961 |
function sucuriscan_harden_phpversion(){
|
6962 |
$phpv = phpversion();
|
6963 |
-
$cp = ( strncmp($phpv, '5.', 2) < 0 ) ? 0 : 1;
|
6964 |
|
6965 |
sucuriscan_harden_status(
|
6966 |
'Verify PHP version',
|
6967 |
$cp,
|
6968 |
-
|
6969 |
'Using an updated version of PHP (' . $phpv . ')',
|
6970 |
'The version of PHP you are using (' . $phpv . ') is not current, not recommended, and/or not supported',
|
6971 |
'This checks if you have the latest version of PHP installed.',
|
6972 |
-
|
6973 |
);
|
6974 |
}
|
6975 |
|
@@ -6987,19 +7672,19 @@ function sucuriscan_cloudproxy_enabled(){
|
|
6987 |
. 'DDoS, SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is '
|
6988 |
. 'using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site.';
|
6989 |
|
6990 |
-
if( $proxy_info ===
|
6991 |
$status = 0;
|
6992 |
-
$btn_string = '<a href="http://
|
6993 |
}
|
6994 |
|
6995 |
sucuriscan_harden_status(
|
6996 |
'Website Firewall protection',
|
6997 |
$status,
|
6998 |
-
|
6999 |
'Your website is protected by a Website Firewall (WAF)',
|
7000 |
$btn_string . 'Your website is not protected by a Website Firewall (WAF)',
|
7001 |
$description,
|
7002 |
-
|
7003 |
);
|
7004 |
}
|
7005 |
|
@@ -7017,17 +7702,17 @@ function sucuriscan_harden_secretkeys(){
|
|
7017 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
7018 |
$current_keys = SucuriScanOption::get_security_keys();
|
7019 |
|
7020 |
-
if( $wp_config_path ){
|
7021 |
$cp = 1;
|
7022 |
$message = 'The main configuration file was found at: <code>'.$wp_config_path.'</code><br>';
|
7023 |
|
7024 |
-
if(
|
7025 |
-
!empty($current_keys['bad'])
|
7026 |
-
|| !empty($current_keys['missing'])
|
7027 |
){
|
7028 |
$cp = 0;
|
7029 |
}
|
7030 |
-
}else{
|
7031 |
$cp = 0;
|
7032 |
$message = 'The <code>wp-config.php</code> file was not found.<br>';
|
7033 |
}
|
@@ -7038,18 +7723,18 @@ function sucuriscan_harden_secretkeys(){
|
|
7038 |
random elements to the password. In simple terms, a secret key is a password with
|
7039 |
elements that make it harder to generate enough options to break through your
|
7040 |
security barriers.';
|
7041 |
-
$messageok = 'Security keys and salts not set, we recommend
|
7042 |
-
. '<a href="' . SucuriScanTemplate::get_url('posthack') . '" class="button button-primary">'
|
7043 |
. 'Harden</a>';
|
7044 |
|
7045 |
sucuriscan_harden_status(
|
7046 |
'Security keys',
|
7047 |
$cp,
|
7048 |
-
|
7049 |
'Security keys and salts properly created',
|
7050 |
$messageok,
|
7051 |
$message,
|
7052 |
-
|
7053 |
);
|
7054 |
}
|
7055 |
|
@@ -7061,29 +7746,31 @@ function sucuriscan_harden_secretkeys(){
|
|
7061 |
* @return void
|
7062 |
*/
|
7063 |
function sucuriscan_harden_readme(){
|
7064 |
-
$upmsg =
|
7065 |
-
$cp = is_readable(ABSPATH.'/readme.html') ? 0 : 1;
|
7066 |
|
7067 |
// TODO: After hardening create an option to automatically remove this after WP upgrade.
|
7068 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
7069 |
-
if( SucuriScanRequest::post(':harden_readme') && $cp == 0 ){
|
7070 |
-
if( @unlink(ABSPATH.'/readme.html') ===
|
7071 |
-
$upmsg = SucuriScanInterface::error('Unable to remove <code>readme.html</code> file.');
|
7072 |
} else {
|
7073 |
$cp = 1;
|
7074 |
-
$
|
|
|
|
|
7075 |
}
|
7076 |
}
|
7077 |
|
7078 |
-
elseif( SucuriScanRequest::post(':harden_readme_unharden') ){
|
7079 |
-
SucuriScanInterface::error('We can not revert this action, you must create the <code>readme.html</code>
|
7080 |
}
|
7081 |
}
|
7082 |
|
7083 |
sucuriscan_harden_status(
|
7084 |
'Information leakage (readme.html)',
|
7085 |
$cp,
|
7086 |
-
( $cp == 0 ? 'sucuriscan_harden_readme' :
|
7087 |
'<code>readme.html</code> file properly deleted',
|
7088 |
'<code>readme.html</code> not deleted and leaking the WordPress version',
|
7089 |
'It checks whether you have the <code>readme.html</code> file available that leaks your WordPress version',
|
@@ -7100,18 +7787,18 @@ function sucuriscan_harden_readme(){
|
|
7100 |
function sucuriscan_harden_adminuser(){
|
7101 |
global $wpdb;
|
7102 |
|
7103 |
-
$upmsg =
|
7104 |
$user_query = new WP_User_Query(array(
|
7105 |
'search' => 'admin',
|
7106 |
'fields' => array( 'ID', 'user_login' ),
|
7107 |
'search_columns' => array( 'user_login' ),
|
7108 |
));
|
7109 |
$results = $user_query->get_results();
|
7110 |
-
$account_removed = ( count($results) === 0 ? 1 : 0 );
|
7111 |
|
7112 |
-
if( $account_removed === 0 ){
|
7113 |
$upmsg = '<i><strong>Notice.</strong> We do not offer an option to automatically change the user name.
|
7114 |
-
Go to the <a href="'.admin_url('users.php').'" target="_blank">user list</a> and create a new
|
7115 |
administrator user. Once created, log in as that user and remove the default <code>admin</code>
|
7116 |
(make sure to assign all the admin posts to the new user too).</i>';
|
7117 |
}
|
@@ -7119,7 +7806,7 @@ function sucuriscan_harden_adminuser(){
|
|
7119 |
sucuriscan_harden_status(
|
7120 |
'Default admin account',
|
7121 |
$account_removed,
|
7122 |
-
|
7123 |
'Default admin user account (admin) not being used',
|
7124 |
'Default admin user account (admin) being used. Not recommended',
|
7125 |
'It checks whether you have the default <code>admin</code> account enabled, security guidelines recommend creating a new admin user name.',
|
@@ -7133,25 +7820,27 @@ function sucuriscan_harden_adminuser(){
|
|
7133 |
* @return void
|
7134 |
*/
|
7135 |
function sucuriscan_harden_fileeditor(){
|
7136 |
-
$file_editor_disabled = defined('DISALLOW_FILE_EDIT') ? DISALLOW_FILE_EDIT :
|
7137 |
|
7138 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
7139 |
-
$current_time = date('r');
|
7140 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
7141 |
|
7142 |
-
$wp_config_writable = ( file_exists($wp_config_path) && is_writable($wp_config_path) ) ?
|
7143 |
-
$new_wpconfig = $wp_config_writable ? file_get_contents($wp_config_path) : '';
|
7144 |
|
7145 |
-
if( SucuriScanRequest::post(':harden_fileeditor') ){
|
7146 |
-
if( $wp_config_writable ){
|
7147 |
-
if( preg_match('/(.*define\(.DB_COLLATE..*)/', $new_wpconfig, $match) ){
|
7148 |
$disallow_fileedit_definition = "\n\ndefine('DISALLOW_FILE_EDIT', TRUE); // Sucuri Security: {$current_time}\n";
|
7149 |
-
$new_wpconfig = str_replace($match[0], $match[0].$disallow_fileedit_definition, $new_wpconfig);
|
7150 |
}
|
7151 |
|
7152 |
-
|
7153 |
-
|
7154 |
-
$
|
|
|
|
|
7155 |
} else {
|
7156 |
SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
|
7157 |
or is not writable, you will need to put the following code manually there:
|
@@ -7159,13 +7848,15 @@ function sucuriscan_harden_fileeditor(){
|
|
7159 |
}
|
7160 |
}
|
7161 |
|
7162 |
-
elseif( SucuriScanRequest::post(':harden_fileeditor_unharden') ){
|
7163 |
-
if( preg_match("/(.*define\('DISALLOW_FILE_EDIT', TRUE\);.*)/", $new_wpconfig, $match) ){
|
7164 |
-
if( $wp_config_writable ){
|
7165 |
-
$new_wpconfig = str_replace("\n{$match[1]}", '', $new_wpconfig);
|
7166 |
-
file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
|
7167 |
-
|
7168 |
-
$
|
|
|
|
|
7169 |
} else {
|
7170 |
SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
|
7171 |
or is not writable, you will need to remove the following code manually from there:
|
@@ -7184,12 +7875,12 @@ function sucuriscan_harden_fileeditor(){
|
|
7184 |
|
7185 |
sucuriscan_harden_status(
|
7186 |
'Plugin & Theme editor',
|
7187 |
-
( $file_editor_disabled ===
|
7188 |
'sucuriscan_harden_fileeditor',
|
7189 |
'File editor for Plugins and Themes is disabled',
|
7190 |
'File editor for Plugins and Themes is enabled',
|
7191 |
$message,
|
7192 |
-
|
7193 |
);
|
7194 |
}
|
7195 |
|
@@ -7208,7 +7899,7 @@ function sucuriscan_harden_dbtables(){
|
|
7208 |
sucuriscan_harden_status(
|
7209 |
'Database table prefix',
|
7210 |
$hardened,
|
7211 |
-
|
7212 |
'Database table prefix properly modified',
|
7213 |
'Database table set to the default value <code>wp_</code>.',
|
7214 |
'It checks whether your database table prefix has been changed from the default <code>wp_</code>',
|
@@ -7223,8 +7914,8 @@ function sucuriscan_harden_dbtables(){
|
|
7223 |
*/
|
7224 |
function sucuriscan_harden_errorlog(){
|
7225 |
$hardened = 1;
|
7226 |
-
$log_filename = SucuriScan::ini_get('error_log');
|
7227 |
-
$scan_errorlogs = SucuriScanOption::get_option(':scan_errorlogs');
|
7228 |
|
7229 |
$description = 'PHP uses files named as <code>' . $log_filename . '</code> to log errors found in '
|
7230 |
. 'the code, these files may leak sensitive information of your project allowing an attacker '
|
@@ -7232,12 +7923,12 @@ function sucuriscan_harden_errorlog(){
|
|
7232 |
. 'a development environment, and remove them in production mode.';
|
7233 |
|
7234 |
// Search error log files in the project.
|
7235 |
-
if( $scan_errorlogs != 'disabled' ){
|
7236 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
7237 |
-
$sucuri_fileinfo->ignore_files =
|
7238 |
-
$sucuri_fileinfo->ignore_directories =
|
7239 |
-
$error_logs = $sucuri_fileinfo->find_file('error_log');
|
7240 |
-
$total_log_files = count($error_logs);
|
7241 |
} else {
|
7242 |
$error_logs = array();
|
7243 |
$total_log_files = 0;
|
@@ -7248,27 +7939,31 @@ function sucuriscan_harden_errorlog(){
|
|
7248 |
}
|
7249 |
|
7250 |
// Remove every error log file found in the filesystem scan.
|
7251 |
-
if( SucuriScanRequest::post(':run_hardening') ){
|
7252 |
-
if( SucuriScanRequest::post(':harden_errorlog') ){
|
7253 |
$removed_logs = 0;
|
|
|
|
|
|
|
|
|
7254 |
|
7255 |
-
foreach( $error_logs as $i => $error_log_path ){
|
7256 |
-
if( unlink($error_log_path) ){
|
7257 |
-
unset($error_logs[$i]);
|
7258 |
$removed_logs += 1;
|
7259 |
}
|
7260 |
}
|
7261 |
|
7262 |
-
SucuriScanInterface::info( 'Error log files
|
7263 |
}
|
7264 |
}
|
7265 |
|
7266 |
// List the error log files in a HTML table.
|
7267 |
-
if( !empty($error_logs) ){
|
7268 |
$hardened = 0;
|
7269 |
$description .= '</p><ul class="sucuriscan-list-as-table">';
|
7270 |
|
7271 |
-
foreach( $error_logs as $error_log_path ){
|
7272 |
$description .= '<li>' . $error_log_path . '</li>';
|
7273 |
}
|
7274 |
|
@@ -7278,11 +7973,11 @@ function sucuriscan_harden_errorlog(){
|
|
7278 |
sucuriscan_harden_status(
|
7279 |
'Error logs',
|
7280 |
$hardened,
|
7281 |
-
( $hardened == 0 ? 'sucuriscan_harden_errorlog' :
|
7282 |
'There are no error log files in your project.',
|
7283 |
'There are ' . $total_log_files . ' error log files in your project.',
|
7284 |
$description,
|
7285 |
-
|
7286 |
);
|
7287 |
}
|
7288 |
|
@@ -7304,10 +7999,11 @@ function sucuriscan_page(){
|
|
7304 |
$template_variables = array(
|
7305 |
'WordpressVersion' => sucuriscan_wordpress_outdated(),
|
7306 |
'AuditLogs' => sucuriscan_auditlogs(),
|
|
|
7307 |
'CoreFiles' => sucuriscan_core_files(),
|
7308 |
);
|
7309 |
|
7310 |
-
echo SucuriScanTemplate::get_template('integrity', $template_variables);
|
7311 |
}
|
7312 |
|
7313 |
/**
|
@@ -7318,44 +8014,52 @@ function sucuriscan_page(){
|
|
7318 |
* @return void
|
7319 |
*/
|
7320 |
function sucuriscan_integrity_form_submissions(){
|
7321 |
-
if( SucuriScanInterface::check_nonce() ){
|
7322 |
|
7323 |
// Force the execution of the filesystem scanner.
|
7324 |
-
if( SucuriScanRequest::post(':force_scan') !== false ){
|
7325 |
-
SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scan forced at: ' . date('r') );
|
7326 |
-
SucuriScanEvent::filesystem_scan(
|
7327 |
}
|
7328 |
|
7329 |
// Restore, Remove, Mark as fixed the core files.
|
7330 |
-
$allowed_actions = '(restore|
|
7331 |
-
$integrity_action = SucuriScanRequest::post(':integrity_action', $allowed_actions);
|
7332 |
-
|
7333 |
-
if( $integrity_action !==
|
7334 |
-
$cache = new SucuriScanCache('integrity');
|
7335 |
-
$integrity_files = SucuriScanRequest::post(':integrity_files', '_array');
|
7336 |
-
$integrity_types = SucuriScanRequest::post(':integrity_types', '_array');
|
7337 |
-
$files_selected = count($integrity_files);
|
|
|
7338 |
$files_processed = 0;
|
|
|
|
|
|
|
|
|
|
|
7339 |
|
7340 |
-
foreach( $integrity_files as $i => $file_path ){
|
7341 |
$full_path = ABSPATH . $file_path;
|
7342 |
-
$status_type = $integrity_types[$i];
|
7343 |
|
7344 |
-
switch( $integrity_action ){
|
7345 |
case 'restore':
|
7346 |
-
$file_content = SucuriScanAPI::get_original_core_file($file_path);
|
7347 |
-
if( $file_content ){
|
7348 |
$restored = @file_put_contents( $full_path, $file_content, LOCK_EX );
|
7349 |
$files_processed += ( $restored ? 1 : 0 );
|
|
|
7350 |
}
|
7351 |
break;
|
7352 |
-
case '
|
7353 |
-
if( @unlink($full_path) ){
|
7354 |
$files_processed += 1;
|
|
|
7355 |
}
|
7356 |
break;
|
7357 |
case 'fixed':
|
7358 |
-
$cache_key = md5($file_path);
|
7359 |
$cache_value = array(
|
7360 |
'file_path' => $file_path,
|
7361 |
'file_status' => $status_type,
|
@@ -7363,17 +8067,35 @@ function sucuriscan_integrity_form_submissions(){
|
|
7363 |
);
|
7364 |
$cached = $cache->add( $cache_key, $cache_value );
|
7365 |
$files_processed += ( $cached ? 1 : 0 );
|
|
|
7366 |
break;
|
7367 |
}
|
7368 |
}
|
7369 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7370 |
SucuriScanInterface::info(sprintf(
|
7371 |
'<code>%d</code> out of <code>%d</code> files were successfully processed.',
|
7372 |
$files_selected,
|
7373 |
$files_processed
|
7374 |
));
|
7375 |
}
|
7376 |
-
|
7377 |
}
|
7378 |
}
|
7379 |
|
@@ -7385,17 +8107,17 @@ function sucuriscan_integrity_form_submissions(){
|
|
7385 |
* @param boolean $recursive Either TRUE or FALSE if the scan should be performed recursively.
|
7386 |
* @return array List of arrays containing the md5sum and last modification time of the files found.
|
7387 |
*/
|
7388 |
-
function sucuriscan_get_integrity_tree( $dir='./', $recursive=
|
7389 |
$abs_path = rtrim( ABSPATH, '/' );
|
7390 |
|
7391 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
7392 |
-
$sucuri_fileinfo->ignore_files =
|
7393 |
-
$sucuri_fileinfo->ignore_directories =
|
7394 |
$sucuri_fileinfo->run_recursively = $recursive;
|
7395 |
$sucuri_fileinfo->scan_interface = 'opendir';
|
7396 |
-
$integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir,
|
7397 |
|
7398 |
-
if(
|
7399 |
$integrity_tree = array();
|
7400 |
}
|
7401 |
|
@@ -7409,12 +8131,11 @@ function sucuriscan_get_integrity_tree( $dir='./', $recursive=FALSE ){
|
|
7409 |
* @return void
|
7410 |
*/
|
7411 |
function sucuriscan_auditlogs(){
|
7412 |
-
|
7413 |
// Initialize the values for the pagination.
|
7414 |
$max_per_page = SUCURISCAN_AUDITLOGS_PER_PAGE;
|
7415 |
$page_number = SucuriScanTemplate::get_page_number();
|
7416 |
$logs_limit = $page_number * $max_per_page;
|
7417 |
-
$audit_logs = SucuriScanAPI::get_logs($logs_limit);
|
7418 |
|
7419 |
$template_variables = array(
|
7420 |
'PageTitle' => 'Audit Logs',
|
@@ -7426,41 +8147,42 @@ function sucuriscan_auditlogs(){
|
|
7426 |
'AuditLogs.PaginationLinks' => '',
|
7427 |
);
|
7428 |
|
7429 |
-
if( $audit_logs ){
|
7430 |
$counter_i = 0;
|
7431 |
-
$total_items = count($audit_logs->output_data);
|
7432 |
-
$
|
|
|
7433 |
|
7434 |
-
|
7435 |
-
|
7436 |
-
}
|
7437 |
-
|
7438 |
-
for( $i=$offset; $i<$total_items; $i++ ){
|
7439 |
-
if( $counter_i > $max_per_page ){ break; }
|
7440 |
|
7441 |
-
if( isset($audit_logs->output_data[$i]) ){
|
7442 |
-
$audit_log = $audit_logs->output_data[$i];
|
7443 |
|
7444 |
$css_class = ( $counter_i % 2 == 0 ) ? '' : 'alternate';
|
7445 |
$snippet_data = array(
|
7446 |
'AuditLog.CssClass' => $css_class,
|
7447 |
-
'AuditLog.
|
7448 |
-
'AuditLog.
|
7449 |
-
'AuditLog.
|
|
|
|
|
|
|
|
|
7450 |
'AuditLog.Extra' => '',
|
7451 |
);
|
7452 |
|
7453 |
-
// Print every
|
7454 |
-
if( $audit_log['
|
7455 |
-
$css_scrollable = $audit_log['
|
7456 |
$snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
|
7457 |
-
foreach( $audit_log['
|
7458 |
-
$snippet_data['AuditLog.Extra'] .= '<li>' . SucuriScan::escape($log_extra) . '</li>';
|
7459 |
}
|
7460 |
$snippet_data['AuditLog.Extra'] .= '</ul>';
|
7461 |
}
|
7462 |
|
7463 |
-
$template_variables['AuditLogs.List'] .= SucuriScanTemplate::get_snippet('integrity-auditlogs', $snippet_data);
|
7464 |
$counter_i += 1;
|
7465 |
}
|
7466 |
}
|
@@ -7468,10 +8190,10 @@ function sucuriscan_auditlogs(){
|
|
7468 |
$template_variables['AuditLogs.Count'] = $counter_i;
|
7469 |
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
|
7470 |
|
7471 |
-
if( $total_items > 1 ){
|
7472 |
$max_pages = ceil( $audit_logs->total_entries / $max_per_page );
|
7473 |
|
7474 |
-
if( $max_pages > SUCURISCAN_MAX_PAGINATION_BUTTONS ){
|
7475 |
$max_pages = SUCURISCAN_MAX_PAGINATION_BUTTONS;
|
7476 |
}
|
7477 |
|
@@ -7486,7 +8208,71 @@ function sucuriscan_auditlogs(){
|
|
7486 |
}
|
7487 |
}
|
7488 |
|
7489 |
-
return SucuriScanTemplate::get_section('integrity-auditlogs', $template_variables);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7490 |
}
|
7491 |
|
7492 |
/**
|
@@ -7497,24 +8283,24 @@ function sucuriscan_auditlogs(){
|
|
7497 |
function sucuriscan_wordpress_outdated(){
|
7498 |
$site_version = SucuriScan::site_version();
|
7499 |
$updates = get_core_updates();
|
7500 |
-
$cp = ( !is_array($updates) || empty($updates) ? 1 : 0 );
|
7501 |
|
7502 |
$template_variables = array(
|
7503 |
'WordPress.Version' => $site_version,
|
7504 |
-
'WordPress.UpgradeURL' => admin_url('update-core.php'),
|
7505 |
'WordPress.UpdateVisibility' => 'hidden',
|
7506 |
'WordPressBeta.Visibility' => 'hidden',
|
7507 |
'WordPressBeta.Version' => '0.0.0',
|
7508 |
-
'WordPressBeta.UpdateURL' => admin_url('update-core.php'),
|
7509 |
'WordPressBeta.DownloadURL' => '#',
|
7510 |
);
|
7511 |
|
7512 |
-
if( isset($updates[0]) && $updates[0] instanceof stdClass ){
|
7513 |
-
if( $updates[0]->response == 'latest' ){
|
7514 |
$cp = 1;
|
7515 |
}
|
7516 |
|
7517 |
-
elseif( $updates[0]->response == 'development' ){
|
7518 |
$cp = 1;
|
7519 |
$template_variables['WordPressBeta.Visibility'] = 'visible';
|
7520 |
$template_variables['WordPressBeta.Version'] = $updates[0]->version;
|
@@ -7522,15 +8308,15 @@ function sucuriscan_wordpress_outdated(){
|
|
7522 |
}
|
7523 |
}
|
7524 |
|
7525 |
-
if( strcmp($site_version, '3.7') < 0 ){
|
7526 |
$cp = 0;
|
7527 |
}
|
7528 |
|
7529 |
-
if( $cp == 0 ){
|
7530 |
$template_variables['WordPress.UpdateVisibility'] = 'visible';
|
7531 |
}
|
7532 |
|
7533 |
-
return SucuriScanTemplate::get_section('integrity-wpoutdate', $template_variables);
|
7534 |
}
|
7535 |
|
7536 |
/**
|
@@ -7550,46 +8336,52 @@ function sucuriscan_core_files(){
|
|
7550 |
'CoreFiles.BadVisibility' => 'hidden',
|
7551 |
);
|
7552 |
|
7553 |
-
if( $site_version && SucuriScanOption::get_option(':scan_checksums') == 'enabled' ){
|
7554 |
// Check if there are added, removed, or modified files.
|
7555 |
-
$latest_hashes = sucuriscan_check_core_integrity($site_version);
|
7556 |
|
7557 |
-
if( $latest_hashes ){
|
7558 |
-
$cache = new SucuriScanCache('integrity');
|
7559 |
$ignored_files = $cache->get_all();
|
7560 |
$counter = 0;
|
7561 |
|
7562 |
-
foreach( $latest_hashes as $list_type => $file_list ){
|
7563 |
-
if(
|
7564 |
$list_type == 'stable'
|
7565 |
|| empty($file_list)
|
7566 |
){
|
7567 |
continue;
|
7568 |
}
|
7569 |
|
7570 |
-
foreach( $file_list as $file_path ){
|
|
|
|
|
7571 |
// Skip files that were marked as fixed.
|
7572 |
-
if( $ignored_files ){
|
7573 |
-
|
|
|
7574 |
|
7575 |
-
if( array_key_exists($file_path_checksum, $ignored_files) ){
|
7576 |
continue;
|
7577 |
}
|
7578 |
}
|
7579 |
|
7580 |
// Generate the HTML code from the snippet template for this file.
|
7581 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
|
|
7582 |
$template_variables['CoreFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-corefiles', array(
|
7583 |
'CoreFiles.CssClass' => $css_class,
|
7584 |
'CoreFiles.StatusType' => $list_type,
|
7585 |
-
'CoreFiles.StatusAbbr' => substr($list_type, 0, 1),
|
7586 |
'CoreFiles.FilePath' => $file_path,
|
|
|
|
|
|
|
7587 |
));
|
7588 |
$counter += 1;
|
7589 |
}
|
7590 |
}
|
7591 |
|
7592 |
-
if( $counter > 0 ){
|
7593 |
$template_variables['CoreFiles.ListCount'] = $counter;
|
7594 |
$template_variables['CoreFiles.GoodVisibility'] = 'hidden';
|
7595 |
$template_variables['CoreFiles.BadVisibility'] = 'visible';
|
@@ -7599,7 +8391,7 @@ function sucuriscan_core_files(){
|
|
7599 |
}
|
7600 |
}
|
7601 |
|
7602 |
-
return SucuriScanTemplate::get_section('integrity-corefiles', $template_variables);
|
7603 |
}
|
7604 |
|
7605 |
/**
|
@@ -7617,10 +8409,10 @@ function sucuriscan_core_files(){
|
|
7617 |
* @param integer $version Valid version number of the WordPress project.
|
7618 |
* @return array Associative array with these keys: modified, stable, removed, added.
|
7619 |
*/
|
7620 |
-
function sucuriscan_check_core_integrity( $version=0 ){
|
7621 |
-
$latest_hashes = SucuriScanAPI::get_official_checksums($version);
|
7622 |
|
7623 |
-
if(
|
7624 |
|
7625 |
$output = array(
|
7626 |
'added' => array(),
|
@@ -7630,21 +8422,21 @@ function sucuriscan_check_core_integrity( $version=0 ){
|
|
7630 |
);
|
7631 |
|
7632 |
// Get current filesystem tree.
|
7633 |
-
$wp_top_hashes = sucuriscan_get_integrity_tree( ABSPATH , false);
|
7634 |
-
$wp_admin_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-admin', true);
|
7635 |
-
$wp_includes_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-includes', true);
|
7636 |
$wp_core_hashes = array_merge( $wp_top_hashes, $wp_admin_hashes, $wp_includes_hashes );
|
7637 |
|
7638 |
// Compare remote and local checksums and search removed files.
|
7639 |
-
foreach( $latest_hashes as $file_path => $remote_checksum ){
|
7640 |
-
if( sucuriscan_ignore_integrity_filepath($file_path) ){ continue; }
|
7641 |
|
7642 |
-
$full_filepath = sprintf('%s/%s', ABSPATH, $file_path);
|
7643 |
|
7644 |
-
if( file_exists($full_filepath) ){
|
7645 |
-
$local_checksum = @md5_file($full_filepath);
|
7646 |
|
7647 |
-
if( $local_checksum && $local_checksum == $remote_checksum ){
|
7648 |
$output['stable'][] = $file_path;
|
7649 |
} else {
|
7650 |
$output['modified'][] = $file_path;
|
@@ -7655,12 +8447,12 @@ function sucuriscan_check_core_integrity( $version=0 ){
|
|
7655 |
}
|
7656 |
|
7657 |
// Search added files (files not common in a normal wordpress installation).
|
7658 |
-
foreach( $wp_core_hashes as $file_path => $extra_info ){
|
7659 |
-
$file_path = preg_replace('/^\.\/(.*)/', '$1', $file_path);
|
7660 |
|
7661 |
-
if( sucuriscan_ignore_integrity_filepath($file_path) ){ continue; }
|
7662 |
|
7663 |
-
if( !isset($latest_hashes[$file_path]) ){
|
7664 |
$output['added'][] = $file_path;
|
7665 |
}
|
7666 |
}
|
@@ -7674,7 +8466,7 @@ function sucuriscan_check_core_integrity( $version=0 ){
|
|
7674 |
* @param string $file_path File path that will be compared.
|
7675 |
* @return boolean TRUE if the file should be ignored, FALSE otherwise.
|
7676 |
*/
|
7677 |
-
function sucuriscan_ignore_integrity_filepath( $file_path='' ){
|
7678 |
global $wp_local_package;
|
7679 |
|
7680 |
// List of files that will be ignored from the integrity checking.
|
@@ -7705,7 +8497,7 @@ function sucuriscan_ignore_integrity_filepath( $file_path='' ){
|
|
7705 |
* of the project, basically they have files with new variables specifying the
|
7706 |
* language that will be used in the admin panel, site options, and emails.
|
7707 |
*/
|
7708 |
-
if(
|
7709 |
isset($wp_local_package)
|
7710 |
&& $wp_local_package != 'en_US'
|
7711 |
){
|
@@ -7714,13 +8506,13 @@ function sucuriscan_ignore_integrity_filepath( $file_path='' ){
|
|
7714 |
}
|
7715 |
|
7716 |
// Determine whether a file must be ignored from the integrity checks or not.
|
7717 |
-
foreach( $ignore_files as $ignore_pattern ){
|
7718 |
-
if( preg_match('/'.$ignore_pattern.'/', $file_path) ){
|
7719 |
-
return
|
7720 |
}
|
7721 |
}
|
7722 |
|
7723 |
-
return
|
7724 |
}
|
7725 |
|
7726 |
/**
|
@@ -7734,21 +8526,26 @@ function sucuriscan_modified_files(){
|
|
7734 |
'ModifiedFiles.List' => '',
|
7735 |
'ModifiedFiles.SelectOptions' => '',
|
7736 |
'ModifiedFiles.NoFilesVisibility' => 'visible',
|
|
|
7737 |
'ModifiedFiles.Days' => 0,
|
7738 |
);
|
7739 |
|
7740 |
// Find files modified in the last days.
|
7741 |
-
$back_days =
|
7742 |
|
7743 |
-
|
7744 |
-
if( SucuriScanInterface::check_nonce() ){
|
7745 |
-
$back_days = (int) SucuriScanRequest::post(':last_days', '[0-9]+');
|
7746 |
if ( $back_days <= 0 ){ $back_days = 1; }
|
7747 |
-
elseif( $back_days >= 60 ){ $back_days = 60; }
|
|
|
|
|
7748 |
}
|
7749 |
|
|
|
|
|
|
|
|
|
7750 |
// Generate the options for the select field of the page form.
|
7751 |
-
foreach( $valid_day_ranges as $day ){
|
7752 |
$selected_option = ($back_days == $day) ? 'selected="selected"' : '';
|
7753 |
$template_variables['ModifiedFiles.SelectOptions'] .= sprintf(
|
7754 |
'<option value="%d" %s>%d</option>',
|
@@ -7757,40 +8554,46 @@ function sucuriscan_modified_files(){
|
|
7757 |
}
|
7758 |
|
7759 |
// The scanner for modified files can be disabled from the settings page.
|
7760 |
-
if( SucuriScanOption::get_option(':scan_modfiles') == 'enabled' ){
|
7761 |
// Search modified files among the project's files.
|
7762 |
$content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
|
7763 |
|
7764 |
-
if( !empty($content_hashes) ){
|
7765 |
-
$
|
7766 |
-
$back_days = current_time('timestamp') - ( $back_days * 86400);
|
7767 |
$counter = 0;
|
7768 |
|
7769 |
-
foreach( $content_hashes as $file_path => $file_info ){
|
7770 |
-
if(
|
7771 |
isset($file_info['modified_at'])
|
7772 |
&& $file_info['modified_at'] >= $back_days
|
7773 |
){
|
7774 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
7775 |
-
$mod_date = SucuriScan::datetime($file_info['modified_at']);
|
7776 |
|
7777 |
$template_variables['ModifiedFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-modifiedfiles', array(
|
7778 |
'ModifiedFiles.CssClass' => $css_class,
|
7779 |
'ModifiedFiles.CheckSum' => $file_info['checksum'],
|
7780 |
'ModifiedFiles.FilePath' => $file_path,
|
7781 |
'ModifiedFiles.DateTime' => $mod_date,
|
|
|
|
|
|
|
7782 |
));
|
7783 |
$counter += 1;
|
7784 |
}
|
7785 |
}
|
7786 |
|
7787 |
-
if( $counter > 0 ){
|
7788 |
$template_variables['ModifiedFiles.NoFilesVisibility'] = 'hidden';
|
7789 |
}
|
7790 |
}
|
7791 |
}
|
7792 |
|
7793 |
-
|
|
|
|
|
|
|
|
|
7794 |
}
|
7795 |
|
7796 |
/**
|
@@ -7806,12 +8609,12 @@ function sucuriscan_posthack_page(){
|
|
7806 |
// Page pseudo-variables initialization.
|
7807 |
$template_variables = array(
|
7808 |
'PageTitle' => 'Post-Hack',
|
7809 |
-
'UpdateSecretKeys' => sucuriscan_update_secret_keys($process_form),
|
7810 |
-
'ResetPassword' => sucuriscan_posthack_users($process_form),
|
7811 |
-
'ResetPlugins' => sucuriscan_posthack_plugins($process_form),
|
7812 |
);
|
7813 |
|
7814 |
-
echo SucuriScanTemplate::get_template('posthack', $template_variables);
|
7815 |
}
|
7816 |
|
7817 |
/**
|
@@ -7820,20 +8623,20 @@ function sucuriscan_posthack_page(){
|
|
7820 |
* @return boolean TRUE if a form submission should be processed, FALSE otherwise.
|
7821 |
*/
|
7822 |
function sucuriscan_posthack_process_form(){
|
7823 |
-
$process_form = SucuriScanRequest::post(':process_form', '(0|1)');
|
7824 |
|
7825 |
-
if(
|
7826 |
SucuriScanInterface::check_nonce()
|
7827 |
-
&& $process_form !==
|
7828 |
){
|
7829 |
-
if( $process_form === '1' ){
|
7830 |
-
return
|
7831 |
} else {
|
7832 |
-
SucuriScanInterface::error('You need to confirm that you understand the risk of this operation.');
|
7833 |
}
|
7834 |
}
|
7835 |
|
7836 |
-
return
|
7837 |
}
|
7838 |
|
7839 |
/**
|
@@ -7842,7 +8645,7 @@ function sucuriscan_posthack_process_form(){
|
|
7842 |
* @param $process_form Whether a form was submitted or not.
|
7843 |
* @return string HTML code with the information of the process.
|
7844 |
*/
|
7845 |
-
function sucuriscan_update_secret_keys( $process_form=
|
7846 |
$template_variables = array(
|
7847 |
'WPConfigUpdate.Visibility' => 'hidden',
|
7848 |
'WPConfigUpdate.NewConfig' => '',
|
@@ -7850,13 +8653,14 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7850 |
);
|
7851 |
|
7852 |
// Update all WordPress secret keys.
|
7853 |
-
if( $process_form && SucuriScanRequest::post(':update_wpconfig', '1') ){
|
7854 |
$wpconfig_process = SucuriScanEvent::set_new_config_keys();
|
7855 |
|
7856 |
-
if( $wpconfig_process ){
|
7857 |
$template_variables['WPConfigUpdate.Visibility'] = 'visible';
|
|
|
7858 |
|
7859 |
-
if( $wpconfig_process['updated'] ===
|
7860 |
SucuriScanInterface::info( 'Secret keys updated successfully (summary of the operation bellow).' );
|
7861 |
$template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
|
7862 |
$template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
|
@@ -7871,7 +8675,7 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7871 |
$template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
|
7872 |
}
|
7873 |
} else {
|
7874 |
-
SucuriScanInterface::error('<code>wp-config.php</code> file was not found in the default location.' );
|
7875 |
}
|
7876 |
}
|
7877 |
|
@@ -7879,12 +8683,12 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7879 |
$current_keys = SucuriScanOption::get_security_keys();
|
7880 |
$counter = 0;
|
7881 |
|
7882 |
-
foreach( $current_keys as $key_status => $key_list ){
|
7883 |
-
foreach( $key_list as $key_name => $key_value ){
|
7884 |
-
$css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
|
7885 |
$key_value = SucuriScan::excerpt( $key_value, 50 );
|
7886 |
|
7887 |
-
switch( $key_status ){
|
7888 |
case 'good':
|
7889 |
$key_status_text = 'good';
|
7890 |
$key_status_css_class = 'success';
|
@@ -7900,11 +8704,11 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7900 |
break;
|
7901 |
}
|
7902 |
|
7903 |
-
if( isset($key_status_text) ){
|
7904 |
$template_variables['SecurityKeys.List'] .= SucuriScanTemplate::get_snippet('posthack-updatesecretkeys', array(
|
7905 |
'SecurityKey.CssClass' => $css_class,
|
7906 |
-
'SecurityKey.KeyName' => SucuriScan::escape($key_name),
|
7907 |
-
'SecurityKey.KeyValue' => SucuriScan::escape($key_value),
|
7908 |
'SecurityKey.KeyStatusText' => $key_status_text,
|
7909 |
'SecurityKey.KeyStatusCssClass' => $key_status_css_class,
|
7910 |
));
|
@@ -7913,7 +8717,7 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7913 |
}
|
7914 |
}
|
7915 |
|
7916 |
-
return SucuriScanTemplate::get_section('posthack-updatesecretkeys', $template_variables);
|
7917 |
}
|
7918 |
|
7919 |
/**
|
@@ -7923,32 +8727,62 @@ function sucuriscan_update_secret_keys( $process_form=FALSE ){
|
|
7923 |
* @param $process_form Whether a form was submitted or not.
|
7924 |
* @return string HTML code for a table where a list of user accounts will be shown.
|
7925 |
*/
|
7926 |
-
function sucuriscan_posthack_users( $process_form=
|
7927 |
$template_variables = array(
|
7928 |
'ResetPassword.UserList' => '',
|
|
|
|
|
7929 |
);
|
7930 |
|
7931 |
// Process the form submission (if any).
|
7932 |
-
sucuriscan_reset_user_password($process_form);
|
7933 |
|
7934 |
// Fill the user list for ResetPassword action.
|
7935 |
-
$user_list =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7936 |
|
7937 |
-
|
|
|
|
|
|
|
|
|
|
|
7938 |
$counter = 0;
|
7939 |
|
7940 |
-
foreach( $user_list as $user ){
|
7941 |
-
$user->user_registered_timestamp = strtotime($user->user_registered);
|
7942 |
-
$user->user_registered_formatted = SucuriScan::datetime($user->user_registered_timestamp);
|
7943 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
|
|
|
|
|
|
7944 |
|
7945 |
$template_variables['ResetPassword.UserList'] .= SucuriScanTemplate::get_snippet('posthack-resetpassword', array(
|
7946 |
'ResetPassword.UserId' => $user->ID,
|
7947 |
-
'ResetPassword.Username' => SucuriScan::escape($user->user_login),
|
7948 |
-
'ResetPassword.Displayname' => SucuriScan::escape($user->display_name),
|
7949 |
-
'ResetPassword.
|
|
|
7950 |
'ResetPassword.Registered' => $user->user_registered_formatted,
|
7951 |
-
'ResetPassword.Roles' => implode(', ', $user->roles),
|
7952 |
'ResetPassword.CssClass' => $css_class,
|
7953 |
));
|
7954 |
|
@@ -7956,7 +8790,7 @@ function sucuriscan_posthack_users( $process_form=FALSE ){
|
|
7956 |
}
|
7957 |
}
|
7958 |
|
7959 |
-
return SucuriScanTemplate::get_section('posthack-resetpassword', $template_variables);
|
7960 |
}
|
7961 |
|
7962 |
/**
|
@@ -7965,29 +8799,32 @@ function sucuriscan_posthack_users( $process_form=FALSE ){
|
|
7965 |
* @param $process_form Whether a form was submitted or not.
|
7966 |
* @return void
|
7967 |
*/
|
7968 |
-
function sucuriscan_reset_user_password( $process_form=
|
7969 |
-
if( $process_form && SucuriScanRequest::post(':reset_password') ){
|
7970 |
-
$user_identifiers = SucuriScanRequest::post('user_ids', '_array');
|
7971 |
$pwd_changed = array();
|
7972 |
$pwd_not_changed = array();
|
7973 |
|
7974 |
-
if( is_array($user_identifiers) && !empty($user_identifiers) ){
|
7975 |
-
arsort($user_identifiers);
|
7976 |
|
7977 |
-
foreach( $user_identifiers as $user_id ){
|
7978 |
-
if( SucuriScanEvent::set_new_password($user_id) ){
|
7979 |
$pwd_changed[] = $user_id;
|
7980 |
} else {
|
7981 |
$pwd_not_changed[] = $user_id;
|
7982 |
}
|
7983 |
}
|
7984 |
|
7985 |
-
if( !empty($pwd_changed) ){
|
7986 |
-
|
|
|
|
|
|
|
7987 |
}
|
7988 |
|
7989 |
-
if( !empty($pwd_not_changed) ){
|
7990 |
-
SucuriScanInterface::error( 'Password change failed for users: ' . implode(', ',$pwd_not_changed) );
|
7991 |
}
|
7992 |
} else {
|
7993 |
SucuriScanInterface::error( 'You did not select a user from the list.' );
|
@@ -8001,16 +8838,16 @@ function sucuriscan_reset_user_password( $process_form=FALSE ){
|
|
8001 |
* @param boolean $process_form Whether a form was submitted or not.
|
8002 |
* @return void
|
8003 |
*/
|
8004 |
-
function sucuriscan_posthack_plugins( $process_form=
|
8005 |
$template_variables = array(
|
8006 |
'ResetPlugin.PluginList' => '',
|
8007 |
);
|
8008 |
|
8009 |
-
sucuriscan_posthack_reinstall_plugins($process_form);
|
8010 |
$all_plugins = SucuriScanAPI::get_plugins();
|
8011 |
$counter = 0;
|
8012 |
|
8013 |
-
foreach( $all_plugins as $plugin_path => $plugin_data ){
|
8014 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8015 |
$plugin_type_class = ( $plugin_data['PluginType'] == 'free' ) ? 'primary' : 'warning';
|
8016 |
$input_disabled = ( $plugin_data['PluginType'] == 'free' ) ? '' : 'disabled="disabled"';
|
@@ -8020,8 +8857,8 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
|
|
8020 |
$template_variables['ResetPlugin.PluginList'] .= SucuriScanTemplate::get_snippet('posthack-resetplugins', array(
|
8021 |
'ResetPlugin.CssClass' => $css_class,
|
8022 |
'ResetPlugin.Disabled' => $input_disabled,
|
8023 |
-
'ResetPlugin.PluginPath' => SucuriScan::escape($plugin_path),
|
8024 |
-
'ResetPlugin.Plugin' => SucuriScan::excerpt($plugin_data['Name'], 35),
|
8025 |
'ResetPlugin.Version' => $plugin_data['Version'],
|
8026 |
'ResetPlugin.Type' => $plugin_data['PluginType'],
|
8027 |
'ResetPlugin.TypeClass' => $plugin_type_class,
|
@@ -8032,7 +8869,7 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
|
|
8032 |
$counter += 1;
|
8033 |
}
|
8034 |
|
8035 |
-
return SucuriScanTemplate::get_section('posthack-resetplugins', $template_variables);
|
8036 |
}
|
8037 |
|
8038 |
/**
|
@@ -8044,38 +8881,41 @@ function sucuriscan_posthack_plugins( $process_form=FALSE ){
|
|
8044 |
* @param boolean $process_form Whether a form was submitted or not.
|
8045 |
* @return void
|
8046 |
*/
|
8047 |
-
function sucuriscan_posthack_reinstall_plugins( $process_form=
|
8048 |
-
if( $process_form && isset($_POST['sucuriscan_reset_plugins']) ){
|
8049 |
include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
|
8050 |
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); // For plugins_api.
|
8051 |
|
8052 |
-
if( $plugin_list = SucuriScanRequest::post('plugin_path', '_array') ){
|
8053 |
// Create an instance of the FileInfo interface.
|
8054 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
8055 |
-
$sucuri_fileinfo->ignore_files =
|
8056 |
-
$sucuri_fileinfo->ignore_directories =
|
8057 |
|
8058 |
// Get (possible) cached information from the installed plugins.
|
8059 |
$all_plugins = SucuriScanAPI::get_plugins();
|
8060 |
|
8061 |
// Loop through all the installed plugins.
|
8062 |
-
foreach( $_POST['plugin_path'] as $plugin_path ){
|
8063 |
-
if( array_key_exists($plugin_path, $all_plugins) ){
|
8064 |
-
$plugin_data = $all_plugins[$plugin_path];
|
8065 |
|
8066 |
// Check if the plugin can be downloaded from the free market.
|
8067 |
-
if( $plugin_data['IsFreePlugin'] ===
|
8068 |
-
$plugin_info = SucuriScanAPI::get_remote_plugin_data($plugin_data['RepositoryName']);
|
8069 |
|
8070 |
-
if( $plugin_info ){
|
8071 |
// First, remove all files/sub-folders from the plugin's directory.
|
8072 |
-
|
8073 |
-
|
|
|
|
|
8074 |
|
8075 |
// Install a fresh copy of the plugin's files.
|
8076 |
$upgrader_skin = new Plugin_Installer_Skin();
|
8077 |
-
$upgrader = new Plugin_Upgrader($upgrader_skin);
|
8078 |
-
$upgrader->install($plugin_info->download_link);
|
|
|
8079 |
} else {
|
8080 |
SucuriScanInterface::error( 'Could not establish a stable connection with the WordPress plugins market.' );
|
8081 |
}
|
@@ -8099,13 +8939,13 @@ function sucuriscan_lastlogins_page(){
|
|
8099 |
SucuriScanInterface::check_permissions();
|
8100 |
|
8101 |
// Reset the file with the last-logins logs.
|
8102 |
-
if(
|
8103 |
SucuriScanInterface::check_nonce()
|
8104 |
-
&& SucuriScanRequest::post(':reset_lastlogins') !==
|
8105 |
){
|
8106 |
$file_path = sucuriscan_lastlogins_datastore_filepath();
|
8107 |
|
8108 |
-
if( unlink($file_path) ){
|
8109 |
sucuriscan_lastlogins_datastore_exists();
|
8110 |
SucuriScanInterface::info( 'Last-Logins logs were reset successfully.' );
|
8111 |
} else {
|
@@ -8122,7 +8962,7 @@ function sucuriscan_lastlogins_page(){
|
|
8122 |
'FailedLogins' => sucuriscan_failed_logins_panel(),
|
8123 |
);
|
8124 |
|
8125 |
-
echo SucuriScanTemplate::get_template('lastlogins', $template_variables);
|
8126 |
}
|
8127 |
|
8128 |
/**
|
@@ -8135,51 +8975,51 @@ function sucuriscan_lastlogins_page(){
|
|
8135 |
function sucuriscan_lastlogins_admins(){
|
8136 |
// Page pseudo-variables initialization.
|
8137 |
$template_variables = array(
|
8138 |
-
'AdminUsers.List' => ''
|
8139 |
);
|
8140 |
|
8141 |
-
$user_query = new WP_User_Query(array( 'role' => 'Administrator' ));
|
8142 |
$admins = $user_query->get_results();
|
8143 |
|
8144 |
-
foreach( (array) $admins as $admin ){
|
8145 |
-
$last_logins = sucuriscan_get_logins(5, 0, $admin->ID);
|
8146 |
$admin->lastlogins = $last_logins['entries'];
|
8147 |
|
8148 |
$user_snippet = array(
|
8149 |
-
'AdminUsers.Username' => SucuriScan::escape($admin->user_login),
|
8150 |
-
'AdminUsers.Email' => SucuriScan::escape($admin->user_email),
|
8151 |
'AdminUsers.LastLogins' => '',
|
8152 |
'AdminUsers.RegisteredAt' => 'Undefined',
|
8153 |
-
'AdminUsers.UserURL' => admin_url('user-edit.php?user_id='.$admin->ID),
|
8154 |
'AdminUsers.NoLastLogins' => 'visible',
|
8155 |
'AdminUsers.NoLastLoginsTable' => 'hidden',
|
8156 |
);
|
8157 |
|
8158 |
-
if( !empty($admin->lastlogins) ){
|
8159 |
$user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
|
8160 |
$user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
|
8161 |
$user_snippet['AdminUsers.RegisteredAt'] = 'Unknown';
|
8162 |
$counter = 0;
|
8163 |
|
8164 |
-
foreach( $admin->lastlogins as $i => $lastlogin ){
|
8165 |
-
if( $i == 0 ){
|
8166 |
-
$user_snippet['AdminUsers.RegisteredAt'] = SucuriScan::datetime($lastlogin->user_registered_timestamp);
|
8167 |
}
|
8168 |
|
8169 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8170 |
$user_snippet['AdminUsers.LastLogins'] .= SucuriScanTemplate::get_snippet('lastlogins-admins-lastlogin', array(
|
8171 |
-
'AdminUsers.RemoteAddr' => SucuriScan::escape($lastlogin->user_remoteaddr),
|
8172 |
-
'AdminUsers.Datetime' => SucuriScan::datetime($lastlogin->user_lastlogin_timestamp),
|
8173 |
'AdminUsers.CssClass' => $css_class,
|
8174 |
));
|
8175 |
$counter += 1;
|
8176 |
}
|
8177 |
}
|
8178 |
|
8179 |
-
$template_variables['AdminUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-admins', $user_snippet);
|
8180 |
}
|
8181 |
|
8182 |
-
return SucuriScanTemplate::get_section('lastlogins-admins', $template_variables);
|
8183 |
}
|
8184 |
|
8185 |
/**
|
@@ -8203,7 +9043,7 @@ function sucuriscan_lastlogins_all(){
|
|
8203 |
'UserList.NoItemsVisibility' => 'visible',
|
8204 |
);
|
8205 |
|
8206 |
-
if( !sucuriscan_lastlogins_datastore_is_writable() ){
|
8207 |
SucuriScanInterface::error( 'Last-logins datastore file is not writable: <code>'.sucuriscan_lastlogins_datastore_filepath().'</code>' );
|
8208 |
}
|
8209 |
|
@@ -8211,15 +9051,15 @@ function sucuriscan_lastlogins_all(){
|
|
8211 |
$last_logins = sucuriscan_get_logins( $max_per_page, $offset );
|
8212 |
$template_variables['UserList.Total'] = $last_logins['total'];
|
8213 |
|
8214 |
-
if( $last_logins['total'] > $max_per_page ){
|
8215 |
$template_variables['UserList.PaginationVisibility'] = 'visible';
|
8216 |
}
|
8217 |
|
8218 |
-
if( $last_logins['total'] > 0 ){
|
8219 |
$template_variables['UserList.NoItemsVisibility'] = 'hidden';
|
8220 |
}
|
8221 |
|
8222 |
-
foreach( $last_logins['entries'] as $user ){
|
8223 |
$counter += 1;
|
8224 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
8225 |
|
@@ -8230,22 +9070,22 @@ function sucuriscan_lastlogins_all(){
|
|
8230 |
'UserList.Displayname' => '',
|
8231 |
'UserList.Email' => '',
|
8232 |
'UserList.Registered' => '',
|
8233 |
-
'UserList.RemoteAddr' => SucuriScan::escape($user->user_remoteaddr),
|
8234 |
-
'UserList.Hostname' => SucuriScan::escape($user->user_hostname),
|
8235 |
-
'UserList.Datetime' => SucuriScan::escape($user->user_lastlogin),
|
8236 |
-
'UserList.TimeAgo' => SucuriScan::time_ago($user->user_lastlogin),
|
8237 |
-
'UserList.UserURL' => admin_url('user-edit.php?user_id='.$user->user_id),
|
8238 |
'UserList.CssClass' => $css_class,
|
8239 |
);
|
8240 |
|
8241 |
-
if( $user->user_exists ){
|
8242 |
-
$user_dataset['UserList.Username'] = SucuriScan::escape($user->user_login);
|
8243 |
-
$user_dataset['UserList.Displayname'] = SucuriScan::escape($user->display_name);
|
8244 |
-
$user_dataset['UserList.Email'] = SucuriScan::escape($user->user_email);
|
8245 |
-
$user_dataset['UserList.Registered'] = SucuriScan::escape($user->user_registered);
|
8246 |
}
|
8247 |
|
8248 |
-
$template_variables['UserList'] .= SucuriScanTemplate::get_snippet('lastlogins-all', $user_dataset);
|
8249 |
}
|
8250 |
|
8251 |
// Generate the pagination for the list.
|
@@ -8255,7 +9095,7 @@ function sucuriscan_lastlogins_all(){
|
|
8255 |
$max_per_page
|
8256 |
);
|
8257 |
|
8258 |
-
return SucuriScanTemplate::get_section('lastlogins-all', $template_variables);
|
8259 |
}
|
8260 |
|
8261 |
/**
|
@@ -8276,13 +9116,13 @@ function sucuriscan_lastlogins_datastore_filepath(){
|
|
8276 |
function sucuriscan_lastlogins_datastore_exists(){
|
8277 |
$datastore_filepath = sucuriscan_lastlogins_datastore_filepath();
|
8278 |
|
8279 |
-
if( !file_exists($datastore_filepath) ){
|
8280 |
-
if( @file_put_contents($datastore_filepath, "<?php exit(0); ?>\n", LOCK_EX) ){
|
8281 |
-
@chmod($datastore_filepath, 0644);
|
8282 |
}
|
8283 |
}
|
8284 |
|
8285 |
-
return file_exists($datastore_filepath) ? $datastore_filepath :
|
8286 |
}
|
8287 |
|
8288 |
/**
|
@@ -8294,17 +9134,17 @@ function sucuriscan_lastlogins_datastore_exists(){
|
|
8294 |
function sucuriscan_lastlogins_datastore_is_writable(){
|
8295 |
$datastore_filepath = sucuriscan_lastlogins_datastore_exists();
|
8296 |
|
8297 |
-
if($datastore_filepath){
|
8298 |
-
if( !is_writable($datastore_filepath) ){
|
8299 |
-
@chmod($datastore_filepath, 0644);
|
8300 |
}
|
8301 |
|
8302 |
-
if( is_writable($datastore_filepath) ){
|
8303 |
return $datastore_filepath;
|
8304 |
}
|
8305 |
}
|
8306 |
|
8307 |
-
return
|
8308 |
}
|
8309 |
|
8310 |
/**
|
@@ -8316,39 +9156,39 @@ function sucuriscan_lastlogins_datastore_is_writable(){
|
|
8316 |
function sucuriscan_lastlogins_datastore_is_readable(){
|
8317 |
$datastore_filepath = sucuriscan_lastlogins_datastore_exists();
|
8318 |
|
8319 |
-
if( $datastore_filepath && is_readable($datastore_filepath) ){
|
8320 |
return $datastore_filepath;
|
8321 |
}
|
8322 |
|
8323 |
-
return
|
8324 |
}
|
8325 |
|
8326 |
-
if( !function_exists('sucuri_set_lastlogin') ){
|
8327 |
/**
|
8328 |
* Add a new user session to the list of last user logins.
|
8329 |
*
|
8330 |
* @param string $user_login The name of the user account involved in the operation.
|
8331 |
* @return void
|
8332 |
*/
|
8333 |
-
function sucuriscan_set_lastlogin($user_login=''){
|
8334 |
$datastore_filepath = sucuriscan_lastlogins_datastore_is_writable();
|
8335 |
|
8336 |
-
if($datastore_filepath){
|
8337 |
-
$current_user = get_user_by('login', $user_login);
|
8338 |
$remote_addr = SucuriScan::get_remote_addr();
|
8339 |
|
8340 |
$login_info = array(
|
8341 |
'user_id' => $current_user->ID,
|
8342 |
'user_login' => $current_user->user_login,
|
8343 |
'user_remoteaddr' => $remote_addr,
|
8344 |
-
'user_hostname' => @gethostbyaddr($remote_addr),
|
8345 |
-
'user_lastlogin' => current_time('mysql')
|
8346 |
);
|
8347 |
|
8348 |
-
@file_put_contents($datastore_filepath, json_encode($login_info)."\n", FILE_APPEND);
|
8349 |
}
|
8350 |
}
|
8351 |
-
add_action('wp_login', 'sucuriscan_set_lastlogin', 50);
|
8352 |
}
|
8353 |
|
8354 |
/**
|
@@ -8362,18 +9202,18 @@ if( !function_exists('sucuri_set_lastlogin') ){
|
|
8362 |
* @param integer $user_id Optional user identifier to filter the results.
|
8363 |
* @return array The list of all the user logins, and total of entries registered.
|
8364 |
*/
|
8365 |
-
function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
|
8366 |
$datastore_filepath = sucuriscan_lastlogins_datastore_is_readable();
|
8367 |
$last_logins = array(
|
8368 |
'total' => 0,
|
8369 |
'entries' => array(),
|
8370 |
);
|
8371 |
|
8372 |
-
if( $datastore_filepath ){
|
8373 |
$parsed_lines = 0;
|
8374 |
-
$data_lines = SucuriScanFileInfo::file_lines($datastore_filepath);
|
8375 |
|
8376 |
-
if( $data_lines ){
|
8377 |
/**
|
8378 |
* This count will not be 100% accurate considering that we are checking the
|
8379 |
* syntax of each line in the loop bellow, there may be some lines without the
|
@@ -8382,11 +9222,11 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
|
|
8382 |
*
|
8383 |
* @var integer
|
8384 |
*/
|
8385 |
-
$total_lines = count($data_lines);
|
8386 |
$last_logins['total'] = $total_lines;
|
8387 |
|
8388 |
// Get a list with the latest entries in the first positions.
|
8389 |
-
$reversed_lines = array_reverse($data_lines);
|
8390 |
|
8391 |
/**
|
8392 |
* Only the user accounts with administrative privileges can see the logs of all
|
@@ -8395,44 +9235,44 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
|
|
8395 |
* @var object
|
8396 |
*/
|
8397 |
$current_user = wp_get_current_user();
|
8398 |
-
$is_admin_user = (bool) current_user_can('manage_options');
|
8399 |
|
8400 |
-
for( $i
|
8401 |
-
$line = $reversed_lines[$i] ? trim($reversed_lines[$i]) : '';
|
8402 |
|
8403 |
// Check if the data is serialized (which we will consider as insecure).
|
8404 |
-
if( SucuriScan::is_serialized($line) ){
|
8405 |
-
$last_login = @unserialize($line); // TODO: Remove after version 1.7.5
|
8406 |
} else {
|
8407 |
-
$last_login = @json_decode($line,
|
8408 |
}
|
8409 |
|
8410 |
-
if( $last_login ){
|
8411 |
-
$last_login['user_lastlogin_timestamp'] = strtotime($last_login['user_lastlogin']);
|
8412 |
$last_login['user_registered_timestamp'] = 0;
|
8413 |
|
8414 |
// Only administrators can see all login stats.
|
8415 |
-
if(
|
8416 |
continue;
|
8417 |
}
|
8418 |
|
8419 |
// Filter the user identifiers using the value passed tot his function.
|
8420 |
-
if( $user_id > 0 && $last_login['user_id'] != $user_id ){
|
8421 |
continue;
|
8422 |
}
|
8423 |
|
8424 |
// Get the WP_User object and add extra information from the last-login data.
|
8425 |
-
$last_login['user_exists'] =
|
8426 |
-
$user_account = get_userdata($last_login['user_id']);
|
8427 |
|
8428 |
-
if( $user_account ){
|
8429 |
-
$last_login['user_exists'] =
|
8430 |
|
8431 |
-
foreach( $user_account->data as $var_name
|
8432 |
-
$last_login[$var_name] = $var_value;
|
8433 |
|
8434 |
-
if( $var_name == 'user_registered' ){
|
8435 |
-
$last_login['user_registered_timestamp'] = strtotime($var_value);
|
8436 |
}
|
8437 |
}
|
8438 |
}
|
@@ -8446,8 +9286,8 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
|
|
8446 |
$last_logins['total'] -= 1;
|
8447 |
}
|
8448 |
|
8449 |
-
if( preg_match('/^[0-9]+$/', $limit) && $limit>0 ){
|
8450 |
-
if( $parsed_lines >= $limit ){ break; }
|
8451 |
}
|
8452 |
}
|
8453 |
}
|
@@ -8456,7 +9296,7 @@ function sucuriscan_get_logins( $limit=10, $offset=0, $user_id=0 ){
|
|
8456 |
return $last_logins;
|
8457 |
}
|
8458 |
|
8459 |
-
if( !function_exists('sucuri_login_redirect') ){
|
8460 |
/**
|
8461 |
* Hook for the wp-login action to redirect the user to a specific URL after
|
8462 |
* his successfully login to the administrator interface.
|
@@ -8466,53 +9306,53 @@ if( !function_exists('sucuri_login_redirect') ){
|
|
8466 |
* @param boolean $user WordPress user object with the information of the account involved in the operation.
|
8467 |
* @return string URL where the browser must be redirected to.
|
8468 |
*/
|
8469 |
-
function sucuriscan_login_redirect( $redirect_to='', $request=
|
8470 |
-
$login_url = !empty($redirect_to) ? $redirect_to : admin_url();
|
8471 |
|
8472 |
-
if( $user instanceof WP_User && $user->ID ){
|
8473 |
$login_url = add_query_arg( 'sucuriscan_lastlogin', 1, $login_url );
|
8474 |
}
|
8475 |
|
8476 |
return $login_url;
|
8477 |
}
|
8478 |
|
8479 |
-
if( SucuriScanOption::get_option(':lastlogin_redirection') == 'enabled' ){
|
8480 |
-
add_filter('login_redirect', 'sucuriscan_login_redirect', 10, 3);
|
8481 |
}
|
8482 |
}
|
8483 |
|
8484 |
-
if( !function_exists('sucuri_get_user_lastlogin') ){
|
8485 |
/**
|
8486 |
* Display the last user login at the top of the admin interface.
|
8487 |
*
|
8488 |
* @return void
|
8489 |
*/
|
8490 |
function sucuriscan_get_user_lastlogin(){
|
8491 |
-
if(
|
8492 |
-
current_user_can('manage_options')
|
8493 |
-
&& SucuriScanRequest::get(':lastlogin', '1')
|
8494 |
){
|
8495 |
$current_user = wp_get_current_user();
|
8496 |
|
8497 |
// Select the penultimate entry, not the last one.
|
8498 |
-
$last_logins = sucuriscan_get_logins(2, 0, $current_user->ID);
|
8499 |
|
8500 |
-
if( isset($last_logins['entries'][1]) ){
|
8501 |
$row = $last_logins['entries'][1];
|
8502 |
|
8503 |
$lastlogin_message = sprintf(
|
8504 |
'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>',
|
8505 |
-
SucuriScan::datetime($row->user_lastlogin_timestamp),
|
8506 |
$row->user_remoteaddr,
|
8507 |
$row->user_hostname
|
8508 |
);
|
8509 |
-
$lastlogin_message .= chr(32).'(<a href="'.SucuriScanTemplate::get_url('lastlogins').'">view all logs</a>)';
|
8510 |
SucuriScanInterface::info( $lastlogin_message );
|
8511 |
}
|
8512 |
}
|
8513 |
}
|
8514 |
|
8515 |
-
add_action('admin_notices', 'sucuriscan_get_user_lastlogin');
|
8516 |
}
|
8517 |
|
8518 |
/**
|
@@ -8527,31 +9367,31 @@ function sucuriscan_loggedin_users_panel(){
|
|
8527 |
'LoggedInUsers.Total' => 0,
|
8528 |
);
|
8529 |
|
8530 |
-
$logged_in_users = sucuriscan_get_online_users(
|
8531 |
|
8532 |
-
if( is_array($logged_in_users) && !empty($logged_in_users) ){
|
8533 |
-
$template_variables['LoggedInUsers.Total'] = count($logged_in_users);
|
8534 |
$counter = 0;
|
8535 |
|
8536 |
-
foreach( (array) $logged_in_users as $logged_in_user ){
|
8537 |
$counter += 1;
|
8538 |
-
$logged_in_user['last_activity_datetime'] = SucuriScan::datetime($logged_in_user['last_activity']);
|
8539 |
-
$logged_in_user['user_registered_datetime'] = SucuriScan::datetime( strtotime($logged_in_user['user_registered']) );
|
8540 |
|
8541 |
$template_variables['LoggedInUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-loggedin', array(
|
8542 |
-
'LoggedInUsers.Id' => SucuriScan::escape($logged_in_user['user_id']),
|
8543 |
-
'LoggedInUsers.UserURL' => admin_url('user-edit.php?user_id='.$logged_in_user['user_id']),
|
8544 |
-
'LoggedInUsers.UserLogin' => SucuriScan::escape($logged_in_user['user_login']),
|
8545 |
-
'LoggedInUsers.UserEmail' => SucuriScan::escape($logged_in_user['user_email']),
|
8546 |
-
'LoggedInUsers.LastActivity' => SucuriScan::escape($logged_in_user['last_activity_datetime']),
|
8547 |
-
'LoggedInUsers.Registered' => SucuriScan::escape($logged_in_user['user_registered_datetime']),
|
8548 |
-
'LoggedInUsers.RemoveAddr' => SucuriScan::escape($logged_in_user['remote_addr']),
|
8549 |
-
'LoggedInUsers.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate'
|
8550 |
));
|
8551 |
}
|
8552 |
}
|
8553 |
|
8554 |
-
return SucuriScanTemplate::get_section('lastlogins-loggedin', $template_variables);
|
8555 |
}
|
8556 |
|
8557 |
/**
|
@@ -8560,20 +9400,20 @@ function sucuriscan_loggedin_users_panel(){
|
|
8560 |
* @param boolean $add_current_user Whether the current user should be added to the list or not.
|
8561 |
* @return array List of registered users currently in session.
|
8562 |
*/
|
8563 |
-
function sucuriscan_get_online_users( $add_current_user=
|
8564 |
$users = array();
|
8565 |
|
8566 |
-
if( SucuriScan::is_multisite() ){
|
8567 |
-
$users = get_site_transient('online_users');
|
8568 |
} else {
|
8569 |
-
$users = get_transient('online_users');
|
8570 |
}
|
8571 |
|
8572 |
// If not online users but current user is logged in, add it to the list.
|
8573 |
-
if( empty($users) && $add_current_user ){
|
8574 |
$current_user = wp_get_current_user();
|
8575 |
|
8576 |
-
if( $current_user->ID > 0 ){
|
8577 |
sucuriscan_set_online_user( $current_user->user_login, $current_user );
|
8578 |
|
8579 |
return sucuriscan_get_online_users();
|
@@ -8591,17 +9431,17 @@ function sucuriscan_get_online_users( $add_current_user=FALSE ){
|
|
8591 |
* @param array $logged_in_users List of registered users currently in session.
|
8592 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation.
|
8593 |
*/
|
8594 |
-
function sucuriscan_save_online_users( $logged_in_users=array() ){
|
8595 |
$expiration = 30 * 60;
|
8596 |
|
8597 |
-
if( SucuriScan::is_multisite() ){
|
8598 |
-
return set_site_transient('online_users', $logged_in_users, $expiration);
|
8599 |
} else {
|
8600 |
-
return set_transient('online_users', $logged_in_users, $expiration);
|
8601 |
}
|
8602 |
}
|
8603 |
|
8604 |
-
if( !function_exists('sucuriscan_unset_online_user_on_logout') ){
|
8605 |
/**
|
8606 |
* Remove a logged in user from the list of registered users in session when
|
8607 |
* the logout page is requested.
|
@@ -8613,10 +9453,10 @@ if( !function_exists('sucuriscan_unset_online_user_on_logout') ){
|
|
8613 |
$current_user = wp_get_current_user();
|
8614 |
$user_id = $current_user->ID;
|
8615 |
|
8616 |
-
sucuriscan_unset_online_user($user_id, $remote_addr);
|
8617 |
}
|
8618 |
|
8619 |
-
add_action('wp_logout', 'sucuriscan_unset_online_user_on_logout');
|
8620 |
}
|
8621 |
|
8622 |
/**
|
@@ -8627,26 +9467,26 @@ if( !function_exists('sucuriscan_unset_online_user_on_logout') ){
|
|
8627 |
* @param integer $remote_addr IP address of the computer where the user logged in.
|
8628 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation.
|
8629 |
*/
|
8630 |
-
function sucuriscan_unset_online_user( $user_id=0, $remote_addr=0 ){
|
8631 |
$logged_in_users = sucuriscan_get_online_users();
|
8632 |
|
8633 |
// Remove the specified user identifier from the list.
|
8634 |
-
if( is_array($logged_in_users) && !empty($logged_in_users) ){
|
8635 |
-
foreach( $logged_in_users as $i => $user ){
|
8636 |
-
if(
|
8637 |
-
$user['user_id']
|
8638 |
-
&& strcmp($user['remote_addr'], $remote_addr) == 0
|
8639 |
){
|
8640 |
-
unset($logged_in_users[$i]);
|
8641 |
break;
|
8642 |
}
|
8643 |
}
|
8644 |
}
|
8645 |
|
8646 |
-
return sucuriscan_save_online_users($logged_in_users);
|
8647 |
}
|
8648 |
|
8649 |
-
if( !function_exists('sucuriscan_set_online_user') ){
|
8650 |
/**
|
8651 |
* Add an user account to the list of registered users in session.
|
8652 |
*
|
@@ -8654,13 +9494,13 @@ if( !function_exists('sucuriscan_set_online_user') ){
|
|
8654 |
* @param boolean $user The WordPress object containing all the information associated to the user.
|
8655 |
* @return void
|
8656 |
*/
|
8657 |
-
function sucuriscan_set_online_user( $user_login='', $user=
|
8658 |
-
if( $user ){
|
8659 |
// Get logged in user information.
|
8660 |
$current_user = ($user instanceof WP_User) ? $user : wp_get_current_user();
|
8661 |
$current_user_id = $current_user->ID;
|
8662 |
$remote_addr = SucuriScan::get_remote_addr();
|
8663 |
-
$current_time = current_time('timestamp');
|
8664 |
$logged_in_users = sucuriscan_get_online_users();
|
8665 |
|
8666 |
// Build the dataset array that will be stored in the transient variable.
|
@@ -8673,45 +9513,45 @@ if( !function_exists('sucuriscan_set_online_user') ){
|
|
8673 |
'remote_addr' => $remote_addr,
|
8674 |
);
|
8675 |
|
8676 |
-
if( !is_array($logged_in_users) || empty($logged_in_users) ){
|
8677 |
$logged_in_users = array( $current_user_info );
|
8678 |
-
sucuriscan_save_online_users($logged_in_users);
|
8679 |
} else {
|
8680 |
-
$do_nothing =
|
8681 |
-
$update_existing =
|
8682 |
$item_index = 0;
|
8683 |
|
8684 |
// Check if the user is already in the logged-in-user list and update it if is necessary.
|
8685 |
-
foreach( $logged_in_users as $i => $user ){
|
8686 |
-
if(
|
8687 |
$user['user_id'] == $current_user_id
|
8688 |
-
&& strcmp($user['remote_addr'], $remote_addr) == 0
|
8689 |
){
|
8690 |
-
if( $user['last_activity'] < ($current_time - (15 * 60)) ){
|
8691 |
-
$update_existing =
|
8692 |
$item_index = $i;
|
8693 |
break;
|
8694 |
} else {
|
8695 |
-
$do_nothing =
|
8696 |
break;
|
8697 |
}
|
8698 |
}
|
8699 |
}
|
8700 |
|
8701 |
-
if( $update_existing ){
|
8702 |
-
$logged_in_users[$item_index] = $current_user_info;
|
8703 |
-
sucuriscan_save_online_users($logged_in_users);
|
8704 |
-
} elseif($do_nothing){
|
8705 |
// Do nothing.
|
8706 |
} else {
|
8707 |
$logged_in_users[] = $current_user_info;
|
8708 |
-
sucuriscan_save_online_users($logged_in_users);
|
8709 |
}
|
8710 |
}
|
8711 |
}
|
8712 |
}
|
8713 |
|
8714 |
-
add_action('wp_login', 'sucuriscan_set_online_user', 10, 2);
|
8715 |
}
|
8716 |
|
8717 |
/**
|
@@ -8729,19 +9569,19 @@ function sucuriscan_failed_logins_panel(){
|
|
8729 |
'FailedLogins.CollectPasswordsVisibility' => 'visible',
|
8730 |
);
|
8731 |
|
8732 |
-
$max_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
|
8733 |
-
$notify_bruteforce_attack = SucuriScanOption::get_option(':notify_bruteforce_attack');
|
8734 |
$failed_logins = sucuriscan_get_failed_logins();
|
8735 |
-
$old_failed_logins = sucuriscan_get_failed_logins(true);
|
8736 |
|
8737 |
// Merge the new and old failed logins.
|
8738 |
if (
|
8739 |
-
is_array($old_failed_logins)
|
8740 |
-
&& !empty($old_failed_logins)
|
8741 |
) {
|
8742 |
if (
|
8743 |
-
is_array($failed_logins)
|
8744 |
-
&& !empty($failed_logins)
|
8745 |
) {
|
8746 |
$failed_logins = array_merge( $failed_logins, $old_failed_logins );
|
8747 |
} else {
|
@@ -8749,19 +9589,19 @@ function sucuriscan_failed_logins_panel(){
|
|
8749 |
}
|
8750 |
}
|
8751 |
|
8752 |
-
if( $failed_logins ){
|
8753 |
$counter = 0;
|
8754 |
|
8755 |
-
foreach( $failed_logins['entries'] as $login_data ){
|
8756 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8757 |
$wrong_user_password = '<span class="sucuriscan-label-default">hidden</span>';
|
8758 |
|
8759 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
8760 |
if (
|
8761 |
isset($login_data['user_password'])
|
8762 |
-
&& !empty($login_data['user_password'])
|
8763 |
) {
|
8764 |
-
$wrong_user_password = SucuriScan::escape($login_data['user_password']);
|
8765 |
}
|
8766 |
|
8767 |
else {
|
@@ -8772,32 +9612,32 @@ function sucuriscan_failed_logins_panel(){
|
|
8772 |
$template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
|
8773 |
'FailedLogins.CssClass' => $css_class,
|
8774 |
'FailedLogins.Num' => ($counter + 1),
|
8775 |
-
'FailedLogins.Username' => SucuriScan::escape($login_data['user_login']),
|
8776 |
'FailedLogins.Password' => $wrong_user_password,
|
8777 |
-
'FailedLogins.RemoteAddr' => SucuriScan::escape($login_data['remote_addr']),
|
8778 |
-
'FailedLogins.Datetime' => SucuriScan::datetime($login_data['attempt_time']),
|
8779 |
-
'FailedLogins.UserAgent' => SucuriScan::escape($login_data['user_agent']),
|
8780 |
));
|
8781 |
|
8782 |
$counter += 1;
|
8783 |
}
|
8784 |
|
8785 |
-
if( $counter > 0 ){
|
8786 |
$template_variables['FailedLogins.NoItemsVisibility'] = 'hidden';
|
8787 |
}
|
8788 |
}
|
8789 |
|
8790 |
$template_variables['FailedLogins.MaxFailedLogins'] = $max_failed_logins;
|
8791 |
|
8792 |
-
if( $notify_bruteforce_attack == 'enabled' ){
|
8793 |
$template_variables['FailedLogins.WarningVisibility'] = 'hidden';
|
8794 |
}
|
8795 |
|
8796 |
-
if( sucuriscan_collect_wrong_passwords() !== true ){
|
8797 |
$template_variables['FailedLogins.CollectPasswordsVisibility'] = 'hidden';
|
8798 |
}
|
8799 |
|
8800 |
-
return SucuriScanTemplate::get_section('lastlogins-failedlogins', $template_variables);
|
8801 |
}
|
8802 |
|
8803 |
/**
|
@@ -8806,7 +9646,7 @@ function sucuriscan_failed_logins_panel(){
|
|
8806 |
* @return boolean TRUE if the password must be collected, FALSE otherwise.
|
8807 |
*/
|
8808 |
function sucuriscan_collect_wrong_passwords(){
|
8809 |
-
return (bool) ( SucuriScanOption::get_option(':collect_wrong_passwords') === 'enabled' );
|
8810 |
}
|
8811 |
|
8812 |
/**
|
@@ -8821,25 +9661,25 @@ function sucuriscan_collect_wrong_passwords(){
|
|
8821 |
* @param boolean $reset Whether the file will be resetted or not.
|
8822 |
* @return string The full (relative) path where the file is located.
|
8823 |
*/
|
8824 |
-
function sucuriscan_failed_logins_datastore_path( $get_old_logs=false, $reset=false ){
|
8825 |
$file_name = $get_old_logs ? 'sucuri-oldfailedlogins.php' : 'sucuri-failedlogins.php';
|
8826 |
-
$datastore_path = SucuriScan::datastore_folder_path($file_name);
|
8827 |
$default_content = sucuriscan_failed_logins_default_content();
|
8828 |
|
8829 |
// Create the file if it does not exists.
|
8830 |
-
if( !file_exists($datastore_path) || $reset ){
|
8831 |
@file_put_contents( $datastore_path, $default_content, LOCK_EX );
|
8832 |
}
|
8833 |
|
8834 |
// Return the datastore path if the file exists (or was created).
|
8835 |
-
if(
|
8836 |
-
file_exists($datastore_path)
|
8837 |
-
&& is_readable($datastore_path)
|
8838 |
){
|
8839 |
return $datastore_path;
|
8840 |
}
|
8841 |
|
8842 |
-
return
|
8843 |
}
|
8844 |
|
8845 |
/**
|
@@ -8864,15 +9704,15 @@ function sucuriscan_failed_logins_default_content(){
|
|
8864 |
* @param boolean $get_old_logs Whether the old logs will be retrieved or not.
|
8865 |
* @return array Information and entries gathered from the failed logins datastore file.
|
8866 |
*/
|
8867 |
-
function sucuriscan_get_failed_logins( $get_old_logs=false ){
|
8868 |
-
$datastore_path = sucuriscan_failed_logins_datastore_path($get_old_logs);
|
8869 |
$default_content = sucuriscan_failed_logins_default_content();
|
8870 |
-
$default_content_n = substr_count($default_content, "\n");
|
8871 |
|
8872 |
-
if( $datastore_path ){
|
8873 |
-
$lines = SucuriScanFileInfo::file_lines($datastore_path);
|
8874 |
|
8875 |
-
if( $lines ){
|
8876 |
$failed_logins = array(
|
8877 |
'count' => 0,
|
8878 |
'first_attempt' => 0,
|
@@ -8882,16 +9722,16 @@ function sucuriscan_get_failed_logins( $get_old_logs=false ){
|
|
8882 |
);
|
8883 |
|
8884 |
// Read and parse all the entries found in the datastore file.
|
8885 |
-
foreach( $lines as $i => $line ){
|
8886 |
-
if( $i >= $default_content_n ){
|
8887 |
-
$login_data = @json_decode( trim($line),
|
8888 |
-
$login_data['attempt_date'] = date('r', $login_data['attempt_time']);
|
8889 |
|
8890 |
-
if(
|
8891 |
$login_data['user_agent'] = 'Unknown';
|
8892 |
}
|
8893 |
|
8894 |
-
if ( !isset($login_data['user_password'])) {
|
8895 |
$login_data['user_password'] = '';
|
8896 |
}
|
8897 |
|
@@ -8901,9 +9741,9 @@ function sucuriscan_get_failed_logins( $get_old_logs=false ){
|
|
8901 |
}
|
8902 |
|
8903 |
// Calculate the different time between the first and last attempt.
|
8904 |
-
if( $failed_logins['count'] > 0 ){
|
8905 |
-
$z = abs($failed_logins['count'] - 1);
|
8906 |
-
$failed_logins['last_attempt'] = $failed_logins['entries'][$z]['attempt_time'];
|
8907 |
$failed_logins['first_attempt'] = $failed_logins['entries'][0]['attempt_time'];
|
8908 |
$failed_logins['diff_time'] = abs( $failed_logins['last_attempt'] - $failed_logins['first_attempt'] );
|
8909 |
|
@@ -8912,7 +9752,7 @@ function sucuriscan_get_failed_logins( $get_old_logs=false ){
|
|
8912 |
}
|
8913 |
}
|
8914 |
|
8915 |
-
return
|
8916 |
}
|
8917 |
|
8918 |
|
@@ -8925,7 +9765,7 @@ function sucuriscan_get_failed_logins( $get_old_logs=false ){
|
|
8925 |
* @param string $wrong_password Wrong password used during the supposed attack.
|
8926 |
* @return boolean Whether the information of the current failed login event was stored or not.
|
8927 |
*/
|
8928 |
-
function sucuriscan_log_failed_login( $user_login='', $wrong_password='' ){
|
8929 |
$datastore_path = sucuriscan_failed_logins_datastore_path();
|
8930 |
|
8931 |
// Do not collect wrong passwords if it is not necessary.
|
@@ -8933,7 +9773,7 @@ function sucuriscan_log_failed_login( $user_login='', $wrong_password='' ){
|
|
8933 |
$wrong_password = '';
|
8934 |
}
|
8935 |
|
8936 |
-
if( $datastore_path ){
|
8937 |
$login_data = json_encode(array(
|
8938 |
'user_login' => $user_login,
|
8939 |
'user_password' => $wrong_password,
|
@@ -8947,7 +9787,7 @@ function sucuriscan_log_failed_login( $user_login='', $wrong_password='' ){
|
|
8947 |
return $logged;
|
8948 |
}
|
8949 |
|
8950 |
-
return
|
8951 |
}
|
8952 |
|
8953 |
/**
|
@@ -8959,13 +9799,13 @@ function sucuriscan_log_failed_login( $user_login='', $wrong_password='' ){
|
|
8959 |
* @param array $failed_logins Information and entries gathered from the failed logins datastore file.
|
8960 |
* @return boolean Whether the report was sent via email or not.
|
8961 |
*/
|
8962 |
-
function sucuriscan_report_failed_logins( $failed_logins=array() ){
|
8963 |
-
if( $failed_logins && $failed_logins['count'] > 0 ){
|
8964 |
$prettify_mails = SucuriScanMail::prettify_mails();
|
8965 |
$collect_wrong_passwords = sucuriscan_collect_wrong_passwords();
|
8966 |
$mail_content = '';
|
8967 |
|
8968 |
-
if( $prettify_mails ){
|
8969 |
$table_html = '<table border="1" cellspacing="0" cellpadding="0">';
|
8970 |
|
8971 |
// Add the table headers.
|
@@ -8986,16 +9826,16 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
|
|
8986 |
$table_html .= '<tbody>';
|
8987 |
}
|
8988 |
|
8989 |
-
foreach( $failed_logins['entries'] as $login_data ){
|
8990 |
-
if( $prettify_mails ){
|
8991 |
$table_html .= '<tr>';
|
8992 |
-
$table_html .= '<td>' . esc_attr($login_data['user_login']) . '</td>';
|
8993 |
|
8994 |
if ( $collect_wrong_passwords === true ) {
|
8995 |
-
$table_html .= '<td>' . esc_attr($login_data['user_password']) . '</td>';
|
8996 |
}
|
8997 |
|
8998 |
-
$table_html .= '<td>' . esc_attr($login_data['remote_addr']) . '</td>';
|
8999 |
$table_html .= '<td>' . $login_data['attempt_time'] . '</td>';
|
9000 |
$table_html .= '<td>' . $login_data['attempt_date'] . '</td>';
|
9001 |
$table_html .= '</tr>';
|
@@ -9013,20 +9853,20 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
|
|
9013 |
}
|
9014 |
}
|
9015 |
|
9016 |
-
if( $prettify_mails ){
|
9017 |
$table_html .= '</tbody>';
|
9018 |
$table_html .= '</table>';
|
9019 |
$mail_content = $table_html;
|
9020 |
}
|
9021 |
|
9022 |
-
if( SucuriScanEvent::notify_event( 'bruteforce_attack', $mail_content ) ){
|
9023 |
sucuriscan_reset_failed_logins();
|
9024 |
|
9025 |
-
return
|
9026 |
}
|
9027 |
}
|
9028 |
|
9029 |
-
return
|
9030 |
}
|
9031 |
|
9032 |
/**
|
@@ -9038,10 +9878,10 @@ function sucuriscan_report_failed_logins( $failed_logins=array() ){
|
|
9038 |
* @return boolean Whether the datastore file was resetted or not.
|
9039 |
*/
|
9040 |
function sucuriscan_reset_failed_logins(){
|
9041 |
-
$datastore_path = SucuriScan::datastore_folder_path('sucuri-failedlogins.php');
|
9042 |
-
$datastore_backup_path = sucuriscan_failed_logins_datastore_path(true, false);
|
9043 |
$default_content = sucuriscan_failed_logins_default_content();
|
9044 |
-
$current_content = @file_get_contents($datastore_path);
|
9045 |
$current_content = str_replace( $default_content, '', $current_content );
|
9046 |
|
9047 |
@file_put_contents(
|
@@ -9050,29 +9890,7 @@ function sucuriscan_reset_failed_logins(){
|
|
9050 |
FILE_APPEND
|
9051 |
);
|
9052 |
|
9053 |
-
return (bool) sucuriscan_failed_logins_datastore_path(false, true);
|
9054 |
-
}
|
9055 |
-
|
9056 |
-
/**
|
9057 |
-
* Print a HTML code with the settings of the plugin.
|
9058 |
-
*
|
9059 |
-
* @return void
|
9060 |
-
*/
|
9061 |
-
function sucuriscan_settings_page(){
|
9062 |
-
SucuriScanInterface::check_permissions();
|
9063 |
-
|
9064 |
-
$template_variables = array(
|
9065 |
-
'PageTitle' => 'Settings',
|
9066 |
-
'Settings.General' => sucuriscan_settings_general(),
|
9067 |
-
'Settings.Scanner' => sucuriscan_settings_scanner(),
|
9068 |
-
'Settings.IgnoreScanning' => sucuriscan_settings_ignorescanning(),
|
9069 |
-
'Settings.Notifications' => sucuriscan_settings_notifications(),
|
9070 |
-
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
|
9071 |
-
'Settings.TrustIP' => sucuriscan_settings_trust_ip(),
|
9072 |
-
'Settings.Heartbeat' => sucuriscan_settings_heartbeat(),
|
9073 |
-
);
|
9074 |
-
|
9075 |
-
echo SucuriScanTemplate::get_template('settings', $template_variables);
|
9076 |
}
|
9077 |
|
9078 |
/**
|
@@ -9083,7 +9901,7 @@ function sucuriscan_settings_page(){
|
|
9083 |
* @param boolean $page_nonce True if the nonce is valid, False otherwise.
|
9084 |
* @return void
|
9085 |
*/
|
9086 |
-
function sucuriscan_settings_form_submissions( $page_nonce=
|
9087 |
global $sucuriscan_schedule_allowed,
|
9088 |
$sucuriscan_interface_allowed,
|
9089 |
$sucuriscan_notify_options,
|
@@ -9093,174 +9911,230 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
|
|
9093 |
$sucuriscan_verify_ssl_cert;
|
9094 |
|
9095 |
// Use this conditional to avoid double checking.
|
9096 |
-
if( is_null($page_nonce) ){
|
9097 |
$page_nonce = SucuriScanInterface::check_nonce();
|
9098 |
}
|
9099 |
|
9100 |
-
if( $page_nonce ){
|
9101 |
|
9102 |
// Recover API key through the email registered previously.
|
9103 |
-
if( SucuriScanRequest::post(':recover_key') !==
|
9104 |
SucuriScanAPI::recover_key();
|
|
|
9105 |
}
|
9106 |
|
9107 |
// Save API key after it was recovered by the administrator.
|
9108 |
-
if( $api_key = SucuriScanRequest::post(':manual_api_key') ){
|
9109 |
-
SucuriScanAPI::set_plugin_key( $api_key,
|
9110 |
SucuriScanEvent::schedule_task();
|
|
|
9111 |
}
|
9112 |
|
9113 |
// Remove API key from the local storage.
|
9114 |
-
if( SucuriScanRequest::post(':remove_api_key') !==
|
9115 |
-
SucuriScanAPI::set_plugin_key('');
|
9116 |
-
wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
|
|
|
9117 |
SucuriScanEvent::notify_event( 'plugin_change', 'Sucuri API key removed' );
|
9118 |
}
|
9119 |
|
9120 |
// Enable or disable the filesystem scanner.
|
9121 |
-
if( $fs_scanner = SucuriScanRequest::post(':fs_scanner', '(en|dis)able') ){
|
9122 |
$action_d = $fs_scanner . 'd';
|
9123 |
-
|
9124 |
-
|
9125 |
-
|
|
|
|
|
|
|
9126 |
}
|
9127 |
|
9128 |
// Enable or disable the filesystem scanner for modified files.
|
9129 |
-
if( $scan_modfiles = SucuriScanRequest::post(':scan_modfiles', '(en|dis)able') ){
|
9130 |
$action_d = $scan_modfiles . 'd';
|
9131 |
-
|
9132 |
-
|
9133 |
-
|
|
|
|
|
|
|
9134 |
}
|
9135 |
|
9136 |
// Enable or disable the filesystem scanner for file integrity.
|
9137 |
-
if( $scan_checksums = SucuriScanRequest::post(':scan_checksums', '(en|dis)able') ){
|
9138 |
$action_d = $scan_checksums . 'd';
|
9139 |
-
|
9140 |
-
|
9141 |
-
|
|
|
|
|
|
|
9142 |
}
|
9143 |
|
9144 |
// Enable or disable the filesystem scanner for error logs.
|
9145 |
-
if( $ignore_scanning = SucuriScanRequest::post(':ignore_scanning', '(en|dis)able') ){
|
9146 |
$action_d = $ignore_scanning . 'd';
|
9147 |
-
|
9148 |
-
|
9149 |
-
|
|
|
|
|
|
|
9150 |
}
|
9151 |
|
9152 |
// Enable or disable the filesystem scanner for error logs.
|
9153 |
-
if( $scan_errorlogs = SucuriScanRequest::post(':scan_errorlogs', '(en|dis)able') ){
|
9154 |
$action_d = $scan_errorlogs . 'd';
|
9155 |
-
|
9156 |
-
|
9157 |
-
|
|
|
|
|
|
|
9158 |
}
|
9159 |
|
9160 |
// Enable or disable the error logs parsing.
|
9161 |
-
if( $parse_errorlogs = SucuriScanRequest::post(':parse_errorlogs', '(en|dis)able') ){
|
9162 |
$action_d = $parse_errorlogs . 'd';
|
9163 |
-
|
9164 |
-
SucuriScanEvent::notify_event( 'plugin_change', 'Analysis of error logs was: ' . $action_d );
|
9165 |
-
SucuriScanInterface::info( 'Analysis of error logs was <code>' . $action_d . '</code>' );
|
9166 |
-
}
|
9167 |
-
|
9168 |
-
// Update the limit of error log lines to parse.
|
9169 |
-
if( $errorlogs_limit = SucuriScanRequest::post(':errorlogs_limit', '[0-9]+') ){
|
9170 |
-
if ( $errorlogs_limit > 1000 ) {
|
9171 |
-
SucuriScanInterface::error( 'Analyze more than 1,000 lines will take too much time.' );
|
9172 |
-
} else {
|
9173 |
-
SucuriScanOption::update_option(':errorlogs_limit', $errorlogs_limit);
|
9174 |
-
SucuriScanInterface::info( 'Analyze last <code>' . $errorlogs_limit . '</code> entries encountered in the error logs.' );
|
9175 |
|
9176 |
-
|
9177 |
-
|
9178 |
-
|
9179 |
-
|
9180 |
}
|
9181 |
|
9182 |
// Enable or disable the SiteCheck scanner and the malware scan page.
|
9183 |
-
if( $sitecheck_scanner = SucuriScanRequest::post(':sitecheck_scanner', '(en|dis)able') ){
|
9184 |
$action_d = $sitecheck_scanner . 'd';
|
9185 |
-
|
9186 |
-
|
9187 |
-
|
|
|
|
|
|
|
9188 |
}
|
9189 |
|
9190 |
// Modify the schedule of the filesystem scanner.
|
9191 |
-
if( $frequency = SucuriScanRequest::post(':scan_frequency') ){
|
9192 |
-
if( array_key_exists($frequency, $sucuriscan_schedule_allowed) ){
|
9193 |
-
SucuriScanOption::update_option(':scan_frequency', $frequency);
|
9194 |
-
wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
|
9195 |
|
9196 |
-
if( $frequency != '_oneoff' ){
|
9197 |
-
wp_schedule_event( time()+10, $frequency, 'sucuriscan_scheduled_scan' );
|
9198 |
}
|
9199 |
|
9200 |
-
$frequency_title = $sucuriscan_schedule_allowed[$frequency];
|
9201 |
-
|
9202 |
-
|
|
|
|
|
|
|
9203 |
}
|
9204 |
}
|
9205 |
|
9206 |
// Set the method (aka. interface) that will be used to scan the site.
|
9207 |
-
if( $interface = SucuriScanRequest::post(':scan_interface') ){
|
9208 |
-
$allowed_values = array_keys($sucuriscan_interface_allowed);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9209 |
|
9210 |
-
|
9211 |
-
|
9212 |
-
|
9213 |
-
|
|
|
|
|
|
|
9214 |
}
|
9215 |
}
|
9216 |
|
9217 |
// Update the value for the maximum emails per hour.
|
9218 |
-
if( $per_hour = SucuriScanRequest::post(':emails_per_hour') ){
|
9219 |
-
if( array_key_exists($per_hour, $sucuriscan_emails_per_hour) ){
|
9220 |
-
$per_hour_label = $sucuriscan_emails_per_hour[$per_hour];
|
|
|
|
|
9221 |
SucuriScanOption::update_option( ':emails_per_hour', $per_hour );
|
9222 |
-
SucuriScanEvent::
|
9223 |
-
|
|
|
9224 |
} else {
|
9225 |
SucuriScanInterface::error( 'Invalid value for the maximum emails per hour.' );
|
9226 |
}
|
9227 |
}
|
9228 |
|
9229 |
// Update the email where the event notifications will be sent.
|
9230 |
-
if( $new_email = SucuriScanRequest::post(':notify_to') ){
|
9231 |
-
$valid_email = SucuriScan::get_valid_email($new_email);
|
|
|
|
|
|
|
9232 |
|
9233 |
-
if( $valid_email ){
|
9234 |
SucuriScanOption::update_option( ':notify_to', $valid_email );
|
9235 |
-
SucuriScanEvent::
|
9236 |
-
|
|
|
9237 |
} else {
|
9238 |
SucuriScanInterface::error( 'Email format not supported.' );
|
9239 |
}
|
9240 |
}
|
9241 |
|
9242 |
// Update the maximum failed logins per hour before consider it a brute-force attack.
|
9243 |
-
if( $failed_logins = SucuriScanRequest::post(':maximum_failed_logins') ){
|
9244 |
-
if( array_key_exists($failed_logins, $sucuriscan_maximum_failed_logins) ){
|
|
|
|
|
9245 |
SucuriScanOption::update_option( ':maximum_failed_logins', $failed_logins );
|
9246 |
-
$message
|
9247 |
-
logins occurred during the last hour if its quantity is bigger than the value you just
|
9248 |
-
selected, which is <code>' . $failed_logins . '</code>. The information collected per
|
9249 |
-
hour will be resetted if the quantity of failed logins is lower.';
|
9250 |
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9251 |
-
SucuriScanInterface::info($message);
|
9252 |
} else {
|
9253 |
SucuriScanInterface::error( 'Invalid value for the maximum failed logins per hour before consider it a brute-force attack.' );
|
9254 |
}
|
9255 |
}
|
9256 |
|
9257 |
// Update the configuration for the SSL certificate verification.
|
9258 |
-
if( $verify_ssl_cert = SucuriScanRequest::post(':verify_ssl_cert') ){
|
9259 |
-
if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
|
|
|
|
|
9260 |
SucuriScanOption::update_option( ':verify_ssl_cert', $verify_ssl_cert );
|
9261 |
-
$message
|
9262 |
-
. 'while communicating with the Sucuri API service, nor the official '
|
9263 |
-
. 'WordPress API.';
|
9264 |
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9265 |
SucuriScanInterface::info( $message );
|
9266 |
} else {
|
@@ -9268,35 +10142,82 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
|
|
9268 |
}
|
9269 |
}
|
9270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9271 |
// Update the API request timeout.
|
9272 |
-
if( $request_timeout = SucuriScanRequest::post(':request_timeout', '[0-9]+') ){
|
9273 |
-
|
9274 |
-
|
|
|
|
|
|
|
|
|
9275 |
}
|
9276 |
|
9277 |
// Update the collection of failed passwords settings.
|
9278 |
-
if( $collect_wrong_passwords = SucuriScanRequest::post(':collect_wrong_passwords') ){
|
9279 |
-
$collect_wrong_passwords = strtolower($collect_wrong_passwords);
|
9280 |
-
$
|
9281 |
|
9282 |
if ( $collect_wrong_passwords == 'yes' ) {
|
9283 |
$collect_action = 'enabled';
|
|
|
|
|
|
|
|
|
|
|
|
|
9284 |
}
|
9285 |
|
9286 |
-
SucuriScanOption::update_option(':collect_wrong_passwords', $collect_action);
|
9287 |
-
|
|
|
9288 |
}
|
9289 |
|
9290 |
// Update the datastore path (if the new directory exists).
|
9291 |
-
if( $datastore_path = SucuriScanRequest::post(':datastore_path') ){
|
9292 |
$current_datastore_path = SucuriScanOption::datastore_folder_path();
|
9293 |
|
9294 |
-
if ( file_exists($datastore_path) ) {
|
9295 |
-
if ( is_writable($datastore_path) ) {
|
9296 |
-
|
9297 |
-
SucuriScanInterface::info( 'Datastore path changed to <code>' . $datastore_path . '</code>' );
|
9298 |
|
9299 |
-
|
|
|
|
|
|
|
|
|
|
|
9300 |
$new_datastore_path = SucuriScanOption::datastore_folder_path();
|
9301 |
@rename( $current_datastore_path, $new_datastore_path );
|
9302 |
}
|
@@ -9312,41 +10233,61 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
|
|
9312 |
}
|
9313 |
}
|
9314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9315 |
// Update the notification settings.
|
9316 |
-
if( SucuriScanRequest::post(':save_notification_settings') !==
|
9317 |
$options_updated_counter = 0;
|
9318 |
|
9319 |
-
foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
|
9320 |
-
$option_value = SucuriScanRequest::post($alert_type, '(1|0)');
|
9321 |
|
9322 |
-
if( $option_value !==
|
|
|
9323 |
$option_value = ( $option_value == '1' ) ? 'enabled' : 'disabled';
|
9324 |
-
|
9325 |
-
|
|
|
|
|
|
|
|
|
9326 |
}
|
9327 |
}
|
9328 |
|
9329 |
-
if( $options_updated_counter > 0 ){
|
9330 |
-
|
9331 |
-
|
|
|
|
|
|
|
9332 |
}
|
9333 |
}
|
9334 |
|
9335 |
// Update the subject format for the email alerts.
|
9336 |
-
if ( $email_subject = SucuriScanRequest::post(':email_subject') ) {
|
9337 |
$new_email_subject = false;
|
|
|
9338 |
|
9339 |
// Validate the custom subject format.
|
9340 |
if ( $email_subject == 'custom' ) {
|
9341 |
$format_pattern = '/^[0-9a-zA-Z:,\s]+$/';
|
9342 |
-
$custom_email_subject = SucuriScanRequest::post(':custom_email_subject');
|
9343 |
|
9344 |
if (
|
9345 |
$custom_email_subject !== false
|
9346 |
-
&& !empty($custom_email_subject)
|
9347 |
&& preg_match( $format_pattern, $custom_email_subject )
|
9348 |
) {
|
9349 |
-
$new_email_subject = trim($custom_email_subject);
|
9350 |
} else {
|
9351 |
SucuriScanInterface::error( 'Invalid characters found in the email alert subject format.' );
|
9352 |
}
|
@@ -9354,97 +10295,113 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
|
|
9354 |
|
9355 |
// Check if the email subject format is allowed.
|
9356 |
elseif (
|
9357 |
-
is_array($sucuriscan_email_subjects)
|
9358 |
&& in_array( $email_subject, $sucuriscan_email_subjects )
|
9359 |
) {
|
9360 |
-
$new_email_subject = $email_subject;
|
9361 |
}
|
9362 |
|
9363 |
// Proceed with the operation saving the new subject.
|
9364 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
9365 |
SucuriScanOption::update_option( ':email_subject', $new_email_subject );
|
9366 |
-
|
|
|
|
|
9367 |
}
|
9368 |
}
|
9369 |
|
9370 |
// Reset all the plugin's options.
|
9371 |
-
if( SucuriScanRequest::post(':reset_options') !==
|
9372 |
// Notify the event before the API key is removed.
|
9373 |
-
$
|
9374 |
-
SucuriScanEvent::
|
9375 |
-
SucuriScanEvent::notify_event( 'plugin_change', $
|
9376 |
|
9377 |
-
// Remove all plugin
|
9378 |
-
|
9379 |
-
|
9380 |
-
foreach( $options as $option ){
|
9381 |
-
SucuriScanOption::delete_option( $option->option_name );
|
9382 |
-
}
|
9383 |
|
9384 |
// Remove the scheduled tasks.
|
9385 |
-
wp_clear_scheduled_hook('sucuriscan_scheduled_scan');
|
9386 |
|
9387 |
-
SucuriScanInterface::info( 'All plugin options were
|
9388 |
}
|
9389 |
|
9390 |
// Ignore a new event for email notifications.
|
9391 |
-
if( $action = SucuriScanRequest::post(':ignorerule_action', '(add|remove)') ){
|
9392 |
-
$ignore_rule = SucuriScanRequest::post(':ignorerule');
|
9393 |
|
9394 |
-
if( $action == 'add' ){
|
9395 |
-
if( SucuriScanOption::add_ignored_event($ignore_rule) ){
|
9396 |
SucuriScanInterface::info( 'Post-type ignored successfully.' );
|
|
|
9397 |
} else {
|
9398 |
SucuriScanInterface::error( 'The post-type is invalid or it may be already ignored.' );
|
9399 |
}
|
9400 |
}
|
9401 |
|
9402 |
-
elseif( $action == 'remove' ) {
|
9403 |
-
SucuriScanOption::remove_ignored_event($ignore_rule);
|
9404 |
SucuriScanInterface::info( 'Post-type removed from the list successfully.' );
|
|
|
9405 |
}
|
9406 |
}
|
9407 |
|
9408 |
// Ignore a new directory path for the file system scans.
|
9409 |
-
if( $action = SucuriScanRequest::post(':ignorescanning_action', '(ignore|unignore)') ){
|
9410 |
-
$ignore_directories = SucuriScanRequest::post(':ignorescanning_dirs', '_array');
|
9411 |
|
9412 |
-
if( empty($ignore_directories) ){
|
9413 |
-
SucuriScanInterface::error( 'You did not choose a directory
|
9414 |
}
|
9415 |
|
9416 |
-
elseif( $action == 'ignore' ){
|
9417 |
-
foreach( $ignore_directories as $directory_path ){
|
9418 |
-
SucuriScanFSScanner::ignore_directory($directory_path);
|
9419 |
}
|
9420 |
|
9421 |
-
SucuriScanInterface::info( 'Directories selected
|
|
|
|
|
|
|
|
|
9422 |
}
|
9423 |
|
9424 |
-
elseif( $action == 'unignore' ) {
|
9425 |
-
foreach( $ignore_directories as $directory_path ){
|
9426 |
-
SucuriScanFSScanner::unignore_directory($directory_path);
|
9427 |
}
|
9428 |
|
9429 |
-
SucuriScanInterface::info( 'Directories selected
|
|
|
|
|
|
|
|
|
9430 |
}
|
9431 |
}
|
9432 |
|
9433 |
// Trust and IP address to ignore notifications for a subnet.
|
9434 |
-
if( $trust_ip = SucuriScanRequest::post(':trust_ip') ){
|
9435 |
-
if(
|
9436 |
-
SucuriScan::is_valid_ip($trust_ip)
|
9437 |
-
|| SucuriScan::is_valid_cidr($trust_ip)
|
9438 |
){
|
9439 |
-
$cache = new SucuriScanCache('trustip');
|
9440 |
-
$ip_info = SucuriScan::get_ip_info($trust_ip);
|
9441 |
$ip_info['added_at'] = SucuriScan::local_time();
|
9442 |
-
$cache_key = md5($ip_info['remote_addr']);
|
9443 |
|
9444 |
-
if ( $cache->exists($cache_key) ) {
|
9445 |
SucuriScanInterface::error( 'The IP address specified was already trusted.' );
|
9446 |
} elseif ( $cache->add( $cache_key, $ip_info ) ) {
|
9447 |
-
|
|
|
|
|
|
|
9448 |
} else {
|
9449 |
SucuriScanInterface::error( 'The new entry was not saved in the datastore file.' );
|
9450 |
}
|
@@ -9452,61 +10409,95 @@ function sucuriscan_settings_form_submissions( $page_nonce=NULL ){
|
|
9452 |
}
|
9453 |
|
9454 |
// Trust and IP address to ignore notifications for a subnet.
|
9455 |
-
if( $del_trust_ip = SucuriScanRequest::post(':del_trust_ip', '_array') ){
|
9456 |
-
$cache = new SucuriScanCache('trustip');
|
9457 |
|
9458 |
foreach ( $del_trust_ip as $cache_key ) {
|
9459 |
-
$cache->delete($cache_key);
|
9460 |
}
|
9461 |
|
9462 |
SucuriScanInterface::info( 'The IP addresses selected were deleted successfully.' );
|
9463 |
}
|
9464 |
|
9465 |
// Update the settings for the heartbeat API.
|
9466 |
-
if( $heartbeat_status = SucuriScanRequest::post(':heartbeat_status') ){
|
9467 |
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
|
9468 |
|
9469 |
-
if( array_key_exists($heartbeat_status, $statuses_allowed) ){
|
9470 |
-
|
9471 |
-
|
|
|
|
|
|
|
9472 |
} else {
|
9473 |
SucuriScanInterface::error( 'Heartbeat status not allowed.' );
|
9474 |
}
|
9475 |
}
|
9476 |
|
9477 |
// Update the value of the heartbeat pulse.
|
9478 |
-
if( $heartbeat_pulse = SucuriScanRequest::post(':heartbeat_pulse') ){
|
9479 |
$pulses_allowed = SucuriScanHeartbeat::pulses_allowed();
|
9480 |
|
9481 |
-
if( array_key_exists($heartbeat_pulse, $pulses_allowed) ){
|
9482 |
-
|
9483 |
-
|
|
|
|
|
|
|
9484 |
} else {
|
9485 |
SucuriScanInterface::error( 'Heartbeat pulse not allowed.' );
|
9486 |
}
|
9487 |
}
|
9488 |
|
9489 |
// Update the value of the heartbeat interval.
|
9490 |
-
if( $heartbeat_interval = SucuriScanRequest::post(':heartbeat_interval') ){
|
9491 |
$intervals_allowed = SucuriScanHeartbeat::intervals_allowed();
|
9492 |
|
9493 |
-
if( array_key_exists($heartbeat_interval, $intervals_allowed) ){
|
9494 |
-
|
9495 |
-
|
|
|
|
|
|
|
9496 |
} else {
|
9497 |
SucuriScanInterface::error( 'Heartbeat interval not allowed.' );
|
9498 |
}
|
9499 |
}
|
9500 |
|
9501 |
// Enable or disable the auto-start execution of heartbeat.
|
9502 |
-
if( $heartbeat_autostart = SucuriScanRequest::post(':heartbeat_autostart', '(en|dis)able') ){
|
9503 |
$action_d = $heartbeat_autostart . 'd';
|
9504 |
-
|
9505 |
-
|
|
|
|
|
|
|
9506 |
}
|
9507 |
}
|
9508 |
}
|
9509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9510 |
/**
|
9511 |
* Read and parse the content of the general settings template.
|
9512 |
*
|
@@ -9522,39 +10513,42 @@ function sucuriscan_settings_general(){
|
|
9522 |
$page_nonce = SucuriScanInterface::check_nonce();
|
9523 |
|
9524 |
// Process all form submissions.
|
9525 |
-
sucuriscan_settings_form_submissions($page_nonce);
|
9526 |
|
9527 |
// Register the site, get its API key, and store it locally for future usage.
|
9528 |
$api_registered_modal = '';
|
9529 |
|
9530 |
// Whether the form to manually add the API key should be shown or not.
|
9531 |
-
$display_manual_key_form = (bool) ( SucuriScanRequest::post(':recover_key') !==
|
9532 |
|
9533 |
-
if( $page_nonce && SucuriScanRequest::post(':plugin_api_key') !==
|
9534 |
$registered = SucuriScanAPI::register_site();
|
9535 |
|
9536 |
-
if( $registered ){
|
9537 |
$api_registered_modal = SucuriScanTemplate::get_modal('settings-apiregistered', array(
|
9538 |
'Title' => 'Site registered successfully',
|
9539 |
'CssClass' => 'sucuriscan-apikey-registered',
|
9540 |
));
|
9541 |
} else {
|
9542 |
-
$display_manual_key_form =
|
9543 |
}
|
9544 |
}
|
9545 |
|
9546 |
// Get initial variables to decide some things bellow.
|
9547 |
$api_key = SucuriScanAPI::get_plugin_key();
|
9548 |
-
$emails_per_hour = SucuriScanOption::get_option(':emails_per_hour');
|
9549 |
-
$maximum_failed_logins = SucuriScanOption::get_option(':maximum_failed_logins');
|
9550 |
-
$verify_ssl_cert = SucuriScanOption::get_option(':verify_ssl_cert');
|
|
|
|
|
|
|
9551 |
$invalid_domain = false;
|
9552 |
|
9553 |
// Check whether the domain name is valid or not.
|
9554 |
-
if(
|
9555 |
-
$clean_domain = SucuriScan::
|
9556 |
-
$domain_address = @gethostbyname($clean_domain);
|
9557 |
-
$invalid_domain = ( $domain_address == $clean_domain ) ?
|
9558 |
}
|
9559 |
|
9560 |
// Generate the HTML code for the option list in the form select fields.
|
@@ -9563,41 +10557,66 @@ function sucuriscan_settings_general(){
|
|
9563 |
$verify_ssl_cert_options = SucuriScanTemplate::get_select_options( $sucuriscan_verify_ssl_cert, $verify_ssl_cert );
|
9564 |
|
9565 |
$template_variables = array(
|
9566 |
-
'APIKey' => (
|
9567 |
-
'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility(
|
9568 |
-
'APIKey.ManualKeyFormVisibility' => SucuriScanTemplate::visibility($display_manual_key_form),
|
9569 |
-
'APIKey.RemoveVisibility' => SucuriScanTemplate::visibility($api_key),
|
9570 |
-
'InvalidDomainVisibility' => SucuriScanTemplate::visibility($invalid_domain),
|
9571 |
-
'NotifyTo' => SucuriScanOption::get_option(':notify_to'),
|
9572 |
'EmailsPerHour' => 'Undefined',
|
9573 |
'EmailsPerHourOptions' => $emails_per_hour_options,
|
9574 |
'MaximumFailedLogins' => 'Undefined',
|
9575 |
'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
|
9576 |
'VerifySSLCert' => 'Undefined',
|
9577 |
'VerifySSLCertOptions' => $verify_ssl_cert_options,
|
9578 |
-
'RequestTimeout' => SucuriScanOption::get_option(':request_timeout') . ' seconds',
|
9579 |
-
'DatastorePath' => SucuriScanOption::get_option(':datastore_path'),
|
9580 |
'CollectWrongPasswords' => 'No collect passwords',
|
9581 |
'ModalWhenAPIRegistered' => $api_registered_modal,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9582 |
);
|
9583 |
|
9584 |
-
if( array_key_exists($emails_per_hour, $sucuriscan_emails_per_hour) ){
|
9585 |
-
$template_variables['EmailsPerHour'] = $sucuriscan_emails_per_hour[$emails_per_hour];
|
|
|
|
|
|
|
|
|
9586 |
}
|
9587 |
|
9588 |
-
if( array_key_exists($
|
9589 |
-
$template_variables['
|
9590 |
}
|
9591 |
|
9592 |
-
if
|
9593 |
-
$template_variables['
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9594 |
}
|
9595 |
|
9596 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
9597 |
$template_variables['CollectWrongPasswords'] = '<span class="sucuriscan-label-error">Yes, collect passwords</span>';
|
9598 |
}
|
9599 |
|
9600 |
-
return SucuriScanTemplate::get_section('settings-general', $template_variables);
|
9601 |
}
|
9602 |
|
9603 |
/**
|
@@ -9611,18 +10630,24 @@ function sucuriscan_settings_scanner(){
|
|
9611 |
$sucuriscan_interface_allowed;
|
9612 |
|
9613 |
// Get initial variables to decide some things bellow.
|
9614 |
-
$fs_scanner = SucuriScanOption::get_option(':fs_scanner');
|
9615 |
-
$scan_freq = SucuriScanOption::get_option(':scan_frequency');
|
9616 |
-
$scan_interface = SucuriScanOption::get_option(':scan_interface');
|
9617 |
-
$scan_modfiles = SucuriScanOption::get_option(':scan_modfiles');
|
9618 |
-
$scan_checksums = SucuriScanOption::get_option(':scan_checksums');
|
9619 |
-
$scan_errorlogs = SucuriScanOption::get_option(':scan_errorlogs');
|
9620 |
-
$parse_errorlogs = SucuriScanOption::get_option(':parse_errorlogs');
|
9621 |
-
$errorlogs_limit = SucuriScanOption::get_option(':errorlogs_limit');
|
9622 |
-
$ignore_scanning = SucuriScanOption::get_option(':ignore_scanning');
|
9623 |
-
$sitecheck_scanner = SucuriScanOption::get_option(':sitecheck_scanner');
|
9624 |
-
$sitecheck_counter = SucuriScanOption::get_option(':sitecheck_counter');
|
9625 |
-
$runtime_scan_human = SucuriScanFSScanner::get_filesystem_runtime(
|
|
|
|
|
|
|
|
|
|
|
|
|
9626 |
|
9627 |
// Generate the HTML code for the option list in the form select fields.
|
9628 |
$scan_freq_options = SucuriScanTemplate::get_select_options( $sucuriscan_schedule_allowed, $scan_freq );
|
@@ -9667,68 +10692,78 @@ function sucuriscan_settings_scanner(){
|
|
9667 |
/* Filsystem scanning frequency. */
|
9668 |
'ScanningFrequency' => 'Undefined',
|
9669 |
'ScanningFrequencyOptions' => $scan_freq_options,
|
9670 |
-
'ScanningInterface' => ( $scan_interface ? $sucuriscan_interface_allowed[$scan_interface] : 'Undefined' ),
|
9671 |
'ScanningInterfaceOptions' => $scan_interface_options,
|
9672 |
/* Filesystem scanning runtime. */
|
9673 |
'ScanningRuntimeHuman' => $runtime_scan_human,
|
9674 |
'SiteCheckCounter' => $sitecheck_counter,
|
9675 |
'ErrorLogsLimit' => $errorlogs_limit,
|
|
|
|
|
|
|
|
|
9676 |
);
|
9677 |
|
9678 |
-
if( $fs_scanner == 'disabled' ){
|
9679 |
$template_variables['FsScannerStatus'] = 'Disabled';
|
9680 |
$template_variables['FsScannerSwitchText'] = 'Enable';
|
9681 |
$template_variables['FsScannerSwitchValue'] = 'enable';
|
9682 |
$template_variables['FsScannerSwitchCssClass'] = 'button-success';
|
9683 |
}
|
9684 |
|
9685 |
-
if( $scan_modfiles == 'disabled' ){
|
9686 |
$template_variables['ScanModfilesStatus'] = 'Disabled';
|
9687 |
$template_variables['ScanModfilesSwitchText'] = 'Enable';
|
9688 |
$template_variables['ScanModfilesSwitchValue'] = 'enable';
|
9689 |
$template_variables['ScanModfilesSwitchCssClass'] = 'button-success';
|
9690 |
}
|
9691 |
|
9692 |
-
if( $scan_checksums == 'disabled' ){
|
9693 |
$template_variables['ScanChecksumsStatus'] = 'Disabled';
|
9694 |
$template_variables['ScanChecksumsSwitchText'] = 'Enable';
|
9695 |
$template_variables['ScanChecksumsSwitchValue'] = 'enable';
|
9696 |
$template_variables['ScanChecksumsSwitchCssClass'] = 'button-success';
|
9697 |
}
|
9698 |
|
9699 |
-
if( $ignore_scanning == 'disabled' ){
|
9700 |
$template_variables['IgnoreScanningStatus'] = 'Disabled';
|
9701 |
$template_variables['IgnoreScanningSwitchText'] = 'Enable';
|
9702 |
$template_variables['IgnoreScanningSwitchValue'] = 'enable';
|
9703 |
$template_variables['IgnoreScanningSwitchCssClass'] = 'button-success';
|
9704 |
}
|
9705 |
|
9706 |
-
if( $scan_errorlogs == 'disabled' ){
|
9707 |
$template_variables['ScanErrorlogsStatus'] = 'Disabled';
|
9708 |
$template_variables['ScanErrorlogsSwitchText'] = 'Enable';
|
9709 |
$template_variables['ScanErrorlogsSwitchValue'] = 'enable';
|
9710 |
$template_variables['ScanErrorlogsSwitchCssClass'] = 'button-success';
|
9711 |
}
|
9712 |
|
9713 |
-
if( $parse_errorlogs == 'disabled' ){
|
9714 |
$template_variables['ParseErrorLogsStatus'] = 'Disabled';
|
9715 |
$template_variables['ParseErrorLogsSwitchText'] = 'Enable';
|
9716 |
$template_variables['ParseErrorLogsSwitchValue'] = 'enable';
|
9717 |
$template_variables['ParseErrorLogsSwitchCssClass'] = 'button-success';
|
9718 |
}
|
9719 |
|
9720 |
-
if( $sitecheck_scanner == 'disabled' ){
|
9721 |
$template_variables['SiteCheckScannerStatus'] = 'Disabled';
|
9722 |
$template_variables['SiteCheckScannerSwitchText'] = 'Enable';
|
9723 |
$template_variables['SiteCheckScannerSwitchValue'] = 'enable';
|
9724 |
$template_variables['SiteCheckScannerSwitchCssClass'] = 'button-success';
|
9725 |
}
|
9726 |
|
9727 |
-
if( array_key_exists($scan_freq, $sucuriscan_schedule_allowed) ){
|
9728 |
-
$template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[$scan_freq];
|
9729 |
}
|
9730 |
|
9731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
9732 |
}
|
9733 |
|
9734 |
/**
|
@@ -9749,7 +10784,7 @@ function sucuriscan_settings_notifications(){
|
|
9749 |
);
|
9750 |
|
9751 |
if ( $sucuriscan_email_subjects ) {
|
9752 |
-
$email_subject = SucuriScanOption::get_option(':email_subject');
|
9753 |
$is_official_subject = false;
|
9754 |
|
9755 |
foreach ( $sucuriscan_email_subjects as $subject_format ) {
|
@@ -9769,27 +10804,41 @@ function sucuriscan_settings_notifications(){
|
|
9769 |
|
9770 |
if ( $is_official_subject === false ) {
|
9771 |
$template_variables['EmailSubjectCustom.Checked'] = 'checked="checked"';
|
9772 |
-
$template_variables['EmailSubjectCustom.Value'] = SucuriScan::escape($email_subject);
|
9773 |
}
|
9774 |
}
|
9775 |
|
9776 |
$counter = 0;
|
|
|
9777 |
|
9778 |
-
foreach( $sucuriscan_notify_options as $alert_type => $alert_label ){
|
9779 |
-
$alert_value = SucuriScanOption::get_option($alert_type);
|
9780 |
$checked = ( $alert_value == 'enabled' ? 'checked="checked"' : '' );
|
9781 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9782 |
|
9783 |
$template_variables['NotificationOptions'] .= SucuriScanTemplate::get_snippet('settings-notifications', array(
|
9784 |
'Notification.CssClass' => $css_class,
|
9785 |
'Notification.Name' => $alert_type,
|
9786 |
'Notification.Checked' => $checked,
|
9787 |
'Notification.Label' => $alert_label,
|
|
|
9788 |
));
|
9789 |
$counter += 1;
|
9790 |
}
|
9791 |
|
9792 |
-
return SucuriScanTemplate::get_section('settings-notifications', $template_variables);
|
9793 |
}
|
9794 |
|
9795 |
/**
|
@@ -9798,7 +10847,7 @@ function sucuriscan_settings_notifications(){
|
|
9798 |
* @return string Parsed HTML code for the ignored-rules settings panel.
|
9799 |
*/
|
9800 |
function sucuriscan_settings_ignore_rules(){
|
9801 |
-
$notify_new_site_content = SucuriScanOption::get_option(':notify_post_publication');
|
9802 |
|
9803 |
$template_variables = array(
|
9804 |
'IgnoreRules.MessageVisibility' => 'visible',
|
@@ -9806,7 +10855,7 @@ function sucuriscan_settings_ignore_rules(){
|
|
9806 |
'IgnoreRules.PostTypes' => '',
|
9807 |
);
|
9808 |
|
9809 |
-
if( $notify_new_site_content == 'enabled' ){
|
9810 |
$post_types = get_post_types();
|
9811 |
$ignored_events = SucuriScanOption::get_ignored_events();
|
9812 |
|
@@ -9814,14 +10863,14 @@ function sucuriscan_settings_ignore_rules(){
|
|
9814 |
$template_variables['IgnoreRules.TableVisibility'] = 'visible';
|
9815 |
$counter = 0;
|
9816 |
|
9817 |
-
foreach( $post_types as $post_type => $post_type_object ){
|
9818 |
$counter += 1;
|
9819 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
9820 |
-
$post_type_title = ucwords( str_replace('_', chr(32), $post_type) );
|
9821 |
|
9822 |
-
if( array_key_exists($post_type, $ignored_events) ){
|
9823 |
$is_ignored_text = 'YES';
|
9824 |
-
$was_ignored_at = SucuriScan::datetime($ignored_events[$post_type]);
|
9825 |
$is_ignored_class = 'danger';
|
9826 |
$button_action = 'remove';
|
9827 |
$button_class = 'button-primary';
|
@@ -9850,7 +10899,7 @@ function sucuriscan_settings_ignore_rules(){
|
|
9850 |
}
|
9851 |
}
|
9852 |
|
9853 |
-
return SucuriScanTemplate::get_section('settings-ignorerules', $template_variables);
|
9854 |
}
|
9855 |
|
9856 |
/**
|
@@ -9864,7 +10913,7 @@ function sucuriscan_settings_trust_ip(){
|
|
9864 |
'TrustedIPs.NoItems.Visibility' => 'visible',
|
9865 |
);
|
9866 |
|
9867 |
-
$cache = new SucuriScanCache('trustip');
|
9868 |
$trusted_ips = $cache->get_all();
|
9869 |
|
9870 |
if ( $trusted_ips ) {
|
@@ -9880,9 +10929,9 @@ function sucuriscan_settings_trust_ip(){
|
|
9880 |
$template_variables['TrustedIPs.List'] .= SucuriScanTemplate::get_snippet('settings-trustip', array(
|
9881 |
'TrustIP.CssClass' => $css_class,
|
9882 |
'TrustIP.CacheKey' => $cache_key,
|
9883 |
-
'TrustIP.RemoteAddr' => SucuriScan::escape($ip_info->remote_addr),
|
9884 |
-
'TrustIP.CIDRFormat' => SucuriScan::escape($ip_info->cidr_format),
|
9885 |
-
'TrustIP.AddedAt' => SucuriScan::datetime($ip_info->added_at),
|
9886 |
));
|
9887 |
$counter += 1;
|
9888 |
}
|
@@ -9892,7 +10941,7 @@ function sucuriscan_settings_trust_ip(){
|
|
9892 |
}
|
9893 |
}
|
9894 |
|
9895 |
-
return SucuriScanTemplate::get_section('settings-trustip', $template_variables);
|
9896 |
}
|
9897 |
|
9898 |
/**
|
@@ -9910,19 +10959,19 @@ function sucuriscan_settings_ignorescanning(){
|
|
9910 |
$ignore_scanning = SucuriScanFSScanner::will_ignore_scanning();
|
9911 |
|
9912 |
// Allow disable of this option temporarily.
|
9913 |
-
if( SucuriScanRequest::get('no_scan') == 1 ){
|
9914 |
-
$ignore_scanning =
|
9915 |
}
|
9916 |
|
9917 |
// Scan the project and get the ignored paths.
|
9918 |
-
if( $ignore_scanning ===
|
9919 |
$counter = 0;
|
9920 |
$template_variables['IgnoreScanning.DisabledVisibility'] = 'hidden';
|
9921 |
$dir_list_list = SucuriScanFSScanner::get_ignored_directories_live();
|
9922 |
|
9923 |
-
foreach( $dir_list_list as $group => $dir_list ){
|
9924 |
-
foreach( $dir_list as $dir_data ){
|
9925 |
-
$valid_entry =
|
9926 |
$snippet_data = array(
|
9927 |
'IgnoreScanning.CssClass' => '',
|
9928 |
'IgnoreScanning.Directory' => '',
|
@@ -9932,36 +10981,36 @@ function sucuriscan_settings_ignorescanning(){
|
|
9932 |
'IgnoreScanning.IgnoredCssClass' => 'success',
|
9933 |
);
|
9934 |
|
9935 |
-
if( $group == 'is_ignored' ){
|
9936 |
-
$valid_entry =
|
9937 |
-
$snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data['directory_path']);
|
9938 |
-
$snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data['directory_path']);
|
9939 |
-
$snippet_data['IgnoreScanning.IgnoredAt'] = SucuriScan::datetime($dir_data['ignored_at']);
|
9940 |
$snippet_data['IgnoreScanning.IgnoredAtText'] = 'ignored';
|
9941 |
$snippet_data['IgnoreScanning.IgnoredCssClass'] = 'warning';
|
9942 |
}
|
9943 |
|
9944 |
-
elseif( $group == 'is_not_ignored' ){
|
9945 |
-
$valid_entry =
|
9946 |
-
$snippet_data['IgnoreScanning.Directory'] = urlencode($dir_data);
|
9947 |
-
$snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape($dir_data);
|
9948 |
}
|
9949 |
|
9950 |
-
if( $valid_entry ){
|
9951 |
-
$css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
|
9952 |
$snippet_data['IgnoreScanning.CssClass'] = $css_class;
|
9953 |
-
$template_variables['IgnoreScanning.ResourceList'] .= SucuriScanTemplate::get_snippet('settings-ignorescanning', $snippet_data);
|
9954 |
$counter += 1;
|
9955 |
}
|
9956 |
}
|
9957 |
}
|
9958 |
|
9959 |
-
if( $counter > 0 ){
|
9960 |
$template_variables['IgnoreScanning.NoItemsVisibility'] = 'hidden';
|
9961 |
}
|
9962 |
}
|
9963 |
|
9964 |
-
return SucuriScanTemplate::get_section('settings-ignorescanning', $template_variables);
|
9965 |
}
|
9966 |
|
9967 |
/**
|
@@ -9971,10 +11020,10 @@ function sucuriscan_settings_ignorescanning(){
|
|
9971 |
*/
|
9972 |
function sucuriscan_settings_heartbeat(){
|
9973 |
// Current values set in the options table.
|
9974 |
-
$heartbeat_status = SucuriScanOption::get_option(':heartbeat');
|
9975 |
-
$heartbeat_pulse = SucuriScanOption::get_option(':heartbeat_pulse');
|
9976 |
-
$heartbeat_interval = SucuriScanOption::get_option(':heartbeat_interval');
|
9977 |
-
$heartbeat_autostart = SucuriScanOption::get_option(':heartbeat_autostart');
|
9978 |
|
9979 |
// Allowed values for each setting.
|
9980 |
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
|
@@ -10001,26 +11050,26 @@ function sucuriscan_settings_heartbeat(){
|
|
10001 |
'HeartbeatAutostartSwitchCssClass' => 'button-danger',
|
10002 |
);
|
10003 |
|
10004 |
-
if( array_key_exists($heartbeat_status, $statuses_allowed) ){
|
10005 |
-
$template_variables['HeartbeatStatus'] = $statuses_allowed[$heartbeat_status];
|
10006 |
}
|
10007 |
|
10008 |
-
if( array_key_exists($heartbeat_pulse, $pulses_allowed) ){
|
10009 |
-
$template_variables['HeartbeatPulse'] = $pulses_allowed[$heartbeat_pulse];
|
10010 |
}
|
10011 |
|
10012 |
-
if( array_key_exists($heartbeat_interval, $intervals_allowed) ){
|
10013 |
-
$template_variables['HeartbeatInterval'] = $intervals_allowed[$heartbeat_interval];
|
10014 |
}
|
10015 |
|
10016 |
-
if( $heartbeat_autostart == 'disabled' ){
|
10017 |
$template_variables['HeartbeatAutostart'] = 'Disabled';
|
10018 |
$template_variables['HeartbeatAutostartSwitchText'] = 'Enable';
|
10019 |
$template_variables['HeartbeatAutostartSwitchValue'] = 'enable';
|
10020 |
$template_variables['HeartbeatAutostartSwitchCssClass'] = 'button-success';
|
10021 |
}
|
10022 |
|
10023 |
-
return SucuriScanTemplate::get_section('settings-heartbeat', $template_variables);
|
10024 |
}
|
10025 |
|
10026 |
/**
|
@@ -10048,7 +11097,7 @@ function sucuriscan_infosys_page(){
|
|
10048 |
'ErrorLogs' => sucuriscan_infosys_errorlogs(),
|
10049 |
);
|
10050 |
|
10051 |
-
echo SucuriScanTemplate::get_template('infosys', $template_variables);
|
10052 |
}
|
10053 |
|
10054 |
/**
|
@@ -10068,8 +11117,8 @@ function sucuriscan_infosys_htaccess(){
|
|
10068 |
'HTAccess.TextareaVisible' => 'hidden',
|
10069 |
);
|
10070 |
|
10071 |
-
if( $htaccess_path ){
|
10072 |
-
$htaccess_rules = file_get_contents($htaccess_path);
|
10073 |
|
10074 |
$template_variables['HTAccess.MessageType'] = 'updated';
|
10075 |
$template_variables['HTAccess.MessageVisible'] = 'visible';
|
@@ -10077,23 +11126,23 @@ function sucuriscan_infosys_htaccess(){
|
|
10077 |
$template_variables['HTAccess.Content'] = $htaccess_rules;
|
10078 |
$template_variables['HTAccess.Message'] .= 'HTAccess file found in this path <code>'.$htaccess_path.'</code>';
|
10079 |
|
10080 |
-
if( empty($htaccess_rules) ){
|
10081 |
$template_variables['HTAccess.TextareaVisible'] = 'hidden';
|
10082 |
$template_variables['HTAccess.Message'] .= '</p><p>The HTAccess file found is completely empty.';
|
10083 |
}
|
10084 |
-
if( sucuriscan_htaccess_is_standard($htaccess_rules) ){
|
10085 |
$template_variables['HTAccess.Message'] .= '</p><p>
|
10086 |
The main <code>.htaccess</code> file in your site has the standard rules for a WordPress installation. You can customize it to improve the
|
10087 |
performance and change the behaviour of the redirections for pages and posts in your site. To get more information visit the official documentation at
|
10088 |
<a href="http://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29" target="_blank">Codex WordPrexx - Creating and editing (.htaccess)</a>';
|
10089 |
}
|
10090 |
-
}else{
|
10091 |
$template_variables['HTAccess.Message'] = 'Your website does not contains a <code>.htaccess</code> file or it was not found in the default location.';
|
10092 |
$template_variables['HTAccess.MessageType'] = 'error';
|
10093 |
$template_variables['HTAccess.MessageVisible'] = 'visible';
|
10094 |
}
|
10095 |
|
10096 |
-
return SucuriScanTemplate::get_section('infosys-htaccess', $template_variables);
|
10097 |
}
|
10098 |
|
10099 |
/**
|
@@ -10103,13 +11152,13 @@ function sucuriscan_infosys_htaccess(){
|
|
10103 |
* @param string $rules Optional parameter containing a text string with the content of the main htaccess file.
|
10104 |
* @return boolean Either TRUE or FALSE if the rules found in the htaccess file specified are the default ones or not.
|
10105 |
*/
|
10106 |
-
function sucuriscan_htaccess_is_standard($rules=
|
10107 |
-
if( $rules===
|
10108 |
$htaccess_path = SucuriScan::get_htaccess_path();
|
10109 |
-
$rules = $htaccess_path ? file_get_contents($htaccess_path) : '';
|
10110 |
}
|
10111 |
|
10112 |
-
if( !empty($rules) ){
|
10113 |
$standard_lines = array(
|
10114 |
'# BEGIN WordPress',
|
10115 |
'<IfModule mod_rewrite\.c>',
|
@@ -10123,22 +11172,22 @@ function sucuriscan_htaccess_is_standard($rules=FALSE){
|
|
10123 |
'# END WordPress',
|
10124 |
);
|
10125 |
$pattern = '';
|
10126 |
-
$standard_lines_total = count($standard_lines);
|
10127 |
-
foreach($standard_lines as $i
|
10128 |
-
if( $i < ($standard_lines_total-1) ){
|
10129 |
$end_of_line = "\n";
|
10130 |
-
}else{
|
10131 |
$end_of_line = '';
|
10132 |
}
|
10133 |
-
$pattern .= sprintf(
|
10134 |
}
|
10135 |
|
10136 |
-
if( preg_match("/{$pattern}/", $rules) ){
|
10137 |
-
return
|
10138 |
}
|
10139 |
}
|
10140 |
|
10141 |
-
return
|
10142 |
}
|
10143 |
|
10144 |
/**
|
@@ -10157,55 +11206,55 @@ function sucuriscan_infosys_wpconfig(){
|
|
10157 |
$ignore_wp_rules = array( 'DB_PASSWORD' );
|
10158 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
10159 |
|
10160 |
-
if( $wp_config_path ){
|
10161 |
$wp_config_rules = array();
|
10162 |
-
$wp_config_content = SucuriScanFileInfo::file_lines($wp_config_path);
|
10163 |
|
10164 |
// Parse the main configuration file and look for constants and global variables.
|
10165 |
-
foreach( (array) $wp_config_content as $line ){
|
10166 |
// Ignore commented lines.
|
10167 |
-
if ( preg_match('/^\s?(#|\/\/)/', $line) ) { continue; }
|
10168 |
|
10169 |
// Detect PHP constants even if the line if indented.
|
10170 |
-
elseif ( preg_match('/define\(/', $line) ) {
|
10171 |
-
$line = preg_replace('/.*define\((.+)\);.*/', '$1', $line);
|
10172 |
-
$line_parts = explode(',', $line, 2);
|
10173 |
}
|
10174 |
|
10175 |
// Detect global variables like the database table prefix.
|
10176 |
-
elseif( preg_match('/^\$[a-zA-Z_]+/', $line) ){
|
10177 |
$line = preg_replace( '/;\s\/\/.*/', ';', $line );
|
10178 |
-
$line_parts = explode('=', $line, 2);
|
10179 |
}
|
10180 |
|
10181 |
// Ignore other lines.
|
10182 |
else { continue; }
|
10183 |
|
10184 |
// Clean and append the rule to the wp_config_rules variable.
|
10185 |
-
if( isset($line_parts) && count($line_parts)==2 ){
|
10186 |
$key_name = '';
|
10187 |
$key_value = '';
|
10188 |
|
10189 |
// TODO: A foreach loop is not really necessary, find a better way.
|
10190 |
-
foreach( $line_parts as $i => $line_part ){
|
10191 |
-
$line_part = trim($line_part);
|
10192 |
-
$line_part = ltrim($line_part, '$');
|
10193 |
-
$line_part = rtrim($line_part, ';');
|
10194 |
|
10195 |
// Remove single/double quotes at the beginning and end of the string.
|
10196 |
-
$line_part = ltrim($line_part, "'");
|
10197 |
-
$line_part = rtrim($line_part, "'");
|
10198 |
-
$line_part = ltrim($line_part, '"');
|
10199 |
-
$line_part = rtrim($line_part, '"');
|
10200 |
|
10201 |
// Assign the clean strings to specific variables.
|
10202 |
-
if( $i==0 ){ $key_name = $line_part; }
|
10203 |
-
if( $i==1 ){
|
10204 |
-
if( defined($key_name) ){
|
10205 |
-
$key_value = constant($key_name);
|
10206 |
|
10207 |
-
if( is_bool($key_value) ){
|
10208 |
-
$key_value = ( $key_value ===
|
10209 |
}
|
10210 |
} else {
|
10211 |
$key_value = $line_part;
|
@@ -10214,34 +11263,34 @@ function sucuriscan_infosys_wpconfig(){
|
|
10214 |
}
|
10215 |
|
10216 |
// Remove the value of sensitive variables like the database password.
|
10217 |
-
if( in_array($key_name, $ignore_wp_rules) ){
|
10218 |
$key_value = 'hidden';
|
10219 |
}
|
10220 |
|
10221 |
// Append the value to the configuration rules.
|
10222 |
-
$wp_config_rules[$key_name] = $key_value;
|
10223 |
}
|
10224 |
}
|
10225 |
|
10226 |
// Pass the WordPress configuration rules to the template and show them.
|
10227 |
$counter = 0;
|
10228 |
-
foreach( $wp_config_rules as $var_name => $var_value ){
|
10229 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
10230 |
$label_css = 'sucuriscan-monospace';
|
10231 |
|
10232 |
-
if( empty($var_value) ){
|
10233 |
$var_value = 'empty';
|
10234 |
$label_css = 'sucuriscan-label-default';
|
10235 |
}
|
10236 |
|
10237 |
-
elseif( $var_value == 'hidden' ){
|
10238 |
$label_css = 'sucuriscan-label-info';
|
10239 |
}
|
10240 |
|
10241 |
$template_variables['WordpressConfig.Total'] += 1;
|
10242 |
$template_variables['WordpressConfig.Rules'] .= SucuriScanTemplate::get_snippet('infosys-wpconfig', array(
|
10243 |
-
'WordpressConfig.VariableName' => SucuriScan::escape($var_name),
|
10244 |
-
'WordpressConfig.VariableValue' => SucuriScan::escape($var_value),
|
10245 |
'WordpressConfig.VariableCssClass' => $label_css,
|
10246 |
'WordpressConfig.CssClass' => $css_class,
|
10247 |
));
|
@@ -10249,7 +11298,7 @@ function sucuriscan_infosys_wpconfig(){
|
|
10249 |
}
|
10250 |
}
|
10251 |
|
10252 |
-
return SucuriScanTemplate::get_section('infosys-wpconfig', $template_variables);
|
10253 |
}
|
10254 |
|
10255 |
/**
|
@@ -10267,10 +11316,10 @@ function sucuriscan_show_cronjobs(){
|
|
10267 |
$schedules = wp_get_schedules();
|
10268 |
$counter = 0;
|
10269 |
|
10270 |
-
foreach( $cronjobs as $timestamp => $cronhooks ){
|
10271 |
-
foreach( (array) $cronhooks as $hook => $events ){
|
10272 |
-
foreach( (array) $events as $key => $event ){
|
10273 |
-
if( empty($event['args']) ){
|
10274 |
$event['args'] = array( '<em>empty</em>' );
|
10275 |
}
|
10276 |
|
@@ -10278,8 +11327,8 @@ function sucuriscan_show_cronjobs(){
|
|
10278 |
$template_variables['Cronjobs.List'] .= SucuriScanTemplate::get_snippet('infosys-cronjobs', array(
|
10279 |
'Cronjob.Hook' => $hook,
|
10280 |
'Cronjob.Schedule' => $event['schedule'],
|
10281 |
-
'Cronjob.NextTime' => SucuriScan::datetime($timestamp),
|
10282 |
-
'Cronjob.Arguments' => SucuriScan::implode(', ', $event['args']),
|
10283 |
'Cronjob.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
|
10284 |
));
|
10285 |
$counter += 1;
|
@@ -10287,7 +11336,7 @@ function sucuriscan_show_cronjobs(){
|
|
10287 |
}
|
10288 |
}
|
10289 |
|
10290 |
-
return SucuriScanTemplate::get_section('infosys-cronjobs', $template_variables);
|
10291 |
}
|
10292 |
|
10293 |
/**
|
@@ -10299,44 +11348,66 @@ function sucuriscan_show_cronjobs(){
|
|
10299 |
* @return void
|
10300 |
*/
|
10301 |
function sucuriscan_infosys_form_submissions(){
|
10302 |
-
if( SucuriScanInterface::check_nonce() ){
|
10303 |
|
10304 |
// Modify the scheduled tasks (run now, remove, re-schedule).
|
10305 |
$allowed_actions = '(runnow|hourly|twicedaily|daily|remove)';
|
10306 |
|
10307 |
-
if( $cronjob_action = SucuriScanRequest::post( ':cronjob_action', $allowed_actions ) ){
|
10308 |
-
$cronjobs = SucuriScanRequest::post(':cronjobs', '_array');
|
10309 |
|
10310 |
-
if( !empty($cronjobs) ){
|
10311 |
-
$total_tasks = count($cronjobs);
|
10312 |
|
10313 |
-
|
10314 |
-
|
10315 |
-
|
10316 |
-
|
10317 |
-
|
10318 |
-
|
10319 |
-
|
10320 |
-
|
10321 |
-
|
10322 |
-
|
10323 |
-
|
10324 |
-
|
10325 |
-
|
10326 |
-
|
10327 |
-
|
10328 |
-
|
10329 |
-
|
10330 |
-
|
10331 |
-
|
10332 |
-
|
10333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10334 |
}
|
10335 |
} else {
|
10336 |
SucuriScanInterface::error( 'No scheduled tasks were selected from the list.' );
|
10337 |
}
|
10338 |
}
|
10339 |
-
|
10340 |
}
|
10341 |
}
|
10342 |
|
@@ -10349,48 +11420,56 @@ function sucuriscan_infosys_errorlogs(){
|
|
10349 |
$template_variables = array(
|
10350 |
'ErrorLog.Path' => '',
|
10351 |
'ErrorLog.Exists' => 'No',
|
10352 |
-
'ErrorLog.NoItemsVisibility' => '
|
10353 |
-
'ErrorLog.DisabledVisibility' => '
|
|
|
|
|
10354 |
'ErrorLog.FileSize' => '0B',
|
10355 |
'ErrorLog.List' => '',
|
10356 |
);
|
10357 |
|
10358 |
$error_log_path = realpath( ABSPATH . '/error_log' );
|
10359 |
-
$
|
10360 |
-
$
|
|
|
|
|
|
|
|
|
|
|
10361 |
|
10362 |
-
if ( $error_log_path
|
10363 |
$template_variables['ErrorLog.Path'] = $error_log_path;
|
10364 |
$template_variables['ErrorLog.Exists'] = 'Yes';
|
10365 |
-
$template_variables['ErrorLog.FileSize'] = SucuriScan::human_filesize( filesize($error_log_path) );
|
10366 |
-
$template_variables['ErrorLog.DisabledVisibility'] = 'hidden';
|
10367 |
|
10368 |
$last_lines = SucuriScanFileInfo::tail_file( $error_log_path, $errorlogs_limit );
|
10369 |
-
$error_logs = SucuriScanFSScanner::parse_error_logs($last_lines);
|
10370 |
-
$error_logs = array_reverse($error_logs);
|
10371 |
-
$
|
10372 |
|
10373 |
foreach ( $error_logs as $error_log ) {
|
10374 |
-
$css_class = ( $
|
10375 |
$template_variables['ErrorLog.List'] .= SucuriScanTemplate::get_snippet('infosys-errorlogs', array(
|
10376 |
'ErrorLog.CssClass' => $css_class,
|
10377 |
'ErrorLog.DateTime' => SucuriScan::datetime( $error_log->timestamp ),
|
10378 |
'ErrorLog.ErrorType' => SucuriScan::escape( $error_log->error_type ),
|
10379 |
-
'ErrorLog.ErrorCode' => SucuriScan::escape($error_log->error_code),
|
10380 |
-
'ErrorLog.ErrorAbbr' => strtoupper( substr($error_log->error_code, 0, 1) ),
|
10381 |
'ErrorLog.ErrorMessage' => SucuriScan::escape( $error_log->error_message ),
|
10382 |
'ErrorLog.FilePath' => SucuriScan::escape( $error_log->file_path ),
|
10383 |
'ErrorLog.LineNumber' => SucuriScan::escape( $error_log->line_number ),
|
10384 |
));
|
10385 |
-
$
|
10386 |
}
|
10387 |
|
10388 |
-
if ( $
|
10389 |
-
$template_variables['ErrorLog.
|
10390 |
}
|
|
|
|
|
10391 |
}
|
10392 |
|
10393 |
-
return SucuriScanTemplate::get_section('infosys-errorlogs', $template_variables);
|
10394 |
}
|
10395 |
|
10396 |
/**
|
@@ -10408,14 +11487,17 @@ function sucuriscan_server_info(){
|
|
10408 |
$info_vars = array(
|
10409 |
'Plugin_version' => SUCURISCAN_VERSION,
|
10410 |
'Plugin_checksum' => SUCURISCAN_PLUGIN_CHECKSUM,
|
10411 |
-
'Last_filesystem_scan' => SucuriScanFSScanner::get_filesystem_runtime(
|
10412 |
'Using_CloudProxy' => 'Unknown',
|
|
|
|
|
10413 |
'HTTP_Host' => 'Unknown',
|
10414 |
'Host_Name' => 'Unknown',
|
10415 |
-
'
|
|
|
10416 |
'Remote_Address' => SucuriScan::get_remote_addr(),
|
10417 |
'Remote_Address_Header' => SucuriScan::get_remote_addr_header(),
|
10418 |
-
'Operating_system' => sprintf('%s (%d Bit)', PHP_OS, PHP_INT_SIZE*8),
|
10419 |
'Server' => 'Unknown',
|
10420 |
'Developer_mode' => 'OFF',
|
10421 |
'Memory_usage' => 'N/A',
|
@@ -10424,29 +11506,34 @@ function sucuriscan_server_info(){
|
|
10424 |
'PHP_version' => PHP_VERSION,
|
10425 |
);
|
10426 |
|
10427 |
-
$proxy_info = SucuriScan::is_behind_cloudproxy(
|
|
|
|
|
10428 |
$info_vars['HTTP_Host'] = $proxy_info['http_host'];
|
10429 |
$info_vars['Host_Name'] = $proxy_info['host_name'];
|
10430 |
$info_vars['Host_Address'] = $proxy_info['host_addr'];
|
|
|
|
|
10431 |
$info_vars['Using_CloudProxy'] = $proxy_info['status'] ? 'Yes' : 'No';
|
|
|
10432 |
|
10433 |
-
if( defined('WP_DEBUG') && WP_DEBUG ){
|
10434 |
$info_vars['Developer_mode'] = 'ON';
|
10435 |
}
|
10436 |
|
10437 |
-
if( function_exists('memory_get_usage') ){
|
10438 |
-
$info_vars['Memory_usage'] = round(memory_get_usage() / 1024 / 1024, 2).' MB';
|
10439 |
}
|
10440 |
|
10441 |
-
if( isset($_SERVER['SERVER_SOFTWARE']) ){
|
10442 |
-
$info_vars['Server'] = SucuriScan::escape($_SERVER['SERVER_SOFTWARE']);
|
10443 |
}
|
10444 |
|
10445 |
-
if( $wpdb ){
|
10446 |
-
$info_vars['MySQL_version'] = $wpdb->get_var('SELECT VERSION() AS version');
|
10447 |
|
10448 |
-
$mysql_info = $wpdb->get_results('SHOW VARIABLES LIKE "sql_mode"');
|
10449 |
-
if( is_array($mysql_info) && !empty($mysql_info[0]->Value) ){
|
10450 |
$info_vars['SQL_mode'] = $mysql_info[0]->Value;
|
10451 |
}
|
10452 |
}
|
@@ -10462,17 +11549,17 @@ function sucuriscan_server_info(){
|
|
10462 |
'max_input_time',
|
10463 |
);
|
10464 |
|
10465 |
-
foreach( $field_names as $php_flag ){
|
10466 |
-
$php_flag_value = SucuriScan::ini_get($php_flag);
|
10467 |
$php_flag_name = 'PHP_' . $php_flag;
|
10468 |
-
$info_vars[$php_flag_name] = $php_flag_value ? $php_flag_value : 'N/A';
|
10469 |
}
|
10470 |
|
10471 |
$counter = 0;
|
10472 |
|
10473 |
-
foreach( $info_vars as $var_name => $var_value ){
|
10474 |
-
$css_class = ( $counter %2 == 0 ) ? '' : 'alternate';
|
10475 |
-
$var_name = str_replace('_', chr(32), $var_name);
|
10476 |
|
10477 |
$template_variables['ServerInfo.Variables'] .= SucuriScanTemplate::get_snippet('infosys-serverinfo', array(
|
10478 |
'ServerInfo.CssClass' => $css_class,
|
@@ -10482,6 +11569,6 @@ function sucuriscan_server_info(){
|
|
10482 |
$counter += 1;
|
10483 |
}
|
10484 |
|
10485 |
-
return SucuriScanTemplate::get_section('infosys-serverinfo', $template_variables);
|
10486 |
}
|
10487 |
|
4 |
Plugin URI: http://wordpress.sucuri.net/
|
5 |
Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
|
6 |
Author: Sucuri, INC
|
7 |
+
Version: 1.7.6
|
8 |
Author URI: http://sucuri.net
|
9 |
*/
|
10 |
|
42 |
);
|
43 |
|
44 |
// Terminate execution if any of the functions mentioned above is not defined.
|
45 |
+
foreach ( $sucuriscan_dependencies as $dependency ) {
|
46 |
+
if ( ! function_exists( $dependency ) ) {
|
47 |
exit(0);
|
48 |
}
|
49 |
}
|
61 |
/**
|
62 |
* Unique name of the plugin through out all the code.
|
63 |
*/
|
64 |
+
define( 'SUCURISCAN', 'sucuriscan' );
|
65 |
|
66 |
/**
|
67 |
* Current version of the plugin's code.
|
68 |
*/
|
69 |
+
define( 'SUCURISCAN_VERSION', '1.7.6' );
|
70 |
|
71 |
/**
|
72 |
* The name of the Sucuri plugin main file.
|
73 |
*/
|
74 |
+
define( 'SUCURISCAN_PLUGIN_FILE', 'sucuri.php' );
|
75 |
|
76 |
/**
|
77 |
* The name of the folder where the plugin's files will be located.
|
78 |
*/
|
79 |
+
define( 'SUCURISCAN_PLUGIN_FOLDER', 'sucuri-scanner' );
|
80 |
|
81 |
/**
|
82 |
* The fullpath where the plugin's files will be located.
|
83 |
*/
|
84 |
+
define( 'SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER );
|
85 |
|
86 |
/**
|
87 |
* The fullpath of the main plugin file.
|
88 |
*/
|
89 |
+
define( 'SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE );
|
90 |
|
91 |
/**
|
92 |
* The local URL where the plugin's files and assets are served.
|
93 |
*/
|
94 |
+
define( 'SUCURISCAN_URL', rtrim( plugin_dir_url( SUCURISCAN_PLUGIN_FILEPATH ), '/' ) );
|
95 |
|
96 |
/**
|
97 |
* Checksum of this file to check the integrity of the plugin.
|
98 |
*/
|
99 |
+
define( 'SUCURISCAN_PLUGIN_CHECKSUM', @md5_file( SUCURISCAN_PLUGIN_FILEPATH ) );
|
100 |
|
101 |
/**
|
102 |
* Remote URL where the public Sucuri API service is running.
|
103 |
*/
|
104 |
+
define( 'SUCURISCAN_API', 'https://wordpress.sucuri.net/api/' );
|
105 |
|
106 |
/**
|
107 |
* Latest version of the public Sucuri API.
|
108 |
*/
|
109 |
+
define( 'SUCURISCAN_API_VERSION', 'v1' );
|
110 |
|
111 |
/**
|
112 |
* Remote URL where the CloudProxy API service is running.
|
113 |
*/
|
114 |
+
define( 'SUCURISCAN_CLOUDPROXY_API', 'https://waf.sucuri.net/api' );
|
115 |
|
116 |
/**
|
117 |
* Latest version of the CloudProxy API.
|
118 |
*/
|
119 |
+
define( 'SUCURISCAN_CLOUDPROXY_API_VERSION', 'v2' );
|
120 |
|
121 |
/**
|
122 |
* The maximum quantity of entries that will be displayed in the last login page.
|
123 |
*/
|
124 |
+
define( 'SUCURISCAN_LASTLOGINS_USERSLIMIT', 25 );
|
125 |
|
126 |
/**
|
127 |
* The maximum quantity of entries that will be displayed in the audit logs page.
|
128 |
*/
|
129 |
+
define( 'SUCURISCAN_AUDITLOGS_PER_PAGE', 50 );
|
130 |
|
131 |
/**
|
132 |
* The maximum quantity of buttons in the paginations.
|
133 |
*/
|
134 |
+
define( 'SUCURISCAN_MAX_PAGINATION_BUTTONS', 20 );
|
135 |
|
136 |
/**
|
137 |
* The minimum quantity of seconds to wait before each filesystem scan.
|
138 |
*/
|
139 |
+
define( 'SUCURISCAN_MINIMUM_RUNTIME', 10800 );
|
140 |
|
141 |
/**
|
142 |
* The life time of the cache for the results of the SiteCheck scans.
|
143 |
*/
|
144 |
+
define( 'SUCURISCAN_SITECHECK_LIFETIME', 1200 );
|
145 |
|
146 |
/**
|
147 |
* The life time of the cache for the results of the get_plugins function.
|
148 |
*/
|
149 |
+
define( 'SUCURISCAN_GET_PLUGINS_LIFETIME', 1800 );
|
150 |
|
151 |
/**
|
152 |
* Plugin's global variables.
|
156 |
* conditional will act as a container helping in the readability of the code
|
157 |
* considering the total number of lines that this file will have.
|
158 |
*/
|
159 |
+
if ( defined( 'SUCURISCAN' ) ){
|
160 |
|
161 |
/**
|
162 |
* List an associative array with the sub-pages of this plugin.
|
187 |
*/
|
188 |
|
189 |
$sucuriscan_notify_options = array(
|
190 |
+
'sucuriscan_notify_plugin_change' => 'Receive email alerts for <strong>Sucuri</strong> plugin changes',
|
191 |
+
'sucuriscan_prettify_mails' => 'Receive email alerts in HTML <em>(there may be issues with some mail services)</em>',
|
192 |
'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
|
193 |
+
'sucuriscan_notify_user_registration' => 'user:Receive email alerts for new user registration',
|
194 |
+
'sucuriscan_notify_success_login' => 'user:Receive email alerts for successful login attempts',
|
195 |
+
'sucuriscan_notify_failed_login' => 'user:Receive email alerts for failed login attempts',
|
196 |
+
'sucuriscan_notify_bruteforce_attack' => 'user:Receive email alerts for password guessing brute force attacks',
|
197 |
+
'sucuriscan_notify_post_publication' => 'Receive email alerts for new content <em>(posts, attachments, forms, etc)</em>',
|
198 |
+
'sucuriscan_notify_website_updated' => 'Receive email alerts when the WordPress version is updated',
|
199 |
+
'sucuriscan_notify_settings_updated' => 'Receive email alerts when your website settings are updated',
|
200 |
+
'sucuriscan_notify_theme_editor' => 'Receive email alerts when a file is modified with theme/plugin editor',
|
201 |
+
'sucuriscan_notify_plugin_installed' => 'plugin:Receive email alerts when a plugin is installed',
|
202 |
+
'sucuriscan_notify_plugin_activated' => 'plugin:Receive email alerts when a plugin is activated',
|
203 |
+
'sucuriscan_notify_plugin_deactivated' => 'plugin:Receive email alerts when a plugin is deactivated',
|
204 |
+
'sucuriscan_notify_plugin_updated' => 'plugin:Receive email alerts when a plugin is updated',
|
205 |
+
'sucuriscan_notify_plugin_deleted' => 'plugin:Receive email alerts when a plugin is deleted',
|
206 |
+
'sucuriscan_notify_widget_added' => 'widget:Receive email alerts when a widget is added to a sidebar',
|
207 |
+
'sucuriscan_notify_widget_deleted' => 'widget:Receive email alerts when a widget is deleted from a sidebar',
|
208 |
+
'sucuriscan_notify_theme_installed' => 'theme:Receive email alerts when a theme is installed',
|
209 |
+
'sucuriscan_notify_theme_activated' => 'theme:Receive email alerts when a theme is activated',
|
210 |
+
'sucuriscan_notify_theme_updated' => 'theme:Receive email alerts when a theme is updated',
|
211 |
+
'sucuriscan_notify_theme_deleted' => 'theme:Receive email alerts when a theme is deleted',
|
212 |
);
|
213 |
|
214 |
$sucuriscan_schedule_allowed = array(
|
248 |
);
|
249 |
|
250 |
$sucuriscan_no_notices_in = array(
|
251 |
+
/* Value of the page parameter to ignore. */
|
252 |
);
|
253 |
|
254 |
$sucuriscan_email_subjects = array(
|
269 |
* information to the Sucuri API service where a security and integrity scan
|
270 |
* will be performed against the hashes provided and the official versions.
|
271 |
*/
|
272 |
+
add_action( 'sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan' );
|
273 |
|
274 |
/**
|
275 |
* Initialize the execute of the main plugin's functions.
|
293 |
*
|
294 |
* @see Class SucuriScanHook
|
295 |
*/
|
296 |
+
if ( class_exists( 'SucuriScanHook' ) ){
|
297 |
$sucuriscan_hooks = array(
|
298 |
// Passes.
|
299 |
'add_attachment',
|
312 |
'user_register',
|
313 |
'wp_login',
|
314 |
'wp_login_failed',
|
315 |
+
'wp_trash_post',
|
316 |
);
|
317 |
|
318 |
+
foreach ( $sucuriscan_hooks as $hook_name ){
|
319 |
$hook_func = 'SucuriScanHook::hook_' . $hook_name;
|
320 |
add_action( $hook_name, $hook_func, 50 );
|
321 |
}
|
335 |
* the plugin to execute the filesystem scans, the project integrity, and the
|
336 |
* email notifications.
|
337 |
*/
|
338 |
+
add_action( 'admin_notices', 'SucuriScanInterface::setup_notice' );
|
|
|
339 |
|
340 |
/**
|
341 |
* Heartbeat API
|
381 |
* @param string $var_name Name of a variable with an optional colon at the beginning.
|
382 |
* @return string Full name of the variable with the extra characters (if needed).
|
383 |
*/
|
384 |
+
public static function variable_prefix( $var_name = '' ){
|
385 |
+
if ( preg_match( '/^:(.*)/', $var_name, $match ) ){
|
386 |
$var_name = sprintf( '%s_%s', SUCURISCAN, $match[1] );
|
387 |
}
|
388 |
|
395 |
* @param string $property The configuration option name.
|
396 |
* @return string Value of the configuration option as a string on success.
|
397 |
*/
|
398 |
+
public static function ini_get( $property = '' ){
|
399 |
+
$ini_value = ini_get( $property );
|
400 |
|
401 |
+
if ( empty($ini_value) || is_null( $ini_value ) ){
|
402 |
+
switch ( $property ){
|
403 |
case 'error_log': $ini_value = 'error_log'; break;
|
404 |
case 'safe_mode': $ini_value = 'Off'; break;
|
405 |
case 'allow_url_fopen': $ini_value = '1'; break;
|
421 |
* @param string $text The text which is to be encoded.
|
422 |
* @return string The encoded text with HTML entities.
|
423 |
*/
|
424 |
+
public static function escape( $text = '' ){
|
425 |
// Escape the value of the variable using a built-in function if possible.
|
426 |
+
if ( function_exists( 'esc_attr' ) ){
|
427 |
+
$text = esc_attr( $text );
|
428 |
} else {
|
429 |
+
$text = htmlspecialchars( $text );
|
430 |
}
|
431 |
|
432 |
return $text;
|
438 |
* @param integer $length Length of the string that will be generated.
|
439 |
* @return string The random string generated.
|
440 |
*/
|
441 |
+
public static function random_char( $length = 4 ){
|
442 |
$string = '';
|
443 |
+
$chars = range( 'a','z' );
|
444 |
|
445 |
+
for ( $i = 0; $i < $length; $i++ ){
|
446 |
+
$string .= $chars[ rand( 0, count( $chars ) -1 ) ];
|
447 |
}
|
448 |
|
449 |
return $string;
|
458 |
* @param integer $decimals How many decimals should be returned after the translation.
|
459 |
* @return string Human readable representation of the given number in Kylo, Mega, Giga, etc.
|
460 |
*/
|
461 |
+
public static function human_filesize( $bytes = 0, $decimals = 2 ){
|
462 |
$sz = 'BKMGTP';
|
463 |
+
$factor = floor( (strlen( $bytes ) - 1) / 3 );
|
464 |
+
return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . @$sz[ $factor ];
|
465 |
}
|
466 |
|
467 |
/**
|
471 |
* @param string $path The relative path that needs to be completed to get the absolute path.
|
472 |
* @return string The full filesystem path including the directory specified.
|
473 |
*/
|
474 |
+
public static function datastore_folder_path( $path = '' ){
|
475 |
+
$datastore_path = SucuriScanOption::get_option( ':datastore_path' );
|
476 |
+
$datastore_dirname = 'sucuri';
|
477 |
|
478 |
// Use the uploads folder by default.
|
479 |
if ( empty($datastore_path) ) {
|
480 |
+
$uploads_path = false;
|
481 |
+
|
482 |
+
// Multisite installations may have different paths.
|
483 |
+
if ( function_exists( 'wp_upload_dir' ) ) {
|
484 |
+
$upload_dir = wp_upload_dir();
|
485 |
+
|
486 |
+
if ( isset($upload_dir['basedir']) ) {
|
487 |
+
$uploads_path = rtrim( $upload_dir['basedir'], '/' );
|
488 |
+
}
|
489 |
}
|
490 |
|
491 |
+
if ( $uploads_path === false ) {
|
492 |
+
if ( defined( 'WP_CONTENT_DIR' ) ) {
|
493 |
+
$uploads_path = rtrim( WP_CONTENT_DIR, '/' ) . '/uploads';
|
494 |
+
} else {
|
495 |
+
$uploads_path = rtrim( ABSPATH, '/' ) . '/wp-content/uploads';
|
496 |
+
}
|
497 |
}
|
498 |
|
499 |
+
$datastore_path = $uploads_path . '/' . $datastore_dirname;
|
500 |
SucuriScanOption::update_option( ':datastore_path', $datastore_path );
|
501 |
}
|
502 |
|
503 |
+
$wp_filepath = rtrim( $datastore_path, '/' ) . '/' . $path;
|
504 |
|
505 |
return $wp_filepath;
|
506 |
}
|
511 |
* @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
|
512 |
*/
|
513 |
public static function is_multisite(){
|
514 |
+
if (
|
515 |
+
function_exists( 'is_multisite' )
|
516 |
&& is_multisite()
|
517 |
){
|
518 |
+
return true;
|
519 |
}
|
520 |
|
521 |
+
return false;
|
522 |
}
|
523 |
|
524 |
/**
|
527 |
* @return string The version number of Wordpress installed.
|
528 |
*/
|
529 |
public static function site_version(){
|
530 |
+
global $wp_version;
|
531 |
+
|
532 |
+
if ( $wp_version === null ) {
|
533 |
+
$wp_version_path = ABSPATH . WPINC . '/version.php';
|
534 |
+
|
535 |
+
if ( file_exists( $wp_version_path ) ) {
|
536 |
+
include($wp_version_path);
|
537 |
+
$wp_version = isset($wp_version) ? $wp_version : '0.0';
|
538 |
+
}
|
539 |
|
540 |
+
else {
|
541 |
+
$option_version = get_option( 'version' );
|
542 |
+
$wp_version = $option_version ? $option_version : '0.0';
|
543 |
+
}
|
|
|
544 |
}
|
545 |
|
546 |
+
return $wp_version;
|
|
|
547 |
}
|
548 |
|
549 |
/**
|
552 |
* @return string Absolute path of the WordPress configuration file.
|
553 |
*/
|
554 |
public static function get_wpconfig_path(){
|
555 |
+
if ( defined( 'ABSPATH' ) ){
|
556 |
$file_path = ABSPATH . '/wp-config.php';
|
557 |
|
558 |
// if wp-config.php doesn't exist, or is not readable check one directory up.
|
559 |
+
if ( ! file_exists( $file_path ) ){
|
560 |
$file_path = ABSPATH . '/../wp-config.php';
|
561 |
}
|
562 |
|
563 |
// Remove duplicated double slashes.
|
564 |
+
$file_path = realpath( $file_path );
|
565 |
|
566 |
+
if ( $file_path ){
|
567 |
return $file_path;
|
568 |
}
|
569 |
}
|
570 |
|
571 |
+
return false;
|
572 |
}
|
573 |
|
574 |
/**
|
577 |
* @return string Absolute path of the main WordPress htaccess file.
|
578 |
*/
|
579 |
public static function get_htaccess_path(){
|
580 |
+
if ( defined( 'ABSPATH' ) ){
|
581 |
$base_dirs = array(
|
582 |
+
rtrim( ABSPATH, '/' ),
|
583 |
+
dirname( ABSPATH ),
|
584 |
+
dirname( dirname( ABSPATH ) ),
|
585 |
);
|
586 |
|
587 |
+
foreach ( $base_dirs as $base_dir ){
|
588 |
+
$htaccess_path = sprintf( '%s/.htaccess', $base_dir );
|
589 |
|
590 |
+
if ( file_exists( $htaccess_path ) ){
|
591 |
return $htaccess_path;
|
592 |
}
|
593 |
}
|
594 |
}
|
595 |
|
596 |
+
return false;
|
597 |
}
|
598 |
|
599 |
/**
|
602 |
* @return string Secret key definition pattern.
|
603 |
*/
|
604 |
public static function secret_key_pattern(){
|
605 |
+
return '/define\(\'([A-Z_]+)\',(\s+)?\'(.*)\'\);/';
|
606 |
}
|
607 |
|
608 |
/**
|
611 |
* @param boolean $return_header Whether the header name where the address was found must be returned.
|
612 |
* @return string The real ip address of the user in the current request.
|
613 |
*/
|
614 |
+
public static function get_remote_addr( $return_header = false ){
|
615 |
$remote_addr = '';
|
616 |
$header_used = 'unknown';
|
617 |
|
618 |
+
if (
|
619 |
+
self::support_reverse_proxy()
|
620 |
+
|| self::is_behind_cloudproxy()
|
621 |
+
) {
|
622 |
$alternatives = array(
|
623 |
'HTTP_X_SUCURI_CLIENTIP',
|
624 |
'HTTP_X_REAL_IP',
|
631 |
'REMOTE_ADDR',
|
632 |
);
|
633 |
|
634 |
+
foreach ( $alternatives as $alternative ){
|
635 |
+
if (
|
636 |
+
isset($_SERVER[ $alternative ])
|
637 |
+
&& self::is_valid_ip( $_SERVER[ $alternative ] )
|
638 |
){
|
639 |
+
$remote_addr = $_SERVER[ $alternative ];
|
640 |
$header_used = $alternative;
|
641 |
break;
|
642 |
}
|
643 |
}
|
644 |
}
|
645 |
|
646 |
+
elseif ( isset($_SERVER['REMOTE_ADDR']) ) {
|
647 |
$remote_addr = $_SERVER['REMOTE_ADDR'];
|
648 |
$header_used = 'REMOTE_ADDR';
|
649 |
}
|
650 |
|
651 |
+
if ( $remote_addr == '::1' ){
|
652 |
$remote_addr = '127.0.0.1';
|
653 |
}
|
654 |
|
655 |
+
if ( $return_header ){
|
656 |
return $header_used;
|
657 |
}
|
658 |
|
665 |
* @return string The HTTP header used to retrieve the remote address.
|
666 |
*/
|
667 |
public static function get_remote_addr_header(){
|
668 |
+
return self::get_remote_addr( true );
|
669 |
}
|
670 |
|
671 |
/**
|
674 |
* @return string The user-agent from the current request.
|
675 |
*/
|
676 |
public static function get_user_agent(){
|
677 |
+
if ( isset($_SERVER['HTTP_USER_AGENT']) ){
|
678 |
+
return self::escape( $_SERVER['HTTP_USER_AGENT'] );
|
679 |
}
|
680 |
|
681 |
+
return false;
|
682 |
}
|
683 |
|
684 |
/**
|
686 |
*
|
687 |
* @return string The domain of the current site.
|
688 |
*/
|
689 |
+
public static function get_domain( $return_tld = false ){
|
690 |
+
if ( function_exists( 'get_site_url' ) ) {
|
691 |
$site_url = get_site_url();
|
692 |
+
$pattern = '/([fhtps]+:\/\/)?([^:\/]+)(:[0-9:]+)?(\/.*)?/';
|
693 |
+
$replacement = ( $return_tld === true ) ? '$2' : '$2$3$4';
|
694 |
+
$domain_name = @preg_replace( $pattern, $replacement, $site_url );
|
|
|
695 |
|
696 |
+
return $domain_name;
|
697 |
}
|
698 |
|
699 |
+
return false;
|
700 |
+
}
|
701 |
+
|
702 |
+
/**
|
703 |
+
* Get top-level domain (TLD) of the website.
|
704 |
+
*
|
705 |
+
* @return string Top-level domain (TLD) of the website.
|
706 |
+
*/
|
707 |
+
public static function get_top_level_domain(){
|
708 |
+
return self::get_domain( true );
|
709 |
+
}
|
710 |
|
711 |
+
/**
|
712 |
+
* Check whether reverse proxy servers must be supported.
|
713 |
+
*
|
714 |
+
* @return boolean TRUE if reverse proxies must be supported, FALSE otherwise.
|
715 |
+
*/
|
716 |
+
public static function support_reverse_proxy(){
|
717 |
+
return (bool) ( SucuriScanOption::get_option( ':revproxy' ) === 'enabled' );
|
718 |
}
|
719 |
|
720 |
/**
|
723 |
* @param boolean $verbose Return an array with the hostname, address, and status, or not.
|
724 |
* @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
|
725 |
*/
|
726 |
+
public static function is_behind_cloudproxy( $verbose = false ){
|
727 |
+
$http_host = self::get_top_level_domain();
|
728 |
+
$host_by_addr = @gethostbyname( $http_host );
|
729 |
+
$host_by_name = @gethostbyaddr( $host_by_addr );
|
730 |
+
$status = (bool) preg_match( '/^cloudproxy[0-9]+\.sucuri\.net$/', $host_by_name );
|
731 |
|
732 |
/*
|
733 |
* If the DNS reversion failed but the CloudProxy API key is set, then consider
|
735 |
* checking, but that is not something that will affect us, only the client.
|
736 |
*/
|
737 |
if (
|
738 |
+
$status === false
|
739 |
&& SucuriScanAPI::get_cloudproxy_key()
|
740 |
) {
|
741 |
+
$status = true;
|
742 |
}
|
743 |
|
744 |
+
if ( $verbose ){
|
745 |
return array(
|
746 |
'http_host' => $http_host,
|
747 |
'host_name' => $host_by_name,
|
761 |
* @return string The administrator email address.
|
762 |
*/
|
763 |
public static function get_site_email(){
|
764 |
+
$email = get_option( 'admin_email' );
|
765 |
|
766 |
+
if ( self::is_valid_email( $email ) ){
|
767 |
return $email;
|
768 |
}
|
769 |
|
770 |
+
return false;
|
771 |
}
|
772 |
|
773 |
/**
|
776 |
* @return integer Return current Unix timestamp.
|
777 |
*/
|
778 |
public static function local_time(){
|
779 |
+
if ( function_exists( 'current_time' ) ){
|
780 |
+
return current_time( 'timestamp' );
|
781 |
} else {
|
782 |
return time();
|
783 |
}
|
793 |
* @param integer $timestamp Unix timestamp.
|
794 |
* @return string The date, translated if locale specifies it.
|
795 |
*/
|
796 |
+
public static function datetime( $timestamp = 0 ){
|
797 |
+
if ( is_numeric( $timestamp ) && $timestamp > 0 ){
|
798 |
+
$date_format = get_option( 'date_format' );
|
799 |
+
$time_format = get_option( 'time_format' );
|
800 |
$timezone_format = sprintf( '%s %s', $date_format, $time_format );
|
801 |
|
802 |
return date_i18n( $timezone_format, $timestamp );
|
803 |
}
|
804 |
|
805 |
+
return null;
|
806 |
}
|
807 |
|
808 |
/**
|
813 |
public static function current_datetime(){
|
814 |
$local_time = self::local_time();
|
815 |
|
816 |
+
return self::datetime( $local_time );
|
817 |
}
|
818 |
|
819 |
/**
|
822 |
* @param integer $timestamp The Unix time number of the date/time before now.
|
823 |
* @return string The time passed since the timestamp specified.
|
824 |
*/
|
825 |
+
public static function time_ago( $timestamp = 0 ){
|
826 |
+
if ( ! is_numeric( $timestamp ) ){
|
827 |
+
$timestamp = strtotime( $timestamp );
|
828 |
}
|
829 |
|
830 |
$local_time = self::local_time();
|
831 |
+
$diff = abs( $local_time - intval( $timestamp ) );
|
832 |
|
833 |
+
if ( $diff == 0 ){ return 'just now'; }
|
834 |
|
835 |
$intervals = array(
|
836 |
+
1 => array( 'year', 31556926, ),
|
837 |
+
$diff < 31556926 => array( 'month', 2628000, ),
|
838 |
+
$diff < 2629744 => array( 'week', 604800, ),
|
839 |
+
$diff < 604800 => array( 'day', 86400, ),
|
840 |
+
$diff < 86400 => array( 'hour', 3600, ),
|
841 |
+
$diff < 3600 => array( 'minute', 60, ),
|
842 |
+
$diff < 60 => array( 'second', 1, ),
|
843 |
);
|
844 |
|
845 |
+
$value = floor( $diff / $intervals[1][1] );
|
846 |
$time_ago = sprintf(
|
847 |
'%s %s%s ago',
|
848 |
$value,
|
861 |
* @param string $text A text containing alpha-numeric and special characters.
|
862 |
* @return string A valid variable name.
|
863 |
*/
|
864 |
+
public static function human2var( $text = '' ){
|
865 |
+
$text = strtolower( $text );
|
866 |
$pattern = '/[^a-z0-9_]/';
|
867 |
+
$var_name = preg_replace( $pattern, '_', $text );
|
868 |
|
869 |
return $var_name;
|
870 |
}
|
875 |
* @param string $data The data that will be checked.
|
876 |
* @return boolean TRUE if the data was serialized, FALSE otherwise.
|
877 |
*/
|
878 |
+
public static function is_serialized( $data = '' ){
|
879 |
+
return ( is_string( $data ) && preg_match( '/^(a|O):[0-9]+:.+/', $data ) );
|
880 |
}
|
881 |
|
882 |
/**
|
885 |
* @param string $remote_addr The host IP address.
|
886 |
* @return boolean Whether the IP address specified is valid or not.
|
887 |
*/
|
888 |
+
public static function is_valid_ip( $remote_addr = '' ){
|
889 |
// Check for IPv4 and IPv6.
|
890 |
+
if ( function_exists( 'filter_var' ) ){
|
891 |
return (bool) filter_var( $remote_addr, FILTER_VALIDATE_IP );
|
892 |
}
|
893 |
|
894 |
// Assuming older version of PHP and server, so only will check for IPv4.
|
895 |
+
elseif ( strlen( $remote_addr ) >= 7 ) {
|
896 |
$pattern = '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/';
|
897 |
|
898 |
+
if ( preg_match( $pattern, $remote_addr, $match ) ){
|
899 |
+
for ( $i = 0; $i < 4; $i++ ){
|
900 |
+
if ( $match[ $i ] > 255 ){ return false; }
|
901 |
}
|
902 |
|
903 |
+
return true;
|
904 |
}
|
905 |
}
|
906 |
|
907 |
+
return false;
|
908 |
}
|
909 |
|
910 |
|
914 |
* @param string $remote_addr The supposed ip address that will be checked.
|
915 |
* @return boolean Either TRUE or FALSE if the ip address specified is valid or not.
|
916 |
*/
|
917 |
+
public static function is_valid_cidr( $remote_addr = '' ){
|
918 |
+
if ( preg_match( '/^([0-9\.]{7,15})\/(8|16|24)$/', $remote_addr, $match ) ) {
|
919 |
+
if ( self::is_valid_ip( $match[1] ) ) {
|
920 |
+
return true;
|
921 |
}
|
922 |
}
|
923 |
|
924 |
+
return false;
|
925 |
}
|
926 |
|
927 |
/**
|
930 |
* @param string $remote_addr The supposed ip address that will be formatted.
|
931 |
* @return array Clean address, CIDR range, and CIDR format; FALSE otherwise.
|
932 |
*/
|
933 |
+
public static function get_ip_info( $remote_addr = '' ){
|
934 |
if ( $remote_addr ) {
|
935 |
$addr_info = array();
|
936 |
$ip_parts = explode( '/', $remote_addr );
|
938 |
$addr_info['cidr_range'] = isset($ip_parts[1]) ? $ip_parts[1] : '32';
|
939 |
$addr_info['cidr_format'] = $addr_info['remote_addr'] . '/' . $addr_info['cidr_range'];
|
940 |
|
|
|
941 |
return $addr_info;
|
942 |
}
|
943 |
|
944 |
+
return false;
|
945 |
}
|
946 |
|
947 |
/**
|
956 |
* @param string $email The string that will be validated as an email address.
|
957 |
* @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
|
958 |
*/
|
959 |
+
public static function is_valid_email( $email = '' ){
|
960 |
+
if ( function_exists( 'filter_var' ) ){
|
961 |
+
return (bool) filter_var( $email, FILTER_VALIDATE_EMAIL );
|
962 |
} else {
|
963 |
$pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
|
964 |
+
return (bool) preg_match( $pattern, $email );
|
965 |
}
|
966 |
}
|
967 |
|
972 |
* @param boolean $as_array TRUE to return the list of valid email addresses as an array.
|
973 |
* @return string All the valid email addresses separated by a comma.
|
974 |
*/
|
975 |
+
public static function get_valid_email( $email = '', $as_array = false ){
|
976 |
$valid_emails = array();
|
977 |
|
978 |
+
if ( strpos( $email, ',' ) !== false ){
|
979 |
+
$addresses = explode( ',', $email );
|
980 |
|
981 |
+
foreach ( $addresses as $address ){
|
982 |
+
$address = trim( $address );
|
983 |
|
984 |
+
if ( self::is_valid_email( $address ) ){
|
985 |
$valid_emails[] = $address;
|
986 |
}
|
987 |
}
|
988 |
}
|
989 |
|
990 |
+
elseif ( self::is_valid_email( $email ) ){
|
991 |
$valid_emails[] = $email;
|
992 |
}
|
993 |
|
994 |
+
if ( ! empty($valid_emails) ){
|
995 |
+
if ( $as_array === true ){
|
996 |
return $valid_emails;
|
997 |
}
|
998 |
|
999 |
+
return self::implode( ', ', $valid_emails );
|
1000 |
}
|
1001 |
|
1002 |
+
return false;
|
1003 |
}
|
1004 |
|
1005 |
/**
|
1009 |
* @param integer $length Maximum length of the returned string, default is 10.
|
1010 |
* @return string Short version of the text specified.
|
1011 |
*/
|
1012 |
+
public static function excerpt( $text = '', $length = 10 ){
|
1013 |
+
$text_length = strlen( $text );
|
1014 |
|
1015 |
+
if ( $text_length > $length ){
|
1016 |
return substr( $text, 0, $length ) . '...';
|
1017 |
}
|
1018 |
|
1026 |
* @param integer $length Maximum length of the returned string, default is 10.
|
1027 |
* @return string Short version of the text specified.
|
1028 |
*/
|
1029 |
+
public static function excerpt_rev( $text = '', $length = 10 ){
|
1030 |
+
$str_reversed = strrev( $text );
|
1031 |
$str_excerpt = self::excerpt( $str_reversed, $length );
|
1032 |
+
$text_transformed = strrev( $str_excerpt );
|
1033 |
|
1034 |
return $text_transformed;
|
1035 |
}
|
1040 |
* @param array $list An array or multidimensional array of different values.
|
1041 |
* @return boolean TRUE if the list is multidimensional, FALSE otherwise.
|
1042 |
*/
|
1043 |
+
public static function is_multi_list( $list = array() ){
|
1044 |
+
if ( ! empty($list) ){
|
1045 |
+
foreach ( $list as $item ){
|
1046 |
+
if ( is_array( $item ) ){
|
1047 |
+
return true;
|
1048 |
}
|
1049 |
}
|
1050 |
}
|
1051 |
|
1052 |
+
return false;
|
1053 |
}
|
1054 |
|
1055 |
/**
|
1059 |
* @param array $list The array of strings to implode.
|
1060 |
* @return string String of all the items in the list, with the separator between them.
|
1061 |
*/
|
1062 |
+
public static function implode( $separator = '', $list = array() ){
|
1063 |
+
if ( self::is_multi_list( $list ) ){
|
1064 |
$pieces = array();
|
1065 |
|
1066 |
+
foreach ( $list as $items ){
|
1067 |
$pieces[] = @implode( $separator, $items );
|
1068 |
}
|
1069 |
|
1081 |
* @param string $current_page Identifier of the current page.
|
1082 |
* @return boolean TRUE if the current page must not have noticies.
|
1083 |
*/
|
1084 |
+
public static function no_notices_here( $current_page = false ){
|
1085 |
global $sucuriscan_no_notices_in;
|
1086 |
|
1087 |
if ( $current_page === false ) {
|
1088 |
+
$current_page = SucuriScanRequest::get( 'page' );
|
1089 |
}
|
1090 |
|
1091 |
if (
|
1092 |
isset($sucuriscan_no_notices_in)
|
1093 |
+
&& is_array( $sucuriscan_no_notices_in )
|
1094 |
+
&& ! empty($sucuriscan_no_notices_in)
|
1095 |
) {
|
1096 |
+
return (bool) in_array( $current_page, $sucuriscan_no_notices_in );
|
1097 |
}
|
1098 |
|
1099 |
return false;
|
1100 |
}
|
1101 |
|
1102 |
+
/**
|
1103 |
+
* Check whether the site is running over the Nginx web server.
|
1104 |
+
*
|
1105 |
+
* @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
|
1106 |
+
*/
|
1107 |
+
public static function is_nginx_server(){
|
1108 |
+
return (bool) preg_match( '/^nginx(\/[0-9\.]+)?$/', @$_SERVER['SERVER_SOFTWARE'] );
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
/**
|
1112 |
+
* Check whether the site is running over the Nginx web server.
|
1113 |
+
*
|
1114 |
+
* @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
|
1115 |
+
*/
|
1116 |
+
public static function is_iis_server(){
|
1117 |
+
return (bool) preg_match( '/Microsoft-IIS/i', @$_SERVER['SERVER_SOFTWARE'] );
|
1118 |
+
}
|
1119 |
+
|
1120 |
}
|
1121 |
|
1122 |
/**
|
1139 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1140 |
* @return string The value stored in the specified key inside the global _GET variable.
|
1141 |
*/
|
1142 |
+
public static function request( $list = array(), $key = '', $pattern = '' ){
|
1143 |
+
$key = self::variable_prefix( $key );
|
1144 |
|
1145 |
+
if (
|
1146 |
+
is_array( $list )
|
1147 |
+
&& is_string( $key )
|
1148 |
+
&& isset($list[ $key ])
|
1149 |
){
|
1150 |
// Select the key from the list and escape its content.
|
1151 |
+
$key_value = $list[ $key ];
|
1152 |
|
1153 |
// Define regular expressions for specific value types.
|
1154 |
+
if ( $pattern === '' ){
|
1155 |
$pattern = '/.*/';
|
1156 |
} else {
|
1157 |
+
switch ( $pattern ){
|
1158 |
case '_nonce': $pattern = '/^[a-z0-9]{10}$/'; break;
|
1159 |
case '_page': $pattern = '/^[a-z_]+$/'; break;
|
1160 |
case '_array': $pattern = '_array'; break;
|
1164 |
}
|
1165 |
|
1166 |
// If the request data is an array, then only cast the value.
|
1167 |
+
if ( $pattern == '_array' && is_array( $key_value ) ){
|
1168 |
return (array) $key_value;
|
1169 |
}
|
1170 |
|
1171 |
// Check the format of the request data with a regex defined above.
|
1172 |
+
if ( @preg_match( $pattern, $key_value ) ){
|
1173 |
+
return self::escape( $key_value );
|
1174 |
}
|
1175 |
}
|
1176 |
|
1177 |
+
return false;
|
1178 |
}
|
1179 |
|
1180 |
/**
|
1185 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1186 |
* @return string The value stored in the specified key inside the global _GET variable.
|
1187 |
*/
|
1188 |
+
public static function get( $key = '', $pattern = '' ){
|
1189 |
return self::request( $_GET, $key, $pattern );
|
1190 |
}
|
1191 |
|
1197 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1198 |
* @return string The value stored in the specified key inside the global _POST variable.
|
1199 |
*/
|
1200 |
+
public static function post( $key = '', $pattern = '' ){
|
1201 |
return self::request( $_POST, $key, $pattern );
|
1202 |
}
|
1203 |
|
1209 |
* @param string $pattern Optional pattern to match allowed values in the requested key.
|
1210 |
* @return string The value stored in the specified key inside the global _POST variable.
|
1211 |
*/
|
1212 |
+
public static function get_or_post( $key = '', $pattern = '' ){
|
1213 |
return self::request( $_REQUEST, $key, $pattern );
|
1214 |
}
|
1215 |
|
1241 |
*
|
1242 |
* @var boolean
|
1243 |
*/
|
1244 |
+
public $ignore_files = true;
|
1245 |
|
1246 |
/**
|
1247 |
* Whether the list of folders that can be ignored from the filesystem scan will
|
1250 |
*
|
1251 |
* @var boolean
|
1252 |
*/
|
1253 |
+
public $ignore_directories = true;
|
1254 |
|
1255 |
/**
|
1256 |
* A list of ignored directory paths, these folders will be skipped during the
|
1267 |
*
|
1268 |
* @var boolean
|
1269 |
*/
|
1270 |
+
public $run_recursively = true;
|
1271 |
|
1272 |
/**
|
1273 |
* Class constructor.
|
1285 |
* @param boolean $as_array Whether the result of the operation will be returned as an array or string.
|
1286 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1287 |
*/
|
1288 |
+
public function get_directory_tree_md5( $directory = '', $as_array = false ){
|
1289 |
$project_signatures = '';
|
1290 |
$abs_path = rtrim( ABSPATH, '/' );
|
1291 |
+
$files = $this->get_directory_tree( $directory );
|
1292 |
|
1293 |
+
if ( $as_array ){
|
1294 |
$project_signatures = array();
|
1295 |
}
|
1296 |
|
1297 |
+
if ( $files ){
|
1298 |
+
sort( $files );
|
1299 |
|
1300 |
+
foreach ( $files as $filepath ){
|
1301 |
+
$file_checksum = @md5_file( $filepath );
|
1302 |
+
$filesize = @filesize( $filepath );
|
1303 |
|
1304 |
+
if ( $as_array ){
|
1305 |
$basename = str_replace( $abs_path . '/', '', $filepath );
|
1306 |
+
$project_signatures[ $basename ] = array(
|
1307 |
'filepath' => $filepath,
|
1308 |
'checksum' => $file_checksum,
|
1309 |
'filesize' => $filesize,
|
1310 |
+
'created_at' => @filectime( $filepath ),
|
1311 |
+
'modified_at' => @filemtime( $filepath ),
|
1312 |
);
|
1313 |
} else {
|
1314 |
$filepath = str_replace( $abs_path, $abs_path . '/', $filepath );
|
1316 |
"%s%s%s%s\n",
|
1317 |
$file_checksum,
|
1318 |
$filesize,
|
1319 |
+
chr( 32 ),
|
1320 |
$filepath
|
1321 |
);
|
1322 |
}
|
1334 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1335 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1336 |
*/
|
1337 |
+
public function get_directory_tree( $directory = '' ){
|
1338 |
+
if ( file_exists( $directory ) && is_dir( $directory ) ){
|
1339 |
$tree = array();
|
1340 |
|
1341 |
// Check whether the ignore scanning feature is enabled or not.
|
1342 |
+
if ( SucuriScanFSScanner::will_ignore_scanning() ){
|
1343 |
$this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
|
1344 |
}
|
1345 |
|
1346 |
+
switch ( $this->scan_interface ){
|
1347 |
case 'spl':
|
1348 |
+
if ( $this->is_spl_available() ){
|
1349 |
+
$tree = $this->get_directory_tree_with_spl( $directory );
|
1350 |
} else {
|
1351 |
$this->scan_interface = 'opendir';
|
1352 |
+
$tree = $this->get_directory_tree( $directory );
|
1353 |
}
|
1354 |
break;
|
1355 |
|
1356 |
case 'glob':
|
1357 |
+
$tree = $this->get_directory_tree_with_glob( $directory );
|
1358 |
break;
|
1359 |
|
1360 |
case 'opendir':
|
1361 |
+
$tree = $this->get_directory_tree_with_opendir( $directory );
|
1362 |
break;
|
1363 |
|
1364 |
default:
|
1365 |
$this->scan_interface = 'spl';
|
1366 |
+
$tree = $this->get_directory_tree( $directory );
|
1367 |
break;
|
1368 |
}
|
1369 |
|
1370 |
return $tree;
|
1371 |
}
|
1372 |
|
1373 |
+
return false;
|
1374 |
}
|
1375 |
|
1376 |
/**
|
1380 |
* @param string $directory Directory where the scanner is located at the moment.
|
1381 |
* @return array List of file paths where the file was found.
|
1382 |
*/
|
1383 |
+
public function find_file( $filename = '', $directory = null ){
|
1384 |
$file_paths = array();
|
1385 |
|
1386 |
+
if (
|
1387 |
+
is_null( $directory )
|
1388 |
+
&& defined( 'ABSPATH' )
|
1389 |
){
|
1390 |
$directory = ABSPATH;
|
1391 |
}
|
1392 |
|
1393 |
+
if ( is_dir( $directory ) ){
|
1394 |
$dir_tree = $this->get_directory_tree( $directory );
|
1395 |
|
1396 |
+
foreach ( $dir_tree as $filepath ){
|
1397 |
+
if ( stripos( $filepath, $filename ) !== false ){
|
1398 |
$file_paths[] = $filepath;
|
1399 |
}
|
1400 |
}
|
1413 |
* @return boolean Whether the PHP class "SplFileObject" is available or not.
|
1414 |
*/
|
1415 |
public static function is_spl_available(){
|
1416 |
+
return (bool) class_exists( 'SplFileObject' );
|
1417 |
}
|
1418 |
|
1419 |
/**
|
1430 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1431 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1432 |
*/
|
1433 |
+
private function get_directory_tree_with_spl( $directory = '' ){
|
1434 |
$files = array();
|
1435 |
+
$filepath = realpath( $directory );
|
1436 |
|
1437 |
+
if ( ! class_exists( 'FilesystemIterator' ) ){
|
1438 |
+
return $this->get_directory_tree( $directory, 'opendir' );
|
1439 |
}
|
1440 |
|
1441 |
+
if ( $this->run_recursively ){
|
1442 |
$flags = FilesystemIterator::KEY_AS_PATHNAME
|
1443 |
| FilesystemIterator::CURRENT_AS_FILEINFO
|
1444 |
| FilesystemIterator::SKIP_DOTS
|
1445 |
| FilesystemIterator::UNIX_PATHS;
|
1446 |
$objects = new RecursiveIteratorIterator(
|
1447 |
+
new RecursiveDirectoryIterator( $filepath, $flags ),
|
1448 |
RecursiveIteratorIterator::SELF_FIRST,
|
1449 |
RecursiveIteratorIterator::CATCH_GET_CHILD
|
1450 |
);
|
1451 |
} else {
|
1452 |
+
$objects = new DirectoryIterator( $filepath );
|
1453 |
}
|
1454 |
|
1455 |
+
foreach ( $objects as $filepath => $fileinfo ){
|
1456 |
+
if ( $this->run_recursively ){
|
1457 |
+
$directory = dirname( $filepath );
|
1458 |
$filename = $fileinfo->getFilename();
|
1459 |
} else {
|
1460 |
+
if ( $fileinfo->isDot() || $fileinfo->isDir() ){ continue; }
|
1461 |
|
1462 |
$directory = $fileinfo->getPath();
|
1463 |
$filename = $fileinfo->getFilename();
|
1464 |
$filepath = $directory . '/' . $filename;
|
1465 |
}
|
1466 |
|
1467 |
+
if ( $this->ignore_folderpath( $directory, $filename ) ){ continue; }
|
1468 |
+
if ( $this->ignore_filepath( $filename ) ){ continue; }
|
1469 |
|
1470 |
$files[] = $filepath;
|
1471 |
}
|
1481 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1482 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1483 |
*/
|
1484 |
+
private function get_directory_tree_with_glob( $directory = '' ){
|
1485 |
$files = array();
|
1486 |
|
1487 |
+
$directory_pattern = sprintf( '%s/*', rtrim( $directory,'/' ) );
|
1488 |
+
$files_found = glob( $directory_pattern );
|
1489 |
|
1490 |
+
if ( is_array( $files_found ) ){
|
1491 |
+
foreach ( $files_found as $filepath ){
|
1492 |
+
$filepath = realpath( $filepath );
|
1493 |
+
$directory = dirname( $filepath );
|
1494 |
+
$filepath_parts = explode( '/', $filepath );
|
1495 |
+
$filename = array_pop( $filepath_parts );
|
1496 |
|
1497 |
+
if ( is_dir( $filepath ) ){
|
1498 |
+
if ( $this->ignore_folderpath( $directory, $filename ) ){ continue; }
|
1499 |
|
1500 |
+
if ( $this->run_recursively ){
|
1501 |
+
$sub_files = $this->get_directory_tree_with_glob( $filepath );
|
1502 |
|
1503 |
+
if ( $sub_files ){
|
1504 |
+
$files = array_merge( $files, $sub_files );
|
1505 |
}
|
1506 |
}
|
1507 |
} else {
|
1508 |
+
if ( $this->ignore_filepath( $filename ) ){ continue; }
|
1509 |
$files[] = $filepath;
|
1510 |
}
|
1511 |
}
|
1522 |
* @param string $directory Parent directory where the filesystem scan will start.
|
1523 |
* @return array List of files in the main and subdirectories of the folder specified.
|
1524 |
*/
|
1525 |
+
private function get_directory_tree_with_opendir( $directory = '' ){
|
1526 |
+
$dh = @opendir( $directory );
|
1527 |
+
if ( ! $dh ){ return false; }
|
1528 |
|
1529 |
$files = array();
|
1530 |
+
while ( ($filename = readdir( $dh )) !== false ){
|
1531 |
+
$filepath = realpath( $directory.'/'.$filename );
|
1532 |
|
1533 |
+
if ( is_dir( $filepath ) ){
|
1534 |
+
if ( $this->ignore_folderpath( $directory, $filename ) ){ continue; }
|
1535 |
|
1536 |
+
if ( $this->run_recursively ){
|
1537 |
+
$sub_files = $this->get_directory_tree_with_opendir( $filepath );
|
1538 |
|
1539 |
+
if ( $sub_files ){
|
1540 |
+
$files = array_merge( $files, $sub_files );
|
1541 |
}
|
1542 |
}
|
1543 |
} else {
|
1544 |
+
if ( $this->ignore_filepath( $filename ) ){ continue; }
|
1545 |
$files[] = $filepath;
|
1546 |
}
|
1547 |
}
|
1548 |
|
1549 |
+
closedir( $dh );
|
1550 |
return $files;
|
1551 |
}
|
1552 |
|
1557 |
* @param string $filename Name of the folder or file being scanned at the moment.
|
1558 |
* @return boolean Either TRUE or FALSE representing that the scan should ignore this folder or not.
|
1559 |
*/
|
1560 |
+
private function ignore_folderpath( $directory = '', $filename = '' ){
|
1561 |
// Ignoring current and parent folders.
|
1562 |
+
if ( $filename == '.' || $filename == '..' ){ return true; }
|
1563 |
|
1564 |
+
if ( $this->ignore_directories ){
|
1565 |
// Ignore directories based on a common regular expression.
|
1566 |
$filepath = realpath( $directory . '/' . $filename );
|
1567 |
$pattern = '/\/wp-content\/(uploads|cache|backup|w3tc)/';
|
1568 |
|
1569 |
+
if ( preg_match( $pattern, $filepath ) ){
|
1570 |
+
return true;
|
1571 |
}
|
1572 |
|
1573 |
// Ignore directories specified by the administrator.
|
1574 |
+
if ( ! empty($this->ignored_directories) ){
|
1575 |
+
foreach ( $this->ignored_directories['directories'] as $ignored_dir ){
|
1576 |
+
if ( strpos( $directory, $ignored_dir ) !== false ){
|
1577 |
+
return true;
|
1578 |
}
|
1579 |
}
|
1580 |
}
|
1581 |
}
|
1582 |
|
1583 |
+
return false;
|
1584 |
}
|
1585 |
|
1586 |
/**
|
1589 |
* @param string $filename Name of the folder or file being scanned at the moment.
|
1590 |
* @return boolean Either TRUE or FALSE representing that the scan should ignore this filename or not.
|
1591 |
*/
|
1592 |
+
private function ignore_filepath( $filename = '' ){
|
1593 |
+
if ( ! $this->ignore_files ){ return false; }
|
1594 |
|
1595 |
// Ignoring backup files from our clean ups.
|
1596 |
+
if ( strpos( $filename, '_sucuribackup.' ) !== false ){ return true; }
|
1597 |
|
1598 |
// Any file maching one of these rules WILL NOT be ignored.
|
1599 |
+
if (
|
1600 |
+
( strpos( $filename, '.php' ) !== false) ||
|
1601 |
+
( strpos( $filename, '.htm' ) !== false) ||
|
1602 |
+
( strpos( $filename, '.js' ) !== false) ||
|
1603 |
+
( strcmp( $filename, '.htaccess' ) == 0 ) ||
|
1604 |
+
( strcmp( $filename, 'php.ini' ) == 0 )
|
1605 |
+
){ return false; }
|
1606 |
|
1607 |
+
return true;
|
1608 |
}
|
1609 |
|
1610 |
/**
|
1613 |
* @param array $dir_tree A list of files under a directory.
|
1614 |
* @return array A list of unique directory paths.
|
1615 |
*/
|
1616 |
+
public function get_diretories_only( $dir_tree = array() ){
|
1617 |
$dirs = array();
|
1618 |
|
1619 |
+
if ( is_string( $dir_tree ) ){
|
1620 |
+
$dir_tree = $this->get_directory_tree( $dir_tree );
|
1621 |
}
|
1622 |
|
1623 |
+
if ( is_array( $dir_tree ) && ! empty($dir_tree) ){
|
1624 |
+
foreach ( $dir_tree as $filepath ){
|
1625 |
+
$dir_path = dirname( $filepath );
|
1626 |
|
1627 |
+
if (
|
1628 |
+
! in_array( $dir_path, $dirs )
|
1629 |
+
&& ! in_array( $dir_path, $this->ignored_directories['directories'] )
|
1630 |
){
|
1631 |
$dirs[] = $dir_path;
|
1632 |
}
|
1642 |
* @param string $directory Path of the existing directory that will be removed.
|
1643 |
* @return boolean TRUE if all the files and folder inside the directory were removed.
|
1644 |
*/
|
1645 |
+
public function remove_directory_tree( $directory = '' ){
|
1646 |
+
$all_removed = true;
|
1647 |
+
$dir_tree = $this->get_directory_tree( $directory );
|
1648 |
|
1649 |
+
if ( $dir_tree ){
|
1650 |
$dirs_only = array();
|
1651 |
|
1652 |
+
foreach ( $dir_tree as $filepath ){
|
1653 |
+
if ( is_file( $filepath ) ){
|
1654 |
+
$removed = @unlink( $filepath );
|
1655 |
|
1656 |
+
if ( ! $removed ){
|
1657 |
+
$all_removed = false;
|
1658 |
}
|
1659 |
}
|
1660 |
|
1661 |
+
elseif ( is_dir( $filepath ) ){
|
1662 |
$dirs_only[] = $filepath;
|
1663 |
}
|
1664 |
}
|
1665 |
|
1666 |
+
if ( ! function_exists( 'sucuriscan_strlen_diff' ) ){
|
1667 |
/**
|
1668 |
* Evaluates the difference between the length of two strings.
|
1669 |
*
|
1671 |
* @param string $b Second string of characters that will be measured.
|
1672 |
* @return integer The difference in length between the two strings.
|
1673 |
*/
|
1674 |
+
function sucuriscan_strlen_diff( $a = '', $b = '' ){
|
1675 |
+
return strlen( $b ) - strlen( $a );
|
1676 |
}
|
1677 |
}
|
1678 |
|
1679 |
+
usort( $dirs_only, 'sucuriscan_strlen_diff' );
|
1680 |
|
1681 |
+
foreach ( $dirs_only as $dir_path ){
|
1682 |
+
@rmdir( $dir_path );
|
1683 |
}
|
1684 |
}
|
1685 |
|
1694 |
* @param string $filepath Path to the file.
|
1695 |
* @return array An array where each element is a line in the file.
|
1696 |
*/
|
1697 |
+
public static function file_lines( $filepath = '' ){
|
1698 |
return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
|
1699 |
}
|
1700 |
|
1708 |
* @param boolean $adaptive Whether the buffer will adapt to a specific number of bytes or not.
|
1709 |
* @return string Text contained at the end of the file.
|
1710 |
*/
|
1711 |
+
public static function tail_file( $file_path = '', $lines = 1, $adaptive = true ) {
|
1712 |
$file = @fopen( $file_path, 'rb' );
|
1713 |
$limit = $lines;
|
1714 |
|
1719 |
elseif ( $adaptive && $lines < 10 ) { $buffer = 512; }
|
1720 |
else { $buffer = 4096; }
|
1721 |
|
1722 |
+
if ( fread( $file, 1 ) != "\n" ) { $lines -= 1; }
|
1723 |
|
1724 |
$output = '';
|
1725 |
$chunk = '';
|
1726 |
|
1727 |
+
while ( ftell( $file ) > 0 && $lines >= 0 ) {
|
1728 |
+
$seek = min( ftell( $file ), $buffer );
|
1729 |
fseek( $file, -$seek, SEEK_CUR );
|
1730 |
$chunk = fread( $file, $seek );
|
1731 |
$output = $chunk . $output;
|
1732 |
+
fseek( $file, -mb_strlen( $chunk, '8bit' ), SEEK_CUR );
|
1733 |
$lines -= substr_count( $chunk, "\n" );
|
1734 |
}
|
1735 |
|
1736 |
+
fclose( $file );
|
1737 |
|
1738 |
$lines_arr = explode( "\n", $output );
|
1739 |
+
$lines_count = count( $lines_arr );
|
1740 |
$result = array_slice( $lines_arr, ($lines_count - $limit) );
|
1741 |
|
1742 |
return $result;
|
1743 |
}
|
1744 |
|
1745 |
+
return false;
|
1746 |
+
}
|
1747 |
+
|
1748 |
+
/**
|
1749 |
+
* Gets inode change time of file.
|
1750 |
+
*
|
1751 |
+
* @param string $file_path Path to the file.
|
1752 |
+
* @return integer Time the file was last changed.
|
1753 |
+
*/
|
1754 |
+
public static function creation_time( $file_path = '' ){
|
1755 |
+
if ( file_exists( $file_path ) ) {
|
1756 |
+
clearstatcache( $file_path );
|
1757 |
+
return filectime( $file_path );
|
1758 |
+
}
|
1759 |
+
|
1760 |
+
return 0;
|
1761 |
+
}
|
1762 |
+
|
1763 |
+
/**
|
1764 |
+
* Gets file modification time.
|
1765 |
+
*
|
1766 |
+
* @param string $file_path Path to the file.
|
1767 |
+
* @return integer Time the file was last modified.
|
1768 |
+
*/
|
1769 |
+
public static function modification_time( $file_path = '' ){
|
1770 |
+
if ( file_exists( $file_path ) ) {
|
1771 |
+
clearstatcache( $file_path );
|
1772 |
+
return filemtime( $file_path );
|
1773 |
+
}
|
1774 |
+
|
1775 |
+
return 0;
|
1776 |
}
|
1777 |
|
1778 |
}
|
1805 |
*
|
1806 |
* @var null|string
|
1807 |
*/
|
1808 |
+
private $datastore = null;
|
1809 |
|
1810 |
/**
|
1811 |
* The full path of the datastore file.
|
1822 |
*
|
1823 |
* @var boolean
|
1824 |
*/
|
1825 |
+
private $usable_datastore = false;
|
1826 |
|
1827 |
/**
|
1828 |
* Class constructor.
|
1830 |
* @param string $datastore Unique name (or identifier) of the file with the data.
|
1831 |
* @return void
|
1832 |
*/
|
1833 |
+
public function __construct( $datastore = '' ){
|
1834 |
$this->datastore = $datastore;
|
1835 |
$this->datastore_path = $this->datastore_file_path();
|
1836 |
$this->usable_datastore = (bool) $this->datastore_path;
|
1857 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1858 |
* @return string Default content of every datastore file.
|
1859 |
*/
|
1860 |
+
private function datastore_info( $finfo = array() ){
|
1861 |
$attrs = $this->datastore_default_info();
|
1862 |
$info_is_available = (bool) isset($finfo['info']);
|
1863 |
$info = "<?php\n";
|
1864 |
|
1865 |
+
foreach ( $attrs as $attr_name => $attr_value ){
|
1866 |
+
if (
|
1867 |
$info_is_available
|
1868 |
&& $attr_name != 'updated_on'
|
1869 |
+
&& isset($finfo['info'][ $attr_name ])
|
1870 |
){
|
1871 |
+
$attr_value = $finfo['info'][ $attr_name ];
|
1872 |
}
|
1873 |
|
1874 |
$info .= sprintf( "// %s=%s;\n", $attr_name, $attr_value );
|
1888 |
* @return string The full path where the datastore file is located, FALSE otherwise.
|
1889 |
*/
|
1890 |
private function datastore_file_path(){
|
1891 |
+
if ( ! is_null( $this->datastore ) ){
|
1892 |
$folder_path = $this->datastore_folder_path();
|
1893 |
$file_path = $folder_path . 'sucuri-' . $this->datastore . '.php';
|
1894 |
|
1895 |
// Create the datastore file is it does not exists and the folder is writable.
|
1896 |
+
if (
|
1897 |
+
! file_exists( $file_path )
|
1898 |
+
&& is_writable( $folder_path )
|
1899 |
){
|
1900 |
@file_put_contents( $file_path, $this->datastore_info(), LOCK_EX );
|
1901 |
}
|
1902 |
|
1903 |
// Continue the operation after an attemp to create the datastore file.
|
1904 |
+
if (
|
1905 |
+
file_exists( $file_path )
|
1906 |
+
&& is_writable( $file_path )
|
1907 |
+
&& is_readable( $file_path )
|
1908 |
){
|
1909 |
return $file_path;
|
1910 |
}
|
1911 |
}
|
1912 |
|
1913 |
+
return false;
|
1914 |
}
|
1915 |
|
1916 |
/**
|
1921 |
* @param string $action Either "valid", "content", or "header".
|
1922 |
* @return string Cache key pattern.
|
1923 |
*/
|
1924 |
+
private function key_pattern( $action = 'valid' ){
|
1925 |
+
if ( $action == 'valid' ){
|
1926 |
return '/^([0-9a-zA-Z_]+)$/';
|
1927 |
}
|
1928 |
|
1929 |
+
if ( $action == 'content' ){
|
1930 |
return '/^([0-9a-zA-Z_]+):(.+)/';
|
1931 |
}
|
1932 |
|
1933 |
+
if ( $action == 'header' ){
|
1934 |
return '/^\/\/ ([a-z_]+)=(.*);$/';
|
1935 |
}
|
1936 |
|
1937 |
+
return false;
|
1938 |
}
|
1939 |
|
1940 |
/**
|
1943 |
* @param string $key Unique name to identify the data in the datastore file.
|
1944 |
* @return boolean TRUE if the format of the key name is valid, FALSE otherwise.
|
1945 |
*/
|
1946 |
+
private function valid_key_name( $key = '' ){
|
1947 |
+
return (bool) preg_match( $this->key_pattern( 'valid' ), $key );
|
1948 |
}
|
1949 |
|
1950 |
/**
|
1953 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
1954 |
* @return boolean TRUE if the operation finished successfully, FALSE otherwise.
|
1955 |
*/
|
1956 |
+
private function save_new_entries( $finfo = array() ){
|
1957 |
+
$data_string = $this->datastore_info( $finfo );
|
1958 |
|
1959 |
+
if ( ! empty($finfo) ){
|
1960 |
+
foreach ( $finfo['entries'] as $key => $data ){
|
1961 |
+
if ( $this->valid_key_name( $key ) ){
|
1962 |
+
$data = json_encode( $data );
|
1963 |
$data_string .= sprintf( "%s:%s\n", $key, $data );
|
1964 |
}
|
1965 |
}
|
1979 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
1980 |
* @return array Rainbow table with the key names and decoded values.
|
1981 |
*/
|
1982 |
+
private function get_datastore_content( $assoc = false ){
|
1983 |
$data_object = array(
|
1984 |
'info' => array(),
|
1985 |
'entries' => array(),
|
1986 |
);
|
1987 |
|
1988 |
+
if ( $this->usable_datastore ){
|
1989 |
+
$data_lines = SucuriScanFileInfo::file_lines( $this->datastore_path );
|
1990 |
|
1991 |
+
if ( ! empty($data_lines) ){
|
1992 |
+
foreach ( $data_lines as $line ){
|
1993 |
+
if ( preg_match( $this->key_pattern( 'header' ), $line, $match ) ){
|
1994 |
+
$data_object['info'][ $match[1] ] = $match[2];
|
1995 |
}
|
1996 |
|
1997 |
+
elseif ( preg_match( $this->key_pattern( 'content' ), $line, $match ) ){
|
1998 |
+
if (
|
1999 |
+
$this->valid_key_name( $match[1] )
|
2000 |
+
&& ! array_key_exists( $match[1], $data_object )
|
2001 |
){
|
2002 |
+
$data_object['entries'][ $match[1] ] = @json_decode( $match[2], $assoc );
|
2003 |
}
|
2004 |
}
|
2005 |
}
|
2024 |
public function get_datastore_info(){
|
2025 |
$finfo = $this->get_datastore_content();
|
2026 |
|
2027 |
+
if ( ! empty($finfo['info']) ){
|
2028 |
return $finfo['info'];
|
2029 |
}
|
2030 |
|
2031 |
+
return false;
|
2032 |
}
|
2033 |
|
2034 |
/**
|
2037 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
2038 |
* @return integer Total number of unique entries found in the datastore file.
|
2039 |
*/
|
2040 |
+
public function get_count( $finfo = null ){
|
2041 |
+
if ( ! is_array( $finfo ) ){
|
2042 |
$finfo = $this->get_datastore_content();
|
2043 |
}
|
2044 |
|
2045 |
+
return count( $finfo['entries'] );
|
2046 |
}
|
2047 |
|
2048 |
/**
|
2055 |
* @param array $finfo Rainbow table with the key names and decoded values.
|
2056 |
* @return boolean TRUE if the life time of the data has expired, FALSE otherwise.
|
2057 |
*/
|
2058 |
+
public function data_has_expired( $lifetime = 0, $finfo = null ){
|
2059 |
+
if ( is_null( $finfo ) ){
|
2060 |
$finfo = $this->get_datastore_content();
|
2061 |
}
|
2062 |
|
2063 |
+
if ( $lifetime > 0 && ! empty($finfo['info']) ){
|
2064 |
+
$diff_time = time() - intval( $finfo['info']['updated_on'] );
|
2065 |
|
2066 |
+
if ( $diff_time >= $lifetime ){
|
2067 |
+
return true;
|
2068 |
}
|
2069 |
}
|
2070 |
|
2071 |
+
return false;
|
2072 |
}
|
2073 |
|
2074 |
/**
|
2081 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2082 |
* @return boolean TRUE if the operation finished successfully, FALSE otherwise.
|
2083 |
*/
|
2084 |
+
private function handle_key_data( $key = '', $data = null, $action = '', $lifetime = 0, $assoc = false ){
|
2085 |
+
if ( preg_match( '/^(add|set|get|get_all|exists|delete)$/', $action ) ){
|
2086 |
+
if (
|
2087 |
+
$this->valid_key_name( $key )
|
2088 |
&& $this->usable_datastore
|
2089 |
){
|
2090 |
+
$finfo = $this->get_datastore_content( $assoc );
|
2091 |
|
2092 |
+
switch ( $action ){
|
2093 |
case 'add': /* no_break */
|
2094 |
case 'set':
|
2095 |
+
$finfo['entries'][ $key ] = $data;
|
2096 |
+
return $this->save_new_entries( $finfo );
|
2097 |
break;
|
2098 |
case 'get':
|
2099 |
+
if (
|
2100 |
+
! $this->data_has_expired( $lifetime, $finfo )
|
2101 |
+
&& array_key_exists( $key, $finfo['entries'] )
|
2102 |
){
|
2103 |
+
return $finfo['entries'][ $key ];
|
2104 |
}
|
2105 |
break;
|
2106 |
case 'get_all': /* no_break */
|
2107 |
+
if ( ! $this->data_has_expired( $lifetime, $finfo ) ) {
|
2108 |
return $finfo['entries'];
|
2109 |
}
|
2110 |
case 'exists':
|
2111 |
+
if (
|
2112 |
+
! $this->data_has_expired( $lifetime, $finfo )
|
2113 |
+
&& array_key_exists( $key, $finfo['entries'] )
|
2114 |
){
|
2115 |
+
return true;
|
2116 |
}
|
2117 |
break;
|
2118 |
case 'delete':
|
2119 |
+
unset($finfo['entries'][ $key ]);
|
2120 |
+
return $this->save_new_entries( $finfo );
|
2121 |
break;
|
2122 |
}
|
2123 |
}
|
2124 |
}
|
2125 |
|
2126 |
+
return false;
|
2127 |
}
|
2128 |
|
2129 |
/**
|
2136 |
* @param string $data Mixed data stored in the datastore file following the unique key name.
|
2137 |
* @return boolean TRUE if the data was stored successfully, FALSE otherwise.
|
2138 |
*/
|
2139 |
+
public function add( $key = '', $data = '' ){
|
2140 |
return $this->handle_key_data( $key, $data, 'add' );
|
2141 |
}
|
2142 |
|
2147 |
* @param string $data Mixed data stored in the datastore file following the unique key name.
|
2148 |
* @return boolean TRUE if the data was stored successfully, FALSE otherwise.
|
2149 |
*/
|
2150 |
+
public function set( $key = '', $data = '' ){
|
2151 |
return $this->handle_key_data( $key, $data, 'set' );
|
2152 |
}
|
2153 |
|
2159 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2160 |
* @return string Mixed data stored in the datastore file following the unique key name.
|
2161 |
*/
|
2162 |
+
public function get( $key = '', $lifetime = 0, $assoc = false ){
|
2163 |
+
$assoc = ( $assoc == 'array' ? true : $assoc );
|
2164 |
|
2165 |
+
return $this->handle_key_data( $key, null, 'get', $lifetime, $assoc );
|
2166 |
}
|
2167 |
|
2168 |
/**
|
2172 |
* @param boolean $assoc When TRUE returned objects will be converted into associative arrays.
|
2173 |
* @return string Mixed data stored in the datastore file following the unique key name.
|
2174 |
*/
|
2175 |
+
public function get_all( $lifetime = 0, $assoc = false ){
|
2176 |
+
$assoc = ( $assoc == 'array' ? true : $assoc );
|
2177 |
|
2178 |
+
return $this->handle_key_data( 'temp', null, 'get_all', $lifetime, $assoc );
|
2179 |
}
|
2180 |
|
2181 |
/**
|
2184 |
* @param string $key Unique name to identify the data in the datastore file.
|
2185 |
* @return boolean TRUE if the key exists in the datastore file, FALSE otherwise.
|
2186 |
*/
|
2187 |
+
public function exists( $key = '' ){
|
2188 |
+
return $this->handle_key_data( $key, null, 'exists' );
|
2189 |
}
|
2190 |
|
2191 |
/**
|
2194 |
* @param string $key Unique name to identify the data in the datastore file.
|
2195 |
* @return boolean TRUE if the entries were removed, FALSE otherwise.
|
2196 |
*/
|
2197 |
+
public function delete( $key = '' ){
|
2198 |
+
return $this->handle_key_data( $key, null, 'delete' );
|
2199 |
}
|
2200 |
|
2201 |
/**
|
2206 |
public function flush(){
|
2207 |
$finfo = $this->get_datastore_content();
|
2208 |
|
2209 |
+
return $this->save_new_entries( $finfo );
|
2210 |
}
|
2211 |
|
2212 |
}
|
2241 |
*
|
2242 |
* @return array Default plugin option values.
|
2243 |
*/
|
2244 |
+
public static function get_default_option_values(){
|
2245 |
$defaults = array(
|
2246 |
+
'sucuriscan_api_key' => false,
|
2247 |
'sucuriscan_account' => '',
|
2248 |
'sucuriscan_datastore_path' => '',
|
2249 |
'sucuriscan_fs_scanner' => 'enabled',
|
2250 |
'sucuriscan_scan_frequency' => 'hourly',
|
2251 |
'sucuriscan_scan_interface' => 'spl',
|
2252 |
+
'sucuriscan_scan_modfiles' => 'disabled',
|
2253 |
'sucuriscan_scan_checksums' => 'enabled',
|
2254 |
+
'sucuriscan_scan_errorlogs' => 'disabled',
|
2255 |
'sucuriscan_sitecheck_scanner' => 'enabled',
|
2256 |
'sucuriscan_sitecheck_counter' => 0,
|
2257 |
'sucuriscan_parse_errorlogs' => 'enabled',
|
2278 |
'sucuriscan_heartbeat_pulse' => 15,
|
2279 |
'sucuriscan_heartbeat_interval' => 'standard',
|
2280 |
'sucuriscan_heartbeat_autostart' => 'enabled',
|
2281 |
+
'sucuriscan_ads_visibility' => 'enabled',
|
2282 |
+
'sucuriscan_audit_report' => 'disabled',
|
2283 |
+
'sucuriscan_logs4report' => 500,
|
2284 |
+
'sucuriscan_revproxy' => 'disabled',
|
2285 |
);
|
2286 |
|
2287 |
return $defaults;
|
2293 |
* @param string|array $settings Either an array that will be complemented or a string with the name of the option.
|
2294 |
* @return string|array The default values for the specified options.
|
2295 |
*/
|
2296 |
+
public static function get_default_options( $settings = '' ){
|
2297 |
$default_options = self::get_default_option_values();
|
2298 |
|
2299 |
// Use framework built-in function.
|
2300 |
+
if ( function_exists( 'get_option' ) ) {
|
2301 |
+
$admin_email = get_option( 'admin_email' );
|
2302 |
$default_options['sucuriscan_account'] = $admin_email;
|
2303 |
$default_options['sucuriscan_notify_to'] = $admin_email;
|
2304 |
}
|
2305 |
|
2306 |
+
if ( is_array( $settings ) ) {
|
2307 |
+
foreach ( $default_options as $option_name => $option_value ) {
|
2308 |
+
if ( ! isset($settings[ $option_name ]) ){
|
2309 |
+
$settings[ $option_name ] = $option_value;
|
2310 |
}
|
2311 |
}
|
2312 |
|
2313 |
return $settings;
|
2314 |
}
|
2315 |
|
2316 |
+
if (
|
2317 |
+
is_string( $settings )
|
2318 |
+
&& ! empty($settings)
|
2319 |
+
&& array_key_exists( $settings, $default_options )
|
2320 |
+
) {
|
2321 |
+
return $default_options[ $settings ];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2322 |
}
|
2323 |
|
2324 |
+
return false;
|
2325 |
}
|
2326 |
|
2327 |
/**
|
2341 |
* @param string $option_name Optional parameter that you can use to filter the results to one option.
|
2342 |
* @return string The value (or default value) of the option specified.
|
2343 |
*/
|
2344 |
+
public static function get_option( $option_name = '' ){
|
2345 |
+
if ( function_exists( 'update_option' ) ) {
|
2346 |
+
$option_name = self::variable_prefix( $option_name );
|
2347 |
+
$option_value = get_option( $option_name );
|
2348 |
+
|
2349 |
+
if ( $option_value === false && preg_match( '/^sucuriscan_/', $option_name ) ){
|
2350 |
+
$option_value = self::get_default_options( $option_name );
|
2351 |
+
}
|
2352 |
+
|
2353 |
+
return $option_value;
|
2354 |
+
}
|
2355 |
|
2356 |
+
return false;
|
2357 |
}
|
2358 |
|
2359 |
/**
|
2370 |
* @param string $option_value The new value for the option, can be an integer, string, array, or object.
|
2371 |
* @return boolean True if option value has changed, false if not or if update failed.
|
2372 |
*/
|
2373 |
+
public static function update_option( $option_name = '', $option_value = '' ){
|
2374 |
+
if ( function_exists( 'update_option' ) ) {
|
2375 |
+
$option_name = self::variable_prefix( $option_name );
|
2376 |
|
2377 |
return update_option( $option_name, $option_value );
|
2378 |
}
|
2379 |
|
2380 |
+
return false;
|
2381 |
}
|
2382 |
|
2383 |
/**
|
2390 |
* @param string $option_name Name of the option to be deleted.
|
2391 |
* @return boolean True, if option is successfully deleted. False on failure, or option does not exist.
|
2392 |
*/
|
2393 |
+
public static function delete_option( $option_name = '' ){
|
2394 |
+
if ( function_exists( 'delete_option' ) ) {
|
2395 |
+
$option_name = self::variable_prefix( $option_name );
|
2396 |
|
2397 |
return delete_option( $option_name );
|
2398 |
}
|
2399 |
|
2400 |
+
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2401 |
}
|
2402 |
|
2403 |
/**
|
2404 |
+
* Delete all the plugin options from the database.
|
2405 |
*
|
2406 |
+
* @return void
|
|
|
2407 |
*/
|
2408 |
+
public static function delete_plugin_options(){
|
2409 |
+
global $wpdb;
|
|
|
|
|
|
|
|
|
|
|
2410 |
|
2411 |
+
$options = $wpdb->get_results(
|
2412 |
+
"SELECT * FROM {$wpdb->options}
|
2413 |
+
WHERE option_name LIKE 'sucuriscan%'
|
2414 |
+
ORDER BY option_id ASC"
|
2415 |
+
);
|
2416 |
|
2417 |
+
foreach ( $options as $option ) {
|
2418 |
+
self::delete_option( $option->option_name );
|
2419 |
}
|
|
|
|
|
2420 |
}
|
2421 |
|
2422 |
/**
|
2427 |
* @return array All the options stored by Wordpress in the database, except the transient options.
|
2428 |
*/
|
2429 |
public static function get_site_options(){
|
2430 |
+
global $wpdb;
|
2431 |
+
|
2432 |
$settings = array();
|
2433 |
+
$results = $wpdb->get_results(
|
2434 |
+
"SELECT * FROM {$wpdb->options}
|
2435 |
+
WHERE option_name NOT LIKE '%_transient_%'
|
2436 |
+
ORDER BY option_id ASC"
|
2437 |
+
);
|
2438 |
|
2439 |
+
foreach ( $results as $row ) {
|
2440 |
+
$settings[ $row->option_name ] = $row->option_value;
|
2441 |
}
|
2442 |
|
2443 |
return $settings;
|
2450 |
* @param array $request The content of the global variable GET or POST considering SERVER[REQUEST_METHOD].
|
2451 |
* @return array A list of all the options that were changes through this request.
|
2452 |
*/
|
2453 |
+
public static function what_options_were_changed( $request = array() ){
|
2454 |
$options_changed = array(
|
2455 |
'original' => array(),
|
2456 |
'changed' => array()
|
2458 |
|
2459 |
$site_options = self::get_site_options();
|
2460 |
|
2461 |
+
foreach ( $request as $req_name => $req_value ){
|
2462 |
+
if (
|
2463 |
+
array_key_exists( $req_name, $site_options )
|
2464 |
+
&& $site_options[ $req_name ] != $req_value
|
2465 |
){
|
2466 |
+
$options_changed['original'][ $req_name ] = $site_options[ $req_name ];
|
2467 |
+
$options_changed['changed'][ $req_name ] = $req_value;
|
2468 |
}
|
2469 |
}
|
2470 |
|
2478 |
*/
|
2479 |
public static function check_options_nonce(){
|
2480 |
// Create the option_page value if permalink submission.
|
2481 |
+
if (
|
2482 |
+
! isset($_POST['option_page'])
|
2483 |
&& isset($_POST['permalink_structure'])
|
2484 |
){
|
2485 |
$_POST['option_page'] = 'permalink';
|
2486 |
}
|
2487 |
|
2488 |
// Check if the option_page has an allowed value.
|
2489 |
+
if ( $option_page = SucuriScanRequest::post( 'option_page' ) ){
|
2490 |
+
$nonce = '_wpnonce';
|
2491 |
$action = '';
|
2492 |
|
2493 |
+
switch ( $option_page ){
|
2494 |
case 'general': /* no_break */
|
2495 |
case 'writing': /* no_break */
|
2496 |
case 'reading': /* no_break */
|
2505 |
}
|
2506 |
|
2507 |
// Check the nonce validity.
|
2508 |
+
if (
|
2509 |
+
! empty($action)
|
2510 |
+
&& isset($_REQUEST[ $nonce ])
|
2511 |
+
&& wp_verify_nonce( $_REQUEST[ $nonce ], $action )
|
2512 |
){
|
2513 |
+
return true;
|
2514 |
}
|
2515 |
}
|
2516 |
|
2517 |
+
return false;
|
2518 |
}
|
2519 |
|
2520 |
/**
|
2524 |
* @return array List of ignored posts-types to send notifications.
|
2525 |
*/
|
2526 |
public static function get_ignored_events(){
|
2527 |
+
$post_types = self::get_option( ':ignored_events' );
|
2528 |
+
$post_types_arr = false;
|
2529 |
|
2530 |
// Encode (old) serialized data into JSON.
|
2531 |
+
if ( self::is_serialized( $post_types ) ){
|
2532 |
+
$post_types_arr = @unserialize( $post_types );
|
2533 |
+
$post_types_fix = json_encode( $post_types_arr );
|
2534 |
self::update_option( ':ignored_events', $post_types_fix );
|
2535 |
|
2536 |
return $post_types_arr;
|
2537 |
}
|
2538 |
|
2539 |
// Decode JSON-encoded data as an array.
|
2540 |
+
elseif ( preg_match( '/^\{.+\}$/', $post_types ) ){
|
2541 |
+
$post_types_arr = @json_decode( $post_types, true );
|
2542 |
}
|
2543 |
|
2544 |
+
if ( ! is_array( $post_types_arr ) ){
|
2545 |
$post_types_arr = array();
|
2546 |
}
|
2547 |
|
2554 |
* @param string $event_name Unique post-type name.
|
2555 |
* @return boolean Whether the event was ignored or not.
|
2556 |
*/
|
2557 |
+
public static function add_ignored_event( $event_name = '' ){
|
2558 |
+
if ( function_exists( 'get_post_types' ) ){
|
2559 |
$post_types = get_post_types();
|
2560 |
|
2561 |
// Check if the event is a registered post-type.
|
2562 |
+
if ( array_key_exists( $event_name, $post_types ) ){
|
2563 |
$ignored_events = self::get_ignored_events();
|
2564 |
|
2565 |
// Check if the event is not ignored already.
|
2566 |
+
if ( ! array_key_exists( $event_name, $ignored_events ) ){
|
2567 |
+
$ignored_events[ $event_name ] = time();
|
2568 |
+
$saved = self::update_option( ':ignored_events', json_encode( $ignored_events ) );
|
2569 |
|
2570 |
return $saved;
|
2571 |
}
|
2572 |
}
|
2573 |
}
|
2574 |
|
2575 |
+
return false;
|
2576 |
}
|
2577 |
|
2578 |
/**
|
2581 |
* @param string $event_name Unique post-type name.
|
2582 |
* @return boolean Whether the event was removed from the list or not.
|
2583 |
*/
|
2584 |
+
public static function remove_ignored_event( $event_name = '' ){
|
2585 |
$ignored_events = self::get_ignored_events();
|
2586 |
|
2587 |
+
if ( array_key_exists( $event_name, $ignored_events ) ){
|
2588 |
+
unset( $ignored_events[ $event_name ] );
|
2589 |
+
$saved = self::update_option( ':ignored_events', json_encode( $ignored_events ) );
|
2590 |
|
2591 |
return $saved;
|
2592 |
}
|
2593 |
|
2594 |
+
return false;
|
2595 |
}
|
2596 |
|
2597 |
/**
|
2600 |
* @param string $event_name Unique post-type name.
|
2601 |
* @return boolean Whether an event is being ignored or not.
|
2602 |
*/
|
2603 |
+
public static function is_ignored_event( $event_name = '' ){
|
2604 |
+
$event_name = strtolower( $event_name );
|
2605 |
$ignored_events = self::get_ignored_events();
|
2606 |
|
2607 |
+
if ( array_key_exists( $event_name, $ignored_events ) ){
|
2608 |
+
return true;
|
2609 |
}
|
2610 |
|
2611 |
+
return false;
|
2612 |
}
|
2613 |
|
2614 |
/**
|
2634 |
'SECURE_AUTH_SALT',
|
2635 |
);
|
2636 |
|
2637 |
+
foreach ( $key_names as $key_name ){
|
2638 |
+
if ( defined( $key_name ) ){
|
2639 |
+
$key_value = constant( $key_name );
|
2640 |
|
2641 |
+
if ( stripos( $key_value, 'unique phrase' ) !== false ){
|
2642 |
+
$response['bad'][ $key_name ] = $key_value;
|
2643 |
} else {
|
2644 |
+
$response['good'][ $key_name ] = $key_value;
|
2645 |
}
|
2646 |
} else {
|
2647 |
+
$response['missing'][ $key_name ] = false;
|
2648 |
}
|
2649 |
}
|
2650 |
|
2678 |
public static function schedule_task(){
|
2679 |
$task_name = 'sucuriscan_scheduled_scan';
|
2680 |
|
2681 |
+
if ( ! wp_next_scheduled( $task_name ) ){
|
2682 |
wp_schedule_event( time() + 10, 'twicedaily', $task_name );
|
2683 |
}
|
2684 |
|
2692 |
* @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
|
2693 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation respectively.
|
2694 |
*/
|
2695 |
+
private static function verify_run( $runtime = 0, $force_scan = false ){
|
2696 |
$option_name = ':runtime';
|
2697 |
+
$last_run = SucuriScanOption::get_option( $option_name );
|
2698 |
$current_time = time();
|
2699 |
|
2700 |
// The filesystem scanner can be disabled from the settings page.
|
2701 |
+
if (
|
2702 |
+
SucuriScanOption::get_option( ':fs_scanner' ) == 'disabled'
|
2703 |
+
&& $force_scan === false
|
2704 |
){
|
2705 |
+
return false;
|
2706 |
}
|
2707 |
|
2708 |
// Check if the last runtime is too near the current time.
|
2709 |
+
if ( $last_run && ! $force_scan ){
|
2710 |
$runtime_diff = $current_time - $runtime;
|
2711 |
|
2712 |
+
if ( $last_run >= $runtime_diff ){
|
2713 |
+
return false;
|
2714 |
}
|
2715 |
}
|
2716 |
|
2717 |
SucuriScanOption::update_option( $option_name, $current_time );
|
2718 |
|
2719 |
+
return true;
|
2720 |
}
|
2721 |
|
2722 |
/**
|
2727 |
*/
|
2728 |
private static function report_site_version(){
|
2729 |
$option_name = ':site_version';
|
2730 |
+
$reported_version = SucuriScanOption::get_option( $option_name );
|
2731 |
$wp_version = self::site_version();
|
2732 |
|
2733 |
+
if ( $reported_version != $wp_version ){
|
2734 |
+
SucuriScanEvent::report_info_event( 'WordPress version detected ' . $wp_version );
|
2735 |
SucuriScanOption::update_option( $option_name, $wp_version );
|
2736 |
|
2737 |
+
return true;
|
2738 |
}
|
2739 |
|
2740 |
+
return false;
|
2741 |
}
|
2742 |
|
2743 |
/**
|
2748 |
* @param boolean $force_scan Whether the filesystem scan was forced by an administrator user or not.
|
2749 |
* @return boolean TRUE if the filesystem scan was successful, FALSE otherwise.
|
2750 |
*/
|
2751 |
+
public static function filesystem_scan( $force_scan = false ){
|
2752 |
$minimum_runtime = SUCURISCAN_MINIMUM_RUNTIME;
|
2753 |
|
2754 |
+
if (
|
2755 |
self::verify_run( $minimum_runtime, $force_scan )
|
2756 |
+
&& class_exists( 'SucuriScanFileInfo' )
|
2757 |
&& SucuriScanAPI::get_plugin_key()
|
2758 |
){
|
2759 |
self::report_site_version();
|
2760 |
|
2761 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
2762 |
+
$sucuri_fileinfo->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
|
2763 |
+
$signatures = $sucuri_fileinfo->get_directory_tree_md5( ABSPATH );
|
2764 |
|
2765 |
+
if ( $signatures ){
|
2766 |
$hashes_sent = SucuriScanAPI::send_hashes( $signatures );
|
2767 |
|
2768 |
+
if ( $hashes_sent ){
|
2769 |
SucuriScanInterface::info( 'Successful filesystem scan' );
|
2770 |
SucuriScanOption::update_option( ':runtime', time() );
|
2771 |
+
return true;
|
2772 |
} else {
|
2773 |
SucuriScanInterface::error( 'The file hashes could not be stored.' );
|
2774 |
}
|
2777 |
}
|
2778 |
}
|
2779 |
|
2780 |
+
return false;
|
2781 |
}
|
2782 |
|
2783 |
/**
|
2788 |
* @param string $message The explanation of the event.
|
2789 |
* @return boolean TRUE if the event was logged in the monitoring service, FALSE otherwise.
|
2790 |
*/
|
2791 |
+
private static function report_event( $severity = 0, $location = '', $message = '' ){
|
2792 |
$user = wp_get_current_user();
|
2793 |
+
$username = false;
|
2794 |
$current_time = date( 'Y-m-d H:i:s' );
|
2795 |
$remote_ip = self::get_remote_addr();
|
2796 |
|
|
|
|
|
|
|
|
|
|
|
2797 |
// Identify current user in session.
|
2798 |
+
if (
|
2799 |
$user instanceof WP_User
|
2800 |
&& isset($user->user_login)
|
2801 |
+
&& ! empty($user->user_login)
|
2802 |
){
|
2803 |
+
if ( $user->user_login != $user->display_name ){
|
2804 |
+
$username = sprintf( "\x20%s (%s),", $user->display_name, $user->user_login );
|
2805 |
} else {
|
2806 |
+
$username = sprintf( "\x20%s,", $user->user_login );
|
2807 |
}
|
2808 |
}
|
2809 |
|
2810 |
+
// Fixing severity value.
|
2811 |
+
$severity = (int) $severity;
|
2812 |
+
|
2813 |
// Convert the severity number into a readable string.
|
2814 |
+
switch ( $severity ){
|
2815 |
case 0: $severity_name = 'Debug'; break;
|
2816 |
case 1: $severity_name = 'Notice'; break;
|
2817 |
case 2: $severity_name = 'Info'; break;
|
2821 |
default: $severity_name = 'Info'; break;
|
2822 |
}
|
2823 |
|
2824 |
+
// Clear event message.
|
2825 |
+
$message = strip_tags( $message );
|
2826 |
+
$message = str_replace( "\r", '', $message );
|
2827 |
+
$message = str_replace( "\n", '', $message );
|
2828 |
+
$message = str_replace( "\t", '', $message );
|
2829 |
+
|
2830 |
$event_message = sprintf(
|
2831 |
'%s:%s %s; %s',
|
2832 |
$severity_name,
|
2835 |
$message
|
2836 |
);
|
2837 |
|
2838 |
+
return SucuriScanAPI::send_log( $event_message );
|
2839 |
+
}
|
2840 |
+
|
2841 |
+
/**
|
2842 |
+
* Reports a debug event on the website.
|
2843 |
+
*
|
2844 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2845 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2846 |
+
*/
|
2847 |
+
public static function report_debug_event( $message = '' ){
|
2848 |
+
return self::report_event( 0, 'core', $message );
|
2849 |
+
}
|
2850 |
+
|
2851 |
+
/**
|
2852 |
+
* Reports a notice event on the website.
|
2853 |
+
*
|
2854 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2855 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2856 |
+
*/
|
2857 |
+
public static function report_notice_event( $message = '' ){
|
2858 |
+
return self::report_event( 1, 'core', $message );
|
2859 |
+
}
|
2860 |
+
|
2861 |
+
/**
|
2862 |
+
* Reports a info event on the website.
|
2863 |
+
*
|
2864 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2865 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2866 |
+
*/
|
2867 |
+
public static function report_info_event( $message = '' ){
|
2868 |
+
return self::report_event( 2, 'core', $message );
|
2869 |
+
}
|
2870 |
+
|
2871 |
+
/**
|
2872 |
+
* Reports a warning event on the website.
|
2873 |
+
*
|
2874 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2875 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2876 |
+
*/
|
2877 |
+
public static function report_warning_event( $message = '' ){
|
2878 |
+
return self::report_event( 3, 'core', $message );
|
2879 |
+
}
|
2880 |
+
|
2881 |
+
/**
|
2882 |
+
* Reports a error event on the website.
|
2883 |
+
*
|
2884 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2885 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2886 |
+
*/
|
2887 |
+
public static function report_error_event( $message = '' ){
|
2888 |
+
return self::report_event( 4, 'core', $message );
|
2889 |
+
}
|
2890 |
+
|
2891 |
+
/**
|
2892 |
+
* Reports a critical event on the website.
|
2893 |
+
*
|
2894 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2895 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2896 |
+
*/
|
2897 |
+
public static function report_critical_event( $message = '' ){
|
2898 |
+
return self::report_event( 5, 'core', $message );
|
2899 |
+
}
|
2900 |
+
|
2901 |
+
/**
|
2902 |
+
* Reports a notice or error event for enable and disable actions.
|
2903 |
+
*
|
2904 |
+
* @param string $message Text witht the explanation of the event or action performed.
|
2905 |
+
* @param string $action An optional text, hopefully either enabled or disabled.
|
2906 |
+
* @return boolean Either true or false depending on the success of the operation.
|
2907 |
+
*/
|
2908 |
+
public static function report_auto_event( $message = '', $action = '' ){
|
2909 |
+
$message = strip_tags( $message );
|
2910 |
+
|
2911 |
+
// Auto-detect the action performed, either enabled or disabled.
|
2912 |
+
if ( preg_match( '/( was )?(enabled|disabled)$/', $message, $match ) ) {
|
2913 |
+
$action = $match[2];
|
2914 |
+
}
|
2915 |
+
|
2916 |
+
// Report the correct event for the action performed.
|
2917 |
+
if ( $action == 'enabled' ) {
|
2918 |
+
return self::report_notice_event( $message );
|
2919 |
+
} elseif ( $action == 'disabled' ) {
|
2920 |
+
return self::report_error_event( $message );
|
2921 |
+
} else {
|
2922 |
+
return self::report_info_event( $message );
|
2923 |
+
}
|
2924 |
}
|
2925 |
|
2926 |
/**
|
2931 |
* @param string $content Body of the email that will be sent to the administrator.
|
2932 |
* @return void
|
2933 |
*/
|
2934 |
+
public static function notify_event( $event = '', $content = '' ){
|
2935 |
+
$notify = SucuriScanOption::get_option( ':notify_' . $event );
|
2936 |
+
$email = SucuriScanOption::get_option( ':notify_to' );
|
2937 |
$email_params = array();
|
2938 |
|
2939 |
if ( self::is_trusted_ip() ) {
|
2940 |
$notify = 'disabled';
|
2941 |
}
|
2942 |
|
2943 |
+
if ( $notify == 'enabled' ){
|
2944 |
+
if ( $event == 'post_publication' ){
|
2945 |
$event = 'post_update';
|
2946 |
}
|
2947 |
|
2948 |
+
elseif ( $event == 'failed_login' ){
|
2949 |
$content .= "<br>\n<br>\n<em>Explanation: Someone failed to login to your site. If you";
|
2950 |
+
$content .= ' are getting too many of these messages, it is likely your site is under a brute';
|
2951 |
+
$content .= ' force attack. You can disable the notifications for failed logins from here [1].';
|
2952 |
$content .= " More details at Password Guessing Brute Force Attacks [2].</em><br>\n<br>\n";
|
2953 |
+
$content .= '[1] ' . SucuriScanTemplate::get_url( 'settings' ) . " <br>\n";
|
2954 |
$content .= "[2] http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing <br>\n";
|
2955 |
}
|
2956 |
|
2957 |
// Send a notification even if the limit of emails per hour was reached.
|
2958 |
+
elseif ( $event == 'bruteforce_attack' ){
|
2959 |
+
$email_params['Force'] = true;
|
2960 |
}
|
2961 |
|
2962 |
+
$title = str_replace( '_', chr( 32 ), $event );
|
2963 |
$mail_sent = SucuriScanMail::send_mail( $email, $title, $content, $email_params );
|
2964 |
|
2965 |
return $mail_sent;
|
2966 |
}
|
2967 |
|
2968 |
+
return false;
|
2969 |
}
|
2970 |
|
2971 |
/**
|
2974 |
* @param string $remote_addr The supposed ip address that will be checked.
|
2975 |
* @return boolean TRUE if the IP address of the user is trusted, FALSE otherwise.
|
2976 |
*/
|
2977 |
+
private static function is_trusted_ip( $remote_addr = '' ){
|
2978 |
+
$cache = new SucuriScanCache( 'trustip' );
|
2979 |
$trusted_ips = $cache->get_all();
|
2980 |
|
2981 |
+
if ( ! $remote_addr ) {
|
2982 |
$remote_addr = SucuriScan::get_remote_addr();
|
2983 |
}
|
2984 |
|
2985 |
+
$addr_md5 = md5( $remote_addr );
|
2986 |
|
2987 |
// Check if the CIDR in range 32 of this IP is trusted.
|
2988 |
if (
|
2989 |
+
is_array( $trusted_ips )
|
2990 |
+
&& ! empty($trusted_ips)
|
2991 |
+
&& array_key_exists( $addr_md5, $trusted_ips )
|
2992 |
) {
|
2993 |
+
return true;
|
2994 |
}
|
2995 |
|
2996 |
if ( $trusted_ips ) {
|
2997 |
foreach ( $trusted_ips as $cache_key => $ip_info ) {
|
2998 |
$ip_parts = explode( '.', $ip_info->remote_addr );
|
2999 |
+
$ip_pattern = false;
|
3000 |
|
3001 |
// Generate the regular expression for CIDR range 24.
|
3002 |
if ( $ip_info->cidr_range == 24 ) {
|
3013 |
$ip_pattern = sprintf( '/^%d(\.[0-9]{1,3}){3}$/', $ip_parts[0] );
|
3014 |
}
|
3015 |
|
3016 |
+
if ( $ip_pattern && preg_match( $ip_pattern, $remote_addr ) ) {
|
3017 |
+
return true;
|
3018 |
}
|
3019 |
}
|
3020 |
}
|
3021 |
|
3022 |
+
return false;
|
3023 |
}
|
3024 |
|
3025 |
/**
|
3028 |
* @param integer $user_id The user identifier that will be changed, this must be different than the user in session.
|
3029 |
* @return boolean Either TRUE or FALSE in case of success or error respectively.
|
3030 |
*/
|
3031 |
+
public static function set_new_password( $user_id = 0 ){
|
3032 |
+
$user_id = intval( $user_id );
|
3033 |
|
3034 |
+
if ( $user_id > 0 && function_exists( 'wp_generate_password' ) ){
|
3035 |
+
$user = get_userdata( $user_id );
|
3036 |
|
3037 |
+
if ( $user instanceof WP_User ){
|
3038 |
+
$new_password = wp_generate_password( 15, true, false );
|
3039 |
|
3040 |
+
$message = 'The password for your user account <strong>"'. $user->display_name .'"</strong> '
|
3041 |
. 'in the website specified above was changed, this is the new password generated automatically '
|
3042 |
. 'by the system, please update as soon as possible.<br><div style="display:inline-block;'
|
3043 |
. 'background:#ddd;font-family:monaco,monospace,courier;font-size:30px;margin:0;padding:15px;'
|
3044 |
. 'border:1px solid #999">'. $new_password .'</div>';
|
3045 |
|
3046 |
+
$data_set = array( 'Force' => true ); // Skip limit for emails per hour.
|
3047 |
SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
|
3048 |
|
3049 |
+
wp_set_password( $new_password, $user_id );
|
3050 |
|
3051 |
+
return true;
|
3052 |
}
|
3053 |
}
|
3054 |
|
3055 |
+
return false;
|
3056 |
}
|
3057 |
|
3058 |
/**
|
3068 |
$new_wpconfig = '';
|
3069 |
$config_path = self::get_wpconfig_path();
|
3070 |
|
3071 |
+
if ( $config_path ){
|
3072 |
$pattern = self::secret_key_pattern();
|
3073 |
$define_tpl = "define('%s',%s'%s');";
|
3074 |
+
$config_lines = SucuriScanFileInfo::file_lines( $config_path );
|
3075 |
$new_keys = SucuriScanAPI::get_new_secret_keys();
|
3076 |
$old_keys = array();
|
3077 |
$old_keys_string = '';
|
3078 |
$new_keys_string = '';
|
3079 |
|
3080 |
+
foreach ( (array) $config_lines as $config_line ){
|
3081 |
+
$config_line = str_replace( "\n", '', $config_line );
|
3082 |
|
3083 |
+
if ( preg_match( $pattern, $config_line, $match ) ){
|
3084 |
$key_name = $match[1];
|
3085 |
|
3086 |
+
if ( array_key_exists( $key_name, $new_keys ) ){
|
3087 |
$white_spaces = $match[2];
|
3088 |
+
$old_keys[ $key_name ] = $match[3];
|
3089 |
+
$config_line = sprintf( $define_tpl, $key_name, $white_spaces, $new_keys[ $key_name ] );
|
3090 |
+
$old_keys_string .= sprintf( $define_tpl, $key_name, $white_spaces, $old_keys[ $key_name ] ) . "\n";
|
3091 |
$new_keys_string .= $config_line . "\n";
|
3092 |
}
|
3093 |
}
|
3096 |
}
|
3097 |
|
3098 |
$response = array(
|
3099 |
+
'updated' => is_writable( $config_path ),
|
3100 |
'old_keys' => $old_keys,
|
3101 |
'old_keys_string' => $old_keys_string,
|
3102 |
'new_keys' => $new_keys,
|
3104 |
'new_wpconfig' => $new_wpconfig,
|
3105 |
);
|
3106 |
|
3107 |
+
if ( $response['updated'] ){
|
3108 |
+
file_put_contents( $config_path, $new_wpconfig, LOCK_EX );
|
3109 |
}
|
3110 |
|
3111 |
return $response;
|
3112 |
}
|
3113 |
|
3114 |
+
return false;
|
3115 |
}
|
3116 |
|
3117 |
}
|
3134 |
class SucuriScanHook extends SucuriScanEvent {
|
3135 |
|
3136 |
/**
|
3137 |
+
* Send to Sucuri servers an alert notifying that an attachment was added to a post.
|
3138 |
*
|
3139 |
* @param integer $id The post identifier.
|
3140 |
* @return void
|
3141 |
*/
|
3142 |
+
public static function hook_add_attachment( $id = 0 ){
|
3143 |
+
if ( $data = get_post( $id ) ) {
|
3144 |
+
$id = $data->ID;
|
3145 |
+
$title = $data->post_title;
|
3146 |
+
$mime_type = $data->post_mime_type;
|
3147 |
+
} else {
|
3148 |
+
$title = 'unknown';
|
3149 |
+
$mime_type = 'unknown';
|
3150 |
+
}
|
3151 |
|
3152 |
+
$message = sprintf( 'Media file added; identifier: %s; name: %s; type: %s', $id, $title, $mime_type );
|
3153 |
+
self::report_notice_event( $message );
|
3154 |
self::notify_event( 'post_publication', $message );
|
3155 |
}
|
3156 |
|
3157 |
/**
|
3158 |
+
* Send an alert notifying that a new link was added to the bookmarks.
|
3159 |
*
|
3160 |
* @param integer $id Identifier of the new link created;
|
3161 |
* @return void
|
3162 |
*/
|
3163 |
+
public static function hook_add_link( $id = 0 ){
|
3164 |
+
if ( $data = get_bookmark( $id ) ) {
|
3165 |
+
$id = $data->link_id;
|
|
|
3166 |
$title = $data->link_name;
|
3167 |
$url = $data->link_url;
|
3168 |
+
$target = $data->link_target;
|
3169 |
} else {
|
3170 |
+
$title = 'unknown';
|
3171 |
$url = 'undefined/url';
|
3172 |
+
$target = '_none';
|
3173 |
}
|
3174 |
|
3175 |
+
$message = sprintf(
|
3176 |
+
'Bookmark link added; identifier: %s; name: %s; url: %s; target: %s',
|
3177 |
+
$id, $title, $url, $target
|
3178 |
+
);
|
3179 |
+
self::report_warning_event( $message );
|
3180 |
self::notify_event( 'post_publication', $message );
|
3181 |
}
|
3182 |
|
3183 |
/**
|
3184 |
+
* Send an alert notifying that a category was created.
|
3185 |
*
|
3186 |
* @param integer $id The identifier of the category created.
|
3187 |
* @return void
|
3188 |
*/
|
3189 |
+
public static function hook_create_category( $id = 0 ){
|
3190 |
+
$title = ( is_int( $id ) ? get_cat_name( $id ) : 'Unknown' );
|
3191 |
|
3192 |
+
$message = sprintf( 'Category created; identifier: %s; name: %s', $id, $title );
|
3193 |
+
self::report_notice_event( $message );
|
3194 |
self::notify_event( 'post_publication', $message );
|
3195 |
}
|
3196 |
|
3197 |
/**
|
3198 |
+
* Send an alert notifying that a post was deleted.
|
3199 |
*
|
3200 |
* @param integer $id The identifier of the post deleted.
|
3201 |
* @return void
|
3202 |
*/
|
3203 |
+
public static function hook_delete_post( $id = 0 ){
|
3204 |
+
self::report_warning_event( 'Post deleted; identifier: ' . $id );
|
3205 |
+
}
|
3206 |
+
|
3207 |
+
/**
|
3208 |
+
* Send an alert notifying that a post was moved to the trash.
|
3209 |
+
*
|
3210 |
+
* @param integer $id The identifier of the trashed post.
|
3211 |
+
* @return void
|
3212 |
+
*/
|
3213 |
+
public static function hook_wp_trash_post( $id = 0 ){
|
3214 |
+
if ( $data = get_post( $id ) ) {
|
3215 |
+
$title = $data->post_title;
|
3216 |
+
$status = $data->post_status;
|
3217 |
+
} else {
|
3218 |
+
$title = 'Unknown';
|
3219 |
+
$status = 'none';
|
3220 |
+
}
|
3221 |
+
|
3222 |
+
$message = sprintf(
|
3223 |
+
'Post moved to trash; identifier: %s; name: %s; status: %s',
|
3224 |
+
$id, $title, $status
|
3225 |
+
);
|
3226 |
+
self::report_warning_event( $message );
|
3227 |
}
|
3228 |
|
3229 |
/**
|
3230 |
+
* Send an alert notifying that a user account was deleted.
|
3231 |
*
|
3232 |
* @param integer $id The identifier of the user account deleted.
|
3233 |
* @return void
|
3234 |
*/
|
3235 |
+
public static function hook_delete_user( $id = 0 ){
|
3236 |
+
self::report_warning_event( 'User account deleted; identifier: ' . $id );
|
3237 |
}
|
3238 |
|
3239 |
/**
|
3240 |
+
* Send an alert notifying that an attempt to reset the password
|
3241 |
* of an user account was executed.
|
3242 |
*
|
3243 |
* @return void
|
3244 |
*/
|
3245 |
public static function hook_login_form_resetpass(){
|
3246 |
// Detecting WordPress 2.8.3 vulnerability - $key is array.
|
3247 |
+
if ( isset($_GET['key']) && is_array( $_GET['key'] ) ) {
|
3248 |
+
self::report_critical_event( 'Attempt to reset password by attacking WP/2.8.3 bug' );
|
3249 |
}
|
3250 |
}
|
3251 |
|
3252 |
/**
|
3253 |
+
* Send an alert notifying that the state of a post was changed
|
3254 |
* from private to published. This will only applies for posts not pages.
|
3255 |
*
|
3256 |
* @param integer $id The identifier of the post changed.
|
3257 |
* @return void
|
3258 |
*/
|
3259 |
+
public static function hook_private_to_published( $id = 0 ){
|
3260 |
+
if ( $data = get_post( $id ) ) {
|
|
|
|
|
3261 |
$title = $data->post_title;
|
3262 |
+
$p_type = ucwords( $data->post_type );
|
3263 |
} else {
|
3264 |
$title = 'Unknown';
|
3265 |
$p_type = 'Publication';
|
3266 |
}
|
3267 |
|
3268 |
// Check whether the post-type is being ignored to send notifications.
|
3269 |
+
if ( ! SucuriScanOption::is_ignored_event( $p_type ) ) {
|
3270 |
+
$message = sprintf(
|
3271 |
+
'%s (private to published); identifier: %s; name: %s',
|
3272 |
+
$p_type, $id, $title
|
3273 |
+
);
|
3274 |
+
self::report_notice_event( $message );
|
3275 |
self::notify_event( 'post_publication', $message );
|
3276 |
}
|
3277 |
}
|
3278 |
|
3279 |
/**
|
3280 |
+
* Send an alert notifying that a post was published.
|
3281 |
*
|
3282 |
* @param integer $id The identifier of the post or page published.
|
3283 |
* @return void
|
3284 |
*/
|
3285 |
+
public static function hook_publish( $id = 0 ){
|
3286 |
+
if ( $data = get_post( $id ) ) {
|
|
|
|
|
3287 |
$title = $data->post_title;
|
3288 |
+
$p_type = ucwords( $data->post_type );
|
3289 |
$action = ( $data->post_date == $data->post_modified ? 'created' : 'updated' );
|
3290 |
} else {
|
3291 |
$title = 'Unknown';
|
3293 |
$action = 'published';
|
3294 |
}
|
3295 |
|
3296 |
+
$message = sprintf(
|
3297 |
+
'%s was %s; identifier: %s; name: %s',
|
3298 |
+
$p_type, $action, $id, $title
|
3299 |
+
);
|
3300 |
+
self::report_notice_event( $message );
|
3301 |
self::notify_event( 'post_publication', $message );
|
3302 |
}
|
3303 |
|
3307 |
* @param integer $id The identifier of the post or page published.
|
3308 |
* @return void
|
3309 |
*/
|
3310 |
+
public static function hook_publish_page( $id = 0 ){
|
3311 |
+
self::hook_publish( $id );
|
3312 |
}
|
3313 |
|
3314 |
/**
|
3317 |
* @param integer $id The identifier of the post or page published.
|
3318 |
* @return void
|
3319 |
*/
|
3320 |
+
public static function hook_publish_post( $id = 0 ){
|
3321 |
+
self::hook_publish( $id );
|
3322 |
}
|
3323 |
|
3324 |
/**
|
3327 |
* @param integer $id The identifier of the post or page published.
|
3328 |
* @return void
|
3329 |
*/
|
3330 |
+
public static function hook_publish_phone( $id = 0 ){
|
3331 |
+
self::hook_publish( $id );
|
3332 |
}
|
3333 |
|
3334 |
/**
|
3337 |
* @param integer $id The identifier of the post or page published.
|
3338 |
* @return void
|
3339 |
*/
|
3340 |
+
public static function hook_xmlrpc_publish_post( $id = 0 ){
|
3341 |
+
self::hook_publish( $id );
|
3342 |
}
|
3343 |
|
3344 |
/**
|
3345 |
+
* Send an alert notifying that an attempt to retrieve the password
|
3346 |
* of an user account was tried.
|
3347 |
*
|
3348 |
* @param string $title The name of the user account involved in the trasaction.
|
3349 |
* @return void
|
3350 |
*/
|
3351 |
+
public static function hook_retrieve_password( $title = '' ){
|
3352 |
+
if ( empty($title) ) { $title = 'unknown'; }
|
3353 |
|
3354 |
+
self::report_error_event( 'Password retrieval attempt: ' . $title );
|
3355 |
}
|
3356 |
|
3357 |
/**
|
3358 |
+
* Send an alert notifying that the theme of the site was changed.
|
3359 |
*
|
3360 |
* @param string $title The name of the new theme selected to used through out the site.
|
3361 |
* @return void
|
3362 |
*/
|
3363 |
+
public static function hook_switch_theme( $title = '' ){
|
3364 |
+
if ( empty($title) ) { $title = 'unknown'; }
|
3365 |
|
3366 |
+
$message = 'Theme activated: ' . $title;
|
3367 |
+
self::report_warning_event( $message );
|
3368 |
+
self::notify_event( 'theme_activated', $message );
|
3369 |
}
|
3370 |
|
3371 |
/**
|
3372 |
+
* Send an alert notifying that a new user account was created.
|
3373 |
*
|
3374 |
* @param integer $id The identifier of the new user account created.
|
3375 |
* @return void
|
3376 |
*/
|
3377 |
+
public static function hook_user_register( $id = 0 ){
|
3378 |
+
if ( $data = get_userdata( $id ) ) {
|
3379 |
+
$title = $data->user_login;
|
3380 |
+
$email = $data->user_email;
|
3381 |
+
$roles = @implode( ', ', $data->roles );
|
3382 |
+
} else {
|
3383 |
+
$title = 'unknown';
|
3384 |
+
$email = 'user@domain.com';
|
3385 |
+
$roles = 'none';
|
3386 |
+
}
|
3387 |
|
3388 |
+
$message = sprintf(
|
3389 |
+
'User account created; identifier: %s; name: %s; email: %s; roles: %s',
|
3390 |
+
$id, $title, $email, $roles
|
3391 |
+
);
|
3392 |
+
self::report_warning_event( $message );
|
3393 |
self::notify_event( 'user_registration', $message );
|
3394 |
}
|
3395 |
|
3396 |
/**
|
3397 |
+
* Send an alert notifying that an attempt to login into the
|
3398 |
* administration panel was successful.
|
3399 |
*
|
3400 |
* @param string $title The name of the user account involved in the transaction.
|
3401 |
* @return void
|
3402 |
*/
|
3403 |
+
public static function hook_wp_login( $title = '' ){
|
3404 |
+
if ( empty($title) ) { $title = 'Unknown'; }
|
3405 |
|
3406 |
+
$message = 'User authentication succeeded: ' . $title;
|
3407 |
+
self::report_notice_event( $message );
|
3408 |
self::notify_event( 'success_login', $message );
|
3409 |
}
|
3410 |
|
3411 |
/**
|
3412 |
+
* Send an alert notifying that an attempt to login into the
|
3413 |
* administration panel failed.
|
3414 |
*
|
3415 |
* @param string $title The name of the user account involved in the transaction.
|
3416 |
* @return void
|
3417 |
*/
|
3418 |
+
public static function hook_wp_login_failed( $title = '' ){
|
3419 |
+
if ( empty($title) ){ $title = 'Unknown'; }
|
3420 |
|
3421 |
+
$title = sanitize_user( $title, true );
|
3422 |
+
$password = SucuriScanRequest::post( 'pwd' );
|
3423 |
$message = 'User authentication failed: ' . $title;
|
3424 |
|
3425 |
+
self::report_error_event( $message );
|
3426 |
+
|
3427 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
3428 |
$message .= "<br>\nUser wrong password: " . $password;
|
3429 |
}
|
3430 |
|
|
|
3431 |
self::notify_event( 'failed_login', $message );
|
3432 |
|
3433 |
// Log the failed login in the internal datastore for future reports.
|
3434 |
$logged = sucuriscan_log_failed_login( $title, $password );
|
3435 |
|
3436 |
// Check if the quantity of failed logins will be considered as a brute-force attack.
|
3437 |
+
if ( $logged ){
|
3438 |
$failed_logins = sucuriscan_get_failed_logins();
|
3439 |
|
3440 |
+
if ( $failed_logins ){
|
3441 |
$max_time = 3600;
|
3442 |
+
$maximum_failed_logins = SucuriScanOption::get_option( 'sucuriscan_maximum_failed_logins' );
|
3443 |
|
3444 |
/**
|
3445 |
* If the time passed is within the hour, and the quantity of failed logins
|
3448 |
* settings page), then send an email notification reporting the event and
|
3449 |
* specifying that it may be a brute-force attack against the login page.
|
3450 |
*/
|
3451 |
+
if (
|
3452 |
$failed_logins['diff_time'] <= $max_time
|
3453 |
&& $failed_logins['count'] >= $maximum_failed_logins
|
3454 |
){
|
3455 |
+
sucuriscan_report_failed_logins( $failed_logins );
|
3456 |
}
|
3457 |
|
3458 |
/**
|
3464 |
* first entry of that file in case of future attempts during the next sixty
|
3465 |
* minutes.
|
3466 |
*/
|
3467 |
+
elseif ( $failed_logins['diff_time'] > $max_time ){
|
3468 |
sucuriscan_reset_failed_logins();
|
3469 |
+
sucuriscan_log_failed_login( $title );
|
3470 |
}
|
3471 |
}
|
3472 |
}
|
3483 |
*/
|
3484 |
public static function hook_undefined_actions(){
|
3485 |
|
3486 |
+
$plugin_activate_actions = '(activate|deactivate)(\-selected)?';
|
3487 |
+
$plugin_update_actions = '(upgrade-plugin|do-plugin-upgrade|update-selected)';
|
3488 |
+
|
3489 |
// Plugin activation and/or deactivation.
|
3490 |
+
if (
|
3491 |
+
current_user_can( 'activate_plugins' )
|
3492 |
&& (
|
3493 |
+
SucuriScanRequest::get_or_post( 'action', $plugin_activate_actions )
|
3494 |
+
|| SucuriScanRequest::get_or_post( 'action2', $plugin_activate_actions )
|
3495 |
)
|
3496 |
){
|
3497 |
$plugin_list = array();
|
3498 |
+
$items_affected = array();
|
3499 |
+
|
3500 |
+
// Get the action performed through action or action2 params.
|
3501 |
+
$action_d = SucuriScanRequest::get_or_post( 'action' );
|
3502 |
+
if ( $action_d == '-1' ) { $action_d = SucuriScanRequest::get_or_post( 'action2' ); }
|
3503 |
+
$action_d .= 'd';
|
3504 |
|
3505 |
+
if (
|
3506 |
+
SucuriScanRequest::get( 'plugin', '.+' )
|
3507 |
+
&& strpos( $_SERVER['REQUEST_URI'], 'plugins.php' ) !== false
|
3508 |
){
|
3509 |
+
$plugin_list[] = SucuriScanRequest::get( 'plugin' );
|
|
|
3510 |
}
|
3511 |
|
3512 |
+
elseif (
|
3513 |
isset($_POST['checked'])
|
3514 |
+
&& is_array( $_POST['checked'] )
|
3515 |
+
&& ! empty($_POST['checked'])
|
3516 |
){
|
3517 |
+
$plugin_list = SucuriScanRequest::post( 'checked', '_array' );
|
3518 |
+
$action_d = str_replace( '-selected', '', $action_d );
|
3519 |
}
|
3520 |
|
3521 |
+
foreach ( $plugin_list as $plugin ){
|
3522 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3523 |
|
3524 |
+
if (
|
3525 |
+
! empty($plugin_info['Name'])
|
3526 |
+
&& ! empty($plugin_info['Version'])
|
3527 |
){
|
3528 |
+
$items_affected[] = sprintf(
|
3529 |
+
'%s (v%s; %s)',
|
3530 |
+
self::escape( $plugin_info['Name'] ),
|
3531 |
+
self::escape( $plugin_info['Version'] ),
|
3532 |
+
self::escape( $plugin )
|
|
|
3533 |
);
|
|
|
|
|
|
|
3534 |
}
|
3535 |
}
|
3536 |
+
|
3537 |
+
// Report activated/deactivated plugins at once.
|
3538 |
+
if ( ! empty($items_affected) ) {
|
3539 |
+
$message_tpl = ( count( $items_affected ) > 1 )
|
3540 |
+
? 'Plugins %s: (multiple entries): %s'
|
3541 |
+
: 'Plugin %s: %s';
|
3542 |
+
$message = sprintf(
|
3543 |
+
$message_tpl,
|
3544 |
+
$action_d,
|
3545 |
+
@implode( ',', $items_affected )
|
3546 |
+
);
|
3547 |
+
self::report_warning_event( $message );
|
3548 |
+
self::notify_event( 'plugin_' . $action_d, $message );
|
3549 |
+
}
|
3550 |
}
|
3551 |
|
3552 |
// Plugin update request.
|
3553 |
+
elseif (
|
3554 |
+
current_user_can( 'update_plugins' )
|
3555 |
&& (
|
3556 |
+
SucuriScanRequest::get_or_post( 'action', $plugin_update_actions )
|
3557 |
+
|| SucuriScanRequest::get_or_post( 'action2', $plugin_update_actions )
|
3558 |
)
|
3559 |
){
|
3560 |
$plugin_list = array();
|
3561 |
+
$items_affected = array();
|
3562 |
|
3563 |
+
if (
|
3564 |
+
SucuriScanRequest::get( 'plugin', '.+' )
|
3565 |
+
&& strpos( $_SERVER['REQUEST_URI'], 'wp-admin/update.php' ) !== false
|
3566 |
){
|
3567 |
+
$plugin_list[] = SucuriScanRequest::get( 'plugin', '.+' );
|
3568 |
}
|
3569 |
|
3570 |
+
elseif (
|
3571 |
isset($_POST['checked'])
|
3572 |
+
&& is_array( $_POST['checked'] )
|
3573 |
+
&& ! empty($_POST['checked'])
|
3574 |
){
|
3575 |
+
$plugin_list = SucuriScanRequest::post( 'checked', '_array' );
|
3576 |
}
|
3577 |
|
3578 |
+
foreach ( $plugin_list as $plugin ){
|
3579 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3580 |
|
3581 |
+
if (
|
3582 |
+
! empty($plugin_info['Name'])
|
3583 |
+
&& ! empty($plugin_info['Version'])
|
3584 |
){
|
3585 |
+
$items_affected[] = sprintf(
|
3586 |
+
'%s (v%s; %s)',
|
3587 |
+
self::escape( $plugin_info['Name'] ),
|
3588 |
+
self::escape( $plugin_info['Version'] ),
|
3589 |
+
self::escape( $plugin )
|
3590 |
);
|
|
|
|
|
|
|
3591 |
}
|
3592 |
}
|
3593 |
+
|
3594 |
+
// Report updated plugins at once.
|
3595 |
+
if ( ! empty($items_affected) ) {
|
3596 |
+
$message_tpl = ( count( $items_affected ) > 1 )
|
3597 |
+
? 'Plugins updated: (multiple entries): %s'
|
3598 |
+
: 'Plugin updated: %s';
|
3599 |
+
$message = sprintf(
|
3600 |
+
$message_tpl,
|
3601 |
+
@implode( ',', $items_affected )
|
3602 |
+
);
|
3603 |
+
self::report_warning_event( $message );
|
3604 |
+
self::notify_event( 'plugin_updated', $message );
|
3605 |
+
}
|
3606 |
}
|
3607 |
|
3608 |
// Plugin installation request.
|
3609 |
+
elseif (
|
3610 |
+
current_user_can( 'install_plugins' )
|
3611 |
+
&& SucuriScanRequest::get( 'action', '(install|upload)-plugin' )
|
3612 |
){
|
3613 |
+
if ( isset($_FILES['pluginzip']) ){
|
3614 |
+
$plugin = self::escape( $_FILES['pluginzip']['name'] );
|
3615 |
} else {
|
3616 |
+
$plugin = SucuriScanRequest::get( 'plugin', '.+' );
|
3617 |
|
3618 |
+
if ( ! $plugin ){ $plugin = 'Unknown'; }
|
3619 |
}
|
3620 |
|
3621 |
+
$message = 'Plugin installed: ' . self::escape( $plugin );
|
3622 |
+
SucuriScanEvent::report_warning_event( $message );
|
3623 |
self::notify_event( 'plugin_installed', $message );
|
3624 |
}
|
3625 |
|
3626 |
// Plugin deletion request.
|
3627 |
+
elseif (
|
3628 |
+
current_user_can( 'delete_plugins' )
|
3629 |
+
&& SucuriScanRequest::post( 'action', 'delete-selected' )
|
3630 |
+
&& SucuriScanRequest::post( 'verify-delete', '1' )
|
3631 |
){
|
3632 |
+
$plugin_list = SucuriScanRequest::post( 'checked', '_array' );
|
3633 |
+
$items_affected = array();
|
3634 |
|
3635 |
+
foreach ( (array) $plugin_list as $plugin ){
|
3636 |
$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
3637 |
|
3638 |
+
if (
|
3639 |
+
! empty($plugin_info['Name'])
|
3640 |
+
&& ! empty($plugin_info['Version'])
|
3641 |
){
|
3642 |
+
$items_affected[] = sprintf(
|
3643 |
+
'%s (v%s; %s)',
|
3644 |
+
self::escape( $plugin_info['Name'] ),
|
3645 |
+
self::escape( $plugin_info['Version'] ),
|
3646 |
+
self::escape( $plugin )
|
3647 |
);
|
|
|
|
|
|
|
3648 |
}
|
3649 |
}
|
3650 |
+
|
3651 |
+
// Report deleted plugins at once.
|
3652 |
+
if ( ! empty($items_affected) ) {
|
3653 |
+
$message_tpl = ( count( $items_affected ) > 1 )
|
3654 |
+
? 'Plugins deleted: (multiple entries): %s'
|
3655 |
+
: 'Plugin deleted: %s';
|
3656 |
+
$message = sprintf(
|
3657 |
+
$message_tpl,
|
3658 |
+
@implode( ',', $items_affected )
|
3659 |
+
);
|
3660 |
+
self::report_warning_event( $message );
|
3661 |
+
self::notify_event( 'plugin_deleted', $message );
|
3662 |
+
}
|
3663 |
}
|
3664 |
|
3665 |
// Plugin editor request.
|
3666 |
+
elseif (
|
3667 |
+
current_user_can( 'edit_plugins' )
|
3668 |
+
&& SucuriScanRequest::post( 'action', 'update' )
|
3669 |
+
&& SucuriScanRequest::post( 'plugin', '.+' )
|
3670 |
+
&& SucuriScanRequest::post( 'file', '.+' )
|
3671 |
+
&& strpos( $_SERVER['REQUEST_URI'], 'plugin-editor.php' ) !== false
|
3672 |
){
|
3673 |
+
$filename = SucuriScanRequest::post( 'file' );
|
3674 |
+
$message = 'Plugin editor used in: ' . SucuriScan::escape( $filename );
|
3675 |
+
self::report_error_event( $message );
|
3676 |
self::notify_event( 'theme_editor', $message );
|
3677 |
}
|
3678 |
|
3679 |
// Theme editor request.
|
3680 |
+
elseif (
|
3681 |
+
current_user_can( 'edit_themes' )
|
3682 |
+
&& SucuriScanRequest::post( 'action', 'update' )
|
3683 |
+
&& SucuriScanRequest::post( 'theme', '.+' )
|
3684 |
+
&& SucuriScanRequest::post( 'file', '.+' )
|
3685 |
+
&& strpos( $_SERVER['REQUEST_URI'], 'theme-editor.php' ) !== false
|
3686 |
){
|
3687 |
+
$theme_name = SucuriScanRequest::post( 'theme' );
|
3688 |
+
$filename = SucuriScanRequest::post( 'file' );
|
3689 |
+
$message = 'Theme editor used in: ' . SucuriScan::escape( $theme_name ) . '/' . SucuriScan::escape( $filename );
|
3690 |
+
self::report_error_event( $message );
|
3691 |
self::notify_event( 'theme_editor', $message );
|
3692 |
}
|
3693 |
|
3694 |
+
// Theme installation request.
|
3695 |
+
elseif (
|
3696 |
+
current_user_can( 'install_themes' )
|
3697 |
+
&& SucuriScanRequest::get( 'action', 'install-theme' )
|
|
|
|
|
|
|
|
|
|
|
3698 |
){
|
3699 |
+
$theme = SucuriScanRequest::get( 'theme', '.+' );
|
3700 |
|
3701 |
+
if ( ! $theme ){ $theme = 'Unknown'; }
|
|
|
|
|
|
|
3702 |
|
3703 |
+
$message = 'Theme installed: ' . self::escape( $theme );
|
3704 |
+
SucuriScanEvent::report_warning_event( $message );
|
3705 |
+
self::notify_event( 'theme_installed', $message );
|
3706 |
+
}
|
3707 |
|
3708 |
+
// Theme deletion request.
|
3709 |
+
elseif (
|
3710 |
+
current_user_can( 'delete_themes' )
|
3711 |
+
&& SucuriScanRequest::get_or_post( 'action', 'delete' )
|
3712 |
+
&& SucuriScanRequest::get_or_post( 'stylesheet', '.+' )
|
3713 |
+
){
|
3714 |
+
$theme = SucuriScanRequest::get( 'stylesheet', '.+' );
|
3715 |
+
|
3716 |
+
if ( ! $theme ){ $theme = 'Unknown'; }
|
3717 |
+
|
3718 |
+
$message = 'Theme deleted: ' . self::escape( $theme );
|
3719 |
+
SucuriScanEvent::report_warning_event( $message );
|
3720 |
+
self::notify_event( 'theme_deleted', $message );
|
3721 |
+
}
|
3722 |
+
|
3723 |
+
// Theme update request.
|
3724 |
+
elseif (
|
3725 |
+
current_user_can( 'update_themes' )
|
3726 |
+
&& SucuriScanRequest::get( 'action', '(upgrade-theme|do-theme-upgrade)' )
|
3727 |
+
&& SucuriScanRequest::post( 'checked', '_array' )
|
3728 |
+
){
|
3729 |
+
$themes = SucuriScanRequest::post( 'checked', '_array' );
|
3730 |
+
$items_affected = array();
|
3731 |
+
|
3732 |
+
foreach ( (array) $themes as $theme ){
|
3733 |
+
$theme_info = wp_get_theme( $theme );
|
3734 |
+
$theme_name = ucwords( $theme );
|
3735 |
+
$theme_version = '0.0';
|
3736 |
+
|
3737 |
+
if ( $theme_info->exists() ){
|
3738 |
+
$theme_name = $theme_info->get( 'Name' );
|
3739 |
+
$theme_version = $theme_info->get( 'Version' );
|
3740 |
+
}
|
3741 |
+
|
3742 |
+
$items_affected[] = sprintf(
|
3743 |
+
'%s (v%s; %s)',
|
3744 |
+
self::escape( $theme_name ),
|
3745 |
+
self::escape( $theme_version ),
|
3746 |
+
self::escape( $theme )
|
3747 |
);
|
3748 |
+
}
|
3749 |
|
3750 |
+
// Report updated themes at once.
|
3751 |
+
if ( ! empty($items_affected) ) {
|
3752 |
+
$message_tpl = ( count( $items_affected ) > 1 )
|
3753 |
+
? 'Themes updated: (multiple entries): %s'
|
3754 |
+
: 'Theme updated: %s';
|
3755 |
+
$message = sprintf(
|
3756 |
+
$message_tpl,
|
3757 |
+
@implode( ',', $items_affected )
|
3758 |
+
);
|
3759 |
+
self::report_warning_event( $message );
|
3760 |
self::notify_event( 'theme_updated', $message );
|
3761 |
}
|
3762 |
}
|
3763 |
|
3764 |
// WordPress update request.
|
3765 |
+
elseif (
|
3766 |
+
current_user_can( 'update_core' )
|
3767 |
+
&& SucuriScanRequest::get( 'action', '(do-core-upgrade|do-core-reinstall)' )
|
3768 |
+
&& SucuriScanRequest::post( 'upgrade' )
|
3769 |
){
|
3770 |
+
$message = 'WordPress updated to version: ' . SucuriScanRequest::post( 'version' );
|
3771 |
+
self::report_critical_event( $message );
|
3772 |
self::notify_event( 'website_updated', $message );
|
3773 |
}
|
3774 |
|
3775 |
// Widget addition or deletion.
|
3776 |
+
elseif (
|
3777 |
+
current_user_can( 'edit_theme_options' )
|
3778 |
+
&& SucuriScanRequest::post( 'action', 'save-widget' )
|
3779 |
+
&& SucuriScanRequest::post( 'id_base' ) !== false
|
3780 |
+
&& SucuriScanRequest::post( 'sidebar' ) !== false
|
3781 |
){
|
3782 |
+
if ( SucuriScanRequest::post( 'delete_widget', '1' ) ){
|
3783 |
$action_d = 'deleted';
|
3784 |
$action_text = 'deleted from';
|
3785 |
} else {
|
3789 |
|
3790 |
$message = sprintf(
|
3791 |
'Widget %s (%s) %s %s (#%d; size %dx%d)',
|
3792 |
+
SucuriScanRequest::post( 'id_base' ),
|
3793 |
+
SucuriScanRequest::post( 'widget-id' ),
|
3794 |
$action_text,
|
3795 |
+
SucuriScanRequest::post( 'sidebar' ),
|
3796 |
+
SucuriScanRequest::post( 'widget_number' ),
|
3797 |
+
SucuriScanRequest::post( 'widget-width' ),
|
3798 |
+
SucuriScanRequest::post( 'widget-height' )
|
3799 |
);
|
3800 |
|
3801 |
+
self::report_warning_event( $message );
|
3802 |
self::notify_event( 'widget_' . $action_d, $message );
|
3803 |
}
|
3804 |
|
3805 |
// Detect any Wordpress settings modification.
|
3806 |
+
elseif (
|
3807 |
+
current_user_can( 'manage_options' )
|
3808 |
&& SucuriScanOption::check_options_nonce()
|
3809 |
){
|
3810 |
// Get the settings available in the database and compare them with the submission.
|
3811 |
+
$options_changed = SucuriScanOption::what_options_were_changed( $_POST );
|
|
|
3812 |
$options_changed_str = '';
|
3813 |
+
$options_changed_simple = '';
|
3814 |
$options_changed_count = 0;
|
3815 |
|
3816 |
// Generate the list of options changed.
|
3817 |
+
foreach ( $options_changed['original'] as $option_name => $option_value ){
|
3818 |
$options_changed_count += 1;
|
3819 |
$options_changed_str .= sprintf(
|
3820 |
"The value of the option <b>%s</b> was changed from <b>'%s'</b> to <b>'%s'</b>.<br>\n",
|
3821 |
+
self::escape( $option_name ),
|
3822 |
+
self::escape( $option_value ),
|
3823 |
+
self::escape( $options_changed['changed'][ $option_name ] )
|
3824 |
+
);
|
3825 |
+
$options_changed_simple .= sprintf(
|
3826 |
+
"%s: from '%s' to '%s',",
|
3827 |
+
self::escape( $option_name ),
|
3828 |
+
self::escape( $option_value ),
|
3829 |
+
self::escape( $options_changed['changed'][ $option_name ] )
|
3830 |
);
|
3831 |
}
|
3832 |
|
3833 |
// Get the option group (name of the page where the request was originated).
|
3834 |
$option_page = isset($_POST['option_page']) ? $_POST['option_page'] : 'options';
|
3835 |
+
$page_referer = false;
|
3836 |
|
3837 |
// Check which of these option groups where modified.
|
3838 |
+
switch ( $option_page ){
|
3839 |
case 'options':
|
3840 |
$page_referer = 'Global';
|
3841 |
break;
|
3845 |
case 'discussion': /* no_break */
|
3846 |
case 'media': /* no_break */
|
3847 |
case 'permalink':
|
3848 |
+
$page_referer = ucwords( $option_page );
|
3849 |
break;
|
3850 |
default:
|
3851 |
$page_referer = 'Common';
|
3852 |
break;
|
3853 |
}
|
3854 |
|
3855 |
+
if ( $page_referer && $options_changed_count > 0 ){
|
3856 |
+
$message = $page_referer . ' settings changed';
|
3857 |
+
SucuriScanEvent::report_error_event( sprintf(
|
3858 |
+
'%s: (multiple entries): %s',
|
3859 |
+
$message,
|
3860 |
+
rtrim( $options_changed_simple, ',' )
|
3861 |
+
) );
|
3862 |
self::notify_event( 'settings_updated', $message . "<br>\n" . $options_changed_str );
|
3863 |
}
|
3864 |
}
|
3896 |
* @return boolean Whether the SSL certs will be verified while sending a request.
|
3897 |
*/
|
3898 |
public static function verify_ssl_cert(){
|
3899 |
+
return ( self::get_option( ':verify_ssl_cert' ) === 'true' );
|
3900 |
}
|
3901 |
|
3902 |
/**
|
3905 |
* @return integer Seconds to consider a HTTP request timeout.
|
3906 |
*/
|
3907 |
public static function request_timeout(){
|
3908 |
+
return intval( self::get_option( ':request_timeout' ) );
|
3909 |
}
|
3910 |
|
3911 |
/**
|
3936 |
* @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
|
3937 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
3938 |
*/
|
3939 |
+
private static function api_call( $url = '', $method = 'GET', $params = array(), $args = array() ) {
|
3940 |
+
if ( ! $url ){ return false; }
|
3941 |
|
3942 |
$req_args = array(
|
3943 |
'method' => $method,
|
3945 |
'redirection' => 2,
|
3946 |
'httpversion' => '1.0',
|
3947 |
'user-agent' => self::user_agent(),
|
3948 |
+
'blocking' => true,
|
3949 |
'headers' => array(),
|
3950 |
'cookies' => array(),
|
3951 |
+
'compress' => false,
|
3952 |
+
'decompress' => false,
|
3953 |
'sslverify' => self::verify_ssl_cert(),
|
3954 |
);
|
3955 |
|
3956 |
// Update the request arguments with the values passed tot he function.
|
3957 |
+
foreach ( $args as $arg_name => $arg_value ) {
|
3958 |
+
if ( array_key_exists( $arg_name, $req_args ) ) {
|
3959 |
+
$req_args[ $arg_name ] = $arg_value;
|
3960 |
}
|
3961 |
}
|
3962 |
|
3963 |
+
if ( $method == 'GET' ) {
|
3964 |
+
if ( ! empty($params) ) {
|
3965 |
+
$url = sprintf( '%s?%s', $url, http_build_query( $params ) );
|
3966 |
}
|
3967 |
|
3968 |
$response = wp_remote_get( $url, $req_args );
|
3969 |
}
|
3970 |
|
3971 |
+
elseif ( $method == 'POST' ) {
|
3972 |
$req_args['body'] = $params;
|
3973 |
$response = wp_remote_post( $url, $req_args );
|
3974 |
}
|
3975 |
|
3976 |
+
if ( isset($response) ) {
|
3977 |
+
if ( is_wp_error( $response ) ) {
|
3978 |
SucuriScanInterface::error(sprintf(
|
3979 |
'Something went wrong with an API call (%s action): %s',
|
3980 |
( isset($params['a']) ? $params['a'] : 'unknown' ),
|
3984 |
$response['body_raw'] = $response['body'];
|
3985 |
|
3986 |
// Check if the response data is JSON-encoded, then decode it.
|
3987 |
+
if (
|
3988 |
isset($response['headers']['content-type'])
|
3989 |
&& $response['headers']['content-type'] == 'application/json'
|
3990 |
+
) {
|
3991 |
+
$assoc = ( isset($args['assoc']) && $args['assoc'] === true ) ? true : false;
|
3992 |
+
$response['body'] = @json_decode( $response['body_raw'], $assoc );
|
3993 |
}
|
3994 |
|
3995 |
// Check if the response data is serialized (which we will consider as insecure).
|
3996 |
+
elseif ( self::is_serialized( $response['body'] ) ) {
|
3997 |
+
$response['body_raw'] = null;
|
3998 |
$response['body'] = 'ERROR:Serialized data is not supported.';
|
3999 |
}
|
4000 |
|
4004 |
SucuriScanInterface::error( 'HTTP method not allowed: ' . $method );
|
4005 |
}
|
4006 |
|
4007 |
+
return false;
|
4008 |
}
|
4009 |
|
4010 |
/**
|
4012 |
*
|
4013 |
* @param string $api_key An unique string of characters to identify this installation.
|
4014 |
* @param boolean $validate Whether the format of the key should be validated before store it.
|
4015 |
+
* @return boolean Either true or false if the key was saved successfully or not respectively.
|
4016 |
*/
|
4017 |
+
public static function set_plugin_key( $api_key = '', $validate = false ) {
|
4018 |
+
if ( $validate ) {
|
4019 |
+
if ( ! preg_match( '/^[a-z0-9]{32}$/', $api_key ) ) {
|
4020 |
SucuriScanInterface::error( 'Invalid API key format' );
|
4021 |
+
return false;
|
4022 |
}
|
4023 |
}
|
4024 |
|
4025 |
+
if ( ! empty($api_key) ) {
|
4026 |
SucuriScanEvent::notify_event( 'plugin_change', 'API key updated successfully: ' . $api_key );
|
4027 |
}
|
4028 |
|
4032 |
/**
|
4033 |
* Retrieve the API key from the local storage.
|
4034 |
*
|
4035 |
+
* @return string|boolean The API key or false if it does not exists.
|
4036 |
*/
|
4037 |
public static function get_plugin_key(){
|
4038 |
+
$api_key = self::get_option( ':api_key' );
|
4039 |
|
4040 |
+
if ( $api_key && strlen( $api_key ) > 10 ) {
|
4041 |
return $api_key;
|
4042 |
}
|
4043 |
|
4044 |
+
return false;
|
4045 |
}
|
4046 |
|
4047 |
/**
|
4051 |
* slash, the first part of it is in fact the key and the second part is the
|
4052 |
* unique identifier of the site in the remote server.
|
4053 |
*
|
4054 |
+
* @return array|boolean false if the key is invalid or not present, an array otherwise.
|
4055 |
*/
|
4056 |
public static function get_cloudproxy_key(){
|
4057 |
$option_name = ':cloudproxy_apikey';
|
4058 |
+
$api_key = self::get_option( $option_name );
|
4059 |
|
4060 |
// Check if the cloudproxy-waf plugin was previously installed.
|
4061 |
+
if ( ! $api_key ) {
|
4062 |
+
$api_key = self::get_option( 'sucuriwaf_apikey' );
|
4063 |
|
4064 |
+
if ( $api_key ) {
|
4065 |
self::update_option( $option_name, $api_key );
|
4066 |
+
self::delete_option( 'sucuriwaf_apikey' );
|
4067 |
}
|
4068 |
}
|
4069 |
|
4070 |
// Check the validity of the API key.
|
4071 |
+
$match = self::is_valid_cloudproxy_key( $api_key, true );
|
4072 |
|
4073 |
+
if ( $match ) {
|
4074 |
return array(
|
4075 |
'string' => $match[1].'/'.$match[2],
|
4076 |
'k' => $match[1],
|
4077 |
+
's' => $match[2],
|
4078 |
);
|
4079 |
}
|
4080 |
|
4081 |
+
return false;
|
4082 |
}
|
4083 |
|
4084 |
/**
|
4086 |
*
|
4087 |
* @param string $api_key The CloudProxy API key.
|
4088 |
* @param boolean $return_match Whether the parts of the API key must be returned or not.
|
4089 |
+
* @return boolean true if the API key specified is valid, false otherwise.
|
4090 |
*/
|
4091 |
+
public static function is_valid_cloudproxy_key( $api_key = '', $return_match = false ) {
|
4092 |
$pattern = '/^([a-z0-9]{32})\/([a-z0-9]{32})$/';
|
4093 |
|
4094 |
+
if ( $api_key && preg_match( $pattern, $api_key, $match ) ) {
|
4095 |
+
if ( $return_match ){ return $match; }
|
4096 |
|
4097 |
+
return true;
|
4098 |
}
|
4099 |
|
4100 |
+
return false;
|
4101 |
}
|
4102 |
|
4103 |
/**
|
4109 |
* @param array $args Request arguments like the timeout, redirections, headers, cookies, etc.
|
4110 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
4111 |
*/
|
4112 |
+
public static function api_call_wordpress( $method = 'GET', $params = array(), $send_api_key = true, $args = array() ) {
|
4113 |
$url = SUCURISCAN_API;
|
4114 |
+
$params[ SUCURISCAN_API_VERSION ] = 1;
|
4115 |
$params['p'] = 'wordpress';
|
4116 |
|
4117 |
+
if ( $send_api_key ) {
|
4118 |
$api_key = self::get_plugin_key();
|
4119 |
|
4120 |
+
if ( ! $api_key ){ return false; }
|
4121 |
|
4122 |
$params['k'] = $api_key;
|
4123 |
}
|
4134 |
* @param array $params Parameters for the request defined in an associative array of key-value.
|
4135 |
* @return array Array of results including HTTP headers or WP_Error if the request failed.
|
4136 |
*/
|
4137 |
+
public static function api_call_cloudproxy( $method = 'GET', $params = array() ) {
|
4138 |
+
$send_request = false;
|
4139 |
|
4140 |
+
if ( isset($params['k']) && isset($params['s']) ) {
|
4141 |
+
$send_request = true;
|
4142 |
} else {
|
4143 |
$api_key = self::get_cloudproxy_key();
|
4144 |
|
4145 |
+
if ( $api_key ) {
|
4146 |
+
$send_request = true;
|
4147 |
$params['k'] = $api_key['k'];
|
4148 |
$params['s'] = $api_key['s'];
|
4149 |
}
|
4150 |
}
|
4151 |
|
4152 |
+
if ( $send_request ) {
|
4153 |
$url = SUCURISCAN_CLOUDPROXY_API;
|
4154 |
+
$params[ SUCURISCAN_CLOUDPROXY_API_VERSION ] = 1;
|
4155 |
|
4156 |
$response = self::api_call( $url, $method, $params );
|
4157 |
|
4158 |
return $response;
|
4159 |
}
|
4160 |
|
4161 |
+
return false;
|
4162 |
}
|
4163 |
|
4164 |
/**
|
4167 |
* in the administrator panel explaining the result of the operation.
|
4168 |
*
|
4169 |
* @param array $response Array of results including HTTP headers or WP_Error if the request failed.
|
4170 |
+
* @return boolean Either true or false in case of success or failure of the API response (respectively).
|
4171 |
+
*/
|
4172 |
+
private static function handle_response( $response = array() ) {
|
4173 |
+
if ( $response ) {
|
4174 |
+
if ( $response['body'] instanceof stdClass ) {
|
4175 |
+
if ( isset($response['body']->status) ) {
|
4176 |
+
if ( $response['body']->status == 1 ) {
|
4177 |
+
return true;
|
4178 |
} else {
|
4179 |
$action_message = 'Unknown error, there is no more information.';
|
4180 |
|
4182 |
$action_message = $response['body']->messages[0];
|
4183 |
}
|
4184 |
|
4185 |
+
SucuriScanInterface::error( ucwords( $response['body']->action ) . ': ' . $action_message );
|
4186 |
}
|
4187 |
} else {
|
4188 |
SucuriScanInterface::error( 'Could not determine the status of an API call.' );
|
4189 |
}
|
4190 |
} else {
|
4191 |
+
$error_message = 'non JSON-encoded response.';
|
4192 |
+
|
4193 |
+
if (
|
4194 |
+
isset($response['response'])
|
4195 |
+
&& isset($response['response']['message'])
|
4196 |
+
&& isset($response['response']['code'])
|
4197 |
+
&& $response['response']['code'] !== 200
|
4198 |
+
) {
|
4199 |
+
$error_message = sprintf(
|
4200 |
+
'(%s) %s',
|
4201 |
+
$response['response']['code'],
|
4202 |
+
$response['response']['message']
|
4203 |
+
);
|
4204 |
+
}
|
4205 |
+
|
4206 |
+
SucuriScanInterface::error( 'Malformed API response: ' . $error_message );
|
4207 |
}
|
4208 |
}
|
4209 |
|
4210 |
+
return false;
|
4211 |
}
|
4212 |
|
4213 |
/**
|
4214 |
* Send a request to the API to register this site.
|
4215 |
*
|
4216 |
+
* @return boolean true if the API key was generated, false otherwise.
|
4217 |
*/
|
4218 |
public static function register_site(){
|
4219 |
$response = self::api_call_wordpress( 'POST', array(
|
4220 |
'e' => self::get_site_email(),
|
4221 |
's' => self::get_domain(),
|
4222 |
'a' => 'register_site',
|
4223 |
+
), false );
|
4224 |
|
4225 |
+
if ( self::handle_response( $response ) ) {
|
4226 |
self::set_plugin_key( $response['body']->output->api_key );
|
4227 |
SucuriScanEvent::schedule_task();
|
4228 |
SucuriScanEvent::notify_event( 'plugin_change', 'Site registered and API key generated' );
|
4229 |
+
SucuriScanInterface::info( 'The API key for your site was successfully generated and saved.' );
|
4230 |
|
4231 |
+
return true;
|
4232 |
}
|
4233 |
|
4234 |
+
return false;
|
4235 |
}
|
4236 |
|
4237 |
/**
|
4238 |
* Send a request to recover a previously registered API key.
|
4239 |
*
|
4240 |
+
* @return boolean true if the API key was sent to the administrator email, false otherwise.
|
4241 |
*/
|
4242 |
public static function recover_key(){
|
4243 |
$clean_domain = self::get_domain();
|
4246 |
'e' => self::get_site_email(),
|
4247 |
's' => $clean_domain,
|
4248 |
'a' => 'recover_key',
|
4249 |
+
), false );
|
4250 |
|
4251 |
+
if ( self::handle_response( $response ) ) {
|
4252 |
SucuriScanEvent::notify_event( 'plugin_change', 'API key recovered for domain: ' . $clean_domain );
|
4253 |
SucuriScanInterface::info( $response['body']->output->message );
|
4254 |
|
4255 |
+
return true;
|
4256 |
}
|
4257 |
|
4258 |
+
return false;
|
4259 |
}
|
4260 |
|
4261 |
/**
|
4265 |
* this plugin.
|
4266 |
*
|
4267 |
* @param string $event The information gathered through out the normal functioning of the site.
|
4268 |
+
* @return boolean true if the event was logged in the monitoring service, false otherwise.
|
4269 |
*/
|
4270 |
+
public static function send_log( $event = '' ) {
|
4271 |
+
if ( ! empty($event) ) {
|
4272 |
$response = self::api_call_wordpress( 'POST', array(
|
4273 |
'a' => 'send_log',
|
4274 |
'm' => $event,
|
4275 |
+
), true, array( 'timeout' => 20 ) );
|
4276 |
|
4277 |
+
if ( self::handle_response( $response ) ) {
|
4278 |
+
return true;
|
4279 |
}
|
4280 |
}
|
4281 |
|
4282 |
+
return false;
|
4283 |
}
|
4284 |
|
4285 |
/**
|
4288 |
* @param integer $lines How many lines from the log file will be retrieved.
|
4289 |
* @return string The response of the API service.
|
4290 |
*/
|
4291 |
+
public static function get_logs( $lines = 50 ) {
|
4292 |
$response = self::api_call_wordpress( 'GET', array(
|
4293 |
'a' => 'get_logs',
|
4294 |
'l' => $lines,
|
4295 |
) );
|
4296 |
|
4297 |
+
if ( self::handle_response( $response ) ) {
|
4298 |
$response['body']->output_data = array();
|
4299 |
$log_pattern = '/^([0-9-: ]+) (.*) : (.*)/';
|
4300 |
$extra_pattern = '/(.+ \(multiple entries\):) (.+)/';
|
4301 |
+
$generic_pattern = '/^([A-Z][a-z]{3,7}): ([0-9a-zA-Z@_\s\.\-\(\)]+, )?(\S+; )?(.+)/';
|
4302 |
+
$auth_pattern = '/^User authentication (succeeded|failed): ([^<;]+)/';
|
4303 |
|
4304 |
+
foreach ( $response['body']->output as $log ) {
|
4305 |
+
if ( preg_match( $log_pattern, $log, $log_match ) ) {
|
4306 |
$log_data = array(
|
4307 |
+
'event' => 'notice',
|
4308 |
'datetime' => $log_match[1],
|
4309 |
+
'timestamp' => strtotime( $log_match[1] ),
|
4310 |
'account' => $log_match[2],
|
4311 |
+
'username' => 'system',
|
4312 |
+
'remote_addr' => '::1',
|
4313 |
'message' => $log_match[3],
|
4314 |
+
'file_list' => false,
|
4315 |
+
'file_list_count' => 0,
|
4316 |
);
|
4317 |
|
4318 |
$log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
|
4319 |
+
$log_data['message'] = str_replace( '<br>', '; ', $log_data['message'] );
|
4320 |
+
|
4321 |
+
// Extract more information from the generic audit logs.
|
4322 |
+
if ( preg_match( $generic_pattern, $log_data['message'], $log_extra ) ) {
|
4323 |
+
$log_data['event'] = strtolower( $log_extra[1] );
|
4324 |
+
$log_data['message'] = trim( $log_extra[4] );
|
4325 |
|
4326 |
+
// Extract the remote address from the generic logs.
|
4327 |
+
if ( ! empty($log_extra[3]) ) {
|
4328 |
+
$log_data['remote_addr'] = str_replace( ";\x20", '', $log_extra[3] );
|
4329 |
+
}
|
4330 |
+
|
4331 |
+
// Extract the username from the authentication logs.
|
4332 |
+
if ( ! empty($log_extra[2]) ) {
|
4333 |
+
$log_data['username'] = preg_replace( '/.*\((\S+)\),\s$/', '$1', $log_extra[2] );
|
4334 |
+
$log_data['username'] = str_replace( ",\x20", '', $log_data['username'] );
|
4335 |
+
}
|
4336 |
+
|
4337 |
+
// Match old user authentication logs.
|
4338 |
+
$log_data['message'] = str_replace( 'logged in', 'authentication succeeded', $log_data['message'] );
|
4339 |
+
|
4340 |
+
if ( preg_match( $auth_pattern, $log_data['message'], $user_match ) ) {
|
4341 |
+
$log_data['username'] = $user_match[2];
|
4342 |
+
}
|
4343 |
+
}
|
4344 |
+
|
4345 |
+
// Extract more information from the special formatted logs.
|
4346 |
+
if ( preg_match( $extra_pattern, $log_data['message'], $log_extra ) ) {
|
4347 |
$log_data['message'] = $log_extra[1];
|
4348 |
+
$log_data['file_list'] = explode( ',', $log_extra[2] );
|
4349 |
+
$log_data['file_list_count'] = count( $log_data['file_list'] );
|
4350 |
}
|
4351 |
|
4352 |
$response['body']->output_data[] = $log_data;
|
4356 |
return $response['body'];
|
4357 |
}
|
4358 |
|
4359 |
+
return false;
|
4360 |
+
}
|
4361 |
+
|
4362 |
+
/**
|
4363 |
+
* Get a list of valid audit event types with their respective colors.
|
4364 |
+
*
|
4365 |
+
* @return array Valid audit event types with their colors.
|
4366 |
+
*/
|
4367 |
+
public static function get_audit_event_types(){
|
4368 |
+
$event_types = array(
|
4369 |
+
'critical' => '#000000',
|
4370 |
+
'debug' => '#c690ec',
|
4371 |
+
'error' => '#f27d7d',
|
4372 |
+
'info' => '#5bc0de',
|
4373 |
+
'notice' => '#428bca',
|
4374 |
+
'warning' => '#f0ad4e',
|
4375 |
+
);
|
4376 |
+
|
4377 |
+
return $event_types;
|
4378 |
+
}
|
4379 |
+
|
4380 |
+
/**
|
4381 |
+
* Parse the event logs with multiple entries.
|
4382 |
+
*
|
4383 |
+
* @param string $event_log Event log that will be processed.
|
4384 |
+
* @return array List of parts of the event log.
|
4385 |
+
*/
|
4386 |
+
public static function parse_multiple_entries( $event_log = '' ) {
|
4387 |
+
if ( preg_match( '/^(.*:\s)\(multiple entries\):\s(.+)/', $event_log, $match ) ) {
|
4388 |
+
$event_log = array();
|
4389 |
+
$event_log[] = trim( $match[1] );
|
4390 |
+
$grouped_items = @explode( ',', $match[2] );
|
4391 |
+
$event_log = array_merge( $event_log, $grouped_items );
|
4392 |
+
}
|
4393 |
+
|
4394 |
+
return $event_log;
|
4395 |
+
}
|
4396 |
+
|
4397 |
+
/**
|
4398 |
+
* Collect the information for the audit log report.
|
4399 |
+
*
|
4400 |
+
* @param integer $lines How many lines from the log file will be retrieved.
|
4401 |
+
* @return array All the information necessary to display the audit logs report.
|
4402 |
+
*/
|
4403 |
+
public static function get_audit_report( $lines = 50 ) {
|
4404 |
+
$audit_logs = self::get_logs( $lines );
|
4405 |
+
|
4406 |
+
if (
|
4407 |
+
$audit_logs instanceof stdClass
|
4408 |
+
&& property_exists( $audit_logs, 'total_entries' )
|
4409 |
+
&& property_exists( $audit_logs, 'output_data' )
|
4410 |
+
&& ! empty($audit_logs->output_data)
|
4411 |
+
) {
|
4412 |
+
// Data structure that will be returned.
|
4413 |
+
$report = array(
|
4414 |
+
'total_events' => 0,
|
4415 |
+
'start_timestamp' => 0,
|
4416 |
+
'end_timestamp' => 0,
|
4417 |
+
'event_colors' => array(),
|
4418 |
+
'events_per_type' => array(),
|
4419 |
+
'events_per_user' => array(),
|
4420 |
+
'events_per_ipaddress' => array(),
|
4421 |
+
'events_per_login' => array(
|
4422 |
+
'successful' => 0,
|
4423 |
+
'failed' => 0,
|
4424 |
+
),
|
4425 |
+
);
|
4426 |
+
|
4427 |
+
// Get a list of valid audit event types.
|
4428 |
+
$event_types = self::get_audit_event_types();
|
4429 |
+
foreach ( $event_types as $event => $event_color ) {
|
4430 |
+
$report['events_per_type'][ $event ] = 0;
|
4431 |
+
$report['event_colors'][] = sprintf( "'%s'", $event_color );
|
4432 |
+
}
|
4433 |
+
|
4434 |
+
// Collect information for each report chart.
|
4435 |
+
foreach ( $audit_logs->output_data as $event ) {
|
4436 |
+
$report['total_events'] += 1;
|
4437 |
+
|
4438 |
+
// Increment the number of events for this event type.
|
4439 |
+
if ( array_key_exists( $event['event'], $report['events_per_type'] ) ) {
|
4440 |
+
$report['events_per_type'][ $event['event'] ] += 1;
|
4441 |
+
} else {
|
4442 |
+
$report['events_per_type'][ $event['event'] ] = 1;
|
4443 |
+
}
|
4444 |
+
|
4445 |
+
// Find the lowest datetime among the filtered events.
|
4446 |
+
if (
|
4447 |
+
$event['timestamp'] <= $report['start_timestamp']
|
4448 |
+
|| $report['start_timestamp'] === 0
|
4449 |
+
) {
|
4450 |
+
$report['start_timestamp'] = $event['timestamp'];
|
4451 |
+
}
|
4452 |
+
|
4453 |
+
// Find the highest datetime among the filtered events.
|
4454 |
+
if ( $event['timestamp'] >= $report['end_timestamp'] ) {
|
4455 |
+
$report['end_timestamp'] = $event['timestamp'];
|
4456 |
+
}
|
4457 |
+
|
4458 |
+
// Increment the number of events generated by this user account.
|
4459 |
+
if ( array_key_exists( $event['username'], $report['events_per_user'] ) ) {
|
4460 |
+
$report['events_per_user'][ $event['username'] ] += 1;
|
4461 |
+
} else {
|
4462 |
+
$report['events_per_user'][ $event['username'] ] = 1;
|
4463 |
+
}
|
4464 |
+
|
4465 |
+
// Increment the number of events generated from this remote address.
|
4466 |
+
if ( array_key_exists( $event['remote_addr'], $report['events_per_ipaddress'] ) ) {
|
4467 |
+
$report['events_per_ipaddress'][ $event['remote_addr'] ] += 1;
|
4468 |
+
} else {
|
4469 |
+
$report['events_per_ipaddress'][ $event['remote_addr'] ] = 1;
|
4470 |
+
}
|
4471 |
+
|
4472 |
+
// Detect successful and failed user authentications.
|
4473 |
+
$auth_pattern = '/^User authentication (succeeded|failed):/';
|
4474 |
+
if ( preg_match( $auth_pattern, $event['message'], $match ) ) {
|
4475 |
+
if ( $match[1] == 'succeeded' ) {
|
4476 |
+
$report['events_per_login']['successful'] += 1;
|
4477 |
+
} else {
|
4478 |
+
$report['events_per_login']['failed'] += 1;
|
4479 |
+
}
|
4480 |
+
}
|
4481 |
+
|
4482 |
+
// Backward compatibility for previous user login messages.
|
4483 |
+
elseif ( preg_match( '/^User logged in:/', $event['message'] ) ) {
|
4484 |
+
$report['events_per_login']['successful'] += 1;
|
4485 |
+
}
|
4486 |
+
}
|
4487 |
+
|
4488 |
+
if ( $report['total_events'] > 0 ) {
|
4489 |
+
return $report;
|
4490 |
+
}
|
4491 |
+
}
|
4492 |
+
|
4493 |
+
return false;
|
4494 |
}
|
4495 |
|
4496 |
/**
|
4500 |
* changes in the system.
|
4501 |
*
|
4502 |
* @param string $hashes The information gathered after the scanning of the site's files.
|
4503 |
+
* @return boolean true if the hashes were stored, false otherwise.
|
4504 |
*/
|
4505 |
+
public static function send_hashes( $hashes = '' ) {
|
4506 |
+
if ( ! empty($hashes) ) {
|
4507 |
$response = self::api_call_wordpress( 'POST', array(
|
4508 |
'a' => 'send_hashes',
|
4509 |
'h' => $hashes,
|
4510 |
) );
|
4511 |
|
4512 |
+
if ( self::handle_response( $response ) ) {
|
4513 |
+
return true;
|
4514 |
}
|
4515 |
}
|
4516 |
|
4517 |
+
return false;
|
4518 |
}
|
4519 |
|
4520 |
/**
|
4526 |
* @param boolean $api_key The CloudProxy API key.
|
4527 |
* @return array A hash with the settings of a CloudProxy account.
|
4528 |
*/
|
4529 |
+
public static function get_cloudproxy_settings( $api_key = false ) {
|
4530 |
$params = array( 'a' => 'show_settings' );
|
4531 |
|
4532 |
+
if ( $api_key ) {
|
4533 |
$params = array_merge( $params, $api_key );
|
4534 |
}
|
4535 |
|
4536 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4537 |
|
4538 |
+
if ( self::handle_response( $response ) ) {
|
4539 |
return $response['body']->output;
|
4540 |
}
|
4541 |
|
4542 |
+
return false;
|
4543 |
}
|
4544 |
|
4545 |
/**
|
4548 |
* @param boolean $api_key The CloudProxy API key.
|
4549 |
* @return string Message explaining the result of the operation.
|
4550 |
*/
|
4551 |
+
public static function clear_cloudproxy_cache( $api_key = false ) {
|
4552 |
$params = array( 'a' => 'clear_cache' );
|
4553 |
|
4554 |
+
if ( $api_key ) {
|
4555 |
$params = array_merge( $params, $api_key );
|
4556 |
}
|
4557 |
|
4558 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4559 |
|
4560 |
+
if ( self::handle_response( $response ) ) {
|
4561 |
return $response['body'];
|
4562 |
}
|
4563 |
|
4564 |
+
return false;
|
4565 |
}
|
4566 |
|
4567 |
/**
|
4579 |
* @param string $date An optional date to filter the result to a specific timespan: yyyy-mm-dd.
|
4580 |
* @return array A list of objects with the detailed version of each request blocked by our service.
|
4581 |
*/
|
4582 |
+
public static function get_cloudproxy_logs( $api_key = false, $date = '' ) {
|
4583 |
$params = array(
|
4584 |
'a' => 'audit_trails',
|
4585 |
+
'date' => date( 'Y-m-d' ),
|
4586 |
);
|
4587 |
|
4588 |
+
if ( preg_match( '/^[0-9]{4}(\-[0-9]{2}){2}$/', $date ) ) {
|
4589 |
$params['date'] = $date;
|
4590 |
}
|
4591 |
|
4592 |
+
if ( $api_key ) {
|
4593 |
$params = array_merge( $params, $api_key );
|
4594 |
}
|
4595 |
|
4596 |
$response = self::api_call_cloudproxy( 'GET', $params );
|
4597 |
|
4598 |
+
if ( self::handle_response( $response ) ) {
|
4599 |
return $response['body']->output;
|
4600 |
}
|
4601 |
|
4602 |
+
return false;
|
4603 |
}
|
4604 |
|
4605 |
/**
|
4611 |
* @param string $domain The clean version of the website's domain.
|
4612 |
* @return object Serialized data of the scanning results for the site specified.
|
4613 |
*/
|
4614 |
+
public static function get_sitecheck_results( $domain = '' ) {
|
4615 |
+
if ( ! empty($domain) ) {
|
4616 |
$url = 'http://sitecheck.sucuri.net/';
|
4617 |
$response = self::api_call( $url, 'GET', array(
|
4618 |
'scan' => $domain,
|
4620 |
'clear' => 1,
|
4621 |
'json' => 1,
|
4622 |
), array(
|
4623 |
+
'assoc' => true,
|
4624 |
+
) );
|
4625 |
|
4626 |
+
if ( $response ) {
|
4627 |
return $response['body'];
|
4628 |
}
|
4629 |
}
|
4630 |
|
4631 |
+
return false;
|
4632 |
+
}
|
4633 |
+
|
4634 |
+
/**
|
4635 |
+
* Extract detailed information from a SiteCheck malware payload.
|
4636 |
+
*
|
4637 |
+
* @param array $malware Array with two entries with basic malware information.
|
4638 |
+
* @return array Detailed information of the malware found by SiteCheck.
|
4639 |
+
*/
|
4640 |
+
public static function get_sitecheck_malware( $malware = array() ) {
|
4641 |
+
if ( count( $malware ) >= 2 ) {
|
4642 |
+
$data_set = array(
|
4643 |
+
'alert_message' => '',
|
4644 |
+
'infected_url' => '',
|
4645 |
+
'malware_type' => '',
|
4646 |
+
'malware_docs' => '',
|
4647 |
+
'malware_payload' => '',
|
4648 |
+
);
|
4649 |
+
|
4650 |
+
// Extract the information from the alert message.
|
4651 |
+
$alert_parts = explode( ':', $malware[0], 2 );
|
4652 |
+
|
4653 |
+
if ( isset($alert_parts[1]) ) {
|
4654 |
+
$data_set['alert_message'] = $alert_parts[0];
|
4655 |
+
$data_set['infected_url'] = $alert_parts[1];
|
4656 |
+
}
|
4657 |
+
|
4658 |
+
// Extract the information from the malware message.
|
4659 |
+
$malware_parts = explode( "\n", $malware[1] );
|
4660 |
+
|
4661 |
+
if ( isset($malware_parts[1]) ) {
|
4662 |
+
if ( preg_match( '/(.+)\. Details: (.+)/', $malware_parts[0], $match ) ) {
|
4663 |
+
$data_set['malware_type'] = $match[1];
|
4664 |
+
$data_set['malware_docs'] = $match[2];
|
4665 |
+
}
|
4666 |
+
|
4667 |
+
$payload = trim( $malware_parts[1] );
|
4668 |
+
$payload = html_entity_decode( $payload );
|
4669 |
+
|
4670 |
+
if ( preg_match( '/<div id=\'HiddenDiv\'>(.+)<\/div>/', $payload, $match ) ) {
|
4671 |
+
$data_set['malware_payload'] = trim( $match[1] );
|
4672 |
+
}
|
4673 |
+
}
|
4674 |
+
|
4675 |
+
return $data_set;
|
4676 |
+
}
|
4677 |
+
|
4678 |
+
return false;
|
4679 |
}
|
4680 |
|
4681 |
/**
|
4688 |
$pattern = self::secret_key_pattern();
|
4689 |
$response = self::api_call( 'https://api.wordpress.org/secret-key/1.1/salt/', 'GET' );
|
4690 |
|
4691 |
+
if ( $response && preg_match_all( $pattern, $response['body'], $match ) ) {
|
4692 |
$new_keys = array();
|
4693 |
|
4694 |
+
foreach ( $match[1] as $i => $value ) {
|
4695 |
+
$new_keys[ $value ] = $match[3][ $i ];
|
4696 |
}
|
4697 |
|
4698 |
return $new_keys;
|
4699 |
}
|
4700 |
|
4701 |
+
return false;
|
4702 |
}
|
4703 |
|
4704 |
/**
|
4709 |
* @param integer $version Valid version number of the WordPress project.
|
4710 |
* @return object Associative object with the relative filepath and the checksums of the project files.
|
4711 |
*/
|
4712 |
+
public static function get_official_checksums( $version = 0 ) {
|
4713 |
$url = 'http://api.wordpress.org/core/checksums/1.0/';
|
4714 |
$language = 'en_US'; /* WPLANG does not works. */
|
4715 |
$response = self::api_call( $url, 'GET', array(
|
4717 |
'locale' => $language,
|
4718 |
));
|
4719 |
|
4720 |
+
if ( $response ) {
|
4721 |
+
if ( $response['body'] instanceof stdClass ) {
|
4722 |
$json_data = $response['body'];
|
4723 |
} else {
|
4724 |
+
$json_data = @json_decode( $response['body'] );
|
4725 |
}
|
4726 |
|
4727 |
+
if (
|
4728 |
isset($json_data->checksums)
|
4729 |
+
&& ! empty($json_data->checksums)
|
4730 |
+
) {
|
4731 |
$checksums = $json_data->checksums;
|
4732 |
|
4733 |
// Convert the object list to an array for better handle of the data.
|
4734 |
+
if ( $checksums instanceof stdClass ) {
|
4735 |
$checksums = (array) $checksums;
|
4736 |
}
|
4737 |
|
4739 |
}
|
4740 |
}
|
4741 |
|
4742 |
+
return false;
|
4743 |
}
|
4744 |
|
4745 |
/**
|
4751 |
*/
|
4752 |
public static function get_plugins(){
|
4753 |
// Check if the cache library was loaded.
|
4754 |
+
$can_cache = class_exists( 'SucuriScanCache' );
|
4755 |
|
4756 |
+
if ( $can_cache ) {
|
4757 |
+
$cache = new SucuriScanCache( 'plugindata' );
|
4758 |
$cached_data = $cache->get( 'plugins', SUCURISCAN_GET_PLUGINS_LIFETIME, 'array' );
|
4759 |
|
4760 |
// Return the previously cached results of this function.
|
4761 |
+
if ( $cached_data !== false ) {
|
4762 |
return $cached_data;
|
4763 |
}
|
4764 |
}
|
4769 |
$wp_market = 'https://wordpress.org/plugins/%s/';
|
4770 |
|
4771 |
// Loop through each plugin data and complement its information with more attributes.
|
4772 |
+
foreach ( $plugins as $plugin_path => $plugin_data ) {
|
4773 |
// Default values for the plugin extra attributes.
|
4774 |
$repository = '';
|
4775 |
$repository_name = '';
|
4776 |
+
$is_free_plugin = false;
|
4777 |
|
4778 |
// If the plugin's info object has already a plugin_uri.
|
4779 |
+
if (
|
4780 |
isset($plugin_data['PluginURI'])
|
4781 |
+
&& preg_match( $pattern, $plugin_data['PluginURI'], $match )
|
4782 |
+
) {
|
4783 |
$repository = $match[0];
|
4784 |
$repository_name = $match[2];
|
4785 |
+
$is_free_plugin = true;
|
4786 |
}
|
4787 |
|
4788 |
// Retrieve the WordPress plugin page from the plugin's filename.
|
4789 |
else {
|
4790 |
+
if ( strpos( $plugin_path, '/' ) !== false ) {
|
4791 |
+
$plugin_path_parts = explode( '/', $plugin_path, 2 );
|
4792 |
} else {
|
4793 |
+
$plugin_path_parts = explode( '.', $plugin_path, 2 );
|
4794 |
}
|
4795 |
|
4796 |
+
if ( isset($plugin_path_parts[0]) ) {
|
4797 |
+
$possible_repository = sprintf( $wp_market, $plugin_path_parts[0] );
|
4798 |
+
$resp = wp_remote_head( $possible_repository );
|
4799 |
|
4800 |
+
if (
|
4801 |
+
! is_wp_error( $resp )
|
4802 |
&& $resp['response']['code'] == 200
|
4803 |
+
) {
|
4804 |
$repository = $possible_repository;
|
4805 |
$repository_name = $plugin_path_parts[0];
|
4806 |
+
$is_free_plugin = true;
|
4807 |
}
|
4808 |
}
|
4809 |
}
|
4810 |
|
4811 |
// Complement the plugin's information with these attributes.
|
4812 |
+
$plugins[ $plugin_path ]['Repository'] = $repository;
|
4813 |
+
$plugins[ $plugin_path ]['RepositoryName'] = $repository_name;
|
4814 |
+
$plugins[ $plugin_path ]['IsFreePlugin'] = $is_free_plugin;
|
4815 |
+
$plugins[ $plugin_path ]['PluginType'] = ( $is_free_plugin ? 'free' : 'premium' );
|
4816 |
+
$plugins[ $plugin_path ]['IsPluginActive'] = false;
|
4817 |
|
4818 |
+
if ( is_plugin_active( $plugin_path ) ) {
|
4819 |
+
$plugins[ $plugin_path ]['IsPluginActive'] = true;
|
4820 |
}
|
4821 |
}
|
4822 |
|
4823 |
+
if ( $can_cache ) {
|
4824 |
// Add the information of the plugins to the file-based cache.
|
4825 |
$cache->add( 'plugins', $plugins );
|
4826 |
}
|
4845 |
* @param string $plugin Frienly name of the plugin.
|
4846 |
* @return object Object on success, WP_Error on failure.
|
4847 |
*/
|
4848 |
+
public static function get_remote_plugin_data( $plugin = '' ) {
|
4849 |
+
if ( ! empty($plugin) ) {
|
4850 |
+
$url = sprintf( 'http://api.wordpress.org/plugins/info/1.0/%s.json', $plugin );
|
4851 |
$response = self::api_call( $url, 'GET' );
|
4852 |
|
4853 |
+
if ( $response ) {
|
4854 |
+
if ( $response['body'] instanceof stdClass ) {
|
4855 |
return $response['body'];
|
4856 |
}
|
4857 |
}
|
4858 |
}
|
4859 |
|
4860 |
+
return false;
|
4861 |
}
|
4862 |
|
4863 |
/**
|
4871 |
*
|
4872 |
* @param string $filepath Relative file path of a project core file.
|
4873 |
* @param string $version Optional site version, default will be the global version number.
|
4874 |
+
* @return string Full content of the official file retrieved, false if the file was not found.
|
4875 |
*/
|
4876 |
+
public static function get_original_core_file( $filepath = '', $version = 0 ) {
|
4877 |
+
if ( ! empty($filepath) ) {
|
4878 |
+
if ( $version == 0 ) {
|
4879 |
$version = self::site_version();
|
4880 |
}
|
4881 |
|
4882 |
$url = sprintf( 'http://core.svn.wordpress.org/tags/%s/%s', $version, $filepath );
|
4883 |
$response = self::api_call( $url, 'GET' );
|
4884 |
|
4885 |
+
if ( $response ) {
|
4886 |
+
if (
|
4887 |
isset($response['headers']['content-length'])
|
4888 |
&& $response['headers']['content-length'] > 0
|
4889 |
+
&& is_string( $response['body'] )
|
4890 |
+
) {
|
4891 |
return $response['body'];
|
4892 |
}
|
4893 |
}
|
4894 |
}
|
4895 |
|
4896 |
+
return false;
|
4897 |
}
|
4898 |
|
4899 |
}
|
4914 |
* @return boolean Whether the emails will be in HTML or Plain/Text.
|
4915 |
*/
|
4916 |
public static function prettify_mails(){
|
4917 |
+
return ( self::get_option( ':prettify_mails' ) === 'enabled' );
|
4918 |
}
|
4919 |
|
4920 |
/**
|
4926 |
* @param array $data_set Optional parameter to add more information to the notification.
|
4927 |
* @return boolean Whether the email contents were sent successfully.
|
4928 |
*/
|
4929 |
+
public static function send_mail( $email = '', $subject = '', $message = '', $data_set = array() ){
|
4930 |
$headers = array();
|
4931 |
+
$subject = ucwords( strtolower( $subject ) );
|
4932 |
+
$force = false;
|
4933 |
+
$debug = false;
|
4934 |
|
4935 |
// Check whether the mail will be printed in the site instead of sent.
|
4936 |
+
if (
|
4937 |
isset($data_set['Debug'])
|
4938 |
+
&& $data_set['Debug'] == true
|
4939 |
){
|
4940 |
+
$debug = true;
|
4941 |
unset($data_set['Debug']);
|
4942 |
}
|
4943 |
|
4944 |
// Check whether the mail will be even if the limit per hour was reached or not.
|
4945 |
+
if (
|
4946 |
isset($data_set['Force'])
|
4947 |
+
&& $data_set['Force'] == true
|
4948 |
){
|
4949 |
+
$force = true;
|
4950 |
unset($data_set['Force']);
|
4951 |
}
|
4952 |
|
4953 |
// Check whether the email notifications will be sent in HTML or Plain/Text.
|
4954 |
+
if ( self::prettify_mails() ){
|
4955 |
$headers = array( 'content-type: text/html' );
|
4956 |
$data_set['PrettifyType'] = 'pretty';
|
4957 |
} else {
|
4958 |
+
$message = strip_tags( $message );
|
4959 |
}
|
4960 |
|
4961 |
+
if ( ! self::emails_per_hour_reached() || $force || $debug ){
|
4962 |
+
$message = self::prettify_mail( $subject, $message, $data_set );
|
4963 |
|
4964 |
+
if ( $debug ){ die($message); }
|
4965 |
|
4966 |
+
$subject = self::get_email_subject( $subject );
|
4967 |
$mail_sent = wp_mail( $email, $subject, $message, $headers );
|
4968 |
|
4969 |
+
if ( $mail_sent ){
|
4970 |
+
$emails_sent_num = (int) self::get_option( ':emails_sent' );
|
4971 |
self::update_option( ':emails_sent', $emails_sent_num + 1 );
|
4972 |
self::update_option( ':last_email_at', time() );
|
4973 |
|
4974 |
+
return true;
|
4975 |
}
|
4976 |
}
|
4977 |
|
4978 |
+
return false;
|
4979 |
}
|
4980 |
|
4981 |
/**
|
4984 |
* @param string $event The reason of the message that will be sent.
|
4985 |
* @return string A text with the subject for the email alert.
|
4986 |
*/
|
4987 |
+
private static function get_email_subject( $event = '' ){
|
4988 |
$domain_name = self::get_domain();
|
4989 |
$remote_addr = self::get_remote_addr();
|
4990 |
+
$email_subject = self::get_option( ':email_subject' );
|
4991 |
|
4992 |
if ( $email_subject ) {
|
4993 |
$email_subject = str_replace(
|
4994 |
array( ':domain', ':event', ':remoteaddr' ),
|
4995 |
array( $domain_name, $event, $remote_addr ),
|
4996 |
+
strip_tags( $email_subject )
|
4997 |
);
|
4998 |
|
4999 |
return $email_subject;
|
5005 |
* loop, but this is the easiest way to control this procedure.
|
5006 |
*/
|
5007 |
else {
|
5008 |
+
self::delete_option( ':email_subject' );
|
5009 |
+
return self::get_email_subject( $event );
|
5010 |
}
|
5011 |
}
|
5012 |
|
5018 |
* @param array $data_set Optional parameter to add more information to the notification.
|
5019 |
* @return string The message formatted in a HTML template.
|
5020 |
*/
|
5021 |
+
private static function prettify_mail( $subject = '', $message = '', $data_set = array() ){
|
5022 |
$prettify_type = isset($data_set['PrettifyType']) ? $data_set['PrettifyType'] : 'simple';
|
5023 |
$template_name = 'notification-' . $prettify_type;
|
5024 |
$user = wp_get_current_user();
|
5025 |
$display_name = '';
|
5026 |
|
5027 |
+
if (
|
5028 |
$user instanceof WP_User
|
5029 |
&& isset($user->user_login)
|
5030 |
+
&& ! empty($user->user_login)
|
5031 |
){
|
5032 |
$display_name = sprintf( 'User: %s (%s)', $user->display_name, $user->user_login );
|
5033 |
}
|
5034 |
|
5035 |
+
// Format list of items when the event has multiple entries.
|
5036 |
+
if ( strpos( $message, 'multiple' ) !== false ) {
|
5037 |
+
$message_parts = SucuriScanAPI::parse_multiple_entries( $message );
|
5038 |
+
|
5039 |
+
if ( is_array( $message_parts ) ) {
|
5040 |
+
$message = ( $prettify_type == 'pretty' ) ? $message_parts[0] . '<ul>' : $message_parts[0];
|
5041 |
+
unset($message_parts[0]);
|
5042 |
+
|
5043 |
+
foreach ( $message_parts as $msg_part ) {
|
5044 |
+
if ( $prettify_type == 'pretty' ) {
|
5045 |
+
$message .= sprintf( "<li>%s</li>\n", $msg_part );
|
5046 |
+
} else {
|
5047 |
+
$message .= sprintf( "- %s\n", $msg_part );
|
5048 |
+
}
|
5049 |
+
}
|
5050 |
+
|
5051 |
+
$message .= ( $prettify_type == 'pretty' ) ? '</ul>' : '';
|
5052 |
+
}
|
5053 |
+
}
|
5054 |
+
|
5055 |
$mail_variables = array(
|
5056 |
'TemplateTitle' => 'Sucuri Alert',
|
5057 |
'Subject' => $subject,
|
5058 |
+
'Website' => self::get_option( 'siteurl' ),
|
5059 |
'RemoteAddress' => self::get_remote_addr(),
|
5060 |
'Message' => $message,
|
5061 |
'User' => $display_name,
|
5062 |
'Time' => SucuriScan::current_datetime(),
|
5063 |
);
|
5064 |
|
5065 |
+
foreach ( $data_set as $var_key => $var_value ){
|
5066 |
+
$mail_variables[ $var_key ] = $var_value;
|
5067 |
}
|
5068 |
|
5069 |
return SucuriScanTemplate::get_section( $template_name, $mail_variables );
|
5075 |
* @return boolean Whether the quota emails per hour was reached.
|
5076 |
*/
|
5077 |
private static function emails_per_hour_reached(){
|
5078 |
+
$max_per_hour = self::get_option( ':emails_per_hour' );
|
5079 |
|
5080 |
+
if ( $max_per_hour != 'unlimited' ){
|
5081 |
// Check if we are still in that sixty minutes.
|
5082 |
$current_time = time();
|
5083 |
+
$last_email_at = self::get_option( ':last_email_at' );
|
5084 |
$diff_time = abs( $current_time - $last_email_at );
|
5085 |
|
5086 |
+
if ( $diff_time <= 3600 ){
|
5087 |
// Check if the quantity of emails sent is bigger than the configured.
|
5088 |
+
$emails_sent = (int) self::get_option( ':emails_sent' );
|
5089 |
+
$max_per_hour = intval( $max_per_hour );
|
5090 |
|
5091 |
+
if ( $emails_sent >= $max_per_hour ){
|
5092 |
+
return true;
|
5093 |
}
|
5094 |
} else {
|
5095 |
// Reset the counter of emails sent.
|
5097 |
}
|
5098 |
}
|
5099 |
|
5100 |
+
return false;
|
5101 |
}
|
5102 |
|
5103 |
}
|
5124 |
* @param array $params List of pseudo-variables that will be replaced in the template.
|
5125 |
* @return string The content of the template with the pseudo-variables replated.
|
5126 |
*/
|
5127 |
+
private static function replace_pseudovars( $content = '', $params = array() ){
|
5128 |
+
if ( is_array( $params ) ){
|
5129 |
+
foreach ( $params as $tpl_key => $tpl_value ){
|
5130 |
$tpl_key = '%%SUCURI.' . $tpl_key . '%%';
|
5131 |
$content = str_replace( $tpl_key, $tpl_value, $content );
|
5132 |
}
|
5134 |
return $content;
|
5135 |
}
|
5136 |
|
5137 |
+
return false;
|
5138 |
}
|
5139 |
|
5140 |
/**
|
5143 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5144 |
* @return array A complementary list of pseudo-variables for the template files.
|
5145 |
*/
|
5146 |
+
private static function shared_params( $params = array() ){
|
5147 |
+
$params = is_array( $params ) ? $params : array();
|
5148 |
|
5149 |
// Base parameters, required to render all the pages.
|
5150 |
+
$params = self::links_and_navbar( $params );
|
5151 |
|
5152 |
// Global parameters, used through out all the pages.
|
5153 |
$params['PageTitle'] = isset($params['PageTitle']) ? '('.$params['PageTitle'].')' : '';
|
5154 |
+
$params['PageNonce'] = wp_create_nonce( 'sucuriscan_page_nonce' );
|
5155 |
$params['PageStyleClass'] = isset($params['PageStyleClass']) ? $params['PageStyleClass'] : 'base';
|
5156 |
$params['CleanDomain'] = self::get_domain();
|
5157 |
$params['AdminEmail'] = self::get_site_email();
|
5158 |
|
5159 |
+
// Hide the advertisements from the layout.
|
5160 |
+
$ads_visibility = SucuriScanOption::get_option( ':ads_visibility' );
|
5161 |
+
|
5162 |
+
if ( $ads_visibility == 'disabled' ) {
|
5163 |
+
$params['LayoutType'] = 'onecolumn';
|
5164 |
+
$params['AdsVisibility'] = 'hidden';
|
5165 |
+
$params['ReviewNavbarButton'] = 'visible';
|
5166 |
+
} else {
|
5167 |
+
$params['LayoutType'] = 'twocolumns';
|
5168 |
+
$params['AdsVisibility'] = 'visible';
|
5169 |
+
$params['ReviewNavbarButton'] = 'hidden';
|
5170 |
+
}
|
5171 |
+
|
5172 |
return $params;
|
5173 |
}
|
5174 |
|
5178 |
* @param boolean $visible Whether the condition executed returned a positive value or not.
|
5179 |
* @return string A string indicating the visibility of a HTML component.
|
5180 |
*/
|
5181 |
+
public static function visibility( $visible = false ){
|
5182 |
+
return ( $visible === true ? 'visible' : 'hidden' );
|
5183 |
}
|
5184 |
|
5185 |
/**
|
5189 |
* @param string $page Short name of the page that will be generated.
|
5190 |
* @return string Full string containing the link of the page.
|
5191 |
*/
|
5192 |
+
public static function get_url( $page = '' ){
|
5193 |
+
$url_path = admin_url( 'admin.php?page=sucuriscan' );
|
5194 |
|
5195 |
+
if ( ! empty($page) ){
|
5196 |
+
$url_path .= '_' . strtolower( $page );
|
5197 |
}
|
5198 |
|
5199 |
return $url_path;
|
5207 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5208 |
* @return array A complementary list of pseudo-variables for the template files.
|
5209 |
*/
|
5210 |
+
private static function links_and_navbar( $params = array() ){
|
5211 |
global $sucuriscan_pages;
|
5212 |
|
5213 |
+
$params = is_array( $params ) ? $params : array();
|
5214 |
+
$sub_pages = is_array( $sucuriscan_pages ) ? $sucuriscan_pages : array();
|
5215 |
|
5216 |
$params['Navbar'] = '';
|
5217 |
$params['CurrentPageFunc'] = '';
|
5218 |
|
5219 |
+
if ( $_page = self::get( 'page', '_page' ) ){
|
5220 |
$params['CurrentPageFunc'] = $_page;
|
5221 |
}
|
5222 |
|
5223 |
+
foreach ( $sub_pages as $sub_page_func => $sub_page_title ){
|
5224 |
if (
|
5225 |
$sub_page_func == 'sucuriscan_scanner'
|
5226 |
&& self::is_sitecheck_disabled()
|
5230 |
|
5231 |
$func_parts = explode( '_', $sub_page_func, 2 );
|
5232 |
|
5233 |
+
if ( isset($func_parts[1]) ){
|
5234 |
$unique_name = $func_parts[1];
|
5235 |
+
$pseudo_var = 'URL.' . ucwords( $unique_name );
|
5236 |
} else {
|
5237 |
$unique_name = '';
|
5238 |
$pseudo_var = 'URL.Home';
|
5239 |
}
|
5240 |
|
5241 |
+
$params[ $pseudo_var ] = self::get_url( $unique_name );
|
5242 |
|
5243 |
$navbar_item_css_class = 'nav-tab';
|
5244 |
|
5245 |
+
if ( $params['CurrentPageFunc'] == $sub_page_func ){
|
5246 |
+
$navbar_item_css_class .= chr( 32 ) . 'nav-tab-active';
|
5247 |
}
|
5248 |
|
5249 |
$params['Navbar'] .= sprintf(
|
5250 |
'<a class="%s" href="%s">%s</a>' . "\n",
|
5251 |
$navbar_item_css_class,
|
5252 |
+
$params[ $pseudo_var ],
|
5253 |
$sub_page_title
|
5254 |
);
|
5255 |
}
|
5266 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5267 |
* @return string The formatted HTML content of the base template.
|
5268 |
*/
|
5269 |
+
public static function get_base_template( $html = '', $params = array() ){
|
5270 |
+
$params = is_array( $params ) ? $params : array();
|
5271 |
|
5272 |
+
$params = self::shared_params( $params );
|
5273 |
$params['PageContent'] = $html;
|
5274 |
|
5275 |
return self::get_template( 'base', $params );
|
5285 |
* @param boolean $type Either page, section or snippet indicating the type of template that will be retrieved.
|
5286 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
5287 |
*/
|
5288 |
+
public static function get_template( $template = '', $params = array(), $type = 'page' ){
|
5289 |
+
switch ( $type ){
|
5290 |
case 'page': /* no_break */
|
5291 |
case 'section':
|
5292 |
$template_path_pattern = '%s/%s/inc/tpl/%s.html.tpl';
|
5297 |
}
|
5298 |
|
5299 |
$template_content = '';
|
5300 |
+
$template_path = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
|
5301 |
+
$params = is_array( $params ) ? $params : array();
|
5302 |
|
5303 |
+
if ( file_exists( $template_path ) && is_readable( $template_path ) ){
|
5304 |
+
$template_content = @file_get_contents( $template_path );
|
5305 |
|
5306 |
$params['SucuriURL'] = SUCURISCAN_URL;
|
5307 |
|
5308 |
// Detect the current page URL.
|
5309 |
+
if ( $_page = self::get( 'page', '_page' ) ){
|
5310 |
+
$params['CurrentURL'] = admin_url( 'admin.php?page=' . $_page );
|
5311 |
} else {
|
5312 |
$params['CurrentURL'] = admin_url();
|
5313 |
}
|
5314 |
|
5315 |
// Replace the global pseudo-variables in the section/snippets templates.
|
5316 |
+
if (
|
5317 |
$template == 'base'
|
5318 |
&& isset($params['PageContent'])
|
5319 |
+
&& preg_match( '/%%SUCURI\.(.+)%%/', $params['PageContent'] )
|
5320 |
){
|
5321 |
$params['PageContent'] = self::replace_pseudovars( $params['PageContent'], $params );
|
5322 |
}
|
5324 |
$template_content = self::replace_pseudovars( $template_content, $params );
|
5325 |
}
|
5326 |
|
5327 |
+
if ( $template == 'base' || $type != 'page' ){
|
5328 |
return $template_content;
|
5329 |
}
|
5330 |
|
5340 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5341 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
5342 |
*/
|
5343 |
+
public static function get_section( $template = '', $params = array() ){
|
5344 |
+
$params = self::shared_params( $params );
|
5345 |
|
5346 |
return self::get_template( $template, $params, 'section' );
|
5347 |
}
|
5355 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5356 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
5357 |
*/
|
5358 |
+
public static function get_modal( $template = '', $params = array() ) {
|
5359 |
$required = array(
|
5360 |
'Title' => 'Lorem ipsum dolor sit amet',
|
5361 |
'CssClass' => '',
|
5367 |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>',
|
5368 |
);
|
5369 |
|
5370 |
+
if ( ! empty($template) && $template != 'none' ){
|
5371 |
+
$params['Content'] = self::get_section( $template );
|
5372 |
}
|
5373 |
|
5374 |
+
foreach ( $required as $param_name => $param_value ){
|
5375 |
+
if ( ! isset($params[ $param_name ]) ){
|
5376 |
+
$params[ $param_name ] = $param_value;
|
5377 |
}
|
5378 |
}
|
5379 |
|
5380 |
+
$params = self::shared_params( $params );
|
5381 |
|
5382 |
return self::get_template( 'modalwindow', $params, 'section' );
|
5383 |
}
|
5391 |
* @param array $params A hash containing the pseudo-variable name as the key and the value that will replace it.
|
5392 |
* @return string The formatted HTML page after replace all the pseudo-variables.
|
5393 |
*/
|
5394 |
+
public static function get_snippet( $template = '', $params = array() ) {
|
5395 |
return self::get_template( $template, $params, 'snippet' );
|
5396 |
}
|
5397 |
|
5402 |
* @param string $selected_val Value of the option that will be selected by default.
|
5403 |
* @return string Option list for a select form field.
|
5404 |
*/
|
5405 |
+
public static function get_select_options( $allowed_values = array(), $selected_val = '' ){
|
5406 |
$options = '';
|
5407 |
|
5408 |
+
foreach ( $allowed_values as $option_name => $option_label ){
|
5409 |
$selected_str = '';
|
5410 |
|
5411 |
+
if ( $option_name == $selected_val ){
|
5412 |
$selected_str = 'selected="selected"';
|
5413 |
}
|
5414 |
|
5427 |
* @return integer Page number of the link clicked in a pagination.
|
5428 |
*/
|
5429 |
public static function get_page_number(){
|
5430 |
+
$paged = self::get( 'paged', '[0-9]{1,5}' );
|
5431 |
|
5432 |
+
return ( $paged ? intval( $paged ) : 1 );
|
5433 |
}
|
5434 |
|
5435 |
/**
|
5440 |
* @param integer $max_per_page Maximum number of items that will be shown per page.
|
5441 |
* @return string HTML code for a pagination generated using the provided data.
|
5442 |
*/
|
5443 |
+
public static function get_pagination( $base_url = '', $total_items = 0, $max_per_page = 1 ){
|
5444 |
// Calculate the number of links for the pagination.
|
5445 |
$html_links = '';
|
5446 |
$page_number = self::get_page_number();
|
5447 |
+
$max_pages = ceil( $total_items / $max_per_page );
|
5448 |
+
$extra_url = '';
|
5449 |
+
|
5450 |
+
// Fix for inline anchor URLs.
|
5451 |
+
if ( preg_match( '/^(.+)(#.+)$/', $base_url, $match ) ) {
|
5452 |
+
$base_url = $match[1];
|
5453 |
+
$extra_url = $match[2];
|
5454 |
+
}
|
5455 |
|
5456 |
// Generate the HTML links for the pagination.
|
5457 |
+
for ( $j = 1; $j <= $max_pages; $j++ ){
|
5458 |
$link_class = 'sucuriscan-pagination-link';
|
5459 |
|
5460 |
+
if ( $page_number == $j ){
|
5461 |
+
$link_class .= chr( 32 ) . 'sucuriscan-pagination-active';
|
5462 |
}
|
5463 |
|
5464 |
$html_links .= sprintf(
|
5465 |
+
'<li><a href="%s&paged=%d%s" class="%s">%s</a></li>',
|
5466 |
+
$base_url, $j, $extra_url, $link_class, $j
|
5467 |
);
|
5468 |
}
|
5469 |
|
5476 |
* @return boolean TRUE if the SiteCheck scanner and malware scan page are disabled.
|
5477 |
*/
|
5478 |
public static function is_sitecheck_disabled(){
|
5479 |
+
return (bool) ( SucuriScanOption::get_option( ':sitecheck_scanner' ) === 'disabled' );
|
5480 |
}
|
5481 |
|
5482 |
/**
|
5485 |
* @return boolean TRUE if the SiteCheck scanner and malware scan page are enabled.
|
5486 |
*/
|
5487 |
public static function is_sitecheck_enabled(){
|
5488 |
+
return (bool) ( SucuriScanOption::get_option( ':sitecheck_scanner' ) !== 'disabled' );
|
5489 |
}
|
5490 |
|
5491 |
}
|
5507 |
* @param boolean $format Whether the timestamp must be formatted as date/time or not.
|
5508 |
* @return string The timestamp of the runtime, or an string with the date/time.
|
5509 |
*/
|
5510 |
+
public static function get_filesystem_runtime( $format = false ){
|
5511 |
+
$runtime = SucuriScanOption::get_option( ':runtime' );
|
5512 |
|
5513 |
+
if ( $runtime > 0 ){
|
5514 |
+
if ( $format ){
|
5515 |
+
return SucuriScan::datetime( $runtime );
|
5516 |
}
|
5517 |
|
5518 |
return $runtime;
|
5519 |
}
|
5520 |
|
5521 |
+
if ( $format ){
|
5522 |
return '<em>Unknown</em>';
|
5523 |
}
|
5524 |
|
5525 |
+
return false;
|
5526 |
}
|
5527 |
|
5528 |
/**
|
5534 |
* @return boolean Whether the feature to ignore files is enabled or not.
|
5535 |
*/
|
5536 |
public static function will_ignore_scanning(){
|
5537 |
+
return ( SucuriScanOption::get_option( ':ignore_scanning' ) === 'enabled' );
|
5538 |
}
|
5539 |
|
5540 |
/**
|
5543 |
* @param string $directory_path The (full) absolute path of a directory.
|
5544 |
* @return boolean TRUE if the directory path was added to the list, FALSE otherwise.
|
5545 |
*/
|
5546 |
+
public static function ignore_directory( $directory_path = '' ){
|
5547 |
+
$cache = new SucuriScanCache( 'ignorescanning' );
|
5548 |
|
5549 |
// Use the checksum of the directory path as the cache key.
|
5550 |
+
$cache_key = md5( $directory_path );
|
5551 |
$cache_value = array(
|
5552 |
'directory_path' => $directory_path,
|
5553 |
'ignored_at' => self::local_time(),
|
5563 |
* @param string $directory_path The (full) absolute path of a directory.
|
5564 |
* @return boolean TRUE if the directory path was removed to the list, FALSE otherwise.
|
5565 |
*/
|
5566 |
+
public static function unignore_directory( $directory_path = '' ){
|
5567 |
+
$cache = new SucuriScanCache( 'ignorescanning' );
|
5568 |
|
5569 |
// Use the checksum of the directory path as the cache key.
|
5570 |
+
$cache_key = md5( $directory_path );
|
5571 |
+
$removed = $cache->delete( $cache_key );
|
5572 |
|
5573 |
return $removed;
|
5574 |
}
|
5602 |
'ignored_at_list' => array(),
|
5603 |
);
|
5604 |
|
5605 |
+
$cache = new SucuriScanCache( 'ignorescanning' );
|
5606 |
$cache_lifetime = 0; // It is not necessary to expire this cache.
|
5607 |
$ignored_directories = $cache->get_all( $cache_lifetime, 'array' );
|
5608 |
|
5609 |
+
if ( $ignored_directories ){
|
5610 |
$response['raw'] = $ignored_directories;
|
5611 |
|
5612 |
+
foreach ( $ignored_directories as $checksum => $data ){
|
5613 |
$response['checksums'][] = $checksum;
|
5614 |
$response['directories'][] = $data['directory_path'];
|
5615 |
$response['ignored_at_list'][] = $data['ignored_at'];
|
5638 |
// Get the ignored directories from the cache.
|
5639 |
$ignored_directories = self::get_ignored_directories();
|
5640 |
|
5641 |
+
if ( $ignored_directories ){
|
5642 |
$response['is_ignored'] = $ignored_directories['raw'];
|
5643 |
}
|
5644 |
|
5645 |
// Scan the project and file all directories.
|
5646 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
5647 |
+
$sucuri_fileinfo->ignore_files = true;
|
5648 |
+
$sucuri_fileinfo->ignore_directories = true;
|
5649 |
+
$sucuri_fileinfo->scan_interface = SucuriScanOption::get_option( ':scan_interface' );
|
5650 |
+
$directory_list = $sucuri_fileinfo->get_diretories_only( ABSPATH );
|
5651 |
|
5652 |
+
if ( $directory_list ){
|
5653 |
$response['is_not_ignored'] = $directory_list;
|
5654 |
}
|
5655 |
|
5662 |
* @param array $error_logs The content of an error log file, or an array with the lines.
|
5663 |
* @return array List of valid error logs with their attributes separated.
|
5664 |
*/
|
5665 |
+
public static function parse_error_logs( $error_logs = array() ){
|
5666 |
$logs_arr = array();
|
5667 |
$pattern = '/^'
|
5668 |
+
. '(\[(\S+) ([0-9:]{5,8})( \S+)?\] )?' // Detect date, time, and timezone.
|
5669 |
+
. '(PHP )?([a-zA-Z ]+):\s' // Detect PHP error severity.
|
5670 |
+
. '(.+) in (.+)' // Detect error message, and file path.
|
5671 |
+
. '(:| on line )([0-9]+)' // Detect line number.
|
5672 |
. '$/';
|
5673 |
|
5674 |
+
if ( is_string( $error_logs ) ) {
|
5675 |
$error_logs = explode( "\n", $error_logs );
|
5676 |
}
|
5677 |
|
5678 |
foreach ( (array) $error_logs as $line ) {
|
5679 |
+
if ( ! is_string( $line ) || empty($line) ) { continue; }
|
5680 |
|
5681 |
+
if ( preg_match( $pattern, $line, $match ) ) {
|
5682 |
$data_set = array(
|
5683 |
'date' => '',
|
5684 |
'time' => '',
|
5695 |
// Basic attributes from the scrapping.
|
5696 |
$data_set['date'] = $match[2];
|
5697 |
$data_set['time'] = $match[3];
|
5698 |
+
$data_set['time_zone'] = trim( $match[4] );
|
5699 |
+
$data_set['error_type'] = trim( $match[6] );
|
5700 |
+
$data_set['error_message'] = trim( $match[7] );
|
5701 |
+
$data_set['file_path'] = trim( $match[8] );
|
5702 |
$data_set['line_number'] = (int) $match[10];
|
5703 |
|
5704 |
// Additional data from the attributes.
|
5713 |
$valid_types = array( 'warning', 'notice', 'error' );
|
5714 |
|
5715 |
foreach ( $valid_types as $valid_type ) {
|
5716 |
+
if ( stripos( $data_set['error_type'], $valid_type ) !== false ) {
|
5717 |
$data_set['error_code'] = $valid_type;
|
5718 |
break;
|
5719 |
}
|
5751 |
public static function register_script(){
|
5752 |
global $pagenow;
|
5753 |
|
5754 |
+
$status = SucuriScanOption::get_option( ':heartbeat' );
|
5755 |
|
5756 |
// Enable heartbeat everywhere.
|
5757 |
+
if ( $status == 'enabled' ){ /* do_nothing */ }
|
5758 |
|
5759 |
// Disable heartbeat everywhere.
|
5760 |
+
elseif ( $status == 'disabled' ){
|
5761 |
+
wp_deregister_script( 'heartbeat' );
|
5762 |
}
|
5763 |
|
5764 |
// Disable heartbeat only on the dashboard and home pages.
|
5765 |
+
elseif (
|
5766 |
$status == 'dashboard'
|
5767 |
&& $pagenow == 'index.php'
|
5768 |
){
|
5769 |
+
wp_deregister_script( 'heartbeat' );
|
5770 |
}
|
5771 |
|
5772 |
// Disable heartbeat everywhere except in post edition.
|
5773 |
+
elseif (
|
5774 |
$status == 'addpost'
|
5775 |
&& $pagenow != 'post.php'
|
5776 |
&& $pagenow != 'post-new.php'
|
5777 |
){
|
5778 |
+
wp_deregister_script( 'heartbeat' );
|
5779 |
}
|
5780 |
}
|
5781 |
|
5789 |
* @param array $settings Heartbeat settings.
|
5790 |
* @return array Updated version of the heartbeat settings.
|
5791 |
*/
|
5792 |
+
public static function update_settings( $settings = array() ){
|
5793 |
+
$pulse = SucuriScanOption::get_option( ':heartbeat_pulse' );
|
5794 |
+
$autostart = SucuriScanOption::get_option( ':heartbeat_autostart' );
|
5795 |
|
5796 |
+
if ( $pulse < 15 || $pulse > 60 ){
|
5797 |
+
SucuriScanOption::delete_option( ':heartbeat_pulse' );
|
5798 |
$pulse = 15;
|
5799 |
}
|
5800 |
|
5801 |
$settings['interval'] = $pulse;
|
5802 |
+
$settings['autostart'] = ( $autostart == 'disabled' ? false : true );
|
5803 |
|
5804 |
return $settings;
|
5805 |
}
|
5812 |
* @param string $screen_id Identifier of the screen the heartbeat occurred on.
|
5813 |
* @return array Response with new data.
|
5814 |
*/
|
5815 |
+
public static function respond_to_received( $response = array(), $data = array(), $screen_id = '' ) {
|
5816 |
+
$interval = SucuriScanOption::get_option( ':heartbeat_interval' );
|
5817 |
|
5818 |
+
if (
|
5819 |
$interval == 'slow'
|
5820 |
|| $interval == 'fast'
|
5821 |
|| $interval == 'standard'
|
5822 |
){
|
5823 |
$response['heartbeat_interval'] = $interval;
|
5824 |
} else {
|
5825 |
+
SucuriScanOption::delete_option( ':heartbeat_interval' );
|
5826 |
}
|
5827 |
|
5828 |
return $response;
|
5835 |
* @param string $screen_id Identifier of the screen the heartbeat occurred on.
|
5836 |
* @return array Response with new data.
|
5837 |
*/
|
5838 |
+
public static function respond_to_send( $response = array(), $screen_id = '' ) {
|
5839 |
return $response;
|
5840 |
}
|
5841 |
|
5874 |
public static function pulses_allowed(){
|
5875 |
$pulses = array();
|
5876 |
|
5877 |
+
for ( $i = 15; $i <= 60; $i++ ){
|
5878 |
+
$pulses[ $i ] = sprintf( 'Run every %d seconds', $i );
|
5879 |
}
|
5880 |
|
5881 |
return $pulses;
|
5920 |
public static function enqueue_scripts(){
|
5921 |
$asset_version = '';
|
5922 |
|
5923 |
+
if ( strlen( SUCURISCAN_PLUGIN_CHECKSUM ) >= 7 ){
|
5924 |
+
$asset_version = substr( SUCURISCAN_PLUGIN_CHECKSUM, 0, 7 );
|
5925 |
}
|
5926 |
|
5927 |
wp_register_style( 'sucuriscan', SUCURISCAN_URL . '/inc/css/sucuriscan-default-css.css', array(), $asset_version );
|
5928 |
wp_register_script( 'sucuriscan', SUCURISCAN_URL . '/inc/js/sucuriscan-scripts.js', array(), $asset_version );
|
|
|
5929 |
wp_enqueue_style( 'sucuriscan' );
|
5930 |
wp_enqueue_script( 'sucuriscan' );
|
5931 |
+
|
5932 |
+
if (
|
5933 |
+
SucuriScanRequest::get( 'page', 'sucuriscan' ) !== false
|
5934 |
+
&& SucuriScanOption::get_option( ':audit_report' ) !== 'disabled'
|
5935 |
+
) {
|
5936 |
+
wp_register_script( 'sucuriscan2', SUCURISCAN_URL . '/inc/js/d3.v3.min.js', array(), $asset_version );
|
5937 |
+
wp_register_script( 'sucuriscan3', SUCURISCAN_URL . '/inc/js/c3.min.js', array(), $asset_version );
|
5938 |
+
wp_enqueue_script( 'sucuriscan2' );
|
5939 |
+
wp_enqueue_script( 'sucuriscan3' );
|
5940 |
+
}
|
5941 |
}
|
5942 |
|
5943 |
/**
|
5948 |
public static function add_interface_menu(){
|
5949 |
global $sucuriscan_pages;
|
5950 |
|
5951 |
+
if (
|
5952 |
+
function_exists( 'add_menu_page' )
|
5953 |
&& $sucuriscan_pages
|
5954 |
){
|
5955 |
// Add main menu link.
|
5962 |
SUCURISCAN_URL . '/inc/images/menu-icon.png'
|
5963 |
);
|
5964 |
|
5965 |
+
$sub_pages = is_array( $sucuriscan_pages ) ? $sucuriscan_pages : array();
|
5966 |
|
5967 |
+
foreach ( $sub_pages as $sub_page_func => $sub_page_title ){
|
5968 |
if (
|
5969 |
$sub_page_func == 'sucuriscan_scanner'
|
5970 |
&& SucuriScanTemplate::is_sitecheck_disabled()
|
5995 |
* @return void
|
5996 |
*/
|
5997 |
public static function handle_old_plugins(){
|
5998 |
+
if ( class_exists( 'SucuriScanFileInfo' ) ){
|
5999 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
6000 |
+
$sucuri_fileinfo->ignore_files = false;
|
6001 |
+
$sucuri_fileinfo->ignore_directories = false;
|
6002 |
|
6003 |
$plugins = array(
|
6004 |
'sucuri-wp-plugin/sucuri.php',
|
6005 |
'sucuri-cloudproxy-waf/cloudproxy.php',
|
6006 |
);
|
6007 |
|
6008 |
+
foreach ( $plugins as $plugin ){
|
6009 |
$plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin );
|
6010 |
|
6011 |
+
if ( file_exists( $plugin_directory ) ){
|
6012 |
+
if ( is_plugin_active( $plugin ) ){
|
6013 |
+
deactivate_plugins( $plugin );
|
6014 |
}
|
6015 |
|
6016 |
+
$plugin_removed = $sucuri_fileinfo->remove_directory_tree( $plugin_directory );
|
6017 |
}
|
6018 |
}
|
6019 |
}
|
6028 |
public static function create_datastore_folder(){
|
6029 |
$plugin_upload_folder = SucuriScan::datastore_folder_path();
|
6030 |
|
6031 |
+
if ( ! file_exists( $plugin_upload_folder ) ) {
|
6032 |
+
$datastore_folder_created = @mkdir( $plugin_upload_folder, 0755, true );
|
6033 |
+
|
6034 |
+
if ( $datastore_folder_created ) {
|
6035 |
// Create last-logins datastore file.
|
6036 |
sucuriscan_lastlogins_datastore_exists();
|
6037 |
|
6045 |
// Create an index.html to avoid directory listing.
|
6046 |
@file_put_contents(
|
6047 |
$plugin_upload_folder . '/index.html',
|
6048 |
+
'<!-- Prevent the directory listing. -->',
|
6049 |
LOCK_EX
|
6050 |
);
|
6051 |
} else {
|
6052 |
SucuriScanInterface::error(
|
6053 |
+
'Data folder does not exists and could not be created. You will need to either
|
6054 |
+
change the location of the directory from the generanl settings panel located
|
6055 |
+
in the plugin settings page or create this directory manually and give it write
|
6056 |
+
permissions:<code>' . $plugin_upload_folder . '</code>'
|
6057 |
);
|
6058 |
}
|
6059 |
}
|
6065 |
* @return void
|
6066 |
*/
|
6067 |
public static function check_permissions(){
|
6068 |
+
if (
|
6069 |
+
! function_exists( 'current_user_can' )
|
6070 |
+
|| ! current_user_can( 'manage_options' )
|
6071 |
){
|
6072 |
+
$page = SucuriScanRequest::get( 'page', '_page' );
|
6073 |
+
wp_die( __( 'Access denied by <b>Sucuri</b> to see <code>' . $page . '</code>' ) );
|
6074 |
}
|
6075 |
}
|
6076 |
|
6082 |
* @return boolean Either TRUE or FALSE if the nonce is valid or not respectively.
|
6083 |
*/
|
6084 |
public static function check_nonce(){
|
6085 |
+
if ( ! empty($_POST) ){
|
6086 |
$nonce_name = 'sucuriscan_page_nonce';
|
6087 |
+
$nonce_value = SucuriScanRequest::post( $nonce_name, '_nonce' );
|
6088 |
|
6089 |
+
if ( ! $nonce_value || ! wp_verify_nonce( $nonce_value, $nonce_name ) ){
|
6090 |
+
wp_die( __( 'WordPress Nonce verification failed, try again going back and checking the form.' ) );
|
6091 |
|
6092 |
+
return false;
|
6093 |
}
|
6094 |
}
|
6095 |
|
6096 |
+
return true;
|
6097 |
}
|
6098 |
|
6099 |
/**
|
6103 |
* @param string $message The message that will be printed in the alert.
|
6104 |
* @return void
|
6105 |
*/
|
6106 |
+
private static function admin_notice( $type = 'updated', $message = '' ){
|
6107 |
+
$alert_id = rand( 100, 999 );
|
6108 |
+
if ( ! empty($message) ): ?>
|
6109 |
<div id="sucuriscan-alert-<?php echo $alert_id; ?>" class="<?php echo $type; ?> sucuriscan-alert sucuriscan-alert-<?php echo $type; ?>">
|
6110 |
<a href="javascript:void(0)" class="close" onclick="sucuriscan_alert_close('<?php echo $alert_id; ?>')">×</a>
|
6111 |
+
<p><?php _e( $message ); ?></p>
|
6112 |
</div>
|
6113 |
<?php endif;
|
6114 |
}
|
6119 |
* @param string $error_msg The message that will be printed in the alert.
|
6120 |
* @return void
|
6121 |
*/
|
6122 |
+
public static function error( $error_msg = '' ){
|
6123 |
self::admin_notice( 'error', '<b>Sucuri:</b> ' . $error_msg );
|
6124 |
}
|
6125 |
|
6129 |
* @param string $info_msg The message that will be printed in the alert.
|
6130 |
* @return void
|
6131 |
*/
|
6132 |
+
public static function info( $info_msg = '' ){
|
6133 |
self::admin_notice( 'updated', '<b>Sucuri:</b> ' . $info_msg );
|
6134 |
}
|
6135 |
|
6141 |
* @return void
|
6142 |
*/
|
6143 |
public static function setup_notice(){
|
6144 |
+
if (
|
6145 |
+
current_user_can( 'manage_options' )
|
6146 |
&& SucuriScan::no_notices_here() === false
|
6147 |
+
&& ! SucuriScanAPI::get_plugin_key()
|
6148 |
+
&& SucuriScanRequest::post( ':plugin_api_key' ) === false
|
6149 |
+
&& SucuriScanRequest::post( ':recover_key' ) === false
|
6150 |
+
&& ! SucuriScanRequest::post( ':manual_api_key' )
|
6151 |
+
) {
|
6152 |
+
echo SucuriScanTemplate::get_section( 'setup-notice' );
|
6153 |
}
|
6154 |
}
|
6155 |
|
6166 |
SucuriScanInterface::check_permissions();
|
6167 |
|
6168 |
// Check if the information is already cached.
|
6169 |
+
$cache = new SucuriScanCache( 'sitecheck' );
|
6170 |
$scan_results = $cache->get( 'scan_results', SUCURISCAN_SITECHECK_LIFETIME, 'array' );
|
6171 |
|
6172 |
+
if (
|
6173 |
(
|
6174 |
$scan_results
|
6175 |
+
&& ! empty( $scan_results )
|
6176 |
) || (
|
6177 |
SucuriScanInterface::check_nonce()
|
6178 |
+
&& SucuriScanRequest::post( ':malware_scan', '1' )
|
6179 |
)
|
6180 |
){
|
6181 |
+
sucuriscan_sitecheck_info( $scan_results );
|
6182 |
} else {
|
6183 |
+
echo SucuriScanTemplate::get_template( 'malwarescan', array(
|
6184 |
'PageTitle' => 'Malware Scan',
|
6185 |
'PageStyleClass' => 'scanner-loading',
|
6186 |
+
) );
|
6187 |
}
|
6188 |
}
|
6189 |
|
6193 |
* @param array $res Array with information of the scanning.
|
6194 |
* @return void
|
6195 |
*/
|
6196 |
+
function sucuriscan_sitecheck_info( $res = array() ){
|
6197 |
// Will be TRUE only if the scanning results were retrieved from the cache.
|
6198 |
$display_results = (bool) $res;
|
6199 |
$clean_domain = SucuriScan::get_domain();
|
6200 |
|
6201 |
// If the results are not cached, then request a new scanning.
|
6202 |
+
if ( $res === false ){
|
6203 |
+
$res = SucuriScanAPI::get_sitecheck_results( $clean_domain );
|
6204 |
|
6205 |
// Check for error messages in the request's response.
|
6206 |
+
if ( is_string( $res ) ){
|
6207 |
+
if ( preg_match( '/^ERROR:(.*)/', $res, $error_m ) ){
|
6208 |
SucuriScanInterface::error( 'The site <code>' . $clean_domain . '</code> was not scanned: ' . $error_m[1] );
|
6209 |
} else {
|
6210 |
SucuriScanInterface::error( 'SiteCheck error: ' . $res );
|
6212 |
}
|
6213 |
|
6214 |
else {
|
6215 |
+
$cache = new SucuriScanCache( 'sitecheck' );
|
6216 |
+
$display_results = true;
|
6217 |
|
6218 |
// Cache the scanning results to reduce memory lose.
|
6219 |
+
if ( ! $cache->add( 'scan_results', $res ) ){
|
6220 |
SucuriScanInterface::error( 'Could not cache the results of the SiteCheck scanning.' );
|
6221 |
}
|
6222 |
}
|
6224 |
|
6225 |
// Count the number of scans.
|
6226 |
if ( $display_results === true ) {
|
6227 |
+
$sitecheck_counter = (int) SucuriScanOption::get_option( ':sitecheck_counter' );
|
6228 |
SucuriScanOption::update_option( ':sitecheck_counter', $sitecheck_counter + 1 );
|
6229 |
}
|
6230 |
|
6232 |
?>
|
6233 |
|
6234 |
|
6235 |
+
<?php if ( $display_results ): ?>
|
6236 |
|
6237 |
<?php
|
6238 |
// Check for general warnings, and return the information for Infected/Clean site.
|
6239 |
+
$malware_warns_exist = isset($res['MALWARE']['WARN']) ? true : false;
|
6240 |
+
$blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ? true : false;
|
6241 |
+
$outdated_warns_exist = isset($res['OUTDATEDSCAN']) ? true : false;
|
6242 |
+
$recommendations_exist = isset($res['RECOMMENDATIONS']) ? true : false;
|
6243 |
|
6244 |
// Check whether this WordPress installation needs an update.
|
6245 |
global $wp_version;
|
6246 |
+
$wordpress_updated = false;
|
6247 |
+
$updates = function_exists( 'get_core_updates' ) ? get_core_updates() : array();
|
6248 |
|
6249 |
+
if (
|
6250 |
+
! is_array( $updates )
|
6251 |
+
|| empty($updates)
|
6252 |
+
|| $updates[0]->response == 'latest'
|
6253 |
+
) {
|
6254 |
+
$wordpress_updated = true;
|
6255 |
}
|
6256 |
|
6257 |
+
if ( is_array( $res ) ) {
|
6258 |
+
// Include the Thickbox library.
|
6259 |
+
add_thickbox();
|
6260 |
+
|
6261 |
// Initialize the CSS classes with default values.
|
|
|
6262 |
$sucuriscan_css_malware = 'sucuriscan-border-good';
|
6263 |
$sitecheck_results_tab = '';
|
6264 |
$blacklist_status_tab = '';
|
6265 |
$website_details_tab = '';
|
6266 |
|
6267 |
// Generate the CSS classes for the blacklist status.
|
6268 |
+
if ( $blacklist_warns_exist ){
|
|
|
6269 |
$blacklist_status_tab = 'sucuriscan-red-tab';
|
6270 |
}
|
6271 |
|
6272 |
// Generate the CSS classes for the SiteCheck scanning results.
|
6273 |
+
if ( $malware_warns_exist ){
|
6274 |
$sucuriscan_css_malware = 'sucuriscan-border-bad';
|
6275 |
$sitecheck_results_tab = 'sucuriscan-red-tab';
|
6276 |
}
|
6277 |
|
6278 |
// Generate the CSS classes for the outdated/recommendations panel.
|
6279 |
+
if ( $outdated_warns_exist || $recommendations_exist ){
|
6280 |
$website_details_tab = 'sucuriscan-red-tab';
|
6281 |
}
|
6282 |
|
6284 |
}
|
6285 |
?>
|
6286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6287 |
|
6288 |
<div class="sucuriscan-tabs">
|
6289 |
|
6290 |
|
6291 |
<ul>
|
6292 |
+
<li class="<?php _e( $sitecheck_results_tab ) ?>">
|
6293 |
<a href="#" data-tabname="sitecheck-results">Remote Scanner Results</a>
|
6294 |
</li>
|
6295 |
+
<li class="<?php _e( $website_details_tab ) ?>">
|
6296 |
<a href="#" data-tabname="website-details">Website Details</a>
|
6297 |
</li>
|
6298 |
<li>
|
6299 |
<a href="#" data-tabname="website-links">IFrames / Links / Scripts</a>
|
6300 |
</li>
|
6301 |
+
<li class="<?php _e( $blacklist_status_tab ) ?>">
|
6302 |
<a href="#" data-tabname="blacklist-status">Blacklist Status</a>
|
6303 |
</li>
|
6304 |
<li>
|
6311 |
|
6312 |
|
6313 |
<div id="sucuriscan-sitecheck-results">
|
6314 |
+
<table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-details">
|
6315 |
+
<thead>
|
6316 |
+
<tr>
|
6317 |
+
<th colspan="3" class="thead-with-button">
|
6318 |
+
<?php if ( $malware_warns_exist ): ?>
|
6319 |
+
<span>Site compromised (malware was identified)</span>
|
6320 |
+
<a href="http://sucuri.net/website-antivirus/" target="_blank"
|
6321 |
+
class="thead-topright-action button-primary">Clean website</a>
|
6322 |
+
<?php else: ?>
|
6323 |
+
<span>Site clean (no malware was identified)</span>
|
6324 |
+
<?php endif; ?>
|
6325 |
+
</th>
|
6326 |
+
</tr>
|
6327 |
+
</thead>
|
6328 |
+
|
6329 |
+
<tbody>
|
6330 |
+
|
6331 |
+
<?php if ( $malware_warns_exist ): ?>
|
6332 |
+
|
6333 |
+
<?php foreach ( $res['MALWARE']['WARN'] as $key => $malres ): ?>
|
6334 |
+
<?php $malres = SucuriScanAPI::get_sitecheck_malware( $malres ); ?>
|
6335 |
+
<tr>
|
6336 |
+
<?php if ( $malres !== false ): ?>
|
6337 |
+
<td>
|
6338 |
+
<a href="<?php _e( $malres['malware_docs'] ); ?>" target="_blank">
|
6339 |
+
<?php _e( $malres['alert_message'] ); ?>
|
6340 |
+
</a>
|
6341 |
+
</td>
|
6342 |
+
|
6343 |
+
<td>
|
6344 |
+
<span class="sucuriscan-monospace"><?php _e( $malres['malware_type'] ); ?></span>
|
6345 |
+
</td>
|
6346 |
+
|
6347 |
+
<td>
|
6348 |
+
<div class="sucuriscan-malware-link">
|
6349 |
+
<a href="<?php _e( $malres['infected_url'] ); ?>" target="_blank"
|
6350 |
+
class="sucuriscan-label sucuriscan-label-warning">View infected URL</a>
|
6351 |
+
|
6352 |
+
<a href="#TB_inline?width=600&height=300&inlineId=sucuriscan-malware-<?php _e( $key ); ?>"
|
6353 |
+
title="SiteCheck: Malware Payload" class="thickbox sucuriscan-label sucuriscan-label-danger">
|
6354 |
+
View malware</a>
|
6355 |
+
</div>
|
6356 |
+
|
6357 |
+
<div id="sucuriscan-malware-<?php _e( $key ); ?>" style="display:none">
|
6358 |
+
<div class="sucuriscan-malware-payload"><?php _e( $malres['malware_payload'] ); ?></div>
|
6359 |
+
</div>
|
6360 |
+
</td>
|
6361 |
+
<?php endif; ?>
|
6362 |
+
</tr>
|
6363 |
+
<?php endforeach; ?>
|
6364 |
+
|
6365 |
+
<?php else: ?>
|
6366 |
+
|
6367 |
+
<tr>
|
6368 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6369 |
+
<td colspan="3">Malware</td>
|
6370 |
+
</tr>
|
6371 |
+
|
6372 |
+
<tr>
|
6373 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6374 |
+
<td width="220">
|
6375 |
+
<a href="http://kb.sucuri.net/malware/encoded-javascript" target="_blank">
|
6376 |
+
Malicious javascript
|
6377 |
+
</a>
|
6378 |
+
</td>
|
6379 |
+
<td>
|
6380 |
+
<div>
|
6381 |
+
JavaScript is a language (code) that can be executed directly by the browser and
|
6382 |
+
many other applications that support it (PDF, email readers, etc). Because it is
|
6383 |
+
a full programming language executed by the browser, attackers use it heavily to
|
6384 |
+
run malicious code from the compromised sites.
|
6385 |
+
</div>
|
6386 |
+
</td>
|
6387 |
+
</tr>
|
6388 |
+
|
6389 |
+
<tr>
|
6390 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6391 |
+
<td width="220">
|
6392 |
+
<a href="http://kb.sucuri.net/malware/malicious-iframes" target="_blank">
|
6393 |
+
Malicious iframes
|
6394 |
+
</a>
|
6395 |
+
</td>
|
6396 |
+
<td>
|
6397 |
+
<div>
|
6398 |
+
An inline frame (iframe) is used to embed another document within the current
|
6399 |
+
HTML document. Because as the definition implies, it allows you to insert
|
6400 |
+
another document inside the current HTML page. And the attackers use that
|
6401 |
+
feature to insert malicious content into the compromised sites (to redirect to
|
6402 |
+
spam, exploit kits, Fake AV, phishing, etc).
|
6403 |
+
</div>
|
6404 |
+
</td>
|
6405 |
+
</tr>
|
6406 |
+
|
6407 |
+
<tr>
|
6408 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6409 |
+
<td width="220">
|
6410 |
+
<a href="http://kb.sucuri.net/malware/conditional-redirections" target="_blank">
|
6411 |
+
Suspicious redirections (htaccess)
|
6412 |
+
</a>
|
6413 |
+
</td>
|
6414 |
+
<td>
|
6415 |
+
<div>
|
6416 |
+
Conditional redirections are classified differently than the iframe/javascript
|
6417 |
+
ones, because they are generally done though the HTTP headers (via .htaccess) to
|
6418 |
+
redirect users from certain browsers or locations to malware/malicious
|
6419 |
+
locations.
|
6420 |
+
</div>
|
6421 |
+
</td>
|
6422 |
+
</tr>
|
6423 |
|
6424 |
+
<tr>
|
6425 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6426 |
+
<td colspan="3">Blackhat SEO Spam</td>
|
6427 |
+
</tr>
|
6428 |
+
|
6429 |
+
<tr>
|
6430 |
+
<td><span class="sucuriscan-label sucuriscan-label-success">CLEAN</span></td>
|
6431 |
+
<td colspan="3">Anomaly detection</td>
|
6432 |
+
</tr>
|
6433 |
+
|
6434 |
+
<?php endif; ?>
|
6435 |
+
|
6436 |
+
<tr>
|
6437 |
+
<td colspan="3">
|
6438 |
+
<hr/>
|
6439 |
+
<em>
|
6440 |
+
More details at <a href="http://sitecheck.sucuri.net/results/<?php _e( $clean_domain ); ?>"
|
6441 |
+
target="_blank">SiteCheck/<?php _e( $clean_domain ); ?></a>. If our free scanner
|
6442 |
+
did not detect any issue, you may have a more complicated and hidden problem.
|
6443 |
+
You can <a href="http://sucuri.net/signup" target="_blank">sign up</a> with
|
6444 |
+
Sucuri for a complete and in depth scan+cleanup <strong>(not included in the
|
6445 |
+
free checks)</strong>.
|
6446 |
+
</em>
|
6447 |
+
</td>
|
6448 |
+
</tr>
|
6449 |
+
|
6450 |
+
</tbody>
|
6451 |
+
</table>
|
6452 |
</div>
|
6453 |
|
6454 |
|
6457 |
<thead>
|
6458 |
<tr>
|
6459 |
<th colspan="2" class="thead-with-button">
|
6460 |
+
<span>System information</span>
|
6461 |
+
<?php if ( ! $wordpress_updated ): ?>
|
6462 |
+
<a href="<?php echo admin_url( 'update-core.php' ); ?>" class="button button-primary thead-topright-action">
|
6463 |
+
Update to <?php _e( $updates[0]->version ) ?>
|
6464 |
</a>
|
6465 |
<?php endif; ?>
|
6466 |
</th>
|
6484 |
);
|
6485 |
?>
|
6486 |
|
6487 |
+
<?php foreach ( $possible_keys as $result_key => $result_title ): ?>
|
6488 |
+
<?php if ( isset($res['SCAN'][ $result_key ]) ): ?>
|
6489 |
+
<?php $result_value = implode( ', ', $res['SCAN'][ $result_key ] ); ?>
|
6490 |
<tr>
|
6491 |
+
<td><?php _e( $result_title ) ?></td>
|
6492 |
+
<td><span class="sucuriscan-monospace"><?php _e( $result_value ) ?></span></td>
|
6493 |
</tr>
|
6494 |
<?php endif; ?>
|
6495 |
<?php endforeach; ?>
|
6496 |
|
6497 |
<tr>
|
6498 |
<td>WordPress Version</td>
|
6499 |
+
<td><span class="sucuriscan-monospace"><?php _e( $wp_version ) ?></span></td>
|
6500 |
</tr>
|
6501 |
<tr>
|
6502 |
<td>PHP Version</td>
|
6503 |
+
<td><span class="sucuriscan-monospace"><?php _e( phpversion() ) ?></span></td>
|
6504 |
</tr>
|
6505 |
|
6506 |
<!-- List of application details from the site. -->
|
6507 |
<tr>
|
6508 |
<th colspan="2">Web application details</th>
|
6509 |
</tr>
|
6510 |
+
<?php if ( isset($res['WEBAPP']) ): ?>
|
6511 |
+
<?php foreach ( $res['WEBAPP'] as $webapp_key => $webapp_details ): ?>
|
6512 |
+
<?php if ( is_array( $webapp_details ) ): ?>
|
6513 |
+
<?php foreach ( $webapp_details as $i => $details ): ?>
|
6514 |
+
<?php
|
6515 |
+
if ( is_array( $details ) ) {
|
6516 |
+
$details = isset($details[0]) ? $details[0] : '';
|
6517 |
+
}
|
6518 |
+
$details_parts = explode( ':', $details, 2 );
|
6519 |
+
$details_abc = isset($details_parts[0]) ? trim( $details_parts[0] ) : '';
|
6520 |
+
$details_xyz = isset($details_parts[1]) ? trim( $details_parts[1] ) : '';
|
6521 |
+
?>
|
6522 |
<tr>
|
6523 |
+
<td><?php _e( $details_abc ) ?></td>
|
6524 |
+
<td><span class="sucuriscan-monospace"><?php _e( $details_xyz ) ?></span></td>
|
|
|
6525 |
</tr>
|
6526 |
<?php endforeach; ?>
|
6527 |
<?php endif; ?>
|
6528 |
<?php endforeach; ?>
|
6529 |
<?php endif; ?>
|
6530 |
|
6531 |
+
<?php if ( isset($res['SYSTEM']['NOTICE']) ): ?>
|
6532 |
+
<?php foreach ( $res['SYSTEM']['NOTICE'] as $j => $notice ): ?>
|
6533 |
+
<?php if ( is_array( $notice ) ){ $notice = implode( ', ', $notice ); } ?>
|
6534 |
<tr>
|
6535 |
<td colspan="2">
|
6536 |
+
<span class="sucuriscan-monospace"><?php _e( $notice ) ?></span>
|
6537 |
</td>
|
6538 |
</tr>
|
6539 |
<?php endforeach; ?>
|
6540 |
<?php endif; ?>
|
6541 |
|
6542 |
+
<?php if ( ! isset($res['WEBAPP']) && ! isset($res['SYSTEM']['NOTICE']) ): ?>
|
6543 |
<tr>
|
6544 |
<td colspan="2"><em>No more information was found.</em></td>
|
6545 |
</tr>
|
6546 |
<?php endif; ?>
|
6547 |
|
6548 |
<!-- Possible recommendations or outdated software on the site. -->
|
6549 |
+
<?php if ( $outdated_warns_exist || $recommendations_exist ): ?>
|
6550 |
<tr>
|
6551 |
<th colspan="2">Recommendations for the site</th>
|
6552 |
</tr>
|
6553 |
<?php endif; ?>
|
6554 |
|
6555 |
<!-- Possible outdated software on the site. -->
|
6556 |
+
<?php if ( $outdated_warns_exist ): ?>
|
6557 |
+
<?php foreach ( $res['OUTDATEDSCAN'] as $outdated ): ?>
|
6558 |
+
<?php if ( count( $outdated ) >= 3 ): ?>
|
6559 |
<tr>
|
6560 |
<td colspan="2" class="sucuriscan-border-bad">
|
6561 |
+
<strong><?php _e( $outdated[0] ) ?></strong>
|
6562 |
+
<em>(<?php _e( $outdated[2] ) ?>)</em>
|
6563 |
+
<span><?php _e( $outdated[1] ) ?></span>
|
6564 |
</td>
|
6565 |
</tr>
|
6566 |
<?php endif; ?>
|
6568 |
<?php endif; ?>
|
6569 |
|
6570 |
<!-- Possible recommendations for the site. -->
|
6571 |
+
<?php if ( $recommendations_exist ): ?>
|
6572 |
+
<?php foreach ( $res['RECOMMENDATIONS'] as $recommendation ): ?>
|
6573 |
+
<?php if ( count( $recommendation ) >= 3 ): ?>
|
6574 |
<tr>
|
6575 |
<td colspan="2" class="sucuriscan-border-bad">
|
6576 |
+
<?php
|
6577 |
+
printf(
|
6578 |
'<strong>%s</strong><br><span>%s</span><br><a href="%s" target="_blank">%s</a>',
|
6579 |
+
SucuriScan::escape( $recommendation[0] ),
|
6580 |
+
SucuriScan::escape( $recommendation[1] ),
|
6581 |
+
SucuriScan::escape( $recommendation[2] ),
|
6582 |
+
SucuriScan::escape( $recommendation[2] )
|
6583 |
+
);
|
6584 |
+
?>
|
6585 |
</td>
|
6586 |
</tr>
|
6587 |
<?php endif; ?>
|
6597 |
<tbody>
|
6598 |
<?php if ( isset($res['LINKS']) ): ?>
|
6599 |
|
6600 |
+
<?php foreach ( $possible_url_keys as $result_url_key => $result_url_title ): ?>
|
6601 |
|
6602 |
+
<?php if ( isset($res['LINKS'][ $result_url_key ]) ): ?>
|
6603 |
<tr>
|
6604 |
<th colspan="2">
|
6605 |
+
<?php
|
6606 |
+
printf(
|
6607 |
'%s (%d found)',
|
6608 |
+
__( $result_url_title ),
|
6609 |
+
count( $res['LINKS'][ $result_url_key ] )
|
6610 |
+
);
|
6611 |
+
?>
|
6612 |
</th>
|
6613 |
</tr>
|
6614 |
|
6615 |
+
<?php foreach ( $res['LINKS'][ $result_url_key ] as $url_path ): ?>
|
6616 |
<tr>
|
6617 |
<td colspan="2">
|
6618 |
+
<span class="sucuriscan-monospace sucuriscan-wraptext"><?php _e( $url_path ) ?></span>
|
6619 |
</td>
|
6620 |
</tr>
|
6621 |
<?php endforeach; ?>
|
6636 |
|
6637 |
|
6638 |
<div id="sucuriscan-blacklist-status">
|
6639 |
+
<table class="wp-list-table widefat sucuriscan-table sucuriscan-scanner-details">
|
6640 |
+
<thead>
|
6641 |
+
<tr>
|
6642 |
+
<th colspan="3" class="thead-with-button">
|
6643 |
+
<span>Site <?php echo $blacklist_warns_exist ? 'blacklisted' : 'blacklist-free'; ?></span>
|
6644 |
+
</th>
|
6645 |
+
</tr>
|
6646 |
+
</thead>
|
6647 |
+
|
6648 |
+
<tbody>
|
6649 |
+
|
6650 |
+
<?php
|
6651 |
+
$blacklist_types = array( 'INFO' => 'CLEAN', 'WARN' => 'WARNING' );
|
6652 |
+
foreach ( $blacklist_types as $type => $group_title ):
|
6653 |
+
?>
|
6654 |
+
|
6655 |
+
<?php if ( isset($res['BLACKLIST'][ $type ]) ): ?>
|
6656 |
+
<?php foreach ( $res['BLACKLIST'][ $type ] as $blres ): ?>
|
6657 |
+
<?php
|
6658 |
+
$report_site = SucuriScan::escape( $blres[0] );
|
6659 |
+
$report_url = SucuriScan::escape( $blres[1] );
|
6660 |
+
$css_blacklist = ( $type == 'INFO' ) ? 'success' : 'danger';
|
6661 |
+
?>
|
6662 |
+
<tr>
|
6663 |
+
<td>
|
6664 |
+
<span class="sucuriscan-label sucuriscan-label-<?php _e( $css_blacklist ); ?>">
|
6665 |
+
<?php _e( $group_title ); ?>
|
6666 |
+
</span>
|
6667 |
+
</td>
|
6668 |
+
<td><?php _e( $report_site ); ?></td>
|
6669 |
+
<td>
|
6670 |
+
<a href="<?php _e( $report_url ); ?>" target="_blank">More details</a>
|
6671 |
+
</td>
|
6672 |
+
</tr>
|
6673 |
+
<?php endforeach; ?>
|
6674 |
<?php endif; ?>
|
6675 |
+
|
6676 |
+
<?php endforeach; ?>
|
6677 |
+
|
6678 |
+
</tbody>
|
6679 |
+
</table>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6680 |
</div>
|
6681 |
|
6682 |
|
6688 |
</div>
|
6689 |
</div>
|
6690 |
|
6691 |
+
<?php if ( $malware_warns_exist || $blacklist_warns_exist ): ?>
|
6692 |
<a href="http://sucuri.net/signup/" target="_blank" class="button button-primary button-hero sucuriscan-cleanup-btn">
|
6693 |
Get your site protected with Sucuri
|
6694 |
</a>
|
6730 |
$template_variables = array(
|
6731 |
'PageTitle' => 'Firewall WAF',
|
6732 |
'Monitoring.InstructionsVisibility' => 'visible',
|
6733 |
+
'Monitoring.Settings' => sucuriscan_monitoring_settings( $api_key ),
|
6734 |
+
'Monitoring.Logs' => sucuriscan_monitoring_logs( $api_key ),
|
6735 |
|
6736 |
/* Pseudo-variables for the monitoring logs. */
|
6737 |
'AuditLogs.List' => '',
|
6742 |
'AuditLogs.AuditPagination' => '',
|
6743 |
);
|
6744 |
|
6745 |
+
if ( $api_key ){
|
6746 |
$template_variables['Monitoring.InstructionsVisibility'] = 'hidden';
|
6747 |
}
|
6748 |
|
6749 |
+
echo SucuriScanTemplate::get_template( 'monitoring', $template_variables );
|
6750 |
}
|
6751 |
|
6752 |
/**
|
6758 |
*/
|
6759 |
function sucuriscan_monitoring_form_submissions(){
|
6760 |
|
6761 |
+
if ( SucuriScanInterface::check_nonce() ){
|
6762 |
|
6763 |
// Add and/or Update the Sucuri WAF API Key (do it before anything else).
|
6764 |
$option_name = ':cloudproxy_apikey';
|
6765 |
+
$api_key = SucuriScanRequest::post( $option_name );
|
6766 |
|
6767 |
+
if ( $api_key !== false ){
|
6768 |
+
if ( SucuriScanAPI::is_valid_cloudproxy_key( $api_key ) ){
|
6769 |
+
SucuriScanOption::update_option( $option_name, $api_key );
|
6770 |
+
SucuriScanOption::update_option( ':revproxy', 'enabled' );
|
6771 |
SucuriScanInterface::info( 'CloudProxy API key saved successfully' );
|
6772 |
+
} elseif ( empty($api_key) ){
|
6773 |
+
SucuriScanOption::delete_option( $option_name );
|
6774 |
+
SucuriScanOption::update_option( ':revproxy', 'disabled' );
|
6775 |
SucuriScanInterface::info( 'CloudProxy API key removed successfully' );
|
6776 |
} else {
|
6777 |
SucuriScanInterface::error( 'Invalid CloudProxy API key, check your settings and try again.' );
|
6779 |
}
|
6780 |
|
6781 |
// Flush the cache of the site(s) associated with the API key.
|
6782 |
+
if ( SucuriScanRequest::post( ':clear_cache', '1' ) ){
|
6783 |
$clear_cache_resp = SucuriScanAPI::clear_cloudproxy_cache();
|
6784 |
|
6785 |
+
if ( $clear_cache_resp ){
|
6786 |
+
if ( isset($clear_cache_resp->messages[0]) ){
|
6787 |
// Clear W3 Total Cache if it is installed.
|
6788 |
+
if ( function_exists( 'w3tc_flush_all' ) ){ w3tc_flush_all(); }
|
6789 |
|
6790 |
+
SucuriScanInterface::info( $clear_cache_resp->messages[0] );
|
6791 |
} else {
|
6792 |
+
SucuriScanInterface::error( 'Could not clear the cache of your site, try later again.' );
|
6793 |
}
|
6794 |
} else {
|
6795 |
SucuriScanInterface::error( 'CloudProxy is not enabled on your site, or your API key is invalid.' );
|
6796 |
}
|
6797 |
}
|
|
|
6798 |
}
|
6799 |
|
6800 |
}
|
6805 |
* @param string $api_key The CloudProxy API key.
|
6806 |
* @return string The parsed-content of the monitoring settings panel.
|
6807 |
*/
|
6808 |
+
function sucuriscan_monitoring_settings( $api_key = '' ){
|
6809 |
$template_variables = array(
|
6810 |
'Monitoring.APIKey' => '',
|
6811 |
'Monitoring.SettingsVisibility' => 'hidden',
|
6812 |
'Monitoring.SettingOptions' => '',
|
6813 |
);
|
6814 |
|
6815 |
+
if ( $api_key ){
|
6816 |
+
$settings = SucuriScanAPI::get_cloudproxy_settings( $api_key );
|
6817 |
|
6818 |
$template_variables['Monitoring.APIKey'] = $api_key['string'];
|
6819 |
|
6820 |
+
if ( $settings ){
|
6821 |
$counter = 0;
|
6822 |
$template_variables['Monitoring.SettingsVisibility'] = 'visible';
|
6823 |
+
$settings = sucuriscan_explain_monitoring_settings( $settings );
|
6824 |
|
6825 |
+
foreach ( $settings as $option_name => $option_value ){
|
6826 |
// Change the name of some options.
|
6827 |
+
if ( $option_name == 'internal_ip' ){
|
6828 |
$option_name = 'hosting_ip';
|
6829 |
}
|
6830 |
|
6831 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
6832 |
+
$option_title = ucwords( str_replace( '_', chr( 32 ), $option_name ) );
|
6833 |
|
6834 |
// Generate a HTML list when the option's value is an array.
|
6835 |
+
if ( is_array( $option_value ) ){
|
6836 |
+
$css_scrollable = count( $option_value ) > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
|
6837 |
$html_list = '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
|
6838 |
|
6839 |
+
foreach ( $option_value as $single_value ){
|
6840 |
$html_list .= '<li>' . $single_value . '</li>';
|
6841 |
}
|
6842 |
|
6866 |
* @param array $settings A hash with the settings of a CloudProxy account.
|
6867 |
* @return array The explained version of the CloudProxy settings.
|
6868 |
*/
|
6869 |
+
function sucuriscan_explain_monitoring_settings( $settings = array() ){
|
6870 |
+
if ( $settings ){
|
6871 |
+
foreach ( $settings as $option_name => $option_value ){
|
6872 |
+
switch ( $option_name ){
|
6873 |
case 'security_level':
|
6874 |
+
$new_value = ucwords( $option_value );
|
6875 |
break;
|
6876 |
case 'proxy_active':
|
6877 |
$new_value = ( $option_value == 1 ) ? 'Active' : 'not active';
|
6878 |
break;
|
6879 |
case 'cache_mode':
|
6880 |
+
$new_value = sucuriscan_cache_mode_title( $option_value );
|
6881 |
break;
|
6882 |
}
|
6883 |
|
6884 |
+
if ( isset($new_value) ){
|
6885 |
$settings->{$option_name} = $new_value;
|
6886 |
}
|
6887 |
}
|
6889 |
return $settings;
|
6890 |
}
|
6891 |
|
6892 |
+
return false;
|
6893 |
}
|
6894 |
|
6895 |
/**
|
6898 |
* @param string $mode The value set for the cache settings of the site.
|
6899 |
* @return string Explanation of the meaning of the cache_mode value.
|
6900 |
*/
|
6901 |
+
function sucuriscan_cache_mode_title( $mode = '' ){
|
6902 |
$title = '';
|
6903 |
|
6904 |
+
switch ( $mode ){
|
6905 |
case 'docache': $title = 'Enabled (recommended)'; break;
|
6906 |
case 'sitecache': $title = 'Site caching (using your site headers)'; break;
|
6907 |
case 'nocache': $title = 'Minimal (only for a few minutes)'; break;
|
6918 |
* @param string $api_key The CloudProxy API key.
|
6919 |
* @return string The parsed-content of the monitoring logs panel.
|
6920 |
*/
|
6921 |
+
function sucuriscan_monitoring_logs( $api_key = '' ){
|
6922 |
$template_variables = array(
|
6923 |
'AuditLogs.List' => '',
|
6924 |
'AuditLogs.CountText' => 0,
|
6932 |
'AuditLogs.DateDays' => '',
|
6933 |
);
|
6934 |
|
6935 |
+
$date = date( 'Y-m-d' );
|
6936 |
|
6937 |
+
if ( $api_key ){
|
6938 |
// Retrieve the date filter from the GET request (if any).
|
6939 |
+
if ( $date_by_get = SucuriScanRequest::get( 'date', '_yyyymmdd' ) ){
|
6940 |
$date = $date_by_get;
|
6941 |
}
|
6942 |
|
6943 |
// Retrieve the date filter from the POST request (if any).
|
6944 |
+
$year = SucuriScanRequest::post( ':year' );
|
6945 |
+
$month = SucuriScanRequest::post( ':month' );
|
6946 |
+
$day = SucuriScanRequest::post( ':day' );
|
6947 |
|
6948 |
+
if ( $year && $month && $day ){
|
6949 |
$date = sprintf( '%s-%s-%s', $year, $month, $day );
|
6950 |
}
|
6951 |
|
6952 |
$logs_data = SucuriScanAPI::get_cloudproxy_logs( $api_key, $date );
|
6953 |
|
6954 |
+
if ( $logs_data ){
|
6955 |
add_thickbox(); /* Include the Thickbox library. */
|
6956 |
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
|
6957 |
$template_variables['AuditLogs.CountText'] = $logs_data->limit . '/' . $logs_data->total_lines;
|
6958 |
+
$template_variables['AuditLogs.List'] = sucuriscan_monitoring_access_logs( $logs_data->access_logs );
|
6959 |
+
$template_variables['AuditLogs.DenialTypeOptions'] = sucuriscan_monitoring_denial_types( $logs_data->access_logs );
|
6960 |
}
|
6961 |
}
|
6962 |
|
6963 |
+
$template_variables['AuditLogs.TargetDate'] = SucuriScan::escape( $date );
|
6964 |
+
$template_variables['AuditLogs.DateYears'] = sucuriscan_monitoring_dates( 'years', $date );
|
6965 |
+
$template_variables['AuditLogs.DateMonths'] = sucuriscan_monitoring_dates( 'months', $date );
|
6966 |
+
$template_variables['AuditLogs.DateDays'] = sucuriscan_monitoring_dates( 'days', $date );
|
6967 |
|
6968 |
return SucuriScanTemplate::get_section( 'monitoring-logs', $template_variables );
|
6969 |
}
|
6974 |
* @param array $access_logs The logs retrieved from the remote API service.
|
6975 |
* @return string The HTML code to show the access-logs in the page as a table.
|
6976 |
*/
|
6977 |
+
function sucuriscan_monitoring_access_logs( $access_logs = array() ){
|
6978 |
$logs_html = '';
|
6979 |
|
6980 |
+
if ( $access_logs && ! empty($access_logs) ){
|
6981 |
$counter = 0;
|
6982 |
$needed_attrs = array(
|
6983 |
'request_date',
|
6997 |
'http_user_agent',
|
6998 |
);
|
6999 |
|
7000 |
+
$filter_by_denial_type = false;
|
7001 |
+
$filter_by_keyword = false;
|
7002 |
+
$filter_query = false;
|
7003 |
|
7004 |
+
if ( $q = SucuriScanRequest::post( ':monitoring_denial_type' ) ){
|
7005 |
+
$filter_by_denial_type = true;
|
7006 |
$filter_query = $q;
|
7007 |
}
|
7008 |
|
7009 |
+
if ( $q = SucuriScanRequest::post( ':monitoring_log_filter' ) ){
|
7010 |
+
$filter_by_keyword = true;
|
7011 |
$filter_query = $q;
|
7012 |
}
|
7013 |
|
7014 |
+
foreach ( $access_logs as $access_log ){
|
7015 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
7016 |
$audit_log_snippet = array(
|
7017 |
'AuditLog.Id' => $counter,
|
7019 |
);
|
7020 |
|
7021 |
// If there is a filter, check the access_log data and break the operation if needed.
|
7022 |
+
if ( $filter_query ){
|
7023 |
+
if ( $filter_by_denial_type ){
|
7024 |
+
$denial_type_slug = SucuriScan::human2var( $access_log->sucuri_block_reason );
|
7025 |
|
7026 |
+
if ( $denial_type_slug != $filter_query ){ continue; }
|
7027 |
}
|
7028 |
|
7029 |
+
if (
|
7030 |
$filter_by_keyword
|
7031 |
+
&& strpos( $access_log->remote_addr, $filter_query ) === false
|
7032 |
+
&& strpos( $access_log->resource_path, $filter_query ) === false
|
7033 |
){
|
7034 |
continue;
|
7035 |
}
|
7036 |
}
|
7037 |
|
7038 |
// Generate (dynamically) the pseudo-variables for the template.
|
7039 |
+
foreach ( $needed_attrs as $attr_name ){
|
7040 |
$attr_value = '';
|
7041 |
|
7042 |
+
$attr_title = str_replace( '_', chr( 32 ), $attr_name );
|
7043 |
+
$attr_title = ucwords( $attr_title );
|
7044 |
+
$attr_title = str_replace( chr( 32 ), '', $attr_title );
|
7045 |
$attr_title = 'AuditLog.' . $attr_title;
|
7046 |
|
7047 |
+
if ( isset($access_log->{$attr_name}) ){
|
7048 |
$attr_value = $access_log->{$attr_name};
|
7049 |
|
7050 |
+
if (
|
7051 |
empty($attr_value)
|
7052 |
&& $attr_name == 'sucuri_block_reason'
|
7053 |
){
|
7055 |
}
|
7056 |
}
|
7057 |
|
7058 |
+
elseif ( $attr_name == 'local_request_time' ){
|
7059 |
+
$attr_value = SucuriScan::datetime( $access_log->request_timestamp );
|
7060 |
}
|
7061 |
|
7062 |
+
$audit_log_snippet[ $attr_title ] = SucuriScan::escape( $attr_value );
|
7063 |
}
|
7064 |
|
7065 |
+
$logs_html .= SucuriScanTemplate::get_snippet( 'monitoring-logs', $audit_log_snippet );
|
7066 |
$counter += 1;
|
7067 |
}
|
7068 |
}
|
7079 |
* @param boolean $in_html Whether the list should be converted to a HTML select options or not.
|
7080 |
* @return array Either a list of unique blocking types, or a HTML code.
|
7081 |
*/
|
7082 |
+
function sucuriscan_monitoring_denial_types( $access_logs = array(), $in_html = true ){
|
7083 |
$types = array();
|
7084 |
|
7085 |
+
if ( $access_logs && ! empty($access_logs) ){
|
7086 |
+
foreach ( $access_logs as $access_log ){
|
7087 |
+
if ( ! array_key_exists( $access_log->sucuri_block_reason, $types ) ){
|
7088 |
+
$denial_type_k = SucuriScan::human2var( $access_log->sucuri_block_reason );
|
7089 |
$denial_type_v = $access_log->sucuri_block_reason;
|
7090 |
+
if ( empty($denial_type_v) ){ $denial_type_v = 'Unknown'; }
|
7091 |
+
$types[ $denial_type_k ] = $denial_type_v;
|
7092 |
}
|
7093 |
}
|
7094 |
}
|
7095 |
|
7096 |
+
if ( $in_html ){
|
7097 |
$html_types = '<option value="">Filter</option>';
|
7098 |
+
$selected = SucuriScanRequest::post( ':monitoring_denial_type', '.+' );
|
7099 |
|
7100 |
+
foreach ( $types as $type_key => $type_value ){
|
7101 |
$selected_tag = ( $type_key === $selected ) ? 'selected="selected"' : '';
|
7102 |
$html_types .= sprintf(
|
7103 |
'<option value="%s" %s>%s</option>',
|
7104 |
+
SucuriScan::escape( $type_key ),
|
7105 |
$selected_tag,
|
7106 |
+
SucuriScan::escape( $type_value )
|
7107 |
);
|
7108 |
}
|
7109 |
|
7121 |
* @param boolean $in_html Whether the list should be converted to a HTML select options or not.
|
7122 |
* @return array Either an array with the expected values, or a HTML code.
|
7123 |
*/
|
7124 |
+
function sucuriscan_monitoring_dates( $type = '', $date = '', $in_html = true ){
|
7125 |
$options = array();
|
7126 |
$selected = '';
|
7127 |
|
7128 |
+
if ( preg_match( '/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', $date, $date_m ) ){
|
7129 |
$s_year = $date_m[1];
|
7130 |
$s_month = $date_m[2];
|
7131 |
$s_day = $date_m[3];
|
7135 |
$s_day = '';
|
7136 |
}
|
7137 |
|
7138 |
+
switch ( $type ){
|
7139 |
case 'years':
|
7140 |
$selected = $s_year;
|
7141 |
+
$current_year = (int) date( 'Y' );
|
7142 |
$max_years = 5; /* Maximum number of years to keep the logs. */
|
7143 |
$options = range( ($current_year - $max_years), $current_year );
|
7144 |
break;
|
7156 |
'09' => 'September',
|
7157 |
'10' => 'October',
|
7158 |
'11' => 'November',
|
7159 |
+
'12' => 'December',
|
7160 |
);
|
7161 |
break;
|
7162 |
case 'days':
|
7163 |
+
$options = range( 1, 31 );
|
7164 |
$selected = $s_day;
|
7165 |
break;
|
7166 |
}
|
7167 |
|
7168 |
+
if ( $in_html ){
|
7169 |
$html_options = '';
|
7170 |
|
7171 |
+
foreach ( $options as $key => $value ){
|
7172 |
+
if ( is_numeric( $value ) ){ $value = str_pad( $value, 2, 0, STR_PAD_LEFT ); }
|
7173 |
|
7174 |
+
if ( $type != 'months' ){ $key = $value; }
|
7175 |
|
7176 |
$selected_tag = ( $key == $selected ) ? 'selected="selected"' : '';
|
7177 |
$html_options .= sprintf( '<option value="%s" %s>%s</option>', $key, $selected_tag, $value );
|
7194 |
function sucuriscan_hardening_page(){
|
7195 |
SucuriScanInterface::check_permissions();
|
7196 |
|
7197 |
+
if (
|
7198 |
+
SucuriScanRequest::post( ':run_hardening' )
|
7199 |
+
&& ! SucuriScanInterface::check_nonce()
|
7200 |
){
|
7201 |
unset($_POST['sucuriscan_run_hardening']);
|
7202 |
}
|
7213 |
sucuriscan_harden_version();
|
7214 |
sucuriscan_cloudproxy_enabled();
|
7215 |
sucuriscan_harden_removegenerator();
|
7216 |
+
|
7217 |
+
if ( SucuriScan::is_nginx_server() === true ) {
|
7218 |
+
sucuriscan_harden_nginx_phpfpm();
|
7219 |
+
} elseif ( SucuriScan::is_iis_server() === true ) {
|
7220 |
+
/* TODO: Include IIS (Internet Information Services) hardening options. */
|
7221 |
+
} else {
|
7222 |
+
sucuriscan_harden_upload();
|
7223 |
+
sucuriscan_harden_wpcontent();
|
7224 |
+
sucuriscan_harden_wpincludes();
|
7225 |
+
}
|
7226 |
+
|
7227 |
sucuriscan_harden_phpversion();
|
7228 |
sucuriscan_harden_secretkeys();
|
7229 |
sucuriscan_harden_readme();
|
7241 |
echo SucuriScanTemplate::get_base_template($_html, array(
|
7242 |
'PageTitle' => 'Hardening',
|
7243 |
'PageContent' => $_html,
|
7244 |
+
'PageStyleClass' => 'hardening',
|
7245 |
));
|
7246 |
return;
|
7247 |
}
|
7260 |
* @param string $updatemsg Optional explanation of the hardening after the submission of the form.
|
7261 |
* @return void
|
7262 |
*/
|
7263 |
+
function sucuriscan_harden_status( $title = '', $status = 0, $type = '', $messageok = '', $messagewarn = '', $desc = null, $updatemsg = null ){ ?>
|
7264 |
<div class="postbox">
|
7265 |
+
<h3><?php _e( $title ) ?></h3>
|
7266 |
|
7267 |
<div class="inside">
|
7268 |
+
<?php if ( $desc != null ): ?>
|
7269 |
+
<p><?php _e( $desc ) ?></p>
|
7270 |
<?php endif; ?>
|
7271 |
|
7272 |
+
<?php if ( $status <= 5 ): ?>
|
7273 |
+
<div class="sucuriscan-hstatus sucuriscan-hstatus-<?php _e( $status ) ?>">
|
7274 |
+
<?php if ( $type != null ): ?>
|
7275 |
+
<?php if ( $status === 1 ): ?>
|
7276 |
+
<input type="submit" name="<?php _e( $type ) ?>_unharden" value="Revert hardening" class="button-secondary" />
|
7277 |
+
<?php elseif ( $status === 0 ): ?>
|
7278 |
+
<input type="submit" name="<?php _e( $type ) ?>" value="Harden" class="button-primary" />
|
7279 |
+
<?php endif; ?>
|
7280 |
<?php endif; ?>
|
|
|
7281 |
|
7282 |
+
<span>
|
7283 |
+
<?php if ( $status === 1 ): ?>
|
7284 |
+
<?php _e( $messageok ) ?>
|
7285 |
+
<?php elseif ( $status === 0 ): ?>
|
7286 |
+
<?php _e( $messagewarn ) ?>
|
7287 |
+
<?php elseif ( $status === 2 ): ?>
|
7288 |
+
Can not be determined.
|
7289 |
+
<?php endif; ?>
|
7290 |
+
</span>
|
7291 |
+
</div>
|
7292 |
+
<?php endif; ?>
|
7293 |
|
7294 |
+
<?php if ( $updatemsg != null ): ?>
|
7295 |
+
<p><?php _e( $updatemsg ) ?></p>
|
7296 |
<?php endif; ?>
|
7297 |
</div>
|
7298 |
</div>
|
7308 |
function sucuriscan_harden_version(){
|
7309 |
$site_version = SucuriScan::site_version();
|
7310 |
$updates = get_core_updates();
|
7311 |
+
$cp = ( ! is_array( $updates ) || empty($updates) ? 1 : 0 );
|
7312 |
|
7313 |
+
if ( isset($updates[0]) && $updates[0] instanceof stdClass ){
|
7314 |
+
if (
|
7315 |
$updates[0]->response == 'latest'
|
7316 |
|| $updates[0]->response == 'development'
|
7317 |
){
|
7319 |
}
|
7320 |
}
|
7321 |
|
7322 |
+
if ( strcmp( $site_version, '3.7' ) < 0 ){
|
7323 |
$cp = 0;
|
7324 |
}
|
7325 |
|
7328 |
to the source code are made public, if there were security fixes then
|
7329 |
someone with malicious intent can use this information to attack any site
|
7330 |
that has not been upgraded.';
|
7331 |
+
$messageok = sprintf( 'Your WordPress installation (%s) is current.', $site_version );
|
7332 |
$messagewarn = sprintf(
|
7333 |
'Your current version (%s) is not current.<br>
|
7334 |
<a href="update-core.php" class="button-primary">Update now!</a>',
|
7335 |
$site_version
|
7336 |
);
|
7337 |
|
7338 |
+
sucuriscan_harden_status( 'Verify WordPress version', $cp, null, $messageok, $messagewarn, $initial_msg );
|
7339 |
}
|
7340 |
|
7341 |
/**
|
7349 |
sucuriscan_harden_status(
|
7350 |
'Remove WordPress version',
|
7351 |
1,
|
7352 |
+
null,
|
7353 |
'WordPress version properly hidden',
|
7354 |
+
null,
|
7355 |
'It checks if your WordPress version is being hidden from being displayed '
|
7356 |
.'in the generator tag (enabled by default with this plugin).'
|
7357 |
);
|
7358 |
}
|
7359 |
|
7360 |
+
function sucuriscan_harden_nginx_phpfpm(){
|
7361 |
+
$description = 'It seems that you are using the Nginx web server, if that is
|
7362 |
+
the case then you will need to add the following code into the global
|
7363 |
+
<code>nginx.conf</code> file or the virtualhost associated with this
|
7364 |
+
website. Choose the correct rules for the directories that you want to
|
7365 |
+
protect. If you encounter errors after restart the web server then revert
|
7366 |
+
the changes and contact the support team of your hosting company, or read
|
7367 |
+
the official article about <a href="http://codex.wordpress.org/Nginx">
|
7368 |
+
WordPress on Nginx</a>.</p>';
|
7369 |
+
|
7370 |
+
$description .= "<pre class='code'># Block PHP files in uploads directory.\nlocation ~* /(?:uploads|files)/.*\.php$ {\n\x20\x20deny all;\n}</pre>";
|
7371 |
+
$description .= "<pre class='code'># Block PHP files in content directory.\nlocation ~* /wp-content/.*\.php$ {\n\x20\x20deny all;\n}</pre>";
|
7372 |
+
$description .= "<pre class='code'># Block PHP files in includes directory.\nlocation ~* /wp-includes/.*\.php$ {\n\x20\x20deny all;\n}</pre>";
|
7373 |
+
|
7374 |
+
$description .= "<pre class='code'>";
|
7375 |
+
$description .= "# Block PHP files in uploads, content, and includes directory.\n";
|
7376 |
+
$description .= "location ~* /(?:uploads|files|wp-content|wp-includes)/.*\.php$ {\n";
|
7377 |
+
$description .= "\x20\x20deny all;\n";
|
7378 |
+
$description .= '}</pre>';
|
7379 |
+
|
7380 |
+
$description .= '<p class="sucuriscan-hidden">';
|
7381 |
+
|
7382 |
+
sucuriscan_harden_status(
|
7383 |
+
'Block PHP files',
|
7384 |
+
999,
|
7385 |
+
null,
|
7386 |
+
null,
|
7387 |
+
null,
|
7388 |
+
$description
|
7389 |
+
);
|
7390 |
+
}
|
7391 |
+
|
7392 |
/**
|
7393 |
* Check whether the WordPress upload folder is protected or not.
|
7394 |
*
|
7400 |
*/
|
7401 |
function sucuriscan_harden_upload(){
|
7402 |
$cp = 1;
|
|
|
7403 |
$datastore_path = SucuriScan::datastore_folder_path();
|
7404 |
+
$htaccess_upload = dirname( $datastore_path ) . '/.htaccess';
|
7405 |
|
7406 |
+
if ( ! is_readable( $htaccess_upload ) ){
|
7407 |
$cp = 0;
|
7408 |
} else {
|
7409 |
$cp = 0;
|
7410 |
+
$fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
|
7411 |
|
7412 |
+
foreach ( $fcontent as $fline ){
|
7413 |
+
if ( stripos( $fline, 'deny from all' ) !== false ){
|
7414 |
$cp = 1;
|
7415 |
break;
|
7416 |
}
|
7417 |
}
|
7418 |
}
|
7419 |
|
7420 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7421 |
+
if ( SucuriScanRequest::post( ':harden_upload' ) && $cp == 0 ){
|
7422 |
+
if ( @file_put_contents( $htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>" ) === false ){
|
7423 |
+
SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
|
7424 |
} else {
|
|
|
7425 |
$cp = 1;
|
7426 |
+
$message = 'Hardening applied to the uploads directory';
|
7427 |
+
SucuriScanEvent::report_notice_event( $message );
|
7428 |
+
SucuriScanInterface::info( $message );
|
7429 |
}
|
7430 |
}
|
7431 |
|
7432 |
+
elseif ( SucuriScanRequest::post( ':harden_upload_unharden' ) ){
|
7433 |
+
$htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
|
7434 |
+
$htaccess_content = $htaccess_upload_writable ? file_get_contents( $htaccess_upload ) : '';
|
7435 |
|
7436 |
+
if ( $htaccess_upload_writable ){
|
7437 |
$cp = 0;
|
7438 |
|
7439 |
+
if ( preg_match( '/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match ) ){
|
7440 |
+
$htaccess_content = str_replace( "<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content );
|
7441 |
+
@file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
|
7442 |
}
|
7443 |
|
7444 |
+
$message = 'Hardening reverted in the uploads directory';
|
7445 |
+
SucuriScanEvent::report_error_event( $message );
|
7446 |
+
SucuriScanInterface::info( $message );
|
7447 |
} else {
|
7448 |
SucuriScanInterface::error(
|
7449 |
'File <code>/wp-content/uploads/.htaccess</code> does not exists or
|
7461 |
'Upload directory properly hardened',
|
7462 |
'Upload directory not hardened',
|
7463 |
'It checks if your upload directory allows PHP execution or if it is browsable.',
|
7464 |
+
null
|
7465 |
);
|
7466 |
}
|
7467 |
|
7476 |
*/
|
7477 |
function sucuriscan_harden_wpcontent(){
|
7478 |
$cp = 1;
|
|
|
7479 |
$htaccess_upload = WP_CONTENT_DIR . '/.htaccess';
|
7480 |
|
7481 |
+
if ( ! is_readable( $htaccess_upload ) ){
|
7482 |
$cp = 0;
|
7483 |
} else {
|
7484 |
$cp = 0;
|
7485 |
+
$fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
|
7486 |
|
7487 |
+
foreach ( $fcontent as $fline ){
|
7488 |
+
if ( stripos( $fline, 'deny from all' ) !== false ){
|
7489 |
$cp = 1;
|
7490 |
break;
|
7491 |
}
|
7492 |
}
|
7493 |
}
|
7494 |
|
7495 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7496 |
+
if ( SucuriScanRequest::post( ':harden_wpcontent' ) && $cp == 0 ){
|
7497 |
+
if ( @file_put_contents( $htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>" ) === false ){
|
7498 |
+
SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
|
7499 |
} else {
|
|
|
7500 |
$cp = 1;
|
7501 |
+
$message = 'Hardening applied to the content directory';
|
7502 |
+
SucuriScanEvent::report_notice_event( $message );
|
7503 |
+
SucuriScanInterface::info( $message );
|
7504 |
}
|
7505 |
}
|
7506 |
|
7507 |
+
elseif ( SucuriScanRequest::post( ':harden_wpcontent_unharden' ) ){
|
7508 |
+
$htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
|
7509 |
+
$htaccess_content = $htaccess_upload_writable ? file_get_contents( $htaccess_upload ) : '';
|
7510 |
|
7511 |
+
if ( $htaccess_upload_writable ){
|
7512 |
$cp = 0;
|
7513 |
|
7514 |
+
if ( preg_match( '/<Files \*\.php>\ndeny from all\n<\/Files>/', $htaccess_content, $match ) ){
|
7515 |
+
$htaccess_content = str_replace( "<Files *.php>\ndeny from all\n</Files>", '', $htaccess_content );
|
7516 |
+
@file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
|
7517 |
}
|
7518 |
|
7519 |
+
$message = 'Hardening reverted in the content directory';
|
7520 |
+
SucuriScanEvent::report_error_event( $message );
|
7521 |
+
SucuriScanInterface::info( $message );
|
7522 |
} else {
|
7523 |
SucuriScanInterface::info(
|
7524 |
'File <code>' . WP_CONTENT_DIR . '/.htaccess</code> does not exists or is not
|
7543 |
'WP-content directory properly hardened',
|
7544 |
'WP-content directory not hardened',
|
7545 |
$description,
|
7546 |
+
null
|
7547 |
);
|
7548 |
}
|
7549 |
|
7559 |
*/
|
7560 |
function sucuriscan_harden_wpincludes(){
|
7561 |
$cp = 1;
|
|
|
7562 |
$htaccess_upload = ABSPATH . '/wp-includes/.htaccess';
|
7563 |
|
7564 |
+
if ( ! is_readable( $htaccess_upload ) ){
|
7565 |
$cp = 0;
|
7566 |
} else {
|
7567 |
$cp = 0;
|
7568 |
+
$fcontent = SucuriScanFileInfo::file_lines( $htaccess_upload );
|
7569 |
|
7570 |
+
foreach ( $fcontent as $fline ){
|
7571 |
+
if ( stripos( $fline, 'deny from all' ) !== false ){
|
7572 |
$cp = 1;
|
7573 |
break;
|
7574 |
}
|
7575 |
}
|
7576 |
}
|
7577 |
|
7578 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7579 |
+
if ( SucuriScanRequest::post( ':harden_wpincludes' ) && $cp == 0 ){
|
7580 |
+
$file_rules = "\n<Files *.php>"
|
7581 |
+
. "\ndeny from all"
|
7582 |
+
. "\n</Files>"
|
7583 |
+
. "\n<Files wp-tinymce.php>"
|
7584 |
+
. "\nallow from all"
|
7585 |
+
. "\n</Files>"
|
7586 |
+
. "\n<Files ms-files.php>"
|
7587 |
+
. "\nallow from all"
|
7588 |
+
. "\n</Files>"
|
7589 |
+
. "\n";
|
7590 |
+
|
7591 |
+
if ( @file_put_contents( $htaccess_upload, $file_rules ) === false ){
|
7592 |
+
SucuriScanInterface::error( 'Unable to create <code>.htaccess</code> file, folder destination is not writable.' );
|
7593 |
} else {
|
|
|
7594 |
$cp = 1;
|
7595 |
+
$message = 'Hardening applied to the library directory';
|
7596 |
+
SucuriScanEvent::report_notice_event( $message );
|
7597 |
+
SucuriScanInterface::info( $message );
|
7598 |
}
|
7599 |
}
|
7600 |
|
7601 |
+
elseif ( SucuriScanRequest::post( ':harden_wpincludes_unharden' ) ){
|
7602 |
+
$htaccess_upload_writable = ( file_exists( $htaccess_upload ) && is_writable( $htaccess_upload ) ) ? true : false;
|
7603 |
+
$htaccess_content = $htaccess_upload_writable ? file_get_contents( $htaccess_upload ) : '';
|
7604 |
|
7605 |
+
if ( $htaccess_upload_writable ){
|
7606 |
$cp = 0;
|
7607 |
+
|
7608 |
+
if ( preg_match_all( '/<Files (\*|wp-tinymce|ms-files)\.php>\n(deny|allow) from all\n<\/Files>/', $htaccess_content, $match ) ){
|
7609 |
+
foreach ( $match[0] as $restriction ){
|
7610 |
+
$htaccess_content = str_replace( $restriction, '', $htaccess_content );
|
7611 |
}
|
7612 |
|
7613 |
+
@file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );
|
7614 |
}
|
7615 |
+
|
7616 |
+
$message = 'Hardening reverted in the library directory';
|
7617 |
+
SucuriScanEvent::report_error_event( $message );
|
7618 |
+
SucuriScanInterface::info( $message );
|
7619 |
} else {
|
7620 |
SucuriScanInterface::error(
|
7621 |
'File <code>wp-includes/.htaccess</code> does not exists or is not
|
7633 |
'WP-Includes directory properly hardened',
|
7634 |
'WP-Includes directory not hardened',
|
7635 |
'This option blocks direct PHP access to any file inside <code>wp-includes</code>.',
|
7636 |
+
null
|
7637 |
);
|
7638 |
}
|
7639 |
|
7645 |
*/
|
7646 |
function sucuriscan_harden_phpversion(){
|
7647 |
$phpv = phpversion();
|
7648 |
+
$cp = ( strncmp( $phpv, '5.', 2 ) < 0 ) ? 0 : 1;
|
7649 |
|
7650 |
sucuriscan_harden_status(
|
7651 |
'Verify PHP version',
|
7652 |
$cp,
|
7653 |
+
null,
|
7654 |
'Using an updated version of PHP (' . $phpv . ')',
|
7655 |
'The version of PHP you are using (' . $phpv . ') is not current, not recommended, and/or not supported',
|
7656 |
'This checks if you have the latest version of PHP installed.',
|
7657 |
+
null
|
7658 |
);
|
7659 |
}
|
7660 |
|
7672 |
. 'DDoS, SQL injections, etc) and helping it remain malware and blacklist free. This test checks if your site is '
|
7673 |
. 'using <a href="http://cloudproxy.sucuri.net/" target="_blank">Sucuri\'s CloudProxy WAF</a> to protect your site.';
|
7674 |
|
7675 |
+
if ( $proxy_info === false ){
|
7676 |
$status = 0;
|
7677 |
+
$btn_string = '<a href="http://goo.gl/qfNkMq" target="_blank" class="button button-primary">Harden</a>';
|
7678 |
}
|
7679 |
|
7680 |
sucuriscan_harden_status(
|
7681 |
'Website Firewall protection',
|
7682 |
$status,
|
7683 |
+
null,
|
7684 |
'Your website is protected by a Website Firewall (WAF)',
|
7685 |
$btn_string . 'Your website is not protected by a Website Firewall (WAF)',
|
7686 |
$description,
|
7687 |
+
null
|
7688 |
);
|
7689 |
}
|
7690 |
|
7702 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
7703 |
$current_keys = SucuriScanOption::get_security_keys();
|
7704 |
|
7705 |
+
if ( $wp_config_path ){
|
7706 |
$cp = 1;
|
7707 |
$message = 'The main configuration file was found at: <code>'.$wp_config_path.'</code><br>';
|
7708 |
|
7709 |
+
if (
|
7710 |
+
! empty($current_keys['bad'])
|
7711 |
+
|| ! empty($current_keys['missing'])
|
7712 |
){
|
7713 |
$cp = 0;
|
7714 |
}
|
7715 |
+
}else {
|
7716 |
$cp = 0;
|
7717 |
$message = 'The <code>wp-config.php</code> file was not found.<br>';
|
7718 |
}
|
7723 |
random elements to the password. In simple terms, a secret key is a password with
|
7724 |
elements that make it harder to generate enough options to break through your
|
7725 |
security barriers.';
|
7726 |
+
$messageok = 'Security keys and salts not set, we recommend to create them for security reasons'
|
7727 |
+
. '<a href="' . SucuriScanTemplate::get_url( 'posthack' ) . '" class="button button-primary">'
|
7728 |
. 'Harden</a>';
|
7729 |
|
7730 |
sucuriscan_harden_status(
|
7731 |
'Security keys',
|
7732 |
$cp,
|
7733 |
+
null,
|
7734 |
'Security keys and salts properly created',
|
7735 |
$messageok,
|
7736 |
$message,
|
7737 |
+
null
|
7738 |
);
|
7739 |
}
|
7740 |
|
7746 |
* @return void
|
7747 |
*/
|
7748 |
function sucuriscan_harden_readme(){
|
7749 |
+
$upmsg = null;
|
7750 |
+
$cp = is_readable( ABSPATH.'/readme.html' ) ? 0 : 1;
|
7751 |
|
7752 |
// TODO: After hardening create an option to automatically remove this after WP upgrade.
|
7753 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7754 |
+
if ( SucuriScanRequest::post( ':harden_readme' ) && $cp == 0 ){
|
7755 |
+
if ( @unlink( ABSPATH.'/readme.html' ) === false ){
|
7756 |
+
$upmsg = SucuriScanInterface::error( 'Unable to remove <code>readme.html</code> file.' );
|
7757 |
} else {
|
7758 |
$cp = 1;
|
7759 |
+
$message = 'Hardening applied to the <code>readme.html</code> file';
|
7760 |
+
SucuriScanEvent::report_notice_event( $message );
|
7761 |
+
SucuriScanInterface::info( $message );
|
7762 |
}
|
7763 |
}
|
7764 |
|
7765 |
+
elseif ( SucuriScanRequest::post( ':harden_readme_unharden' ) ){
|
7766 |
+
SucuriScanInterface::error( 'We can not revert this action, you must create the <code>readme.html</code> manually.' );
|
7767 |
}
|
7768 |
}
|
7769 |
|
7770 |
sucuriscan_harden_status(
|
7771 |
'Information leakage (readme.html)',
|
7772 |
$cp,
|
7773 |
+
( $cp == 0 ? 'sucuriscan_harden_readme' : null ),
|
7774 |
'<code>readme.html</code> file properly deleted',
|
7775 |
'<code>readme.html</code> not deleted and leaking the WordPress version',
|
7776 |
'It checks whether you have the <code>readme.html</code> file available that leaks your WordPress version',
|
7787 |
function sucuriscan_harden_adminuser(){
|
7788 |
global $wpdb;
|
7789 |
|
7790 |
+
$upmsg = null;
|
7791 |
$user_query = new WP_User_Query(array(
|
7792 |
'search' => 'admin',
|
7793 |
'fields' => array( 'ID', 'user_login' ),
|
7794 |
'search_columns' => array( 'user_login' ),
|
7795 |
));
|
7796 |
$results = $user_query->get_results();
|
7797 |
+
$account_removed = ( count( $results ) === 0 ? 1 : 0 );
|
7798 |
|
7799 |
+
if ( $account_removed === 0 ){
|
7800 |
$upmsg = '<i><strong>Notice.</strong> We do not offer an option to automatically change the user name.
|
7801 |
+
Go to the <a href="'.admin_url( 'users.php' ).'" target="_blank">user list</a> and create a new
|
7802 |
administrator user. Once created, log in as that user and remove the default <code>admin</code>
|
7803 |
(make sure to assign all the admin posts to the new user too).</i>';
|
7804 |
}
|
7806 |
sucuriscan_harden_status(
|
7807 |
'Default admin account',
|
7808 |
$account_removed,
|
7809 |
+
null,
|
7810 |
'Default admin user account (admin) not being used',
|
7811 |
'Default admin user account (admin) being used. Not recommended',
|
7812 |
'It checks whether you have the default <code>admin</code> account enabled, security guidelines recommend creating a new admin user name.',
|
7820 |
* @return void
|
7821 |
*/
|
7822 |
function sucuriscan_harden_fileeditor(){
|
7823 |
+
$file_editor_disabled = defined( 'DISALLOW_FILE_EDIT' ) ? DISALLOW_FILE_EDIT : false;
|
7824 |
|
7825 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7826 |
+
$current_time = date( 'r' );
|
7827 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
7828 |
|
7829 |
+
$wp_config_writable = ( file_exists( $wp_config_path ) && is_writable( $wp_config_path ) ) ? true : false;
|
7830 |
+
$new_wpconfig = $wp_config_writable ? file_get_contents( $wp_config_path ) : '';
|
7831 |
|
7832 |
+
if ( SucuriScanRequest::post( ':harden_fileeditor' ) ){
|
7833 |
+
if ( $wp_config_writable ){
|
7834 |
+
if ( preg_match( '/(.*define\(.DB_COLLATE..*)/', $new_wpconfig, $match ) ){
|
7835 |
$disallow_fileedit_definition = "\n\ndefine('DISALLOW_FILE_EDIT', TRUE); // Sucuri Security: {$current_time}\n";
|
7836 |
+
$new_wpconfig = str_replace( $match[0], $match[0].$disallow_fileedit_definition, $new_wpconfig );
|
7837 |
}
|
7838 |
|
7839 |
+
$file_editor_disabled = true;
|
7840 |
+
@file_put_contents( $wp_config_path, $new_wpconfig, LOCK_EX );
|
7841 |
+
$message = 'Hardening applied to the plugin and theme editor';
|
7842 |
+
SucuriScanEvent::report_notice_event( $message );
|
7843 |
+
SucuriScanInterface::info( $message );
|
7844 |
} else {
|
7845 |
SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
|
7846 |
or is not writable, you will need to put the following code manually there:
|
7848 |
}
|
7849 |
}
|
7850 |
|
7851 |
+
elseif ( SucuriScanRequest::post( ':harden_fileeditor_unharden' ) ){
|
7852 |
+
if ( preg_match( "/(.*define\('DISALLOW_FILE_EDIT', TRUE\);.*)/", $new_wpconfig, $match ) ){
|
7853 |
+
if ( $wp_config_writable ){
|
7854 |
+
$new_wpconfig = str_replace( "\n{$match[1]}", '', $new_wpconfig );
|
7855 |
+
file_put_contents( $wp_config_path, $new_wpconfig, LOCK_EX );
|
7856 |
+
$file_editor_disabled = false;
|
7857 |
+
$message = 'Hardening reverted in the plugin and theme editor';
|
7858 |
+
SucuriScanEvent::report_error_event( $message );
|
7859 |
+
SucuriScanInterface::info( $message );
|
7860 |
} else {
|
7861 |
SucuriScanInterface::error( 'The <code>wp-config.php</code> file is not in the default location
|
7862 |
or is not writable, you will need to remove the following code manually from there:
|
7875 |
|
7876 |
sucuriscan_harden_status(
|
7877 |
'Plugin & Theme editor',
|
7878 |
+
( $file_editor_disabled === false ? 0 : 1 ),
|
7879 |
'sucuriscan_harden_fileeditor',
|
7880 |
'File editor for Plugins and Themes is disabled',
|
7881 |
'File editor for Plugins and Themes is enabled',
|
7882 |
$message,
|
7883 |
+
null
|
7884 |
);
|
7885 |
}
|
7886 |
|
7899 |
sucuriscan_harden_status(
|
7900 |
'Database table prefix',
|
7901 |
$hardened,
|
7902 |
+
null,
|
7903 |
'Database table prefix properly modified',
|
7904 |
'Database table set to the default value <code>wp_</code>.',
|
7905 |
'It checks whether your database table prefix has been changed from the default <code>wp_</code>',
|
7914 |
*/
|
7915 |
function sucuriscan_harden_errorlog(){
|
7916 |
$hardened = 1;
|
7917 |
+
$log_filename = SucuriScan::ini_get( 'error_log' );
|
7918 |
+
$scan_errorlogs = SucuriScanOption::get_option( ':scan_errorlogs' );
|
7919 |
|
7920 |
$description = 'PHP uses files named as <code>' . $log_filename . '</code> to log errors found in '
|
7921 |
. 'the code, these files may leak sensitive information of your project allowing an attacker '
|
7923 |
. 'a development environment, and remove them in production mode.';
|
7924 |
|
7925 |
// Search error log files in the project.
|
7926 |
+
if ( $scan_errorlogs != 'disabled' ){
|
7927 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
7928 |
+
$sucuri_fileinfo->ignore_files = false;
|
7929 |
+
$sucuri_fileinfo->ignore_directories = false;
|
7930 |
+
$error_logs = $sucuri_fileinfo->find_file( 'error_log' );
|
7931 |
+
$total_log_files = count( $error_logs );
|
7932 |
} else {
|
7933 |
$error_logs = array();
|
7934 |
$total_log_files = 0;
|
7939 |
}
|
7940 |
|
7941 |
// Remove every error log file found in the filesystem scan.
|
7942 |
+
if ( SucuriScanRequest::post( ':run_hardening' ) ){
|
7943 |
+
if ( SucuriScanRequest::post( ':harden_errorlog' ) ){
|
7944 |
$removed_logs = 0;
|
7945 |
+
SucuriScanEvent::report_notice_event( sprintf(
|
7946 |
+
'Error log files deleted: (multiple entries): %s',
|
7947 |
+
@implode( ',', $error_logs )
|
7948 |
+
) );
|
7949 |
|
7950 |
+
foreach ( $error_logs as $i => $error_log_path ){
|
7951 |
+
if ( unlink( $error_log_path ) ){
|
7952 |
+
unset($error_logs[ $i ]);
|
7953 |
$removed_logs += 1;
|
7954 |
}
|
7955 |
}
|
7956 |
|
7957 |
+
SucuriScanInterface::info( 'Error log files deleted <code>' . $removed_logs . ' out of ' . $total_log_files . '</code>' );
|
7958 |
}
|
7959 |
}
|
7960 |
|
7961 |
// List the error log files in a HTML table.
|
7962 |
+
if ( ! empty($error_logs) ){
|
7963 |
$hardened = 0;
|
7964 |
$description .= '</p><ul class="sucuriscan-list-as-table">';
|
7965 |
|
7966 |
+
foreach ( $error_logs as $error_log_path ){
|
7967 |
$description .= '<li>' . $error_log_path . '</li>';
|
7968 |
}
|
7969 |
|
7973 |
sucuriscan_harden_status(
|
7974 |
'Error logs',
|
7975 |
$hardened,
|
7976 |
+
( $hardened == 0 ? 'sucuriscan_harden_errorlog' : null ),
|
7977 |
'There are no error log files in your project.',
|
7978 |
'There are ' . $total_log_files . ' error log files in your project.',
|
7979 |
$description,
|
7980 |
+
null
|
7981 |
);
|
7982 |
}
|
7983 |
|
7999 |
$template_variables = array(
|
8000 |
'WordpressVersion' => sucuriscan_wordpress_outdated(),
|
8001 |
'AuditLogs' => sucuriscan_auditlogs(),
|
8002 |
+
'AuditReports' => sucuriscan_auditreport(),
|
8003 |
'CoreFiles' => sucuriscan_core_files(),
|
8004 |
);
|
8005 |
|
8006 |
+
echo SucuriScanTemplate::get_template( 'integrity', $template_variables );
|
8007 |
}
|
8008 |
|
8009 |
/**
|
8014 |
* @return void
|
8015 |
*/
|
8016 |
function sucuriscan_integrity_form_submissions(){
|
8017 |
+
if ( SucuriScanInterface::check_nonce() ){
|
8018 |
|
8019 |
// Force the execution of the filesystem scanner.
|
8020 |
+
if ( SucuriScanRequest::post( ':force_scan' ) !== false ){
|
8021 |
+
SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scan forced at: ' . date( 'r' ) );
|
8022 |
+
SucuriScanEvent::filesystem_scan( true );
|
8023 |
}
|
8024 |
|
8025 |
// Restore, Remove, Mark as fixed the core files.
|
8026 |
+
$allowed_actions = '(restore|delete|fixed)';
|
8027 |
+
$integrity_action = SucuriScanRequest::post( ':integrity_action', $allowed_actions );
|
8028 |
+
|
8029 |
+
if ( $integrity_action !== false ){
|
8030 |
+
$cache = new SucuriScanCache( 'integrity' );
|
8031 |
+
$integrity_files = SucuriScanRequest::post( ':integrity_files', '_array' );
|
8032 |
+
$integrity_types = SucuriScanRequest::post( ':integrity_types', '_array' );
|
8033 |
+
$files_selected = count( $integrity_files );
|
8034 |
+
$files_affected = array();
|
8035 |
$files_processed = 0;
|
8036 |
+
$action_titles = array(
|
8037 |
+
'restore' => 'Core file restored',
|
8038 |
+
'delete' => 'Non-core file deleted',
|
8039 |
+
'fixed' => 'Core file marked as fixed',
|
8040 |
+
);
|
8041 |
|
8042 |
+
foreach ( $integrity_files as $i => $file_path ){
|
8043 |
$full_path = ABSPATH . $file_path;
|
8044 |
+
$status_type = $integrity_types[ $i ];
|
8045 |
|
8046 |
+
switch ( $integrity_action ){
|
8047 |
case 'restore':
|
8048 |
+
$file_content = SucuriScanAPI::get_original_core_file( $file_path );
|
8049 |
+
if ( $file_content ){
|
8050 |
$restored = @file_put_contents( $full_path, $file_content, LOCK_EX );
|
8051 |
$files_processed += ( $restored ? 1 : 0 );
|
8052 |
+
$files_affected[] = $full_path;
|
8053 |
}
|
8054 |
break;
|
8055 |
+
case 'delete':
|
8056 |
+
if ( @unlink( $full_path ) ){
|
8057 |
$files_processed += 1;
|
8058 |
+
$files_affected[] = $full_path;
|
8059 |
}
|
8060 |
break;
|
8061 |
case 'fixed':
|
8062 |
+
$cache_key = md5( $file_path );
|
8063 |
$cache_value = array(
|
8064 |
'file_path' => $file_path,
|
8065 |
'file_status' => $status_type,
|
8067 |
);
|
8068 |
$cached = $cache->add( $cache_key, $cache_value );
|
8069 |
$files_processed += ( $cached ? 1 : 0 );
|
8070 |
+
$files_affected[] = $full_path;
|
8071 |
break;
|
8072 |
}
|
8073 |
}
|
8074 |
|
8075 |
+
// Report files affected as a single event.
|
8076 |
+
if ( ! empty($files_affected) ) {
|
8077 |
+
$message_tpl = ( count( $files_affected ) > 1 )
|
8078 |
+
? '%s: (multiple entries): %s'
|
8079 |
+
: '%s: %s';
|
8080 |
+
$message = sprintf(
|
8081 |
+
$message_tpl,
|
8082 |
+
$action_titles[ $integrity_action ],
|
8083 |
+
@implode( ',', $files_affected )
|
8084 |
+
);
|
8085 |
+
|
8086 |
+
switch ( $integrity_action ){
|
8087 |
+
case 'restore': SucuriScanEvent::report_info_event( $message ); break;
|
8088 |
+
case 'delete': SucuriScanEvent::report_notice_event( $message ); break;
|
8089 |
+
case 'fixed': SucuriScanEvent::report_warning_event( $message ); break;
|
8090 |
+
}
|
8091 |
+
}
|
8092 |
+
|
8093 |
SucuriScanInterface::info(sprintf(
|
8094 |
'<code>%d</code> out of <code>%d</code> files were successfully processed.',
|
8095 |
$files_selected,
|
8096 |
$files_processed
|
8097 |
));
|
8098 |
}
|
|
|
8099 |
}
|
8100 |
}
|
8101 |
|
8107 |
* @param boolean $recursive Either TRUE or FALSE if the scan should be performed recursively.
|
8108 |
* @return array List of arrays containing the md5sum and last modification time of the files found.
|
8109 |
*/
|
8110 |
+
function sucuriscan_get_integrity_tree( $dir = './', $recursive = false ){
|
8111 |
$abs_path = rtrim( ABSPATH, '/' );
|
8112 |
|
8113 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
8114 |
+
$sucuri_fileinfo->ignore_files = false;
|
8115 |
+
$sucuri_fileinfo->ignore_directories = false;
|
8116 |
$sucuri_fileinfo->run_recursively = $recursive;
|
8117 |
$sucuri_fileinfo->scan_interface = 'opendir';
|
8118 |
+
$integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, true );
|
8119 |
|
8120 |
+
if ( ! $integrity_tree ){
|
8121 |
$integrity_tree = array();
|
8122 |
}
|
8123 |
|
8131 |
* @return void
|
8132 |
*/
|
8133 |
function sucuriscan_auditlogs(){
|
|
|
8134 |
// Initialize the values for the pagination.
|
8135 |
$max_per_page = SUCURISCAN_AUDITLOGS_PER_PAGE;
|
8136 |
$page_number = SucuriScanTemplate::get_page_number();
|
8137 |
$logs_limit = $page_number * $max_per_page;
|
8138 |
+
$audit_logs = SucuriScanAPI::get_logs( $logs_limit );
|
8139 |
|
8140 |
$template_variables = array(
|
8141 |
'PageTitle' => 'Audit Logs',
|
8147 |
'AuditLogs.PaginationLinks' => '',
|
8148 |
);
|
8149 |
|
8150 |
+
if ( $audit_logs ){
|
8151 |
$counter_i = 0;
|
8152 |
+
$total_items = count( $audit_logs->output_data );
|
8153 |
+
$iterator_start = ($page_number - 1) * $max_per_page;
|
8154 |
+
$iterator_end = $total_items;
|
8155 |
|
8156 |
+
for ( $i = $iterator_start; $i < $total_items; $i++ ){
|
8157 |
+
if ( $counter_i > $max_per_page ){ break; }
|
|
|
|
|
|
|
|
|
8158 |
|
8159 |
+
if ( isset($audit_logs->output_data[ $i ]) ){
|
8160 |
+
$audit_log = $audit_logs->output_data[ $i ];
|
8161 |
|
8162 |
$css_class = ( $counter_i % 2 == 0 ) ? '' : 'alternate';
|
8163 |
$snippet_data = array(
|
8164 |
'AuditLog.CssClass' => $css_class,
|
8165 |
+
'AuditLog.Event' => SucuriScan::escape( $audit_log['event'] ),
|
8166 |
+
'AuditLog.EventTitle' => SucuriScan::escape( ucfirst( $audit_log['event'] ) ),
|
8167 |
+
'AuditLog.DateTime' => SucuriScan::datetime( $audit_log['timestamp'] ),
|
8168 |
+
'AuditLog.Account' => SucuriScan::escape( $audit_log['account'] ),
|
8169 |
+
'AuditLog.Username' => SucuriScan::escape( $audit_log['username'] ),
|
8170 |
+
'AuditLog.RemoteAddress' => SucuriScan::escape( $audit_log['remote_addr'] ),
|
8171 |
+
'AuditLog.Message' => SucuriScan::escape( $audit_log['message'] ),
|
8172 |
'AuditLog.Extra' => '',
|
8173 |
);
|
8174 |
|
8175 |
+
// Print every file_list information item in a separate table.
|
8176 |
+
if ( $audit_log['file_list'] ){
|
8177 |
+
$css_scrollable = $audit_log['file_list_count'] > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
|
8178 |
$snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
|
8179 |
+
foreach ( $audit_log['file_list'] as $log_extra ){
|
8180 |
+
$snippet_data['AuditLog.Extra'] .= '<li>' . SucuriScan::escape( $log_extra ) . '</li>';
|
8181 |
}
|
8182 |
$snippet_data['AuditLog.Extra'] .= '</ul>';
|
8183 |
}
|
8184 |
|
8185 |
+
$template_variables['AuditLogs.List'] .= SucuriScanTemplate::get_snippet( 'integrity-auditlogs', $snippet_data );
|
8186 |
$counter_i += 1;
|
8187 |
}
|
8188 |
}
|
8190 |
$template_variables['AuditLogs.Count'] = $counter_i;
|
8191 |
$template_variables['AuditLogs.NoItemsVisibility'] = 'hidden';
|
8192 |
|
8193 |
+
if ( $total_items > 1 ){
|
8194 |
$max_pages = ceil( $audit_logs->total_entries / $max_per_page );
|
8195 |
|
8196 |
+
if ( $max_pages > SUCURISCAN_MAX_PAGINATION_BUTTONS ){
|
8197 |
$max_pages = SUCURISCAN_MAX_PAGINATION_BUTTONS;
|
8198 |
}
|
8199 |
|
8208 |
}
|
8209 |
}
|
8210 |
|
8211 |
+
return SucuriScanTemplate::get_section( 'integrity-auditlogs', $template_variables );
|
8212 |
+
}
|
8213 |
+
/**
|
8214 |
+
* Print a HTML code with the content of the logs audited by the remote Sucuri
|
8215 |
+
* API service, this page is part of the monitoring tool.
|
8216 |
+
*
|
8217 |
+
* @return void
|
8218 |
+
*/
|
8219 |
+
function sucuriscan_auditreport(){
|
8220 |
+
$audit_report = false;
|
8221 |
+
$logs4report = SucuriScanOption::get_option( ':logs4report' );
|
8222 |
+
|
8223 |
+
if ( SucuriScanOption::get_option( ':audit_report' ) !== 'disabled' ) {
|
8224 |
+
$audit_report = SucuriScanAPI::get_audit_report( $logs4report );
|
8225 |
+
}
|
8226 |
+
|
8227 |
+
$template_variables = array(
|
8228 |
+
'PageTitle' => 'Audit Reports',
|
8229 |
+
'AuditReport.EventColors' => '',
|
8230 |
+
'AuditReport.EventsPerType' => '',
|
8231 |
+
'AuditReport.EventsPerLogin' => '',
|
8232 |
+
'AuditReport.EventsPerUserCategories' => '',
|
8233 |
+
'AuditReport.EventsPerUserSeries' => '',
|
8234 |
+
'AuditReport.EventsPerIPAddressCategories' => '',
|
8235 |
+
'AuditReport.EventsPerIPAddressSeries' => '',
|
8236 |
+
'AuditReport.Logs4Report' => $logs4report,
|
8237 |
+
);
|
8238 |
+
|
8239 |
+
if ( $audit_report ) {
|
8240 |
+
$template_variables['AuditReport.EventColors'] = @implode( ',', $audit_report['event_colors'] );
|
8241 |
+
|
8242 |
+
// Generate report chart data for the events per type.
|
8243 |
+
foreach ( $audit_report['events_per_type'] as $event => $times ) {
|
8244 |
+
$template_variables['AuditReport.EventsPerType'] .= sprintf(
|
8245 |
+
"[ '%s', %d ],\n",
|
8246 |
+
ucwords( $event . "\x20events" ),
|
8247 |
+
$times
|
8248 |
+
);
|
8249 |
+
}
|
8250 |
+
|
8251 |
+
// Generate report chart data for the events per login.
|
8252 |
+
foreach ( $audit_report['events_per_login'] as $event => $times ) {
|
8253 |
+
$template_variables['AuditReport.EventsPerLogin'] .= sprintf(
|
8254 |
+
"[ '%s', %d ],\n",
|
8255 |
+
ucwords( $event . "\x20logins" ),
|
8256 |
+
$times
|
8257 |
+
);
|
8258 |
+
}
|
8259 |
+
|
8260 |
+
// Generate report chart data for the events per user.
|
8261 |
+
foreach ( $audit_report['events_per_user'] as $event => $times ) {
|
8262 |
+
$template_variables['AuditReport.EventsPerUserCategories'] .= sprintf( '"%s",', $event );
|
8263 |
+
$template_variables['AuditReport.EventsPerUserSeries'] .= sprintf( '%d,', $times );
|
8264 |
+
}
|
8265 |
+
|
8266 |
+
// Generate report chart data for the events per remote address.
|
8267 |
+
foreach ( $audit_report['events_per_ipaddress'] as $event => $times ) {
|
8268 |
+
$template_variables['AuditReport.EventsPerIPAddressCategories'] .= sprintf( '"%s",', $event );
|
8269 |
+
$template_variables['AuditReport.EventsPerIPAddressSeries'] .= sprintf( '%d,', $times );
|
8270 |
+
}
|
8271 |
+
|
8272 |
+
return SucuriScanTemplate::get_section( 'integrity-auditreport', $template_variables );
|
8273 |
+
}
|
8274 |
+
|
8275 |
+
return '';
|
8276 |
}
|
8277 |
|
8278 |
/**
|
8283 |
function sucuriscan_wordpress_outdated(){
|
8284 |
$site_version = SucuriScan::site_version();
|
8285 |
$updates = get_core_updates();
|
8286 |
+
$cp = ( ! is_array( $updates ) || empty($updates) ? 1 : 0 );
|
8287 |
|
8288 |
$template_variables = array(
|
8289 |
'WordPress.Version' => $site_version,
|
8290 |
+
'WordPress.UpgradeURL' => admin_url( 'update-core.php' ),
|
8291 |
'WordPress.UpdateVisibility' => 'hidden',
|
8292 |
'WordPressBeta.Visibility' => 'hidden',
|
8293 |
'WordPressBeta.Version' => '0.0.0',
|
8294 |
+
'WordPressBeta.UpdateURL' => admin_url( 'update-core.php' ),
|
8295 |
'WordPressBeta.DownloadURL' => '#',
|
8296 |
);
|
8297 |
|
8298 |
+
if ( isset($updates[0]) && $updates[0] instanceof stdClass ){
|
8299 |
+
if ( $updates[0]->response == 'latest' ){
|
8300 |
$cp = 1;
|
8301 |
}
|
8302 |
|
8303 |
+
elseif ( $updates[0]->response == 'development' ){
|
8304 |
$cp = 1;
|
8305 |
$template_variables['WordPressBeta.Visibility'] = 'visible';
|
8306 |
$template_variables['WordPressBeta.Version'] = $updates[0]->version;
|
8308 |
}
|
8309 |
}
|
8310 |
|
8311 |
+
if ( strcmp( $site_version, '3.7' ) < 0 ){
|
8312 |
$cp = 0;
|
8313 |
}
|
8314 |
|
8315 |
+
if ( $cp == 0 ){
|
8316 |
$template_variables['WordPress.UpdateVisibility'] = 'visible';
|
8317 |
}
|
8318 |
|
8319 |
+
return SucuriScanTemplate::get_section( 'integrity-wpoutdate', $template_variables );
|
8320 |
}
|
8321 |
|
8322 |
/**
|
8336 |
'CoreFiles.BadVisibility' => 'hidden',
|
8337 |
);
|
8338 |
|
8339 |
+
if ( $site_version && SucuriScanOption::get_option( ':scan_checksums' ) == 'enabled' ){
|
8340 |
// Check if there are added, removed, or modified files.
|
8341 |
+
$latest_hashes = sucuriscan_check_core_integrity( $site_version );
|
8342 |
|
8343 |
+
if ( $latest_hashes ){
|
8344 |
+
$cache = new SucuriScanCache( 'integrity' );
|
8345 |
$ignored_files = $cache->get_all();
|
8346 |
$counter = 0;
|
8347 |
|
8348 |
+
foreach ( $latest_hashes as $list_type => $file_list ){
|
8349 |
+
if (
|
8350 |
$list_type == 'stable'
|
8351 |
|| empty($file_list)
|
8352 |
){
|
8353 |
continue;
|
8354 |
}
|
8355 |
|
8356 |
+
foreach ( $file_list as $file_path ){
|
8357 |
+
$full_filepath = sprintf( '%s/%s', rtrim( ABSPATH, '/' ), $file_path );
|
8358 |
+
|
8359 |
// Skip files that were marked as fixed.
|
8360 |
+
if ( $ignored_files ){
|
8361 |
+
// Get the checksum of the base file name.
|
8362 |
+
$file_path_checksum = md5( $file_path );
|
8363 |
|
8364 |
+
if ( array_key_exists( $file_path_checksum, $ignored_files ) ){
|
8365 |
continue;
|
8366 |
}
|
8367 |
}
|
8368 |
|
8369 |
// Generate the HTML code from the snippet template for this file.
|
8370 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8371 |
+
$file_size = @filesize( $full_filepath );
|
8372 |
$template_variables['CoreFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-corefiles', array(
|
8373 |
'CoreFiles.CssClass' => $css_class,
|
8374 |
'CoreFiles.StatusType' => $list_type,
|
|
|
8375 |
'CoreFiles.FilePath' => $file_path,
|
8376 |
+
'CoreFiles.FileSize' => $file_size,
|
8377 |
+
'CoreFiles.FileSizeHuman' => SucuriScan::human_filesize( $file_size ),
|
8378 |
+
'CoreFiles.FileSizeNumber' => number_format( $file_size ),
|
8379 |
));
|
8380 |
$counter += 1;
|
8381 |
}
|
8382 |
}
|
8383 |
|
8384 |
+
if ( $counter > 0 ){
|
8385 |
$template_variables['CoreFiles.ListCount'] = $counter;
|
8386 |
$template_variables['CoreFiles.GoodVisibility'] = 'hidden';
|
8387 |
$template_variables['CoreFiles.BadVisibility'] = 'visible';
|
8391 |
}
|
8392 |
}
|
8393 |
|
8394 |
+
return SucuriScanTemplate::get_section( 'integrity-corefiles', $template_variables );
|
8395 |
}
|
8396 |
|
8397 |
/**
|
8409 |
* @param integer $version Valid version number of the WordPress project.
|
8410 |
* @return array Associative array with these keys: modified, stable, removed, added.
|
8411 |
*/
|
8412 |
+
function sucuriscan_check_core_integrity( $version = 0 ){
|
8413 |
+
$latest_hashes = SucuriScanAPI::get_official_checksums( $version );
|
8414 |
|
8415 |
+
if ( ! $latest_hashes ){ return false; }
|
8416 |
|
8417 |
$output = array(
|
8418 |
'added' => array(),
|
8422 |
);
|
8423 |
|
8424 |
// Get current filesystem tree.
|
8425 |
+
$wp_top_hashes = sucuriscan_get_integrity_tree( ABSPATH , false );
|
8426 |
+
$wp_admin_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-admin', true );
|
8427 |
+
$wp_includes_hashes = sucuriscan_get_integrity_tree( ABSPATH . 'wp-includes', true );
|
8428 |
$wp_core_hashes = array_merge( $wp_top_hashes, $wp_admin_hashes, $wp_includes_hashes );
|
8429 |
|
8430 |
// Compare remote and local checksums and search removed files.
|
8431 |
+
foreach ( $latest_hashes as $file_path => $remote_checksum ){
|
8432 |
+
if ( sucuriscan_ignore_integrity_filepath( $file_path ) ){ continue; }
|
8433 |
|
8434 |
+
$full_filepath = sprintf( '%s/%s', ABSPATH, $file_path );
|
8435 |
|
8436 |
+
if ( file_exists( $full_filepath ) ){
|
8437 |
+
$local_checksum = @md5_file( $full_filepath );
|
8438 |
|
8439 |
+
if ( $local_checksum && $local_checksum == $remote_checksum ){
|
8440 |
$output['stable'][] = $file_path;
|
8441 |
} else {
|
8442 |
$output['modified'][] = $file_path;
|
8447 |
}
|
8448 |
|
8449 |
// Search added files (files not common in a normal wordpress installation).
|
8450 |
+
foreach ( $wp_core_hashes as $file_path => $extra_info ){
|
8451 |
+
$file_path = preg_replace( '/^\.\/(.*)/', '$1', $file_path );
|
8452 |
|
8453 |
+
if ( sucuriscan_ignore_integrity_filepath( $file_path ) ){ continue; }
|
8454 |
|
8455 |
+
if ( ! isset($latest_hashes[ $file_path ]) ){
|
8456 |
$output['added'][] = $file_path;
|
8457 |
}
|
8458 |
}
|
8466 |
* @param string $file_path File path that will be compared.
|
8467 |
* @return boolean TRUE if the file should be ignored, FALSE otherwise.
|
8468 |
*/
|
8469 |
+
function sucuriscan_ignore_integrity_filepath( $file_path = '' ){
|
8470 |
global $wp_local_package;
|
8471 |
|
8472 |
// List of files that will be ignored from the integrity checking.
|
8497 |
* of the project, basically they have files with new variables specifying the
|
8498 |
* language that will be used in the admin panel, site options, and emails.
|
8499 |
*/
|
8500 |
+
if (
|
8501 |
isset($wp_local_package)
|
8502 |
&& $wp_local_package != 'en_US'
|
8503 |
){
|
8506 |
}
|
8507 |
|
8508 |
// Determine whether a file must be ignored from the integrity checks or not.
|
8509 |
+
foreach ( $ignore_files as $ignore_pattern ){
|
8510 |
+
if ( preg_match( '/'.$ignore_pattern.'/', $file_path ) ){
|
8511 |
+
return true;
|
8512 |
}
|
8513 |
}
|
8514 |
|
8515 |
+
return false;
|
8516 |
}
|
8517 |
|
8518 |
/**
|
8526 |
'ModifiedFiles.List' => '',
|
8527 |
'ModifiedFiles.SelectOptions' => '',
|
8528 |
'ModifiedFiles.NoFilesVisibility' => 'visible',
|
8529 |
+
'ModifiedFiles.DisabledVisibility' => 'hidden',
|
8530 |
'ModifiedFiles.Days' => 0,
|
8531 |
);
|
8532 |
|
8533 |
// Find files modified in the last days.
|
8534 |
+
$back_days = SucuriScanRequest::post( ':last_days', '[0-9]+' );
|
8535 |
|
8536 |
+
if ( $back_days !== false ) {
|
|
|
|
|
8537 |
if ( $back_days <= 0 ){ $back_days = 1; }
|
8538 |
+
elseif ( $back_days >= 60 ){ $back_days = 60; }
|
8539 |
+
} else {
|
8540 |
+
$back_days = 7;
|
8541 |
}
|
8542 |
|
8543 |
+
// Fix data type for the back days variable.
|
8544 |
+
$back_days = intval( $back_days );
|
8545 |
+
$template_variables['ModifiedFiles.Days'] = $back_days;
|
8546 |
+
|
8547 |
// Generate the options for the select field of the page form.
|
8548 |
+
foreach ( $valid_day_ranges as $day ){
|
8549 |
$selected_option = ($back_days == $day) ? 'selected="selected"' : '';
|
8550 |
$template_variables['ModifiedFiles.SelectOptions'] .= sprintf(
|
8551 |
'<option value="%d" %s>%d</option>',
|
8554 |
}
|
8555 |
|
8556 |
// The scanner for modified files can be disabled from the settings page.
|
8557 |
+
if ( SucuriScanOption::get_option( ':scan_modfiles' ) == 'enabled' ){
|
8558 |
// Search modified files among the project's files.
|
8559 |
$content_hashes = sucuriscan_get_integrity_tree( ABSPATH.'wp-content', true );
|
8560 |
|
8561 |
+
if ( ! empty($content_hashes) ){
|
8562 |
+
$back_days = current_time( 'timestamp' ) - ( $back_days * 86400);
|
|
|
8563 |
$counter = 0;
|
8564 |
|
8565 |
+
foreach ( $content_hashes as $file_path => $file_info ){
|
8566 |
+
if (
|
8567 |
isset($file_info['modified_at'])
|
8568 |
&& $file_info['modified_at'] >= $back_days
|
8569 |
){
|
8570 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8571 |
+
$mod_date = SucuriScan::datetime( $file_info['modified_at'] );
|
8572 |
|
8573 |
$template_variables['ModifiedFiles.List'] .= SucuriScanTemplate::get_snippet('integrity-modifiedfiles', array(
|
8574 |
'ModifiedFiles.CssClass' => $css_class,
|
8575 |
'ModifiedFiles.CheckSum' => $file_info['checksum'],
|
8576 |
'ModifiedFiles.FilePath' => $file_path,
|
8577 |
'ModifiedFiles.DateTime' => $mod_date,
|
8578 |
+
'ModifiedFiles.FileSize' => $file_info['filesize'],
|
8579 |
+
'ModifiedFiles.FileSizeHuman' => SucuriScan::human_filesize( $file_info['filesize'] ),
|
8580 |
+
'ModifiedFiles.FileSizeNumber' => number_format( $file_info['filesize'] ),
|
8581 |
));
|
8582 |
$counter += 1;
|
8583 |
}
|
8584 |
}
|
8585 |
|
8586 |
+
if ( $counter > 0 ){
|
8587 |
$template_variables['ModifiedFiles.NoFilesVisibility'] = 'hidden';
|
8588 |
}
|
8589 |
}
|
8590 |
}
|
8591 |
|
8592 |
+
else {
|
8593 |
+
$template_variables['ModifiedFiles.DisabledVisibility'] = 'visible';
|
8594 |
+
}
|
8595 |
+
|
8596 |
+
return SucuriScanTemplate::get_section( 'integrity-modifiedfiles', $template_variables );
|
8597 |
}
|
8598 |
|
8599 |
/**
|
8609 |
// Page pseudo-variables initialization.
|
8610 |
$template_variables = array(
|
8611 |
'PageTitle' => 'Post-Hack',
|
8612 |
+
'UpdateSecretKeys' => sucuriscan_update_secret_keys( $process_form ),
|
8613 |
+
'ResetPassword' => sucuriscan_posthack_users( $process_form ),
|
8614 |
+
'ResetPlugins' => sucuriscan_posthack_plugins( $process_form ),
|
8615 |
);
|
8616 |
|
8617 |
+
echo SucuriScanTemplate::get_template( 'posthack', $template_variables );
|
8618 |
}
|
8619 |
|
8620 |
/**
|
8623 |
* @return boolean TRUE if a form submission should be processed, FALSE otherwise.
|
8624 |
*/
|
8625 |
function sucuriscan_posthack_process_form(){
|
8626 |
+
$process_form = SucuriScanRequest::post( ':process_form', '(0|1)' );
|
8627 |
|
8628 |
+
if (
|
8629 |
SucuriScanInterface::check_nonce()
|
8630 |
+
&& $process_form !== false
|
8631 |
){
|
8632 |
+
if ( $process_form === '1' ){
|
8633 |
+
return true;
|
8634 |
} else {
|
8635 |
+
SucuriScanInterface::error( 'You need to confirm that you understand the risk of this operation.' );
|
8636 |
}
|
8637 |
}
|
8638 |
|
8639 |
+
return false;
|
8640 |
}
|
8641 |
|
8642 |
/**
|
8645 |
* @param $process_form Whether a form was submitted or not.
|
8646 |
* @return string HTML code with the information of the process.
|
8647 |
*/
|
8648 |
+
function sucuriscan_update_secret_keys( $process_form = false ){
|
8649 |
$template_variables = array(
|
8650 |
'WPConfigUpdate.Visibility' => 'hidden',
|
8651 |
'WPConfigUpdate.NewConfig' => '',
|
8653 |
);
|
8654 |
|
8655 |
// Update all WordPress secret keys.
|
8656 |
+
if ( $process_form && SucuriScanRequest::post( ':update_wpconfig', '1' ) ){
|
8657 |
$wpconfig_process = SucuriScanEvent::set_new_config_keys();
|
8658 |
|
8659 |
+
if ( $wpconfig_process ){
|
8660 |
$template_variables['WPConfigUpdate.Visibility'] = 'visible';
|
8661 |
+
SucuriScanEvent::report_notice_event( 'Generate new security keys' );
|
8662 |
|
8663 |
+
if ( $wpconfig_process['updated'] === true ){
|
8664 |
SucuriScanInterface::info( 'Secret keys updated successfully (summary of the operation bellow).' );
|
8665 |
$template_variables['WPConfigUpdate.NewConfig'] .= "// Old Keys\n";
|
8666 |
$template_variables['WPConfigUpdate.NewConfig'] .= $wpconfig_process['old_keys_string'];
|
8675 |
$template_variables['WPConfigUpdate.NewConfig'] = $wpconfig_process['new_wpconfig'];
|
8676 |
}
|
8677 |
} else {
|
8678 |
+
SucuriScanInterface::error( '<code>wp-config.php</code> file was not found in the default location.' );
|
8679 |
}
|
8680 |
}
|
8681 |
|
8683 |
$current_keys = SucuriScanOption::get_security_keys();
|
8684 |
$counter = 0;
|
8685 |
|
8686 |
+
foreach ( $current_keys as $key_status => $key_list ){
|
8687 |
+
foreach ( $key_list as $key_name => $key_value ){
|
8688 |
+
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8689 |
$key_value = SucuriScan::excerpt( $key_value, 50 );
|
8690 |
|
8691 |
+
switch ( $key_status ){
|
8692 |
case 'good':
|
8693 |
$key_status_text = 'good';
|
8694 |
$key_status_css_class = 'success';
|
8704 |
break;
|
8705 |
}
|
8706 |
|
8707 |
+
if ( isset($key_status_text) ){
|
8708 |
$template_variables['SecurityKeys.List'] .= SucuriScanTemplate::get_snippet('posthack-updatesecretkeys', array(
|
8709 |
'SecurityKey.CssClass' => $css_class,
|
8710 |
+
'SecurityKey.KeyName' => SucuriScan::escape( $key_name ),
|
8711 |
+
'SecurityKey.KeyValue' => SucuriScan::escape( $key_value ),
|
8712 |
'SecurityKey.KeyStatusText' => $key_status_text,
|
8713 |
'SecurityKey.KeyStatusCssClass' => $key_status_css_class,
|
8714 |
));
|
8717 |
}
|
8718 |
}
|
8719 |
|
8720 |
+
return SucuriScanTemplate::get_section( 'posthack-updatesecretkeys', $template_variables );
|
8721 |
}
|
8722 |
|
8723 |
/**
|
8727 |
* @param $process_form Whether a form was submitted or not.
|
8728 |
* @return string HTML code for a table where a list of user accounts will be shown.
|
8729 |
*/
|
8730 |
+
function sucuriscan_posthack_users( $process_form = false ){
|
8731 |
$template_variables = array(
|
8732 |
'ResetPassword.UserList' => '',
|
8733 |
+
'ResetPassword.PaginationLinks' => '',
|
8734 |
+
'ResetPassword.PaginationVisibility' => 'hidden',
|
8735 |
);
|
8736 |
|
8737 |
// Process the form submission (if any).
|
8738 |
+
sucuriscan_reset_user_password( $process_form );
|
8739 |
|
8740 |
// Fill the user list for ResetPassword action.
|
8741 |
+
$user_list = false;
|
8742 |
+
$page_number = SucuriScanTemplate::get_page_number();
|
8743 |
+
$max_per_page = SUCURISCAN_MAX_PAGINATION_BUTTONS;
|
8744 |
+
$dbquery = new WP_User_Query( array(
|
8745 |
+
'number' => $max_per_page,
|
8746 |
+
'offset' => ( $page_number - 1 ) * $max_per_page,
|
8747 |
+
'fields' => 'all_with_meta',
|
8748 |
+
'orderby' => 'ID',
|
8749 |
+
) );
|
8750 |
+
|
8751 |
+
// Retrieve the results and build the pagination links.
|
8752 |
+
if ( $dbquery ) {
|
8753 |
+
$total_items = $dbquery->get_total();
|
8754 |
+
$user_list = $dbquery->get_results();
|
8755 |
+
|
8756 |
+
$template_variables['ResetPassword.PaginationLinks'] = SucuriScanTemplate::get_pagination(
|
8757 |
+
'%%SUCURI.URL.Posthack%%#reset-users-password',
|
8758 |
+
$total_items,
|
8759 |
+
$max_per_page
|
8760 |
+
);
|
8761 |
|
8762 |
+
if ( $total_items > SUCURISCAN_MAX_PAGINATION_BUTTONS ) {
|
8763 |
+
$template_variables['ResetPassword.PaginationVisibility'] = 'visible';
|
8764 |
+
}
|
8765 |
+
}
|
8766 |
+
|
8767 |
+
if ( $user_list !== false ) {
|
8768 |
$counter = 0;
|
8769 |
|
8770 |
+
foreach ( $user_list as $user ){
|
8771 |
+
$user->user_registered_timestamp = strtotime( $user->user_registered );
|
8772 |
+
$user->user_registered_formatted = SucuriScan::datetime( $user->user_registered_timestamp );
|
8773 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8774 |
+
$display_username = ( $user->user_login != $user->display_name )
|
8775 |
+
? sprintf( '%s (%s)', $user->user_login, $user->display_name )
|
8776 |
+
: $user->user_login;
|
8777 |
|
8778 |
$template_variables['ResetPassword.UserList'] .= SucuriScanTemplate::get_snippet('posthack-resetpassword', array(
|
8779 |
'ResetPassword.UserId' => $user->ID,
|
8780 |
+
'ResetPassword.Username' => SucuriScan::escape( $user->user_login ),
|
8781 |
+
'ResetPassword.Displayname' => SucuriScan::escape( $user->display_name ),
|
8782 |
+
'ResetPassword.DisplayUsername' => SucuriScan::escape( $display_username ),
|
8783 |
+
'ResetPassword.Email' => SucuriScan::escape( $user->user_email ),
|
8784 |
'ResetPassword.Registered' => $user->user_registered_formatted,
|
8785 |
+
'ResetPassword.Roles' => @implode( ', ', $user->roles ),
|
8786 |
'ResetPassword.CssClass' => $css_class,
|
8787 |
));
|
8788 |
|
8790 |
}
|
8791 |
}
|
8792 |
|
8793 |
+
return SucuriScanTemplate::get_section( 'posthack-resetpassword', $template_variables );
|
8794 |
}
|
8795 |
|
8796 |
/**
|
8799 |
* @param $process_form Whether a form was submitted or not.
|
8800 |
* @return void
|
8801 |
*/
|
8802 |
+
function sucuriscan_reset_user_password( $process_form = false ){
|
8803 |
+
if ( $process_form && SucuriScanRequest::post( ':reset_password' ) ){
|
8804 |
+
$user_identifiers = SucuriScanRequest::post( 'user_ids', '_array' );
|
8805 |
$pwd_changed = array();
|
8806 |
$pwd_not_changed = array();
|
8807 |
|
8808 |
+
if ( is_array( $user_identifiers ) && ! empty($user_identifiers) ){
|
8809 |
+
arsort( $user_identifiers );
|
8810 |
|
8811 |
+
foreach ( $user_identifiers as $user_id ){
|
8812 |
+
if ( SucuriScanEvent::set_new_password( $user_id ) ){
|
8813 |
$pwd_changed[] = $user_id;
|
8814 |
} else {
|
8815 |
$pwd_not_changed[] = $user_id;
|
8816 |
}
|
8817 |
}
|
8818 |
|
8819 |
+
if ( ! empty($pwd_changed) ){
|
8820 |
+
$message = 'Password changed for user identifiers <code>' . @implode( ', ',$pwd_changed ) . '</code>';
|
8821 |
+
|
8822 |
+
SucuriScanEvent::report_notice_event( $message );
|
8823 |
+
SucuriScanInterface::info( $message );
|
8824 |
}
|
8825 |
|
8826 |
+
if ( ! empty($pwd_not_changed) ){
|
8827 |
+
SucuriScanInterface::error( 'Password change failed for users: ' . implode( ', ',$pwd_not_changed ) );
|
8828 |
}
|
8829 |
} else {
|
8830 |
SucuriScanInterface::error( 'You did not select a user from the list.' );
|
8838 |
* @param boolean $process_form Whether a form was submitted or not.
|
8839 |
* @return void
|
8840 |
*/
|
8841 |
+
function sucuriscan_posthack_plugins( $process_form = false ){
|
8842 |
$template_variables = array(
|
8843 |
'ResetPlugin.PluginList' => '',
|
8844 |
);
|
8845 |
|
8846 |
+
sucuriscan_posthack_reinstall_plugins( $process_form );
|
8847 |
$all_plugins = SucuriScanAPI::get_plugins();
|
8848 |
$counter = 0;
|
8849 |
|
8850 |
+
foreach ( $all_plugins as $plugin_path => $plugin_data ){
|
8851 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
8852 |
$plugin_type_class = ( $plugin_data['PluginType'] == 'free' ) ? 'primary' : 'warning';
|
8853 |
$input_disabled = ( $plugin_data['PluginType'] == 'free' ) ? '' : 'disabled="disabled"';
|
8857 |
$template_variables['ResetPlugin.PluginList'] .= SucuriScanTemplate::get_snippet('posthack-resetplugins', array(
|
8858 |
'ResetPlugin.CssClass' => $css_class,
|
8859 |
'ResetPlugin.Disabled' => $input_disabled,
|
8860 |
+
'ResetPlugin.PluginPath' => SucuriScan::escape( $plugin_path ),
|
8861 |
+
'ResetPlugin.Plugin' => SucuriScan::excerpt( $plugin_data['Name'], 35 ),
|
8862 |
'ResetPlugin.Version' => $plugin_data['Version'],
|
8863 |
'ResetPlugin.Type' => $plugin_data['PluginType'],
|
8864 |
'ResetPlugin.TypeClass' => $plugin_type_class,
|
8869 |
$counter += 1;
|
8870 |
}
|
8871 |
|
8872 |
+
return SucuriScanTemplate::get_section( 'posthack-resetplugins', $template_variables );
|
8873 |
}
|
8874 |
|
8875 |
/**
|
8881 |
* @param boolean $process_form Whether a form was submitted or not.
|
8882 |
* @return void
|
8883 |
*/
|
8884 |
+
function sucuriscan_posthack_reinstall_plugins( $process_form = false ){
|
8885 |
+
if ( $process_form && isset($_POST['sucuriscan_reset_plugins']) ){
|
8886 |
include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
|
8887 |
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); // For plugins_api.
|
8888 |
|
8889 |
+
if ( $plugin_list = SucuriScanRequest::post( 'plugin_path', '_array' ) ){
|
8890 |
// Create an instance of the FileInfo interface.
|
8891 |
$sucuri_fileinfo = new SucuriScanFileInfo();
|
8892 |
+
$sucuri_fileinfo->ignore_files = false;
|
8893 |
+
$sucuri_fileinfo->ignore_directories = false;
|
8894 |
|
8895 |
// Get (possible) cached information from the installed plugins.
|
8896 |
$all_plugins = SucuriScanAPI::get_plugins();
|
8897 |
|
8898 |
// Loop through all the installed plugins.
|
8899 |
+
foreach ( $_POST['plugin_path'] as $plugin_path ){
|
8900 |
+
if ( array_key_exists( $plugin_path, $all_plugins ) ){
|
8901 |
+
$plugin_data = $all_plugins[ $plugin_path ];
|
8902 |
|
8903 |
// Check if the plugin can be downloaded from the free market.
|
8904 |
+
if ( $plugin_data['IsFreePlugin'] === true ){
|
8905 |
+
$plugin_info = SucuriScanAPI::get_remote_plugin_data( $plugin_data['RepositoryName'] );
|
8906 |
|
8907 |
+
if ( $plugin_info ){
|
8908 |
// First, remove all files/sub-folders from the plugin's directory.
|
8909 |
+
if ( substr_count( $plugin_path, '/' ) >= 1 ) {
|
8910 |
+
$plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin_path );
|
8911 |
+
$sucuri_fileinfo->remove_directory_tree( $plugin_directory );
|
8912 |
+
}
|
8913 |
|
8914 |
// Install a fresh copy of the plugin's files.
|
8915 |
$upgrader_skin = new Plugin_Installer_Skin();
|
8916 |
+
$upgrader = new Plugin_Upgrader( $upgrader_skin );
|
8917 |
+
$upgrader->install( $plugin_info->download_link );
|
8918 |
+
SucuriScanEvent::report_notice_event( 'Plugin re-installed: ' . $plugin_path );
|
8919 |
} else {
|
8920 |
SucuriScanInterface::error( 'Could not establish a stable connection with the WordPress plugins market.' );
|
8921 |
}
|
8939 |
SucuriScanInterface::check_permissions();
|
8940 |
|
8941 |
// Reset the file with the last-logins logs.
|
8942 |
+
if (
|
8943 |
SucuriScanInterface::check_nonce()
|
8944 |
+
&& SucuriScanRequest::post( ':reset_lastlogins' ) !== false
|
8945 |
){
|
8946 |
$file_path = sucuriscan_lastlogins_datastore_filepath();
|
8947 |
|
8948 |
+
if ( unlink( $file_path ) ){
|
8949 |
sucuriscan_lastlogins_datastore_exists();
|
8950 |
SucuriScanInterface::info( 'Last-Logins logs were reset successfully.' );
|
8951 |
} else {
|
8962 |
'FailedLogins' => sucuriscan_failed_logins_panel(),
|
8963 |
);
|
8964 |
|
8965 |
+
echo SucuriScanTemplate::get_template( 'lastlogins', $template_variables );
|
8966 |
}
|
8967 |
|
8968 |
/**
|
8975 |
function sucuriscan_lastlogins_admins(){
|
8976 |
// Page pseudo-variables initialization.
|
8977 |
$template_variables = array(
|
8978 |
+
'AdminUsers.List' => '',
|
8979 |
);
|
8980 |
|
8981 |
+
$user_query = new WP_User_Query( array( 'role' => 'Administrator' ) );
|
8982 |
$admins = $user_query->get_results();
|
8983 |
|
8984 |
+
foreach ( (array) $admins as $admin ){
|
8985 |
+
$last_logins = sucuriscan_get_logins( 5, 0, $admin->ID );
|
8986 |
$admin->lastlogins = $last_logins['entries'];
|
8987 |
|
8988 |
$user_snippet = array(
|
8989 |
+
'AdminUsers.Username' => SucuriScan::escape( $admin->user_login ),
|
8990 |
+
'AdminUsers.Email' => SucuriScan::escape( $admin->user_email ),
|
8991 |
'AdminUsers.LastLogins' => '',
|
8992 |
'AdminUsers.RegisteredAt' => 'Undefined',
|
8993 |
+
'AdminUsers.UserURL' => admin_url( 'user-edit.php?user_id='.$admin->ID ),
|
8994 |
'AdminUsers.NoLastLogins' => 'visible',
|
8995 |
'AdminUsers.NoLastLoginsTable' => 'hidden',
|
8996 |
);
|
8997 |
|
8998 |
+
if ( ! empty($admin->lastlogins) ){
|
8999 |
$user_snippet['AdminUsers.NoLastLogins'] = 'hidden';
|
9000 |
$user_snippet['AdminUsers.NoLastLoginsTable'] = 'visible';
|
9001 |
$user_snippet['AdminUsers.RegisteredAt'] = 'Unknown';
|
9002 |
$counter = 0;
|
9003 |
|
9004 |
+
foreach ( $admin->lastlogins as $i => $lastlogin ){
|
9005 |
+
if ( $i == 0 ){
|
9006 |
+
$user_snippet['AdminUsers.RegisteredAt'] = SucuriScan::datetime( $lastlogin->user_registered_timestamp );
|
9007 |
}
|
9008 |
|
9009 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
9010 |
$user_snippet['AdminUsers.LastLogins'] .= SucuriScanTemplate::get_snippet('lastlogins-admins-lastlogin', array(
|
9011 |
+
'AdminUsers.RemoteAddr' => SucuriScan::escape( $lastlogin->user_remoteaddr ),
|
9012 |
+
'AdminUsers.Datetime' => SucuriScan::datetime( $lastlogin->user_lastlogin_timestamp ),
|
9013 |
'AdminUsers.CssClass' => $css_class,
|
9014 |
));
|
9015 |
$counter += 1;
|
9016 |
}
|
9017 |
}
|
9018 |
|
9019 |
+
$template_variables['AdminUsers.List'] .= SucuriScanTemplate::get_snippet( 'lastlogins-admins', $user_snippet );
|
9020 |
}
|
9021 |
|
9022 |
+
return SucuriScanTemplate::get_section( 'lastlogins-admins', $template_variables );
|
9023 |
}
|
9024 |
|
9025 |
/**
|
9043 |
'UserList.NoItemsVisibility' => 'visible',
|
9044 |
);
|
9045 |
|
9046 |
+
if ( ! sucuriscan_lastlogins_datastore_is_writable() ){
|
9047 |
SucuriScanInterface::error( 'Last-logins datastore file is not writable: <code>'.sucuriscan_lastlogins_datastore_filepath().'</code>' );
|
9048 |
}
|
9049 |
|
9051 |
$last_logins = sucuriscan_get_logins( $max_per_page, $offset );
|
9052 |
$template_variables['UserList.Total'] = $last_logins['total'];
|
9053 |
|
9054 |
+
if ( $last_logins['total'] > $max_per_page ){
|
9055 |
$template_variables['UserList.PaginationVisibility'] = 'visible';
|
9056 |
}
|
9057 |
|
9058 |
+
if ( $last_logins['total'] > 0 ){
|
9059 |
$template_variables['UserList.NoItemsVisibility'] = 'hidden';
|
9060 |
}
|
9061 |
|
9062 |
+
foreach ( $last_logins['entries'] as $user ){
|
9063 |
$counter += 1;
|
9064 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
9065 |
|
9070 |
'UserList.Displayname' => '',
|
9071 |
'UserList.Email' => '',
|
9072 |
'UserList.Registered' => '',
|
9073 |
+
'UserList.RemoteAddr' => SucuriScan::escape( $user->user_remoteaddr ),
|
9074 |
+
'UserList.Hostname' => SucuriScan::escape( $user->user_hostname ),
|
9075 |
+
'UserList.Datetime' => SucuriScan::escape( $user->user_lastlogin ),
|
9076 |
+
'UserList.TimeAgo' => SucuriScan::time_ago( $user->user_lastlogin ),
|
9077 |
+
'UserList.UserURL' => admin_url( 'user-edit.php?user_id='.$user->user_id ),
|
9078 |
'UserList.CssClass' => $css_class,
|
9079 |
);
|
9080 |
|
9081 |
+
if ( $user->user_exists ){
|
9082 |
+
$user_dataset['UserList.Username'] = SucuriScan::escape( $user->user_login );
|
9083 |
+
$user_dataset['UserList.Displayname'] = SucuriScan::escape( $user->display_name );
|
9084 |
+
$user_dataset['UserList.Email'] = SucuriScan::escape( $user->user_email );
|
9085 |
+
$user_dataset['UserList.Registered'] = SucuriScan::escape( $user->user_registered );
|
9086 |
}
|
9087 |
|
9088 |
+
$template_variables['UserList'] .= SucuriScanTemplate::get_snippet( 'lastlogins-all', $user_dataset );
|
9089 |
}
|
9090 |
|
9091 |
// Generate the pagination for the list.
|
9095 |
$max_per_page
|
9096 |
);
|
9097 |
|
9098 |
+
return SucuriScanTemplate::get_section( 'lastlogins-all', $template_variables );
|
9099 |
}
|
9100 |
|
9101 |
/**
|
9116 |
function sucuriscan_lastlogins_datastore_exists(){
|
9117 |
$datastore_filepath = sucuriscan_lastlogins_datastore_filepath();
|
9118 |
|
9119 |
+
if ( ! file_exists( $datastore_filepath ) ){
|
9120 |
+
if ( @file_put_contents( $datastore_filepath, "<?php exit(0); ?>\n", LOCK_EX ) ){
|
9121 |
+
@chmod( $datastore_filepath, 0644 );
|
9122 |
}
|
9123 |
}
|
9124 |
|
9125 |
+
return file_exists( $datastore_filepath ) ? $datastore_filepath : false;
|
9126 |
}
|
9127 |
|
9128 |
/**
|
9134 |
function sucuriscan_lastlogins_datastore_is_writable(){
|
9135 |
$datastore_filepath = sucuriscan_lastlogins_datastore_exists();
|
9136 |
|
9137 |
+
if ( $datastore_filepath ){
|
9138 |
+
if ( ! is_writable( $datastore_filepath ) ){
|
9139 |
+
@chmod( $datastore_filepath, 0644 );
|
9140 |
}
|
9141 |
|
9142 |
+
if ( is_writable( $datastore_filepath ) ){
|
9143 |
return $datastore_filepath;
|
9144 |
}
|
9145 |
}
|
9146 |
|
9147 |
+
return false;
|
9148 |
}
|
9149 |
|
9150 |
/**
|
9156 |
function sucuriscan_lastlogins_datastore_is_readable(){
|
9157 |
$datastore_filepath = sucuriscan_lastlogins_datastore_exists();
|
9158 |
|
9159 |
+
if ( $datastore_filepath && is_readable( $datastore_filepath ) ){
|
9160 |
return $datastore_filepath;
|
9161 |
}
|
9162 |
|
9163 |
+
return false;
|
9164 |
}
|
9165 |
|
9166 |
+
if ( ! function_exists( 'sucuri_set_lastlogin' ) ){
|
9167 |
/**
|
9168 |
* Add a new user session to the list of last user logins.
|
9169 |
*
|
9170 |
* @param string $user_login The name of the user account involved in the operation.
|
9171 |
* @return void
|
9172 |
*/
|
9173 |
+
function sucuriscan_set_lastlogin( $user_login = '' ){
|
9174 |
$datastore_filepath = sucuriscan_lastlogins_datastore_is_writable();
|
9175 |
|
9176 |
+
if ( $datastore_filepath ){
|
9177 |
+
$current_user = get_user_by( 'login', $user_login );
|
9178 |
$remote_addr = SucuriScan::get_remote_addr();
|
9179 |
|
9180 |
$login_info = array(
|
9181 |
'user_id' => $current_user->ID,
|
9182 |
'user_login' => $current_user->user_login,
|
9183 |
'user_remoteaddr' => $remote_addr,
|
9184 |
+
'user_hostname' => @gethostbyaddr( $remote_addr ),
|
9185 |
+
'user_lastlogin' => current_time( 'mysql' )
|
9186 |
);
|
9187 |
|
9188 |
+
@file_put_contents( $datastore_filepath, json_encode( $login_info )."\n", FILE_APPEND );
|
9189 |
}
|
9190 |
}
|
9191 |
+
add_action( 'wp_login', 'sucuriscan_set_lastlogin', 50 );
|
9192 |
}
|
9193 |
|
9194 |
/**
|
9202 |
* @param integer $user_id Optional user identifier to filter the results.
|
9203 |
* @return array The list of all the user logins, and total of entries registered.
|
9204 |
*/
|
9205 |
+
function sucuriscan_get_logins( $limit = 10, $offset = 0, $user_id = 0 ){
|
9206 |
$datastore_filepath = sucuriscan_lastlogins_datastore_is_readable();
|
9207 |
$last_logins = array(
|
9208 |
'total' => 0,
|
9209 |
'entries' => array(),
|
9210 |
);
|
9211 |
|
9212 |
+
if ( $datastore_filepath ){
|
9213 |
$parsed_lines = 0;
|
9214 |
+
$data_lines = SucuriScanFileInfo::file_lines( $datastore_filepath );
|
9215 |
|
9216 |
+
if ( $data_lines ){
|
9217 |
/**
|
9218 |
* This count will not be 100% accurate considering that we are checking the
|
9219 |
* syntax of each line in the loop bellow, there may be some lines without the
|
9222 |
*
|
9223 |
* @var integer
|
9224 |
*/
|
9225 |
+
$total_lines = count( $data_lines );
|
9226 |
$last_logins['total'] = $total_lines;
|
9227 |
|
9228 |
// Get a list with the latest entries in the first positions.
|
9229 |
+
$reversed_lines = array_reverse( $data_lines );
|
9230 |
|
9231 |
/**
|
9232 |
* Only the user accounts with administrative privileges can see the logs of all
|
9235 |
* @var object
|
9236 |
*/
|
9237 |
$current_user = wp_get_current_user();
|
9238 |
+
$is_admin_user = (bool) current_user_can( 'manage_options' );
|
9239 |
|
9240 |
+
for ( $i = $offset; $i < $total_lines; $i++ ){
|
9241 |
+
$line = $reversed_lines[ $i ] ? trim( $reversed_lines[ $i ] ) : '';
|
9242 |
|
9243 |
// Check if the data is serialized (which we will consider as insecure).
|
9244 |
+
if ( SucuriScan::is_serialized( $line ) ){
|
9245 |
+
$last_login = @unserialize( $line ); // TODO: Remove after version 1.7.5
|
9246 |
} else {
|
9247 |
+
$last_login = @json_decode( $line, true );
|
9248 |
}
|
9249 |
|
9250 |
+
if ( $last_login ){
|
9251 |
+
$last_login['user_lastlogin_timestamp'] = strtotime( $last_login['user_lastlogin'] );
|
9252 |
$last_login['user_registered_timestamp'] = 0;
|
9253 |
|
9254 |
// Only administrators can see all login stats.
|
9255 |
+
if ( ! $is_admin_user && $current_user->user_login != $last_login['user_login'] ){
|
9256 |
continue;
|
9257 |
}
|
9258 |
|
9259 |
// Filter the user identifiers using the value passed tot his function.
|
9260 |
+
if ( $user_id > 0 && $last_login['user_id'] != $user_id ){
|
9261 |
continue;
|
9262 |
}
|
9263 |
|
9264 |
// Get the WP_User object and add extra information from the last-login data.
|
9265 |
+
$last_login['user_exists'] = false;
|
9266 |
+
$user_account = get_userdata( $last_login['user_id'] );
|
9267 |
|
9268 |
+
if ( $user_account ){
|
9269 |
+
$last_login['user_exists'] = true;
|
9270 |
|
9271 |
+
foreach ( $user_account->data as $var_name => $var_value ){
|
9272 |
+
$last_login[ $var_name ] = $var_value;
|
9273 |
|
9274 |
+
if ( $var_name == 'user_registered' ){
|
9275 |
+
$last_login['user_registered_timestamp'] = strtotime( $var_value );
|
9276 |
}
|
9277 |
}
|
9278 |
}
|
9286 |
$last_logins['total'] -= 1;
|
9287 |
}
|
9288 |
|
9289 |
+
if ( preg_match( '/^[0-9]+$/', $limit ) && $limit > 0 ){
|
9290 |
+
if ( $parsed_lines >= $limit ){ break; }
|
9291 |
}
|
9292 |
}
|
9293 |
}
|
9296 |
return $last_logins;
|
9297 |
}
|
9298 |
|
9299 |
+
if ( ! function_exists( 'sucuri_login_redirect' ) ){
|
9300 |
/**
|
9301 |
* Hook for the wp-login action to redirect the user to a specific URL after
|
9302 |
* his successfully login to the administrator interface.
|
9306 |
* @param boolean $user WordPress user object with the information of the account involved in the operation.
|
9307 |
* @return string URL where the browser must be redirected to.
|
9308 |
*/
|
9309 |
+
function sucuriscan_login_redirect( $redirect_to = '', $request = null, $user = false ){
|
9310 |
+
$login_url = ! empty($redirect_to) ? $redirect_to : admin_url();
|
9311 |
|
9312 |
+
if ( $user instanceof WP_User && $user->ID ){
|
9313 |
$login_url = add_query_arg( 'sucuriscan_lastlogin', 1, $login_url );
|
9314 |
}
|
9315 |
|
9316 |
return $login_url;
|
9317 |
}
|
9318 |
|
9319 |
+
if ( SucuriScanOption::get_option( ':lastlogin_redirection' ) == 'enabled' ){
|
9320 |
+
add_filter( 'login_redirect', 'sucuriscan_login_redirect', 10, 3 );
|
9321 |
}
|
9322 |
}
|
9323 |
|
9324 |
+
if ( ! function_exists( 'sucuri_get_user_lastlogin' ) ){
|
9325 |
/**
|
9326 |
* Display the last user login at the top of the admin interface.
|
9327 |
*
|
9328 |
* @return void
|
9329 |
*/
|
9330 |
function sucuriscan_get_user_lastlogin(){
|
9331 |
+
if (
|
9332 |
+
current_user_can( 'manage_options' )
|
9333 |
+
&& SucuriScanRequest::get( ':lastlogin', '1' )
|
9334 |
){
|
9335 |
$current_user = wp_get_current_user();
|
9336 |
|
9337 |
// Select the penultimate entry, not the last one.
|
9338 |
+
$last_logins = sucuriscan_get_logins( 2, 0, $current_user->ID );
|
9339 |
|
9340 |
+
if ( isset($last_logins['entries'][1]) ){
|
9341 |
$row = $last_logins['entries'][1];
|
9342 |
|
9343 |
$lastlogin_message = sprintf(
|
9344 |
'Last time you logged in was at <code>%s</code> from <code>%s</code> - <code>%s</code>',
|
9345 |
+
SucuriScan::datetime( $row->user_lastlogin_timestamp ),
|
9346 |
$row->user_remoteaddr,
|
9347 |
$row->user_hostname
|
9348 |
);
|
9349 |
+
$lastlogin_message .= chr( 32 ).'(<a href="'.SucuriScanTemplate::get_url( 'lastlogins' ).'">view all logs</a>)';
|
9350 |
SucuriScanInterface::info( $lastlogin_message );
|
9351 |
}
|
9352 |
}
|
9353 |
}
|
9354 |
|
9355 |
+
add_action( 'admin_notices', 'sucuriscan_get_user_lastlogin' );
|
9356 |
}
|
9357 |
|
9358 |
/**
|
9367 |
'LoggedInUsers.Total' => 0,
|
9368 |
);
|
9369 |
|
9370 |
+
$logged_in_users = sucuriscan_get_online_users( true );
|
9371 |
|
9372 |
+
if ( is_array( $logged_in_users ) && ! empty($logged_in_users) ){
|
9373 |
+
$template_variables['LoggedInUsers.Total'] = count( $logged_in_users );
|
9374 |
$counter = 0;
|
9375 |
|
9376 |
+
foreach ( (array) $logged_in_users as $logged_in_user ){
|
9377 |
$counter += 1;
|
9378 |
+
$logged_in_user['last_activity_datetime'] = SucuriScan::datetime( $logged_in_user['last_activity'] );
|
9379 |
+
$logged_in_user['user_registered_datetime'] = SucuriScan::datetime( strtotime( $logged_in_user['user_registered'] ) );
|
9380 |
|
9381 |
$template_variables['LoggedInUsers.List'] .= SucuriScanTemplate::get_snippet('lastlogins-loggedin', array(
|
9382 |
+
'LoggedInUsers.Id' => SucuriScan::escape( $logged_in_user['user_id'] ),
|
9383 |
+
'LoggedInUsers.UserURL' => admin_url( 'user-edit.php?user_id='.$logged_in_user['user_id'] ),
|
9384 |
+
'LoggedInUsers.UserLogin' => SucuriScan::escape( $logged_in_user['user_login'] ),
|
9385 |
+
'LoggedInUsers.UserEmail' => SucuriScan::escape( $logged_in_user['user_email'] ),
|
9386 |
+
'LoggedInUsers.LastActivity' => SucuriScan::escape( $logged_in_user['last_activity_datetime'] ),
|
9387 |
+
'LoggedInUsers.Registered' => SucuriScan::escape( $logged_in_user['user_registered_datetime'] ),
|
9388 |
+
'LoggedInUsers.RemoveAddr' => SucuriScan::escape( $logged_in_user['remote_addr'] ),
|
9389 |
+
'LoggedInUsers.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
|
9390 |
));
|
9391 |
}
|
9392 |
}
|
9393 |
|
9394 |
+
return SucuriScanTemplate::get_section( 'lastlogins-loggedin', $template_variables );
|
9395 |
}
|
9396 |
|
9397 |
/**
|
9400 |
* @param boolean $add_current_user Whether the current user should be added to the list or not.
|
9401 |
* @return array List of registered users currently in session.
|
9402 |
*/
|
9403 |
+
function sucuriscan_get_online_users( $add_current_user = false ){
|
9404 |
$users = array();
|
9405 |
|
9406 |
+
if ( SucuriScan::is_multisite() ){
|
9407 |
+
$users = get_site_transient( 'online_users' );
|
9408 |
} else {
|
9409 |
+
$users = get_transient( 'online_users' );
|
9410 |
}
|
9411 |
|
9412 |
// If not online users but current user is logged in, add it to the list.
|
9413 |
+
if ( empty($users) && $add_current_user ){
|
9414 |
$current_user = wp_get_current_user();
|
9415 |
|
9416 |
+
if ( $current_user->ID > 0 ){
|
9417 |
sucuriscan_set_online_user( $current_user->user_login, $current_user );
|
9418 |
|
9419 |
return sucuriscan_get_online_users();
|
9431 |
* @param array $logged_in_users List of registered users currently in session.
|
9432 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation.
|
9433 |
*/
|
9434 |
+
function sucuriscan_save_online_users( $logged_in_users = array() ){
|
9435 |
$expiration = 30 * 60;
|
9436 |
|
9437 |
+
if ( SucuriScan::is_multisite() ){
|
9438 |
+
return set_site_transient( 'online_users', $logged_in_users, $expiration );
|
9439 |
} else {
|
9440 |
+
return set_transient( 'online_users', $logged_in_users, $expiration );
|
9441 |
}
|
9442 |
}
|
9443 |
|
9444 |
+
if ( ! function_exists( 'sucuriscan_unset_online_user_on_logout' ) ){
|
9445 |
/**
|
9446 |
* Remove a logged in user from the list of registered users in session when
|
9447 |
* the logout page is requested.
|
9453 |
$current_user = wp_get_current_user();
|
9454 |
$user_id = $current_user->ID;
|
9455 |
|
9456 |
+
sucuriscan_unset_online_user( $user_id, $remote_addr );
|
9457 |
}
|
9458 |
|
9459 |
+
add_action( 'wp_logout', 'sucuriscan_unset_online_user_on_logout' );
|
9460 |
}
|
9461 |
|
9462 |
/**
|
9467 |
* @param integer $remote_addr IP address of the computer where the user logged in.
|
9468 |
* @return boolean Either TRUE or FALSE representing the success or fail of the operation.
|
9469 |
*/
|
9470 |
+
function sucuriscan_unset_online_user( $user_id = 0, $remote_addr = 0 ){
|
9471 |
$logged_in_users = sucuriscan_get_online_users();
|
9472 |
|
9473 |
// Remove the specified user identifier from the list.
|
9474 |
+
if ( is_array( $logged_in_users ) && ! empty($logged_in_users) ){
|
9475 |
+
foreach ( $logged_in_users as $i => $user ){
|
9476 |
+
if (
|
9477 |
+
$user['user_id'] == $user_id
|
9478 |
+
&& strcmp( $user['remote_addr'], $remote_addr ) == 0
|
9479 |
){
|
9480 |
+
unset($logged_in_users[ $i ]);
|
9481 |
break;
|
9482 |
}
|
9483 |
}
|
9484 |
}
|
9485 |
|
9486 |
+
return sucuriscan_save_online_users( $logged_in_users );
|
9487 |
}
|
9488 |
|
9489 |
+
if ( ! function_exists( 'sucuriscan_set_online_user' ) ){
|
9490 |
/**
|
9491 |
* Add an user account to the list of registered users in session.
|
9492 |
*
|
9494 |
* @param boolean $user The WordPress object containing all the information associated to the user.
|
9495 |
* @return void
|
9496 |
*/
|
9497 |
+
function sucuriscan_set_online_user( $user_login = '', $user = false ){
|
9498 |
+
if ( $user ){
|
9499 |
// Get logged in user information.
|
9500 |
$current_user = ($user instanceof WP_User) ? $user : wp_get_current_user();
|
9501 |
$current_user_id = $current_user->ID;
|
9502 |
$remote_addr = SucuriScan::get_remote_addr();
|
9503 |
+
$current_time = current_time( 'timestamp' );
|
9504 |
$logged_in_users = sucuriscan_get_online_users();
|
9505 |
|
9506 |
// Build the dataset array that will be stored in the transient variable.
|
9513 |
'remote_addr' => $remote_addr,
|
9514 |
);
|
9515 |
|
9516 |
+
if ( ! is_array( $logged_in_users ) || empty($logged_in_users) ){
|
9517 |
$logged_in_users = array( $current_user_info );
|
9518 |
+
sucuriscan_save_online_users( $logged_in_users );
|
9519 |
} else {
|
9520 |
+
$do_nothing = false;
|
9521 |
+
$update_existing = false;
|
9522 |
$item_index = 0;
|
9523 |
|
9524 |
// Check if the user is already in the logged-in-user list and update it if is necessary.
|
9525 |
+
foreach ( $logged_in_users as $i => $user ){
|
9526 |
+
if (
|
9527 |
$user['user_id'] == $current_user_id
|
9528 |
+
&& strcmp( $user['remote_addr'], $remote_addr ) == 0
|
9529 |
){
|
9530 |
+
if ( $user['last_activity'] < ($current_time - (15 * 60)) ){
|
9531 |
+
$update_existing = true;
|
9532 |
$item_index = $i;
|
9533 |
break;
|
9534 |
} else {
|
9535 |
+
$do_nothing = true;
|
9536 |
break;
|
9537 |
}
|
9538 |
}
|
9539 |
}
|
9540 |
|
9541 |
+
if ( $update_existing ){
|
9542 |
+
$logged_in_users[ $item_index ] = $current_user_info;
|
9543 |
+
sucuriscan_save_online_users( $logged_in_users );
|
9544 |
+
} elseif ( $do_nothing ){
|
9545 |
// Do nothing.
|
9546 |
} else {
|
9547 |
$logged_in_users[] = $current_user_info;
|
9548 |
+
sucuriscan_save_online_users( $logged_in_users );
|
9549 |
}
|
9550 |
}
|
9551 |
}
|
9552 |
}
|
9553 |
|
9554 |
+
add_action( 'wp_login', 'sucuriscan_set_online_user', 10, 2 );
|
9555 |
}
|
9556 |
|
9557 |
/**
|
9569 |
'FailedLogins.CollectPasswordsVisibility' => 'visible',
|
9570 |
);
|
9571 |
|
9572 |
+
$max_failed_logins = SucuriScanOption::get_option( ':maximum_failed_logins' );
|
9573 |
+
$notify_bruteforce_attack = SucuriScanOption::get_option( ':notify_bruteforce_attack' );
|
9574 |
$failed_logins = sucuriscan_get_failed_logins();
|
9575 |
+
$old_failed_logins = sucuriscan_get_failed_logins( true );
|
9576 |
|
9577 |
// Merge the new and old failed logins.
|
9578 |
if (
|
9579 |
+
is_array( $old_failed_logins )
|
9580 |
+
&& ! empty($old_failed_logins)
|
9581 |
) {
|
9582 |
if (
|
9583 |
+
is_array( $failed_logins )
|
9584 |
+
&& ! empty($failed_logins)
|
9585 |
) {
|
9586 |
$failed_logins = array_merge( $failed_logins, $old_failed_logins );
|
9587 |
} else {
|
9589 |
}
|
9590 |
}
|
9591 |
|
9592 |
+
if ( $failed_logins ){
|
9593 |
$counter = 0;
|
9594 |
|
9595 |
+
foreach ( $failed_logins['entries'] as $login_data ){
|
9596 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
9597 |
$wrong_user_password = '<span class="sucuriscan-label-default">hidden</span>';
|
9598 |
|
9599 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
9600 |
if (
|
9601 |
isset($login_data['user_password'])
|
9602 |
+
&& ! empty($login_data['user_password'])
|
9603 |
) {
|
9604 |
+
$wrong_user_password = SucuriScan::escape( $login_data['user_password'] );
|
9605 |
}
|
9606 |
|
9607 |
else {
|
9612 |
$template_variables['FailedLogins.List'] .= SucuriScanTemplate::get_snippet('lastlogins-failedlogins', array(
|
9613 |
'FailedLogins.CssClass' => $css_class,
|
9614 |
'FailedLogins.Num' => ($counter + 1),
|
9615 |
+
'FailedLogins.Username' => SucuriScan::escape( $login_data['user_login'] ),
|
9616 |
'FailedLogins.Password' => $wrong_user_password,
|
9617 |
+
'FailedLogins.RemoteAddr' => SucuriScan::escape( $login_data['remote_addr'] ),
|
9618 |
+
'FailedLogins.Datetime' => SucuriScan::datetime( $login_data['attempt_time'] ),
|
9619 |
+
'FailedLogins.UserAgent' => SucuriScan::escape( $login_data['user_agent'] ),
|
9620 |
));
|
9621 |
|
9622 |
$counter += 1;
|
9623 |
}
|
9624 |
|
9625 |
+
if ( $counter > 0 ){
|
9626 |
$template_variables['FailedLogins.NoItemsVisibility'] = 'hidden';
|
9627 |
}
|
9628 |
}
|
9629 |
|
9630 |
$template_variables['FailedLogins.MaxFailedLogins'] = $max_failed_logins;
|
9631 |
|
9632 |
+
if ( $notify_bruteforce_attack == 'enabled' ){
|
9633 |
$template_variables['FailedLogins.WarningVisibility'] = 'hidden';
|
9634 |
}
|
9635 |
|
9636 |
+
if ( sucuriscan_collect_wrong_passwords() !== true ){
|
9637 |
$template_variables['FailedLogins.CollectPasswordsVisibility'] = 'hidden';
|
9638 |
}
|
9639 |
|
9640 |
+
return SucuriScanTemplate::get_section( 'lastlogins-failedlogins', $template_variables );
|
9641 |
}
|
9642 |
|
9643 |
/**
|
9646 |
* @return boolean TRUE if the password must be collected, FALSE otherwise.
|
9647 |
*/
|
9648 |
function sucuriscan_collect_wrong_passwords(){
|
9649 |
+
return (bool) ( SucuriScanOption::get_option( ':collect_wrong_passwords' ) === 'enabled' );
|
9650 |
}
|
9651 |
|
9652 |
/**
|
9661 |
* @param boolean $reset Whether the file will be resetted or not.
|
9662 |
* @return string The full (relative) path where the file is located.
|
9663 |
*/
|
9664 |
+
function sucuriscan_failed_logins_datastore_path( $get_old_logs = false, $reset = false ){
|
9665 |
$file_name = $get_old_logs ? 'sucuri-oldfailedlogins.php' : 'sucuri-failedlogins.php';
|
9666 |
+
$datastore_path = SucuriScan::datastore_folder_path( $file_name );
|
9667 |
$default_content = sucuriscan_failed_logins_default_content();
|
9668 |
|
9669 |
// Create the file if it does not exists.
|
9670 |
+
if ( ! file_exists( $datastore_path ) || $reset ){
|
9671 |
@file_put_contents( $datastore_path, $default_content, LOCK_EX );
|
9672 |
}
|
9673 |
|
9674 |
// Return the datastore path if the file exists (or was created).
|
9675 |
+
if (
|
9676 |
+
file_exists( $datastore_path )
|
9677 |
+
&& is_readable( $datastore_path )
|
9678 |
){
|
9679 |
return $datastore_path;
|
9680 |
}
|
9681 |
|
9682 |
+
return false;
|
9683 |
}
|
9684 |
|
9685 |
/**
|
9704 |
* @param boolean $get_old_logs Whether the old logs will be retrieved or not.
|
9705 |
* @return array Information and entries gathered from the failed logins datastore file.
|
9706 |
*/
|
9707 |
+
function sucuriscan_get_failed_logins( $get_old_logs = false ){
|
9708 |
+
$datastore_path = sucuriscan_failed_logins_datastore_path( $get_old_logs );
|
9709 |
$default_content = sucuriscan_failed_logins_default_content();
|
9710 |
+
$default_content_n = substr_count( $default_content, "\n" );
|
9711 |
|
9712 |
+
if ( $datastore_path ){
|
9713 |
+
$lines = SucuriScanFileInfo::file_lines( $datastore_path );
|
9714 |
|
9715 |
+
if ( $lines ){
|
9716 |
$failed_logins = array(
|
9717 |
'count' => 0,
|
9718 |
'first_attempt' => 0,
|
9722 |
);
|
9723 |
|
9724 |
// Read and parse all the entries found in the datastore file.
|
9725 |
+
foreach ( $lines as $i => $line ){
|
9726 |
+
if ( $i >= $default_content_n ){
|
9727 |
+
$login_data = @json_decode( trim( $line ), true );
|
9728 |
+
$login_data['attempt_date'] = date( 'r', $login_data['attempt_time'] );
|
9729 |
|
9730 |
+
if ( ! $login_data['user_agent'] ){
|
9731 |
$login_data['user_agent'] = 'Unknown';
|
9732 |
}
|
9733 |
|
9734 |
+
if ( ! isset($login_data['user_password']) ) {
|
9735 |
$login_data['user_password'] = '';
|
9736 |
}
|
9737 |
|
9741 |
}
|
9742 |
|
9743 |
// Calculate the different time between the first and last attempt.
|
9744 |
+
if ( $failed_logins['count'] > 0 ){
|
9745 |
+
$z = abs( $failed_logins['count'] - 1 );
|
9746 |
+
$failed_logins['last_attempt'] = $failed_logins['entries'][ $z ]['attempt_time'];
|
9747 |
$failed_logins['first_attempt'] = $failed_logins['entries'][0]['attempt_time'];
|
9748 |
$failed_logins['diff_time'] = abs( $failed_logins['last_attempt'] - $failed_logins['first_attempt'] );
|
9749 |
|
9752 |
}
|
9753 |
}
|
9754 |
|
9755 |
+
return false;
|
9756 |
}
|
9757 |
|
9758 |
|
9765 |
* @param string $wrong_password Wrong password used during the supposed attack.
|
9766 |
* @return boolean Whether the information of the current failed login event was stored or not.
|
9767 |
*/
|
9768 |
+
function sucuriscan_log_failed_login( $user_login = '', $wrong_password = '' ){
|
9769 |
$datastore_path = sucuriscan_failed_logins_datastore_path();
|
9770 |
|
9771 |
// Do not collect wrong passwords if it is not necessary.
|
9773 |
$wrong_password = '';
|
9774 |
}
|
9775 |
|
9776 |
+
if ( $datastore_path ){
|
9777 |
$login_data = json_encode(array(
|
9778 |
'user_login' => $user_login,
|
9779 |
'user_password' => $wrong_password,
|
9787 |
return $logged;
|
9788 |
}
|
9789 |
|
9790 |
+
return false;
|
9791 |
}
|
9792 |
|
9793 |
/**
|
9799 |
* @param array $failed_logins Information and entries gathered from the failed logins datastore file.
|
9800 |
* @return boolean Whether the report was sent via email or not.
|
9801 |
*/
|
9802 |
+
function sucuriscan_report_failed_logins( $failed_logins = array() ){
|
9803 |
+
if ( $failed_logins && $failed_logins['count'] > 0 ){
|
9804 |
$prettify_mails = SucuriScanMail::prettify_mails();
|
9805 |
$collect_wrong_passwords = sucuriscan_collect_wrong_passwords();
|
9806 |
$mail_content = '';
|
9807 |
|
9808 |
+
if ( $prettify_mails ){
|
9809 |
$table_html = '<table border="1" cellspacing="0" cellpadding="0">';
|
9810 |
|
9811 |
// Add the table headers.
|
9826 |
$table_html .= '<tbody>';
|
9827 |
}
|
9828 |
|
9829 |
+
foreach ( $failed_logins['entries'] as $login_data ){
|
9830 |
+
if ( $prettify_mails ){
|
9831 |
$table_html .= '<tr>';
|
9832 |
+
$table_html .= '<td>' . esc_attr( $login_data['user_login'] ) . '</td>';
|
9833 |
|
9834 |
if ( $collect_wrong_passwords === true ) {
|
9835 |
+
$table_html .= '<td>' . esc_attr( $login_data['user_password'] ) . '</td>';
|
9836 |
}
|
9837 |
|
9838 |
+
$table_html .= '<td>' . esc_attr( $login_data['remote_addr'] ) . '</td>';
|
9839 |
$table_html .= '<td>' . $login_data['attempt_time'] . '</td>';
|
9840 |
$table_html .= '<td>' . $login_data['attempt_date'] . '</td>';
|
9841 |
$table_html .= '</tr>';
|
9853 |
}
|
9854 |
}
|
9855 |
|
9856 |
+
if ( $prettify_mails ){
|
9857 |
$table_html .= '</tbody>';
|
9858 |
$table_html .= '</table>';
|
9859 |
$mail_content = $table_html;
|
9860 |
}
|
9861 |
|
9862 |
+
if ( SucuriScanEvent::notify_event( 'bruteforce_attack', $mail_content ) ){
|
9863 |
sucuriscan_reset_failed_logins();
|
9864 |
|
9865 |
+
return true;
|
9866 |
}
|
9867 |
}
|
9868 |
|
9869 |
+
return false;
|
9870 |
}
|
9871 |
|
9872 |
/**
|
9878 |
* @return boolean Whether the datastore file was resetted or not.
|
9879 |
*/
|
9880 |
function sucuriscan_reset_failed_logins(){
|
9881 |
+
$datastore_path = SucuriScan::datastore_folder_path( 'sucuri-failedlogins.php' );
|
9882 |
+
$datastore_backup_path = sucuriscan_failed_logins_datastore_path( true, false );
|
9883 |
$default_content = sucuriscan_failed_logins_default_content();
|
9884 |
+
$current_content = @file_get_contents( $datastore_path );
|
9885 |
$current_content = str_replace( $default_content, '', $current_content );
|
9886 |
|
9887 |
@file_put_contents(
|
9890 |
FILE_APPEND
|
9891 |
);
|
9892 |
|
9893 |
+
return (bool) sucuriscan_failed_logins_datastore_path( false, true );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9894 |
}
|
9895 |
|
9896 |
/**
|
9901 |
* @param boolean $page_nonce True if the nonce is valid, False otherwise.
|
9902 |
* @return void
|
9903 |
*/
|
9904 |
+
function sucuriscan_settings_form_submissions( $page_nonce = null ){
|
9905 |
global $sucuriscan_schedule_allowed,
|
9906 |
$sucuriscan_interface_allowed,
|
9907 |
$sucuriscan_notify_options,
|
9911 |
$sucuriscan_verify_ssl_cert;
|
9912 |
|
9913 |
// Use this conditional to avoid double checking.
|
9914 |
+
if ( is_null( $page_nonce ) ){
|
9915 |
$page_nonce = SucuriScanInterface::check_nonce();
|
9916 |
}
|
9917 |
|
9918 |
+
if ( $page_nonce ){
|
9919 |
|
9920 |
// Recover API key through the email registered previously.
|
9921 |
+
if ( SucuriScanRequest::post( ':recover_key' ) !== false ){
|
9922 |
SucuriScanAPI::recover_key();
|
9923 |
+
SucuriScanEvent::report_info_event( 'Recovery of the Sucuri API key was requested.' );
|
9924 |
}
|
9925 |
|
9926 |
// Save API key after it was recovered by the administrator.
|
9927 |
+
if ( $api_key = SucuriScanRequest::post( ':manual_api_key' ) ){
|
9928 |
+
SucuriScanAPI::set_plugin_key( $api_key, true );
|
9929 |
SucuriScanEvent::schedule_task();
|
9930 |
+
SucuriScanEvent::report_info_event( 'Sucuri API key was added manually.' );
|
9931 |
}
|
9932 |
|
9933 |
// Remove API key from the local storage.
|
9934 |
+
if ( SucuriScanRequest::post( ':remove_api_key' ) !== false ){
|
9935 |
+
SucuriScanAPI::set_plugin_key( '' );
|
9936 |
+
wp_clear_scheduled_hook( 'sucuriscan_scheduled_scan' );
|
9937 |
+
SucuriScanEvent::report_critical_event( 'Sucuri API key was deleted.' );
|
9938 |
SucuriScanEvent::notify_event( 'plugin_change', 'Sucuri API key removed' );
|
9939 |
}
|
9940 |
|
9941 |
// Enable or disable the filesystem scanner.
|
9942 |
+
if ( $fs_scanner = SucuriScanRequest::post( ':fs_scanner', '(en|dis)able' ) ){
|
9943 |
$action_d = $fs_scanner . 'd';
|
9944 |
+
$message = 'Main file system scanner was <code>' . $action_d . '</code>';
|
9945 |
+
|
9946 |
+
SucuriScanOption::update_option( ':fs_scanner', $action_d );
|
9947 |
+
SucuriScanEvent::report_auto_event( $message );
|
9948 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9949 |
+
SucuriScanInterface::info( $message );
|
9950 |
}
|
9951 |
|
9952 |
// Enable or disable the filesystem scanner for modified files.
|
9953 |
+
if ( $scan_modfiles = SucuriScanRequest::post( ':scan_modfiles', '(en|dis)able' ) ){
|
9954 |
$action_d = $scan_modfiles . 'd';
|
9955 |
+
$message = 'File system scanner for modified files was <code>' . $action_d . '</code>';
|
9956 |
+
|
9957 |
+
SucuriScanOption::update_option( ':scan_modfiles', $action_d );
|
9958 |
+
SucuriScanEvent::report_auto_event( $message );
|
9959 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9960 |
+
SucuriScanInterface::info( $message );
|
9961 |
}
|
9962 |
|
9963 |
// Enable or disable the filesystem scanner for file integrity.
|
9964 |
+
if ( $scan_checksums = SucuriScanRequest::post( ':scan_checksums', '(en|dis)able' ) ){
|
9965 |
$action_d = $scan_checksums . 'd';
|
9966 |
+
$message = 'File system scanner for file integrity was <code>' . $action_d . '</code>';
|
9967 |
+
|
9968 |
+
SucuriScanOption::update_option( ':scan_checksums', $action_d );
|
9969 |
+
SucuriScanEvent::report_auto_event( $message );
|
9970 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9971 |
+
SucuriScanInterface::info( $message );
|
9972 |
}
|
9973 |
|
9974 |
// Enable or disable the filesystem scanner for error logs.
|
9975 |
+
if ( $ignore_scanning = SucuriScanRequest::post( ':ignore_scanning', '(en|dis)able' ) ){
|
9976 |
$action_d = $ignore_scanning . 'd';
|
9977 |
+
$message = 'File system scanner rules to ignore directories was <code>' . $action_d . '</code>';
|
9978 |
+
|
9979 |
+
SucuriScanOption::update_option( ':ignore_scanning', $action_d );
|
9980 |
+
SucuriScanEvent::report_auto_event( $message );
|
9981 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9982 |
+
SucuriScanInterface::info( $message );
|
9983 |
}
|
9984 |
|
9985 |
// Enable or disable the filesystem scanner for error logs.
|
9986 |
+
if ( $scan_errorlogs = SucuriScanRequest::post( ':scan_errorlogs', '(en|dis)able' ) ){
|
9987 |
$action_d = $scan_errorlogs . 'd';
|
9988 |
+
$message = 'File system scanner for error logs was <code>' . $action_d . '</code>';
|
9989 |
+
|
9990 |
+
SucuriScanOption::update_option( ':scan_errorlogs', $action_d );
|
9991 |
+
SucuriScanEvent::report_auto_event( $message );
|
9992 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
9993 |
+
SucuriScanInterface::info( $message );
|
9994 |
}
|
9995 |
|
9996 |
// Enable or disable the error logs parsing.
|
9997 |
+
if ( $parse_errorlogs = SucuriScanRequest::post( ':parse_errorlogs', '(en|dis)able' ) ){
|
9998 |
$action_d = $parse_errorlogs . 'd';
|
9999 |
+
$message = 'Analysis of main error log file was <code>' . $action_d . '</code>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10000 |
|
10001 |
+
SucuriScanOption::update_option( ':parse_errorlogs', $action_d );
|
10002 |
+
SucuriScanEvent::report_auto_event( $message );
|
10003 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10004 |
+
SucuriScanInterface::info( $message );
|
10005 |
}
|
10006 |
|
10007 |
// Enable or disable the SiteCheck scanner and the malware scan page.
|
10008 |
+
if ( $sitecheck_scanner = SucuriScanRequest::post( ':sitecheck_scanner', '(en|dis)able' ) ){
|
10009 |
$action_d = $sitecheck_scanner . 'd';
|
10010 |
+
$message = 'SiteCheck malware and blacklist scanner was <code>' . $action_d . '</code>';
|
10011 |
+
|
10012 |
+
SucuriScanOption::update_option( ':sitecheck_scanner', $action_d );
|
10013 |
+
SucuriScanEvent::report_auto_event( $message );
|
10014 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10015 |
+
SucuriScanInterface::info( $message );
|
10016 |
}
|
10017 |
|
10018 |
// Modify the schedule of the filesystem scanner.
|
10019 |
+
if ( $frequency = SucuriScanRequest::post( ':scan_frequency' ) ){
|
10020 |
+
if ( array_key_exists( $frequency, $sucuriscan_schedule_allowed ) ){
|
10021 |
+
SucuriScanOption::update_option( ':scan_frequency', $frequency );
|
10022 |
+
wp_clear_scheduled_hook( 'sucuriscan_scheduled_scan' );
|
10023 |
|
10024 |
+
if ( $frequency != '_oneoff' ){
|
10025 |
+
wp_schedule_event( time() + 10, $frequency, 'sucuriscan_scheduled_scan' );
|
10026 |
}
|
10027 |
|
10028 |
+
$frequency_title = strtolower( $sucuriscan_schedule_allowed[ $frequency ] );
|
10029 |
+
$message = 'File system scanning frequency set to <code>' . $frequency_title . '</code>';
|
10030 |
+
|
10031 |
+
SucuriScanEvent::report_info_event( $message );
|
10032 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10033 |
+
SucuriScanInterface::info( $message );
|
10034 |
}
|
10035 |
}
|
10036 |
|
10037 |
// Set the method (aka. interface) that will be used to scan the site.
|
10038 |
+
if ( $interface = SucuriScanRequest::post( ':scan_interface' ) ){
|
10039 |
+
$allowed_values = array_keys( $sucuriscan_interface_allowed );
|
10040 |
+
|
10041 |
+
if ( in_array( $interface, $allowed_values ) ){
|
10042 |
+
$message = 'File system scanning interface set to <code>' . $interface . '</code>';
|
10043 |
+
|
10044 |
+
SucuriScanOption::update_option( ':scan_interface', $interface );
|
10045 |
+
SucuriScanEvent::report_info_event( $message );
|
10046 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10047 |
+
SucuriScanInterface::info( $message );
|
10048 |
+
}
|
10049 |
+
}
|
10050 |
+
|
10051 |
+
// Update the limit of error log lines to parse.
|
10052 |
+
if ( $errorlogs_limit = SucuriScanRequest::post( ':errorlogs_limit', '[0-9]+' ) ){
|
10053 |
+
if ( $errorlogs_limit > 1000 ) {
|
10054 |
+
SucuriScanInterface::error( 'Analyze more than 1,000 lines will take too much time.' );
|
10055 |
+
} else {
|
10056 |
+
SucuriScanOption::update_option( ':errorlogs_limit', $errorlogs_limit );
|
10057 |
+
SucuriScanInterface::info( 'Analyze last <code>' . $errorlogs_limit . '</code> entries encountered in the error logs.' );
|
10058 |
+
|
10059 |
+
if ( $errorlogs_limit == 0 ) {
|
10060 |
+
SucuriScanOption::update_option( ':parse_errorlogs', 'disabled' );
|
10061 |
+
}
|
10062 |
+
}
|
10063 |
+
}
|
10064 |
+
|
10065 |
+
// Reset the plugin security logs.
|
10066 |
+
$allowed_log_files = '(integrity|lastlogins|failedlogins|sitecheck)';
|
10067 |
+
if ( $reset_logfile = SucuriScanRequest::post( ':reset_logfile', $allowed_log_files ) ){
|
10068 |
+
$files_to_delete = array(
|
10069 |
+
'sucuri-' . $reset_logfile . '.php',
|
10070 |
+
'sucuri-old' . $reset_logfile . '.php',
|
10071 |
+
);
|
10072 |
+
|
10073 |
+
foreach ( $files_to_delete as $log_filename ) {
|
10074 |
+
$log_filepath = SucuriScan::datastore_folder_path( $log_filename );
|
10075 |
|
10076 |
+
if ( @unlink( $log_filepath ) ) {
|
10077 |
+
$log_filename_simple = str_replace( '.php', '', $log_filename );
|
10078 |
+
$message = 'Deleted security log <code>' . $log_filename_simple . '</code>';
|
10079 |
+
|
10080 |
+
SucuriScanEvent::report_debug_event( $message );
|
10081 |
+
SucuriScanInterface::info( $message );
|
10082 |
+
}
|
10083 |
}
|
10084 |
}
|
10085 |
|
10086 |
// Update the value for the maximum emails per hour.
|
10087 |
+
if ( $per_hour = SucuriScanRequest::post( ':emails_per_hour' ) ){
|
10088 |
+
if ( array_key_exists( $per_hour, $sucuriscan_emails_per_hour ) ){
|
10089 |
+
$per_hour_label = strtolower( $sucuriscan_emails_per_hour[ $per_hour ] );
|
10090 |
+
$message = 'Maximum email alerts per hour set to <code>' . $per_hour_label . '</code>';
|
10091 |
+
|
10092 |
SucuriScanOption::update_option( ':emails_per_hour', $per_hour );
|
10093 |
+
SucuriScanEvent::report_info_event( $message );
|
10094 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10095 |
+
SucuriScanInterface::info( $message );
|
10096 |
} else {
|
10097 |
SucuriScanInterface::error( 'Invalid value for the maximum emails per hour.' );
|
10098 |
}
|
10099 |
}
|
10100 |
|
10101 |
// Update the email where the event notifications will be sent.
|
10102 |
+
if ( $new_email = SucuriScanRequest::post( ':notify_to' ) ){
|
10103 |
+
$valid_email = SucuriScan::get_valid_email( $new_email );
|
10104 |
+
|
10105 |
+
if ( $valid_email ){
|
10106 |
+
$message = 'Sucuri alerts will be sent to this email: <code>' . $valid_email . '</code>';
|
10107 |
|
|
|
10108 |
SucuriScanOption::update_option( ':notify_to', $valid_email );
|
10109 |
+
SucuriScanEvent::report_info_event( $message );
|
10110 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10111 |
+
SucuriScanInterface::info( $message );
|
10112 |
} else {
|
10113 |
SucuriScanInterface::error( 'Email format not supported.' );
|
10114 |
}
|
10115 |
}
|
10116 |
|
10117 |
// Update the maximum failed logins per hour before consider it a brute-force attack.
|
10118 |
+
if ( $failed_logins = SucuriScanRequest::post( ':maximum_failed_logins' ) ){
|
10119 |
+
if ( array_key_exists( $failed_logins, $sucuriscan_maximum_failed_logins ) ){
|
10120 |
+
$message = 'Consider brute-force attack after <code>' . $failed_logins . '</code> failed logins per hour';
|
10121 |
+
|
10122 |
SucuriScanOption::update_option( ':maximum_failed_logins', $failed_logins );
|
10123 |
+
SucuriScanEvent::report_info_event( $message );
|
|
|
|
|
|
|
10124 |
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10125 |
+
SucuriScanInterface::info( $message );
|
10126 |
} else {
|
10127 |
SucuriScanInterface::error( 'Invalid value for the maximum failed logins per hour before consider it a brute-force attack.' );
|
10128 |
}
|
10129 |
}
|
10130 |
|
10131 |
// Update the configuration for the SSL certificate verification.
|
10132 |
+
if ( $verify_ssl_cert = SucuriScanRequest::post( ':verify_ssl_cert' ) ){
|
10133 |
+
if ( array_key_exists( $verify_ssl_cert, $sucuriscan_verify_ssl_cert ) ){
|
10134 |
+
$message = 'SSL certificate verification for API calls set to <code>' . $verify_ssl_cert . '</code>';
|
10135 |
+
|
10136 |
SucuriScanOption::update_option( ':verify_ssl_cert', $verify_ssl_cert );
|
10137 |
+
SucuriScanEvent::report_warning_event( $message );
|
|
|
|
|
10138 |
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10139 |
SucuriScanInterface::info( $message );
|
10140 |
} else {
|
10142 |
}
|
10143 |
}
|
10144 |
|
10145 |
+
// Enable or disable the audit logs report.
|
10146 |
+
if ( $audit_report = SucuriScanRequest::post( ':audit_report', '(en|dis)able' ) ){
|
10147 |
+
$action_d = $audit_report . 'd';
|
10148 |
+
$message = 'Audit logs report was <code>' . $action_d . '</code>';
|
10149 |
+
|
10150 |
+
SucuriScanOption::update_option( ':audit_report', $action_d );
|
10151 |
+
SucuriScanEvent::report_info_event( $message );
|
10152 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10153 |
+
SucuriScanInterface::info( $message );
|
10154 |
+
}
|
10155 |
+
|
10156 |
+
// Enable or disable the reverse proxy support.
|
10157 |
+
if ( $revproxy = SucuriScanRequest::post( ':revproxy', '(en|dis)able' ) ){
|
10158 |
+
$action_d = $revproxy . 'd';
|
10159 |
+
$message = 'Reverse proxy support was <code>' . $action_d . '</code>';
|
10160 |
+
|
10161 |
+
SucuriScanOption::update_option( ':revproxy', $action_d );
|
10162 |
+
SucuriScanEvent::report_info_event( $message );
|
10163 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10164 |
+
SucuriScanInterface::info( $message );
|
10165 |
+
}
|
10166 |
+
|
10167 |
+
// Update the limit for audit logs report.
|
10168 |
+
if ( $logs4report = SucuriScanRequest::post( ':logs4report', '[0-9]{1,4}' ) ){
|
10169 |
+
$message = 'Limit for audit logs report set to <code>' . $logs4report . '</code>';
|
10170 |
+
|
10171 |
+
SucuriScanOption::update_option( ':logs4report', $logs4report );
|
10172 |
+
SucuriScanEvent::report_info_event( $message );
|
10173 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10174 |
+
SucuriScanInterface::info( $message );
|
10175 |
+
}
|
10176 |
+
|
10177 |
// Update the API request timeout.
|
10178 |
+
if ( $request_timeout = SucuriScanRequest::post( ':request_timeout', '[0-9]+' ) ){
|
10179 |
+
$message = 'API request timeout set to <code>' . $request_timeout . '</code> seconds.';
|
10180 |
+
|
10181 |
+
SucuriScanOption::update_option( ':request_timeout', $request_timeout );
|
10182 |
+
SucuriScanEvent::report_info_event( $message );
|
10183 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10184 |
+
SucuriScanInterface::info( $message );
|
10185 |
}
|
10186 |
|
10187 |
// Update the collection of failed passwords settings.
|
10188 |
+
if ( $collect_wrong_passwords = SucuriScanRequest::post( ':collect_wrong_passwords' ) ){
|
10189 |
+
$collect_wrong_passwords = strtolower( $collect_wrong_passwords );
|
10190 |
+
$message = 'Collect failed login passwords set to <code>%s</code>';
|
10191 |
|
10192 |
if ( $collect_wrong_passwords == 'yes' ) {
|
10193 |
$collect_action = 'enabled';
|
10194 |
+
$message = sprintf( $message, $collect_action );
|
10195 |
+
SucuriScanEvent::report_critical_event( $message );
|
10196 |
+
} else {
|
10197 |
+
$collect_action = 'disabled';
|
10198 |
+
$message = sprintf( $message, $collect_action );
|
10199 |
+
SucuriScanEvent::report_info_event( $message );
|
10200 |
}
|
10201 |
|
10202 |
+
SucuriScanOption::update_option( ':collect_wrong_passwords', $collect_action );
|
10203 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10204 |
+
SucuriScanInterface::info( $message );
|
10205 |
}
|
10206 |
|
10207 |
// Update the datastore path (if the new directory exists).
|
10208 |
+
if ( $datastore_path = SucuriScanRequest::post( ':datastore_path' ) ){
|
10209 |
$current_datastore_path = SucuriScanOption::datastore_folder_path();
|
10210 |
|
10211 |
+
if ( file_exists( $datastore_path ) ) {
|
10212 |
+
if ( is_writable( $datastore_path ) ) {
|
10213 |
+
$message = 'Datastore path set to <code>' . $datastore_path . '</code>';
|
|
|
10214 |
|
10215 |
+
SucuriScanOption::update_option( ':datastore_path', $datastore_path );
|
10216 |
+
SucuriScanEvent::report_info_event( $message );
|
10217 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10218 |
+
SucuriScanInterface::info( $message );
|
10219 |
+
|
10220 |
+
if ( file_exists( $current_datastore_path ) ) {
|
10221 |
$new_datastore_path = SucuriScanOption::datastore_folder_path();
|
10222 |
@rename( $current_datastore_path, $new_datastore_path );
|
10223 |
}
|
10233 |
}
|
10234 |
}
|
10235 |
|
10236 |
+
// Update the advertisement visibility settings.
|
10237 |
+
if ( $ads_visibility = SucuriScanRequest::post( ':ads_visibility' ) ){
|
10238 |
+
$ads_visibility = strtolower( $ads_visibility );
|
10239 |
+
$option_value = ( $ads_visibility == 'hide' ) ? 'disabled' : 'enabled';
|
10240 |
+
$message = sprintf( 'Plugin advertisement set to <code>%s</code>', $option_value );
|
10241 |
+
|
10242 |
+
SucuriScanOption::update_option( ':ads_visibility', $option_value );
|
10243 |
+
SucuriScanEvent::report_info_event( $message );
|
10244 |
+
SucuriScanInterface::info( $message );
|
10245 |
+
}
|
10246 |
+
|
10247 |
// Update the notification settings.
|
10248 |
+
if ( SucuriScanRequest::post( ':save_notification_settings' ) !== false ){
|
10249 |
$options_updated_counter = 0;
|
10250 |
|
10251 |
+
foreach ( $sucuriscan_notify_options as $alert_type => $alert_label ){
|
10252 |
+
$option_value = SucuriScanRequest::post( $alert_type, '(1|0)' );
|
10253 |
|
10254 |
+
if ( $option_value !== false ){
|
10255 |
+
$current_value = SucuriScanOption::get_option( $alert_type );
|
10256 |
$option_value = ( $option_value == '1' ) ? 'enabled' : 'disabled';
|
10257 |
+
|
10258 |
+
// Check that the option value was actually changed.
|
10259 |
+
if ( $current_value !== $option_value ) {
|
10260 |
+
SucuriScanOption::update_option( $alert_type, $option_value );
|
10261 |
+
$options_updated_counter += 1;
|
10262 |
+
}
|
10263 |
}
|
10264 |
}
|
10265 |
|
10266 |
+
if ( $options_updated_counter > 0 ){
|
10267 |
+
$message = 'Alert settings were changed <code>' . $options_updated_counter . ' options</code>';
|
10268 |
+
|
10269 |
+
SucuriScanEvent::report_info_event( $message );
|
10270 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10271 |
+
SucuriScanInterface::info( $message );
|
10272 |
}
|
10273 |
}
|
10274 |
|
10275 |
// Update the subject format for the email alerts.
|
10276 |
+
if ( $email_subject = SucuriScanRequest::post( ':email_subject' ) ) {
|
10277 |
$new_email_subject = false;
|
10278 |
+
$current_value = SucuriScanOption::get_option( ':email_subject' );
|
10279 |
|
10280 |
// Validate the custom subject format.
|
10281 |
if ( $email_subject == 'custom' ) {
|
10282 |
$format_pattern = '/^[0-9a-zA-Z:,\s]+$/';
|
10283 |
+
$custom_email_subject = SucuriScanRequest::post( ':custom_email_subject' );
|
10284 |
|
10285 |
if (
|
10286 |
$custom_email_subject !== false
|
10287 |
+
&& ! empty($custom_email_subject)
|
10288 |
&& preg_match( $format_pattern, $custom_email_subject )
|
10289 |
) {
|
10290 |
+
$new_email_subject = trim( $custom_email_subject );
|
10291 |
} else {
|
10292 |
SucuriScanInterface::error( 'Invalid characters found in the email alert subject format.' );
|
10293 |
}
|
10295 |
|
10296 |
// Check if the email subject format is allowed.
|
10297 |
elseif (
|
10298 |
+
is_array( $sucuriscan_email_subjects )
|
10299 |
&& in_array( $email_subject, $sucuriscan_email_subjects )
|
10300 |
) {
|
10301 |
+
$new_email_subject = trim( $email_subject );
|
10302 |
}
|
10303 |
|
10304 |
// Proceed with the operation saving the new subject.
|
10305 |
+
if (
|
10306 |
+
$new_email_subject !== false
|
10307 |
+
&& $current_value !== $new_email_subject
|
10308 |
+
) {
|
10309 |
+
$message = 'Alert subject format set to <code>' . $new_email_subject . '</code>';
|
10310 |
+
|
10311 |
SucuriScanOption::update_option( ':email_subject', $new_email_subject );
|
10312 |
+
SucuriScanEvent::report_info_event( $message );
|
10313 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10314 |
+
SucuriScanInterface::info( $message );
|
10315 |
}
|
10316 |
}
|
10317 |
|
10318 |
// Reset all the plugin's options.
|
10319 |
+
if ( SucuriScanRequest::post( ':reset_options' ) !== false ){
|
10320 |
// Notify the event before the API key is removed.
|
10321 |
+
$message = 'Sucuri plugin options were reset';
|
10322 |
+
SucuriScanEvent::report_critical_event( $message );
|
10323 |
+
SucuriScanEvent::notify_event( 'plugin_change', $message );
|
10324 |
|
10325 |
+
// Remove all plugin options from the database.
|
10326 |
+
SucuriScanOption::delete_plugin_options();
|
|
|
|
|
|
|
|
|
10327 |
|
10328 |
// Remove the scheduled tasks.
|
10329 |
+
wp_clear_scheduled_hook( 'sucuriscan_scheduled_scan' );
|
10330 |
|
10331 |
+
SucuriScanInterface::info( 'All plugin options were reset successfully' );
|
10332 |
}
|
10333 |
|
10334 |
// Ignore a new event for email notifications.
|
10335 |
+
if ( $action = SucuriScanRequest::post( ':ignorerule_action', '(add|remove)' ) ){
|
10336 |
+
$ignore_rule = SucuriScanRequest::post( ':ignorerule' );
|
10337 |
|
10338 |
+
if ( $action == 'add' ){
|
10339 |
+
if ( SucuriScanOption::add_ignored_event( $ignore_rule ) ){
|
10340 |
SucuriScanInterface::info( 'Post-type ignored successfully.' );
|
10341 |
+
SucuriScanEvent::report_warning_event( 'Changes in <code>' . $ignore_rule . '</code> post-type will be ignored' );
|
10342 |
} else {
|
10343 |
SucuriScanInterface::error( 'The post-type is invalid or it may be already ignored.' );
|
10344 |
}
|
10345 |
}
|
10346 |
|
10347 |
+
elseif ( $action == 'remove' ) {
|
10348 |
+
SucuriScanOption::remove_ignored_event( $ignore_rule );
|
10349 |
SucuriScanInterface::info( 'Post-type removed from the list successfully.' );
|
10350 |
+
SucuriScanEvent::report_notice_event( 'Changes in <code>' . $ignore_rule . '</code> post-type will not be ignored' );
|
10351 |
}
|
10352 |
}
|
10353 |
|
10354 |
// Ignore a new directory path for the file system scans.
|
10355 |
+
if ( $action = SucuriScanRequest::post( ':ignorescanning_action', '(ignore|unignore)' ) ){
|
10356 |
+
$ignore_directories = SucuriScanRequest::post( ':ignorescanning_dirs', '_array' );
|
10357 |
|
10358 |
+
if ( empty($ignore_directories) ){
|
10359 |
+
SucuriScanInterface::error( 'You did not choose a directory from the list.' );
|
10360 |
}
|
10361 |
|
10362 |
+
elseif ( $action == 'ignore' ){
|
10363 |
+
foreach ( $ignore_directories as $directory_path ){
|
10364 |
+
SucuriScanFSScanner::ignore_directory( $directory_path );
|
10365 |
}
|
10366 |
|
10367 |
+
SucuriScanInterface::info( 'Directories selected will be ignored in future scans.' );
|
10368 |
+
SucuriScanEvent::report_warning_event( sprintf(
|
10369 |
+
'Directories will not be scanned: (multiple entries): %s',
|
10370 |
+
@implode( ',', $ignore_directories )
|
10371 |
+
) );
|
10372 |
}
|
10373 |
|
10374 |
+
elseif ( $action == 'unignore' ) {
|
10375 |
+
foreach ( $ignore_directories as $directory_path ){
|
10376 |
+
SucuriScanFSScanner::unignore_directory( $directory_path );
|
10377 |
}
|
10378 |
|
10379 |
+
SucuriScanInterface::info( 'Directories selected will not be ignored anymore.' );
|
10380 |
+
SucuriScanEvent::report_notice_event( sprintf(
|
10381 |
+
'Directories will be scanned: (multiple entries): %s',
|
10382 |
+
@implode( ',', $ignore_directories )
|
10383 |
+
) );
|
10384 |
}
|
10385 |
}
|
10386 |
|
10387 |
// Trust and IP address to ignore notifications for a subnet.
|
10388 |
+
if ( $trust_ip = SucuriScanRequest::post( ':trust_ip' ) ){
|
10389 |
+
if (
|
10390 |
+
SucuriScan::is_valid_ip( $trust_ip )
|
10391 |
+
|| SucuriScan::is_valid_cidr( $trust_ip )
|
10392 |
){
|
10393 |
+
$cache = new SucuriScanCache( 'trustip' );
|
10394 |
+
$ip_info = SucuriScan::get_ip_info( $trust_ip );
|
10395 |
$ip_info['added_at'] = SucuriScan::local_time();
|
10396 |
+
$cache_key = md5( $ip_info['remote_addr'] );
|
10397 |
|
10398 |
+
if ( $cache->exists( $cache_key ) ) {
|
10399 |
SucuriScanInterface::error( 'The IP address specified was already trusted.' );
|
10400 |
} elseif ( $cache->add( $cache_key, $ip_info ) ) {
|
10401 |
+
$message = 'Changes from <code>' . $trust_ip . '</code> will be ignored';
|
10402 |
+
|
10403 |
+
SucuriScanEvent::report_warning_event( $message );
|
10404 |
+
SucuriScanInterface::info( $message );
|
10405 |
} else {
|
10406 |
SucuriScanInterface::error( 'The new entry was not saved in the datastore file.' );
|
10407 |
}
|
10409 |
}
|
10410 |
|
10411 |
// Trust and IP address to ignore notifications for a subnet.
|
10412 |
+
if ( $del_trust_ip = SucuriScanRequest::post( ':del_trust_ip', '_array' ) ){
|
10413 |
+
$cache = new SucuriScanCache( 'trustip' );
|
10414 |
|
10415 |
foreach ( $del_trust_ip as $cache_key ) {
|
10416 |
+
$cache->delete( $cache_key );
|
10417 |
}
|
10418 |
|
10419 |
SucuriScanInterface::info( 'The IP addresses selected were deleted successfully.' );
|
10420 |
}
|
10421 |
|
10422 |
// Update the settings for the heartbeat API.
|
10423 |
+
if ( $heartbeat_status = SucuriScanRequest::post( ':heartbeat_status' ) ){
|
10424 |
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
|
10425 |
|
10426 |
+
if ( array_key_exists( $heartbeat_status, $statuses_allowed ) ){
|
10427 |
+
$message = 'Heartbeat status set to <code>' . $heartbeat_status . '</code>';
|
10428 |
+
|
10429 |
+
SucuriScanOption::update_option( ':heartbeat', $heartbeat_status );
|
10430 |
+
SucuriScanEvent::report_info_event( $message );
|
10431 |
+
SucuriScanInterface::info( $message );
|
10432 |
} else {
|
10433 |
SucuriScanInterface::error( 'Heartbeat status not allowed.' );
|
10434 |
}
|
10435 |
}
|
10436 |
|
10437 |
// Update the value of the heartbeat pulse.
|
10438 |
+
if ( $heartbeat_pulse = SucuriScanRequest::post( ':heartbeat_pulse' ) ){
|
10439 |
$pulses_allowed = SucuriScanHeartbeat::pulses_allowed();
|
10440 |
|
10441 |
+
if ( array_key_exists( $heartbeat_pulse, $pulses_allowed ) ){
|
10442 |
+
$message = 'Heartbeat pulse set to <code>' . $heartbeat_pulse . '</code> seconds.';
|
10443 |
+
|
10444 |
+
SucuriScanOption::update_option( ':heartbeat_pulse', $heartbeat_pulse );
|
10445 |
+
SucuriScanEvent::report_info_event( $message );
|
10446 |
+
SucuriScanInterface::info( $message );
|
10447 |
} else {
|
10448 |
SucuriScanInterface::error( 'Heartbeat pulse not allowed.' );
|
10449 |
}
|
10450 |
}
|
10451 |
|
10452 |
// Update the value of the heartbeat interval.
|
10453 |
+
if ( $heartbeat_interval = SucuriScanRequest::post( ':heartbeat_interval' ) ){
|
10454 |
$intervals_allowed = SucuriScanHeartbeat::intervals_allowed();
|
10455 |
|
10456 |
+
if ( array_key_exists( $heartbeat_interval, $intervals_allowed ) ){
|
10457 |
+
$message = 'Heartbeat interval set to <code>' . $heartbeat_interval . '</code>';
|
10458 |
+
|
10459 |
+
SucuriScanOption::update_option( ':heartbeat_interval', $heartbeat_interval );
|
10460 |
+
SucuriScanEvent::report_info_event( $message );
|
10461 |
+
SucuriScanInterface::info( $message );
|
10462 |
} else {
|
10463 |
SucuriScanInterface::error( 'Heartbeat interval not allowed.' );
|
10464 |
}
|
10465 |
}
|
10466 |
|
10467 |
// Enable or disable the auto-start execution of heartbeat.
|
10468 |
+
if ( $heartbeat_autostart = SucuriScanRequest::post( ':heartbeat_autostart', '(en|dis)able' ) ){
|
10469 |
$action_d = $heartbeat_autostart . 'd';
|
10470 |
+
$message = 'Heartbeat auto-start was <code>' . $action_d . '</code>';
|
10471 |
+
|
10472 |
+
SucuriScanOption::update_option( ':heartbeat_autostart', $action_d );
|
10473 |
+
SucuriScanEvent::report_info_event( $message );
|
10474 |
+
SucuriScanInterface::info( $message );
|
10475 |
}
|
10476 |
}
|
10477 |
}
|
10478 |
|
10479 |
+
/**
|
10480 |
+
* Print a HTML code with the settings of the plugin.
|
10481 |
+
*
|
10482 |
+
* @return void
|
10483 |
+
*/
|
10484 |
+
function sucuriscan_settings_page(){
|
10485 |
+
SucuriScanInterface::check_permissions();
|
10486 |
+
|
10487 |
+
$template_variables = array(
|
10488 |
+
'PageTitle' => 'Settings',
|
10489 |
+
'Settings.General' => sucuriscan_settings_general(),
|
10490 |
+
'Settings.Scanner' => sucuriscan_settings_scanner(),
|
10491 |
+
'Settings.IgnoreScanning' => sucuriscan_settings_ignorescanning(),
|
10492 |
+
'Settings.Notifications' => sucuriscan_settings_notifications(),
|
10493 |
+
'Settings.IgnoreRules' => sucuriscan_settings_ignore_rules(),
|
10494 |
+
'Settings.TrustIP' => sucuriscan_settings_trust_ip(),
|
10495 |
+
'Settings.Heartbeat' => sucuriscan_settings_heartbeat(),
|
10496 |
+
);
|
10497 |
+
|
10498 |
+
echo SucuriScanTemplate::get_template( 'settings', $template_variables );
|
10499 |
+
}
|
10500 |
+
|
10501 |
/**
|
10502 |
* Read and parse the content of the general settings template.
|
10503 |
*
|
10513 |
$page_nonce = SucuriScanInterface::check_nonce();
|
10514 |
|
10515 |
// Process all form submissions.
|
10516 |
+
sucuriscan_settings_form_submissions( $page_nonce );
|
10517 |
|
10518 |
// Register the site, get its API key, and store it locally for future usage.
|
10519 |
$api_registered_modal = '';
|
10520 |
|
10521 |
// Whether the form to manually add the API key should be shown or not.
|
10522 |
+
$display_manual_key_form = (bool) ( SucuriScanRequest::post( ':recover_key' ) !== false );
|
10523 |
|
10524 |
+
if ( $page_nonce && SucuriScanRequest::post( ':plugin_api_key' ) !== false ){
|
10525 |
$registered = SucuriScanAPI::register_site();
|
10526 |
|
10527 |
+
if ( $registered ){
|
10528 |
$api_registered_modal = SucuriScanTemplate::get_modal('settings-apiregistered', array(
|
10529 |
'Title' => 'Site registered successfully',
|
10530 |
'CssClass' => 'sucuriscan-apikey-registered',
|
10531 |
));
|
10532 |
} else {
|
10533 |
+
$display_manual_key_form = true;
|
10534 |
}
|
10535 |
}
|
10536 |
|
10537 |
// Get initial variables to decide some things bellow.
|
10538 |
$api_key = SucuriScanAPI::get_plugin_key();
|
10539 |
+
$emails_per_hour = SucuriScanOption::get_option( ':emails_per_hour' );
|
10540 |
+
$maximum_failed_logins = SucuriScanOption::get_option( ':maximum_failed_logins' );
|
10541 |
+
$verify_ssl_cert = SucuriScanOption::get_option( ':verify_ssl_cert' );
|
10542 |
+
$audit_report = SucuriScanOption::get_option( ':audit_report' );
|
10543 |
+
$logs4report = SucuriScanOption::get_option( ':logs4report' );
|
10544 |
+
$revproxy = SucuriScanOption::get_option( ':revproxy' );
|
10545 |
$invalid_domain = false;
|
10546 |
|
10547 |
// Check whether the domain name is valid or not.
|
10548 |
+
if ( ! $api_key ){
|
10549 |
+
$clean_domain = SucuriScan::get_top_level_domain();
|
10550 |
+
$domain_address = @gethostbyname( $clean_domain );
|
10551 |
+
$invalid_domain = ( $domain_address == $clean_domain ) ? true : false;
|
10552 |
}
|
10553 |
|
10554 |
// Generate the HTML code for the option list in the form select fields.
|
10557 |
$verify_ssl_cert_options = SucuriScanTemplate::get_select_options( $sucuriscan_verify_ssl_cert, $verify_ssl_cert );
|
10558 |
|
10559 |
$template_variables = array(
|
10560 |
+
'APIKey' => ( ! $api_key ? '<em>(not set)</em>' : $api_key ),
|
10561 |
+
'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility( ! $api_key && ! $display_manual_key_form ),
|
10562 |
+
'APIKey.ManualKeyFormVisibility' => SucuriScanTemplate::visibility( $display_manual_key_form ),
|
10563 |
+
'APIKey.RemoveVisibility' => SucuriScanTemplate::visibility( $api_key ),
|
10564 |
+
'InvalidDomainVisibility' => SucuriScanTemplate::visibility( $invalid_domain ),
|
10565 |
+
'NotifyTo' => SucuriScanOption::get_option( ':notify_to' ),
|
10566 |
'EmailsPerHour' => 'Undefined',
|
10567 |
'EmailsPerHourOptions' => $emails_per_hour_options,
|
10568 |
'MaximumFailedLogins' => 'Undefined',
|
10569 |
'MaximumFailedLoginsOptions' => $maximum_failed_logins_options,
|
10570 |
'VerifySSLCert' => 'Undefined',
|
10571 |
'VerifySSLCertOptions' => $verify_ssl_cert_options,
|
10572 |
+
'RequestTimeout' => SucuriScanOption::get_option( ':request_timeout' ) . ' seconds',
|
10573 |
+
'DatastorePath' => SucuriScanOption::get_option( ':datastore_path' ),
|
10574 |
'CollectWrongPasswords' => 'No collect passwords',
|
10575 |
'ModalWhenAPIRegistered' => $api_registered_modal,
|
10576 |
+
/* Audit Logs Report */
|
10577 |
+
'AuditReportStatus' => 'Enabled',
|
10578 |
+
'AuditReportSwitchText' => 'Disable',
|
10579 |
+
'AuditReportSwitchValue' => 'disable',
|
10580 |
+
'AuditReportSwitchCssClass' => 'button-danger',
|
10581 |
+
'AuditReportLimit' => $logs4report,
|
10582 |
+
/* Support Reverse Proxy */
|
10583 |
+
'ReverseProxyStatus' => 'Enabled',
|
10584 |
+
'ReverseProxySwitchText' => 'Disable',
|
10585 |
+
'ReverseProxySwitchValue' => 'disable',
|
10586 |
+
'ReverseProxySwitchCssClass' => 'button-danger',
|
10587 |
);
|
10588 |
|
10589 |
+
if ( array_key_exists( $emails_per_hour, $sucuriscan_emails_per_hour ) ){
|
10590 |
+
$template_variables['EmailsPerHour'] = $sucuriscan_emails_per_hour[ $emails_per_hour ];
|
10591 |
+
}
|
10592 |
+
|
10593 |
+
if ( array_key_exists( $maximum_failed_logins, $sucuriscan_maximum_failed_logins ) ){
|
10594 |
+
$template_variables['MaximumFailedLogins'] = $sucuriscan_maximum_failed_logins[ $maximum_failed_logins ];
|
10595 |
}
|
10596 |
|
10597 |
+
if ( array_key_exists( $verify_ssl_cert, $sucuriscan_verify_ssl_cert ) ){
|
10598 |
+
$template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[ $verify_ssl_cert ];
|
10599 |
}
|
10600 |
|
10601 |
+
if ( $audit_report == 'disabled' ){
|
10602 |
+
$template_variables['AuditReportStatus'] = 'Disabled';
|
10603 |
+
$template_variables['AuditReportSwitchText'] = 'Enable';
|
10604 |
+
$template_variables['AuditReportSwitchValue'] = 'enable';
|
10605 |
+
$template_variables['AuditReportSwitchCssClass'] = 'button-success';
|
10606 |
+
}
|
10607 |
+
|
10608 |
+
if ( $revproxy == 'disabled' ){
|
10609 |
+
$template_variables['ReverseProxyStatus'] = 'Disabled';
|
10610 |
+
$template_variables['ReverseProxySwitchText'] = 'Enable';
|
10611 |
+
$template_variables['ReverseProxySwitchValue'] = 'enable';
|
10612 |
+
$template_variables['ReverseProxySwitchCssClass'] = 'button-success';
|
10613 |
}
|
10614 |
|
10615 |
if ( sucuriscan_collect_wrong_passwords() === true ) {
|
10616 |
$template_variables['CollectWrongPasswords'] = '<span class="sucuriscan-label-error">Yes, collect passwords</span>';
|
10617 |
}
|
10618 |
|
10619 |
+
return SucuriScanTemplate::get_section( 'settings-general', $template_variables );
|
10620 |
}
|
10621 |
|
10622 |
/**
|
10630 |
$sucuriscan_interface_allowed;
|
10631 |
|
10632 |
// Get initial variables to decide some things bellow.
|
10633 |
+
$fs_scanner = SucuriScanOption::get_option( ':fs_scanner' );
|
10634 |
+
$scan_freq = SucuriScanOption::get_option( ':scan_frequency' );
|
10635 |
+
$scan_interface = SucuriScanOption::get_option( ':scan_interface' );
|
10636 |
+
$scan_modfiles = SucuriScanOption::get_option( ':scan_modfiles' );
|
10637 |
+
$scan_checksums = SucuriScanOption::get_option( ':scan_checksums' );
|
10638 |
+
$scan_errorlogs = SucuriScanOption::get_option( ':scan_errorlogs' );
|
10639 |
+
$parse_errorlogs = SucuriScanOption::get_option( ':parse_errorlogs' );
|
10640 |
+
$errorlogs_limit = SucuriScanOption::get_option( ':errorlogs_limit' );
|
10641 |
+
$ignore_scanning = SucuriScanOption::get_option( ':ignore_scanning' );
|
10642 |
+
$sitecheck_scanner = SucuriScanOption::get_option( ':sitecheck_scanner' );
|
10643 |
+
$sitecheck_counter = SucuriScanOption::get_option( ':sitecheck_counter' );
|
10644 |
+
$runtime_scan_human = SucuriScanFSScanner::get_filesystem_runtime( true );
|
10645 |
+
|
10646 |
+
// Get the file path of the security logs.
|
10647 |
+
$integrity_log_path = SucuriScan::datastore_folder_path( 'sucuri-integrity.php' );
|
10648 |
+
$lastlogins_log_path = SucuriScan::datastore_folder_path( 'sucuri-lastlogins.php' );
|
10649 |
+
$failedlogins_log_path = SucuriScan::datastore_folder_path( 'sucuri-failedlogins.php' );
|
10650 |
+
$sitecheck_log_path = SucuriScan::datastore_folder_path( 'sucuri-sitecheck.php' );
|
10651 |
|
10652 |
// Generate the HTML code for the option list in the form select fields.
|
10653 |
$scan_freq_options = SucuriScanTemplate::get_select_options( $sucuriscan_schedule_allowed, $scan_freq );
|
10692 |
/* Filsystem scanning frequency. */
|
10693 |
'ScanningFrequency' => 'Undefined',
|
10694 |
'ScanningFrequencyOptions' => $scan_freq_options,
|
10695 |
+
'ScanningInterface' => ( $scan_interface ? $sucuriscan_interface_allowed[ $scan_interface ] : 'Undefined' ),
|
10696 |
'ScanningInterfaceOptions' => $scan_interface_options,
|
10697 |
/* Filesystem scanning runtime. */
|
10698 |
'ScanningRuntimeHuman' => $runtime_scan_human,
|
10699 |
'SiteCheckCounter' => $sitecheck_counter,
|
10700 |
'ErrorLogsLimit' => $errorlogs_limit,
|
10701 |
+
'IntegrityLogLife' => '0B',
|
10702 |
+
'LastLoginLogLife' => '0B',
|
10703 |
+
'FailedLoginLogLife' => '0B',
|
10704 |
+
'SiteCheckLogLife' => '0B',
|
10705 |
);
|
10706 |
|
10707 |
+
if ( $fs_scanner == 'disabled' ){
|
10708 |
$template_variables['FsScannerStatus'] = 'Disabled';
|
10709 |
$template_variables['FsScannerSwitchText'] = 'Enable';
|
10710 |
$template_variables['FsScannerSwitchValue'] = 'enable';
|
10711 |
$template_variables['FsScannerSwitchCssClass'] = 'button-success';
|
10712 |
}
|
10713 |
|
10714 |
+
if ( $scan_modfiles == 'disabled' ){
|
10715 |
$template_variables['ScanModfilesStatus'] = 'Disabled';
|
10716 |
$template_variables['ScanModfilesSwitchText'] = 'Enable';
|
10717 |
$template_variables['ScanModfilesSwitchValue'] = 'enable';
|
10718 |
$template_variables['ScanModfilesSwitchCssClass'] = 'button-success';
|
10719 |
}
|
10720 |
|
10721 |
+
if ( $scan_checksums == 'disabled' ){
|
10722 |
$template_variables['ScanChecksumsStatus'] = 'Disabled';
|
10723 |
$template_variables['ScanChecksumsSwitchText'] = 'Enable';
|
10724 |
$template_variables['ScanChecksumsSwitchValue'] = 'enable';
|
10725 |
$template_variables['ScanChecksumsSwitchCssClass'] = 'button-success';
|
10726 |
}
|
10727 |
|
10728 |
+
if ( $ignore_scanning == 'disabled' ){
|
10729 |
$template_variables['IgnoreScanningStatus'] = 'Disabled';
|
10730 |
$template_variables['IgnoreScanningSwitchText'] = 'Enable';
|
10731 |
$template_variables['IgnoreScanningSwitchValue'] = 'enable';
|
10732 |
$template_variables['IgnoreScanningSwitchCssClass'] = 'button-success';
|
10733 |
}
|
10734 |
|
10735 |
+
if ( $scan_errorlogs == 'disabled' ){
|
10736 |
$template_variables['ScanErrorlogsStatus'] = 'Disabled';
|
10737 |
$template_variables['ScanErrorlogsSwitchText'] = 'Enable';
|
10738 |
$template_variables['ScanErrorlogsSwitchValue'] = 'enable';
|
10739 |
$template_variables['ScanErrorlogsSwitchCssClass'] = 'button-success';
|
10740 |
}
|
10741 |
|
10742 |
+
if ( $parse_errorlogs == 'disabled' ){
|
10743 |
$template_variables['ParseErrorLogsStatus'] = 'Disabled';
|
10744 |
$template_variables['ParseErrorLogsSwitchText'] = 'Enable';
|
10745 |
$template_variables['ParseErrorLogsSwitchValue'] = 'enable';
|
10746 |
$template_variables['ParseErrorLogsSwitchCssClass'] = 'button-success';
|
10747 |
}
|
10748 |
|
10749 |
+
if ( $sitecheck_scanner == 'disabled' ){
|
10750 |
$template_variables['SiteCheckScannerStatus'] = 'Disabled';
|
10751 |
$template_variables['SiteCheckScannerSwitchText'] = 'Enable';
|
10752 |
$template_variables['SiteCheckScannerSwitchValue'] = 'enable';
|
10753 |
$template_variables['SiteCheckScannerSwitchCssClass'] = 'button-success';
|
10754 |
}
|
10755 |
|
10756 |
+
if ( array_key_exists( $scan_freq, $sucuriscan_schedule_allowed ) ){
|
10757 |
+
$template_variables['ScanningFrequency'] = $sucuriscan_schedule_allowed[ $scan_freq ];
|
10758 |
}
|
10759 |
|
10760 |
+
// Determine the age of the security log files.
|
10761 |
+
$template_variables['IntegrityLogLife'] = SucuriScan::human_filesize( @filesize( $integrity_log_path ) );
|
10762 |
+
$template_variables['LastLoginLogLife'] = SucuriScan::human_filesize( @filesize( $lastlogins_log_path ) );
|
10763 |
+
$template_variables['FailedLoginLogLife'] = SucuriScan::human_filesize( @filesize( $failedlogins_log_path ) );
|
10764 |
+
$template_variables['SiteCheckLogLife'] = SucuriScan::human_filesize( @filesize( $sitecheck_log_path ) );
|
10765 |
+
|
10766 |
+
return SucuriScanTemplate::get_section( 'settings-scanner', $template_variables );
|
10767 |
}
|
10768 |
|
10769 |
/**
|
10784 |
);
|
10785 |
|
10786 |
if ( $sucuriscan_email_subjects ) {
|
10787 |
+
$email_subject = SucuriScanOption::get_option( ':email_subject' );
|
10788 |
$is_official_subject = false;
|
10789 |
|
10790 |
foreach ( $sucuriscan_email_subjects as $subject_format ) {
|
10804 |
|
10805 |
if ( $is_official_subject === false ) {
|
10806 |
$template_variables['EmailSubjectCustom.Checked'] = 'checked="checked"';
|
10807 |
+
$template_variables['EmailSubjectCustom.Value'] = SucuriScan::escape( $email_subject );
|
10808 |
}
|
10809 |
}
|
10810 |
|
10811 |
$counter = 0;
|
10812 |
+
$alert_pattern = '/^([a-z]+:)?(.+)/';
|
10813 |
|
10814 |
+
foreach ( $sucuriscan_notify_options as $alert_type => $alert_label ){
|
10815 |
+
$alert_value = SucuriScanOption::get_option( $alert_type );
|
10816 |
$checked = ( $alert_value == 'enabled' ? 'checked="checked"' : '' );
|
10817 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
10818 |
+
$alert_icon = '';
|
10819 |
+
|
10820 |
+
if ( preg_match( $alert_pattern, $alert_label, $match ) ) {
|
10821 |
+
$alert_group = str_replace( ':', '', $match[1] );
|
10822 |
+
$alert_label = $match[2];
|
10823 |
+
|
10824 |
+
switch ( $alert_group ) {
|
10825 |
+
case 'user': $alert_icon = 'dashicons-before dashicons-admin-users'; break;
|
10826 |
+
case 'plugin': $alert_icon = 'dashicons-before dashicons-admin-plugins'; break;
|
10827 |
+
case 'theme': $alert_icon = 'dashicons-before dashicons-admin-appearance'; break;
|
10828 |
+
}
|
10829 |
+
}
|
10830 |
|
10831 |
$template_variables['NotificationOptions'] .= SucuriScanTemplate::get_snippet('settings-notifications', array(
|
10832 |
'Notification.CssClass' => $css_class,
|
10833 |
'Notification.Name' => $alert_type,
|
10834 |
'Notification.Checked' => $checked,
|
10835 |
'Notification.Label' => $alert_label,
|
10836 |
+
'Notification.LabelIcon' => $alert_icon,
|
10837 |
));
|
10838 |
$counter += 1;
|
10839 |
}
|
10840 |
|
10841 |
+
return SucuriScanTemplate::get_section( 'settings-notifications', $template_variables );
|
10842 |
}
|
10843 |
|
10844 |
/**
|
10847 |
* @return string Parsed HTML code for the ignored-rules settings panel.
|
10848 |
*/
|
10849 |
function sucuriscan_settings_ignore_rules(){
|
10850 |
+
$notify_new_site_content = SucuriScanOption::get_option( ':notify_post_publication' );
|
10851 |
|
10852 |
$template_variables = array(
|
10853 |
'IgnoreRules.MessageVisibility' => 'visible',
|
10855 |
'IgnoreRules.PostTypes' => '',
|
10856 |
);
|
10857 |
|
10858 |
+
if ( $notify_new_site_content == 'enabled' ){
|
10859 |
$post_types = get_post_types();
|
10860 |
$ignored_events = SucuriScanOption::get_ignored_events();
|
10861 |
|
10863 |
$template_variables['IgnoreRules.TableVisibility'] = 'visible';
|
10864 |
$counter = 0;
|
10865 |
|
10866 |
+
foreach ( $post_types as $post_type => $post_type_object ){
|
10867 |
$counter += 1;
|
10868 |
$css_class = ( $counter % 2 == 0 ) ? 'alternate' : '';
|
10869 |
+
$post_type_title = ucwords( str_replace( '_', chr( 32 ), $post_type ) );
|
10870 |
|
10871 |
+
if ( array_key_exists( $post_type, $ignored_events ) ){
|
10872 |
$is_ignored_text = 'YES';
|
10873 |
+
$was_ignored_at = SucuriScan::datetime( $ignored_events[ $post_type ] );
|
10874 |
$is_ignored_class = 'danger';
|
10875 |
$button_action = 'remove';
|
10876 |
$button_class = 'button-primary';
|
10899 |
}
|
10900 |
}
|
10901 |
|
10902 |
+
return SucuriScanTemplate::get_section( 'settings-ignorerules', $template_variables );
|
10903 |
}
|
10904 |
|
10905 |
/**
|
10913 |
'TrustedIPs.NoItems.Visibility' => 'visible',
|
10914 |
);
|
10915 |
|
10916 |
+
$cache = new SucuriScanCache( 'trustip' );
|
10917 |
$trusted_ips = $cache->get_all();
|
10918 |
|
10919 |
if ( $trusted_ips ) {
|
10929 |
$template_variables['TrustedIPs.List'] .= SucuriScanTemplate::get_snippet('settings-trustip', array(
|
10930 |
'TrustIP.CssClass' => $css_class,
|
10931 |
'TrustIP.CacheKey' => $cache_key,
|
10932 |
+
'TrustIP.RemoteAddr' => SucuriScan::escape( $ip_info->remote_addr ),
|
10933 |
+
'TrustIP.CIDRFormat' => SucuriScan::escape( $ip_info->cidr_format ),
|
10934 |
+
'TrustIP.AddedAt' => SucuriScan::datetime( $ip_info->added_at ),
|
10935 |
));
|
10936 |
$counter += 1;
|
10937 |
}
|
10941 |
}
|
10942 |
}
|
10943 |
|
10944 |
+
return SucuriScanTemplate::get_section( 'settings-trustip', $template_variables );
|
10945 |
}
|
10946 |
|
10947 |
/**
|
10959 |
$ignore_scanning = SucuriScanFSScanner::will_ignore_scanning();
|
10960 |
|
10961 |
// Allow disable of this option temporarily.
|
10962 |
+
if ( SucuriScanRequest::get( 'no_scan' ) == 1 ){
|
10963 |
+
$ignore_scanning = false;
|
10964 |
}
|
10965 |
|
10966 |
// Scan the project and get the ignored paths.
|
10967 |
+
if ( $ignore_scanning === true ){
|
10968 |
$counter = 0;
|
10969 |
$template_variables['IgnoreScanning.DisabledVisibility'] = 'hidden';
|
10970 |
$dir_list_list = SucuriScanFSScanner::get_ignored_directories_live();
|
10971 |
|
10972 |
+
foreach ( $dir_list_list as $group => $dir_list ){
|
10973 |
+
foreach ( $dir_list as $dir_data ){
|
10974 |
+
$valid_entry = false;
|
10975 |
$snippet_data = array(
|
10976 |
'IgnoreScanning.CssClass' => '',
|
10977 |
'IgnoreScanning.Directory' => '',
|
10981 |
'IgnoreScanning.IgnoredCssClass' => 'success',
|
10982 |
);
|
10983 |
|
10984 |
+
if ( $group == 'is_ignored' ){
|
10985 |
+
$valid_entry = true;
|
10986 |
+
$snippet_data['IgnoreScanning.Directory'] = urlencode( $dir_data['directory_path'] );
|
10987 |
+
$snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape( $dir_data['directory_path'] );
|
10988 |
+
$snippet_data['IgnoreScanning.IgnoredAt'] = SucuriScan::datetime( $dir_data['ignored_at'] );
|
10989 |
$snippet_data['IgnoreScanning.IgnoredAtText'] = 'ignored';
|
10990 |
$snippet_data['IgnoreScanning.IgnoredCssClass'] = 'warning';
|
10991 |
}
|
10992 |
|
10993 |
+
elseif ( $group == 'is_not_ignored' ){
|
10994 |
+
$valid_entry = true;
|
10995 |
+
$snippet_data['IgnoreScanning.Directory'] = urlencode( $dir_data );
|
10996 |
+
$snippet_data['IgnoreScanning.DirectoryPath'] = SucuriScan::escape( $dir_data );
|
10997 |
}
|
10998 |
|
10999 |
+
if ( $valid_entry ){
|
11000 |
+
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
11001 |
$snippet_data['IgnoreScanning.CssClass'] = $css_class;
|
11002 |
+
$template_variables['IgnoreScanning.ResourceList'] .= SucuriScanTemplate::get_snippet( 'settings-ignorescanning', $snippet_data );
|
11003 |
$counter += 1;
|
11004 |
}
|
11005 |
}
|
11006 |
}
|
11007 |
|
11008 |
+
if ( $counter > 0 ){
|
11009 |
$template_variables['IgnoreScanning.NoItemsVisibility'] = 'hidden';
|
11010 |
}
|
11011 |
}
|
11012 |
|
11013 |
+
return SucuriScanTemplate::get_section( 'settings-ignorescanning', $template_variables );
|
11014 |
}
|
11015 |
|
11016 |
/**
|
11020 |
*/
|
11021 |
function sucuriscan_settings_heartbeat(){
|
11022 |
// Current values set in the options table.
|
11023 |
+
$heartbeat_status = SucuriScanOption::get_option( ':heartbeat' );
|
11024 |
+
$heartbeat_pulse = SucuriScanOption::get_option( ':heartbeat_pulse' );
|
11025 |
+
$heartbeat_interval = SucuriScanOption::get_option( ':heartbeat_interval' );
|
11026 |
+
$heartbeat_autostart = SucuriScanOption::get_option( ':heartbeat_autostart' );
|
11027 |
|
11028 |
// Allowed values for each setting.
|
11029 |
$statuses_allowed = SucuriScanHeartbeat::statuses_allowed();
|
11050 |
'HeartbeatAutostartSwitchCssClass' => 'button-danger',
|
11051 |
);
|
11052 |
|
11053 |
+
if ( array_key_exists( $heartbeat_status, $statuses_allowed ) ){
|
11054 |
+
$template_variables['HeartbeatStatus'] = $statuses_allowed[ $heartbeat_status ];
|
11055 |
}
|
11056 |
|
11057 |
+
if ( array_key_exists( $heartbeat_pulse, $pulses_allowed ) ){
|
11058 |
+
$template_variables['HeartbeatPulse'] = $pulses_allowed[ $heartbeat_pulse ];
|
11059 |
}
|
11060 |
|
11061 |
+
if ( array_key_exists( $heartbeat_interval, $intervals_allowed ) ){
|
11062 |
+
$template_variables['HeartbeatInterval'] = $intervals_allowed[ $heartbeat_interval ];
|
11063 |
}
|
11064 |
|
11065 |
+
if ( $heartbeat_autostart == 'disabled' ){
|
11066 |
$template_variables['HeartbeatAutostart'] = 'Disabled';
|
11067 |
$template_variables['HeartbeatAutostartSwitchText'] = 'Enable';
|
11068 |
$template_variables['HeartbeatAutostartSwitchValue'] = 'enable';
|
11069 |
$template_variables['HeartbeatAutostartSwitchCssClass'] = 'button-success';
|
11070 |
}
|
11071 |
|
11072 |
+
return SucuriScanTemplate::get_section( 'settings-heartbeat', $template_variables );
|
11073 |
}
|
11074 |
|
11075 |
/**
|
11097 |
'ErrorLogs' => sucuriscan_infosys_errorlogs(),
|
11098 |
);
|
11099 |
|
11100 |
+
echo SucuriScanTemplate::get_template( 'infosys', $template_variables );
|
11101 |
}
|
11102 |
|
11103 |
/**
|
11117 |
'HTAccess.TextareaVisible' => 'hidden',
|
11118 |
);
|
11119 |
|
11120 |
+
if ( $htaccess_path ){
|
11121 |
+
$htaccess_rules = file_get_contents( $htaccess_path );
|
11122 |
|
11123 |
$template_variables['HTAccess.MessageType'] = 'updated';
|
11124 |
$template_variables['HTAccess.MessageVisible'] = 'visible';
|
11126 |
$template_variables['HTAccess.Content'] = $htaccess_rules;
|
11127 |
$template_variables['HTAccess.Message'] .= 'HTAccess file found in this path <code>'.$htaccess_path.'</code>';
|
11128 |
|
11129 |
+
if ( empty($htaccess_rules) ){
|
11130 |
$template_variables['HTAccess.TextareaVisible'] = 'hidden';
|
11131 |
$template_variables['HTAccess.Message'] .= '</p><p>The HTAccess file found is completely empty.';
|
11132 |
}
|
11133 |
+
if ( sucuriscan_htaccess_is_standard( $htaccess_rules ) ){
|
11134 |
$template_variables['HTAccess.Message'] .= '</p><p>
|
11135 |
The main <code>.htaccess</code> file in your site has the standard rules for a WordPress installation. You can customize it to improve the
|
11136 |
performance and change the behaviour of the redirections for pages and posts in your site. To get more information visit the official documentation at
|
11137 |
<a href="http://codex.wordpress.org/Using_Permalinks#Creating_and_editing_.28.htaccess.29" target="_blank">Codex WordPrexx - Creating and editing (.htaccess)</a>';
|
11138 |
}
|
11139 |
+
}else {
|
11140 |
$template_variables['HTAccess.Message'] = 'Your website does not contains a <code>.htaccess</code> file or it was not found in the default location.';
|
11141 |
$template_variables['HTAccess.MessageType'] = 'error';
|
11142 |
$template_variables['HTAccess.MessageVisible'] = 'visible';
|
11143 |
}
|
11144 |
|
11145 |
+
return SucuriScanTemplate::get_section( 'infosys-htaccess', $template_variables );
|
11146 |
}
|
11147 |
|
11148 |
/**
|
11152 |
* @param string $rules Optional parameter containing a text string with the content of the main htaccess file.
|
11153 |
* @return boolean Either TRUE or FALSE if the rules found in the htaccess file specified are the default ones or not.
|
11154 |
*/
|
11155 |
+
function sucuriscan_htaccess_is_standard( $rules = false ){
|
11156 |
+
if ( $rules === false ){
|
11157 |
$htaccess_path = SucuriScan::get_htaccess_path();
|
11158 |
+
$rules = $htaccess_path ? file_get_contents( $htaccess_path ) : '';
|
11159 |
}
|
11160 |
|
11161 |
+
if ( ! empty($rules) ){
|
11162 |
$standard_lines = array(
|
11163 |
'# BEGIN WordPress',
|
11164 |
'<IfModule mod_rewrite\.c>',
|
11172 |
'# END WordPress',
|
11173 |
);
|
11174 |
$pattern = '';
|
11175 |
+
$standard_lines_total = count( $standard_lines );
|
11176 |
+
foreach ( $standard_lines as $i => $line ){
|
11177 |
+
if ( $i < ($standard_lines_total -1) ){
|
11178 |
$end_of_line = "\n";
|
11179 |
+
}else {
|
11180 |
$end_of_line = '';
|
11181 |
}
|
11182 |
+
$pattern .= sprintf( '%s%s', $line, $end_of_line );
|
11183 |
}
|
11184 |
|
11185 |
+
if ( preg_match( "/{$pattern}/", $rules ) ){
|
11186 |
+
return true;
|
11187 |
}
|
11188 |
}
|
11189 |
|
11190 |
+
return false;
|
11191 |
}
|
11192 |
|
11193 |
/**
|
11206 |
$ignore_wp_rules = array( 'DB_PASSWORD' );
|
11207 |
$wp_config_path = SucuriScan::get_wpconfig_path();
|
11208 |
|
11209 |
+
if ( $wp_config_path ){
|
11210 |
$wp_config_rules = array();
|
11211 |
+
$wp_config_content = SucuriScanFileInfo::file_lines( $wp_config_path );
|
11212 |
|
11213 |
// Parse the main configuration file and look for constants and global variables.
|
11214 |
+
foreach ( (array) $wp_config_content as $line ){
|
11215 |
// Ignore commented lines.
|
11216 |
+
if ( preg_match( '/^\s?(#|\/\/)/', $line ) ) { continue; }
|
11217 |
|
11218 |
// Detect PHP constants even if the line if indented.
|
11219 |
+
elseif ( preg_match( '/define\(/', $line ) ) {
|
11220 |
+
$line = preg_replace( '/.*define\((.+)\);.*/', '$1', $line );
|
11221 |
+
$line_parts = explode( ',', $line, 2 );
|
11222 |
}
|
11223 |
|
11224 |
// Detect global variables like the database table prefix.
|
11225 |
+
elseif ( preg_match( '/^\$[a-zA-Z_]+/', $line ) ){
|
11226 |
$line = preg_replace( '/;\s\/\/.*/', ';', $line );
|
11227 |
+
$line_parts = explode( '=', $line, 2 );
|
11228 |
}
|
11229 |
|
11230 |
// Ignore other lines.
|
11231 |
else { continue; }
|
11232 |
|
11233 |
// Clean and append the rule to the wp_config_rules variable.
|
11234 |
+
if ( isset($line_parts) && count( $line_parts ) == 2 ){
|
11235 |
$key_name = '';
|
11236 |
$key_value = '';
|
11237 |
|
11238 |
// TODO: A foreach loop is not really necessary, find a better way.
|
11239 |
+
foreach ( $line_parts as $i => $line_part ){
|
11240 |
+
$line_part = trim( $line_part );
|
11241 |
+
$line_part = ltrim( $line_part, '$' );
|
11242 |
+
$line_part = rtrim( $line_part, ';' );
|
11243 |
|
11244 |
// Remove single/double quotes at the beginning and end of the string.
|
11245 |
+
$line_part = ltrim( $line_part, "'" );
|
11246 |
+
$line_part = rtrim( $line_part, "'" );
|
11247 |
+
$line_part = ltrim( $line_part, '"' );
|
11248 |
+
$line_part = rtrim( $line_part, '"' );
|
11249 |
|
11250 |
// Assign the clean strings to specific variables.
|
11251 |
+
if ( $i == 0 ){ $key_name = $line_part; }
|
11252 |
+
if ( $i == 1 ){
|
11253 |
+
if ( defined( $key_name ) ){
|
11254 |
+
$key_value = constant( $key_name );
|
11255 |
|
11256 |
+
if ( is_bool( $key_value ) ){
|
11257 |
+
$key_value = ( $key_value === true ) ? 'TRUE' : 'FALSE';
|
11258 |
}
|
11259 |
} else {
|
11260 |
$key_value = $line_part;
|
11263 |
}
|
11264 |
|
11265 |
// Remove the value of sensitive variables like the database password.
|
11266 |
+
if ( in_array( $key_name, $ignore_wp_rules ) ){
|
11267 |
$key_value = 'hidden';
|
11268 |
}
|
11269 |
|
11270 |
// Append the value to the configuration rules.
|
11271 |
+
$wp_config_rules[ $key_name ] = $key_value;
|
11272 |
}
|
11273 |
}
|
11274 |
|
11275 |
// Pass the WordPress configuration rules to the template and show them.
|
11276 |
$counter = 0;
|
11277 |
+
foreach ( $wp_config_rules as $var_name => $var_value ){
|
11278 |
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
11279 |
$label_css = 'sucuriscan-monospace';
|
11280 |
|
11281 |
+
if ( empty($var_value) ){
|
11282 |
$var_value = 'empty';
|
11283 |
$label_css = 'sucuriscan-label-default';
|
11284 |
}
|
11285 |
|
11286 |
+
elseif ( $var_value == 'hidden' ){
|
11287 |
$label_css = 'sucuriscan-label-info';
|
11288 |
}
|
11289 |
|
11290 |
$template_variables['WordpressConfig.Total'] += 1;
|
11291 |
$template_variables['WordpressConfig.Rules'] .= SucuriScanTemplate::get_snippet('infosys-wpconfig', array(
|
11292 |
+
'WordpressConfig.VariableName' => SucuriScan::escape( $var_name ),
|
11293 |
+
'WordpressConfig.VariableValue' => SucuriScan::escape( $var_value ),
|
11294 |
'WordpressConfig.VariableCssClass' => $label_css,
|
11295 |
'WordpressConfig.CssClass' => $css_class,
|
11296 |
));
|
11298 |
}
|
11299 |
}
|
11300 |
|
11301 |
+
return SucuriScanTemplate::get_section( 'infosys-wpconfig', $template_variables );
|
11302 |
}
|
11303 |
|
11304 |
/**
|
11316 |
$schedules = wp_get_schedules();
|
11317 |
$counter = 0;
|
11318 |
|
11319 |
+
foreach ( $cronjobs as $timestamp => $cronhooks ){
|
11320 |
+
foreach ( (array) $cronhooks as $hook => $events ){
|
11321 |
+
foreach ( (array) $events as $key => $event ){
|
11322 |
+
if ( empty($event['args']) ){
|
11323 |
$event['args'] = array( '<em>empty</em>' );
|
11324 |
}
|
11325 |
|
11327 |
$template_variables['Cronjobs.List'] .= SucuriScanTemplate::get_snippet('infosys-cronjobs', array(
|
11328 |
'Cronjob.Hook' => $hook,
|
11329 |
'Cronjob.Schedule' => $event['schedule'],
|
11330 |
+
'Cronjob.NextTime' => SucuriScan::datetime( $timestamp ),
|
11331 |
+
'Cronjob.Arguments' => SucuriScan::implode( ', ', $event['args'] ),
|
11332 |
'Cronjob.CssClass' => ( $counter % 2 == 0 ) ? '' : 'alternate',
|
11333 |
));
|
11334 |
$counter += 1;
|
11336 |
}
|
11337 |
}
|
11338 |
|
11339 |
+
return SucuriScanTemplate::get_section( 'infosys-cronjobs', $template_variables );
|
11340 |
}
|
11341 |
|
11342 |
/**
|
11348 |
* @return void
|
11349 |
*/
|
11350 |
function sucuriscan_infosys_form_submissions(){
|
11351 |
+
if ( SucuriScanInterface::check_nonce() ){
|
11352 |
|
11353 |
// Modify the scheduled tasks (run now, remove, re-schedule).
|
11354 |
$allowed_actions = '(runnow|hourly|twicedaily|daily|remove)';
|
11355 |
|
11356 |
+
if ( $cronjob_action = SucuriScanRequest::post( ':cronjob_action', $allowed_actions ) ){
|
11357 |
+
$cronjobs = SucuriScanRequest::post( ':cronjobs', '_array' );
|
11358 |
|
11359 |
+
if ( ! empty($cronjobs) ){
|
11360 |
+
$total_tasks = count( $cronjobs );
|
11361 |
|
11362 |
+
// Force execution of the selected scheduled tasks.
|
11363 |
+
if ( $cronjob_action == 'runnow' ) {
|
11364 |
+
SucuriScanInterface::info( $total_tasks . ' tasks were scheduled to run in the next ten seconds.' );
|
11365 |
+
SucuriScanEvent::report_notice_event( sprintf(
|
11366 |
+
'Force execution of scheduled tasks: (multiple entries): %s',
|
11367 |
+
@implode( ',', $cronjobs )
|
11368 |
+
) );
|
11369 |
+
|
11370 |
+
foreach ( $cronjobs as $task_name ){
|
11371 |
+
wp_schedule_single_event( time() + 10, $task_name );
|
11372 |
+
}
|
11373 |
+
}
|
11374 |
+
|
11375 |
+
// Force deletion of the selected scheduled tasks.
|
11376 |
+
elseif ( $cronjob_action == 'remove' ) {
|
11377 |
+
SucuriScanInterface::info( $total_tasks . ' scheduled tasks were removed.' );
|
11378 |
+
SucuriScanEvent::report_notice_event( sprintf(
|
11379 |
+
'Delete scheduled tasks: (multiple entries): %s',
|
11380 |
+
@implode( ',', $cronjobs )
|
11381 |
+
) );
|
11382 |
+
|
11383 |
+
foreach ( $cronjobs as $task_name ){
|
11384 |
+
wp_clear_scheduled_hook( $task_name );
|
11385 |
+
}
|
11386 |
+
}
|
11387 |
+
|
11388 |
+
// Re-schedule the selected scheduled tasks.
|
11389 |
+
elseif (
|
11390 |
+
$cronjob_action == 'hourly'
|
11391 |
+
|| $cronjob_action == 'twicedaily'
|
11392 |
+
|| $cronjob_action == 'daily'
|
11393 |
+
) {
|
11394 |
+
SucuriScanInterface::info( $total_tasks . ' tasks were re-scheduled to run <code>' . $cronjob_action . '</code>.' );
|
11395 |
+
SucuriScanEvent::report_notice_event( sprintf(
|
11396 |
+
'Re-configure scheduled tasks %s: (multiple entries): %s',
|
11397 |
+
$cronjob_action,
|
11398 |
+
@implode( ',', $cronjobs )
|
11399 |
+
) );
|
11400 |
+
|
11401 |
+
foreach ( $cronjobs as $task_name ){
|
11402 |
+
wp_clear_scheduled_hook( $task_name );
|
11403 |
+
$next_due = wp_next_scheduled( $task_name );
|
11404 |
+
wp_schedule_event( $next_due, $cronjob_action, $task_name );
|
11405 |
+
}
|
11406 |
}
|
11407 |
} else {
|
11408 |
SucuriScanInterface::error( 'No scheduled tasks were selected from the list.' );
|
11409 |
}
|
11410 |
}
|
|
|
11411 |
}
|
11412 |
}
|
11413 |
|
11420 |
$template_variables = array(
|
11421 |
'ErrorLog.Path' => '',
|
11422 |
'ErrorLog.Exists' => 'No',
|
11423 |
+
'ErrorLog.NoItemsVisibility' => 'hidden',
|
11424 |
+
'ErrorLog.DisabledVisibility' => 'hidden',
|
11425 |
+
'ErrorLog.InvalidFormatVisibility' => 'hidden',
|
11426 |
+
'ErrorLog.LogsLimit' => '0',
|
11427 |
'ErrorLog.FileSize' => '0B',
|
11428 |
'ErrorLog.List' => '',
|
11429 |
);
|
11430 |
|
11431 |
$error_log_path = realpath( ABSPATH . '/error_log' );
|
11432 |
+
$errorlogs_limit = SucuriScanOption::get_option( ':errorlogs_limit' );
|
11433 |
+
$template_variables['ErrorLog.LogsLimit'] = $errorlogs_limit;
|
11434 |
+
$errorlogs_counter = 0;
|
11435 |
+
|
11436 |
+
if ( SucuriScanOption::get_option( ':parse_errorlogs' ) === 'disabled' ) {
|
11437 |
+
$template_variables['ErrorLog.DisabledVisibility'] = 'visible';
|
11438 |
+
}
|
11439 |
|
11440 |
+
if ( $error_log_path ) {
|
11441 |
$template_variables['ErrorLog.Path'] = $error_log_path;
|
11442 |
$template_variables['ErrorLog.Exists'] = 'Yes';
|
11443 |
+
$template_variables['ErrorLog.FileSize'] = SucuriScan::human_filesize( filesize( $error_log_path ) );
|
|
|
11444 |
|
11445 |
$last_lines = SucuriScanFileInfo::tail_file( $error_log_path, $errorlogs_limit );
|
11446 |
+
$error_logs = SucuriScanFSScanner::parse_error_logs( $last_lines );
|
11447 |
+
$error_logs = array_reverse( $error_logs );
|
11448 |
+
$errorlogs_counter = 0;
|
11449 |
|
11450 |
foreach ( $error_logs as $error_log ) {
|
11451 |
+
$css_class = ( $errorlogs_counter % 2 == 0 ) ? '' : 'alternate';
|
11452 |
$template_variables['ErrorLog.List'] .= SucuriScanTemplate::get_snippet('infosys-errorlogs', array(
|
11453 |
'ErrorLog.CssClass' => $css_class,
|
11454 |
'ErrorLog.DateTime' => SucuriScan::datetime( $error_log->timestamp ),
|
11455 |
'ErrorLog.ErrorType' => SucuriScan::escape( $error_log->error_type ),
|
11456 |
+
'ErrorLog.ErrorCode' => SucuriScan::escape( $error_log->error_code ),
|
11457 |
+
'ErrorLog.ErrorAbbr' => strtoupper( substr( $error_log->error_code, 0, 1 ) ),
|
11458 |
'ErrorLog.ErrorMessage' => SucuriScan::escape( $error_log->error_message ),
|
11459 |
'ErrorLog.FilePath' => SucuriScan::escape( $error_log->file_path ),
|
11460 |
'ErrorLog.LineNumber' => SucuriScan::escape( $error_log->line_number ),
|
11461 |
));
|
11462 |
+
$errorlogs_counter += 1;
|
11463 |
}
|
11464 |
|
11465 |
+
if ( $errorlogs_counter <= 0 ) {
|
11466 |
+
$template_variables['ErrorLog.InvalidFormatVisibility'] = 'visible';
|
11467 |
}
|
11468 |
+
} else {
|
11469 |
+
$template_variables['ErrorLog.NoItemsVisibility'] = 'visible';
|
11470 |
}
|
11471 |
|
11472 |
+
return SucuriScanTemplate::get_section( 'infosys-errorlogs', $template_variables );
|
11473 |
}
|
11474 |
|
11475 |
/**
|
11487 |
$info_vars = array(
|
11488 |
'Plugin_version' => SUCURISCAN_VERSION,
|
11489 |
'Plugin_checksum' => SUCURISCAN_PLUGIN_CHECKSUM,
|
11490 |
+
'Last_filesystem_scan' => SucuriScanFSScanner::get_filesystem_runtime( true ),
|
11491 |
'Using_CloudProxy' => 'Unknown',
|
11492 |
+
'Support_Reverse_Proxy' => 'Unknown',
|
11493 |
+
'Host_Address' => 'Unknown',
|
11494 |
'HTTP_Host' => 'Unknown',
|
11495 |
'Host_Name' => 'Unknown',
|
11496 |
+
'Site_URL' => 'Unknown',
|
11497 |
+
'Top_Level_Domain' => 'Unknown',
|
11498 |
'Remote_Address' => SucuriScan::get_remote_addr(),
|
11499 |
'Remote_Address_Header' => SucuriScan::get_remote_addr_header(),
|
11500 |
+
'Operating_system' => sprintf( '%s (%d Bit)', PHP_OS, PHP_INT_SIZE * 8 ),
|
11501 |
'Server' => 'Unknown',
|
11502 |
'Developer_mode' => 'OFF',
|
11503 |
'Memory_usage' => 'N/A',
|
11506 |
'PHP_version' => PHP_VERSION,
|
11507 |
);
|
11508 |
|
11509 |
+
$proxy_info = SucuriScan::is_behind_cloudproxy( true );
|
11510 |
+
$reverse_proxy = SucuriScan::support_reverse_proxy();
|
11511 |
+
|
11512 |
$info_vars['HTTP_Host'] = $proxy_info['http_host'];
|
11513 |
$info_vars['Host_Name'] = $proxy_info['host_name'];
|
11514 |
$info_vars['Host_Address'] = $proxy_info['host_addr'];
|
11515 |
+
$info_vars['Site_URL'] = SucuriScan::get_domain();
|
11516 |
+
$info_vars['Top_Level_Domain'] = SucuriScan::get_domain( true );
|
11517 |
$info_vars['Using_CloudProxy'] = $proxy_info['status'] ? 'Yes' : 'No';
|
11518 |
+
$info_vars['Support_Reverse_Proxy'] = $reverse_proxy ? 'Yes' : 'No';
|
11519 |
|
11520 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ){
|
11521 |
$info_vars['Developer_mode'] = 'ON';
|
11522 |
}
|
11523 |
|
11524 |
+
if ( function_exists( 'memory_get_usage' ) ){
|
11525 |
+
$info_vars['Memory_usage'] = round( memory_get_usage() / 1024 / 1024, 2 ).' MB';
|
11526 |
}
|
11527 |
|
11528 |
+
if ( isset($_SERVER['SERVER_SOFTWARE']) ){
|
11529 |
+
$info_vars['Server'] = SucuriScan::escape( $_SERVER['SERVER_SOFTWARE'] );
|
11530 |
}
|
11531 |
|
11532 |
+
if ( $wpdb ){
|
11533 |
+
$info_vars['MySQL_version'] = $wpdb->get_var( 'SELECT VERSION() AS version' );
|
11534 |
|
11535 |
+
$mysql_info = $wpdb->get_results( 'SHOW VARIABLES LIKE "sql_mode"' );
|
11536 |
+
if ( is_array( $mysql_info ) && ! empty($mysql_info[0]->Value) ){
|
11537 |
$info_vars['SQL_mode'] = $mysql_info[0]->Value;
|
11538 |
}
|
11539 |
}
|
11549 |
'max_input_time',
|
11550 |
);
|
11551 |
|
11552 |
+
foreach ( $field_names as $php_flag ){
|
11553 |
+
$php_flag_value = SucuriScan::ini_get( $php_flag );
|
11554 |
$php_flag_name = 'PHP_' . $php_flag;
|
11555 |
+
$info_vars[ $php_flag_name ] = $php_flag_value ? $php_flag_value : 'N/A';
|
11556 |
}
|
11557 |
|
11558 |
$counter = 0;
|
11559 |
|
11560 |
+
foreach ( $info_vars as $var_name => $var_value ){
|
11561 |
+
$css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
|
11562 |
+
$var_name = str_replace( '_', chr( 32 ), $var_name );
|
11563 |
|
11564 |
$template_variables['ServerInfo.Variables'] .= SucuriScanTemplate::get_snippet('infosys-serverinfo', array(
|
11565 |
'ServerInfo.CssClass' => $css_class,
|
11569 |
$counter += 1;
|
11570 |
}
|
11571 |
|
11572 |
+
return SucuriScanTemplate::get_section( 'infosys-serverinfo', $template_variables );
|
11573 |
}
|
11574 |
|