Sucuri Security – Auditing, Malware Scanner and Security Hardening - Version 1.7.6

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 Icon 128x128 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 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:#74cc2e;border-color:#2da200;box-shadow:inset 0 1px 0 rgba(155, 230, 120, 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:#4bbe1e}
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:#cc2e2e;border-color:#a20000;box-shadow:inset 0 1px 0 rgba(230, 120, 120, 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:#be1e1e}
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
- /* Integrity Styles */
153
- .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}
154
- .sucuriscan-status-added{background:#dff0d8;color:#3c763d;border-color:#d6e9c6}
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
- .sucuriscan-corefiles-abbrs .sucuriscan-status-type{width:initial;font-size:12px;text-transform:capitalize;float:left;margin-top:4px;margin-right:5px}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 many error log files may
20
- have thousand of lines, so you will only see the latest entries to prevent PHP
21
- interpreter to stop the execution of the parser when the maximum execution time
22
- is reached.
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="2">Audit Logs (%%SUCURI.AuditLogs.Count%% latest logs)</th>
6
  </tr>
7
  <tr>
8
- <th width="150">Date &amp; Time</th>
9
- <th>Event &amp; Message</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="2">
18
  <em>No logs so far.</em>
19
  </td>
20
  </tr>
21
 
22
  <tr class="sucuriscan-%%SUCURI.AuditLogs.PaginationVisibility%%">
23
- <td colspan="2">
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>&nbsp;</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
- <span class="sucuriscan-monospace">%%SUCURI.AuditLog.Message%%</span>
 
 
 
 
 
 
 
6
 
7
- %%SUCURI.AuditLog.Extra%%
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="3" class="sucuriscan-clearfix thead-with-button">
18
  <span>Core integrity (%%SUCURI.CoreFiles.ListCount%% files)</span>
19
- <div class="sucuriscan-pull-right sucuriscan-corefiles-abbrs">
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="3" class="sucuriscan-corefiles-warning">
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>/&lt;root&gt;</code>, <code>/wp-admin</code> and/or <code>/wp-
35
- includes</code>. You may want to check each file to determine if they were
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="70" class="manage-column">Status</th>
48
- <th class="manage-column">Filepath</th>
 
49
  </tr>
50
  </thead>
51
 
@@ -55,7 +50,7 @@
55
 
56
  <tfoot>
57
  <tr>
58
- <td colspan="3">
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="remove">Remove file(s)</option>
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>/&lt;root&gt;</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-status-type sucuriscan-status-%%SUCURI.CoreFiles.StatusType%%">%%SUCURI.CoreFiles.StatusAbbr%%</span></td>
 
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>Filepath</th>
26
- <th width="130">CheckSum</th>
27
- <th width="200">Modification</th>
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><span class="sucuriscan-monospace sucuriscan-ellipsis" title="%%SUCURI.ModifiedFiles.CheckSum%%">%%SUCURI.ModifiedFiles.CheckSum%%</span></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>There isn't information available for this account.</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 a brute-force attack using the
9
- <a href="http://kb.sucuri.net/definitions/attacks/brute-force/password-guessing" target="_blank">password
10
- guessing</a> technique. Multiple failed logins will be considered part of the attack if there are more than
11
- <code>%%SUCURI.FailedLogins.MaxFailedLogins%%</code> during the same hour, that's why you will only see
12
- <em>(in this table)</em> information of the last hour, previous reports will be sent to your email if you
13
- checked the alert option in the settings page to receive notifications of brute-force attacks.
 
 
 
 
14
  </p>
15
 
16
- <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.FailedLogins.WarningVisibility%%">
17
  <p>
18
- The option to notify possible <strong>password guessing</strong> attacks is
19
- disabled, failed logins reports will not be sent to your email when they occur.
20
- Go to the <a href="%%SUCURI.URL.Settings%%#settings-notifications">notification
21
- settings</a> to enable the brute-force attack alerts.
 
 
22
  </p>
23
  </div>
24
 
25
  <div class="sucuriscan-inline-alert-error sucuriscan-%%SUCURI.FailedLogins.CollectPasswordsVisibility%%">
26
  <p>
27
- If you type a wrong password by mistake your password, the plugin will log the
28
- username and password in the security logs <em>(which are text/plain
29
- files)</em>. If someone get access to your API key, or your server fails to
30
- process the PHP files <em>(which is not usual but may happen)</em> then an
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.Displayname%% (%%SUCURI.ResetPassword.Username%%)</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>
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 Key</td>
42
  <td>
43
  <span class="sucuriscan-monospace">%%SUCURI.APIKey%%</span>
44
  </td>
@@ -62,19 +62,31 @@
62
  </tr>
63
 
64
  <tr>
65
- <td>Send alerts to</td>
66
- <td>%%SUCURI.NotifyTo%%</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_notify_to" class="input-text" placeholder="Send notifications to..." />
71
  <button type="submit" class="button-primary">Change</button>
72
  </form>
73
  </td>
74
  </tr>
75
 
76
  <tr class="alternate">
77
- <td>Alerts per hour</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 class="alternate">
105
- <td>Verify SSL Cert</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>Log storage path</td>
144
- <td><span class="sucuriscan-monospace sucuriscan-wraptext" title="%%SUCURI.DatastorePath%%">%%SUCURI.DatastorePath%%</span></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="sucuriscan_datastore_path" class="input-text" placeholder="Directory to save logs..." />
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 Notifications</h3>
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 Notifications</h3>
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 width="50">#Num</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>&nbsp;</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" width="80">Ignored</th>
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">&nbsp;</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 Interface</em> is the method that will be used internally to
19
- retrieve the diretories and files inside the project when the file system
20
- scanners are executed. In the best case <strong>SPL</strong> will be enough
21
- <em>(and it is the default option)</em>, but with older versions of PHP you may
22
- need to choose a different method like <strong>OpenDir</strong> or
23
- <strong>Glob</strong> which provide the same results.
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>Filesystem scanner</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>Scan modified files</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>Integrity checking</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 some files</td>
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>Scan error log files</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>Parse error logs</td>
102
- <td>%%SUCURI.ParseErrorLogsStatus%%</td>
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="sucuriscan_parse_errorlogs" value="%%SUCURI.ParseErrorLogsSwitchValue%%" />
107
- <button type="submit" class="button-primary %%SUCURI.ParseErrorLogsSwitchCssClass%%">%%SUCURI.ParseErrorLogsSwitchText%%</button>
108
  </form>
109
  </td>
110
  </tr>
111
 
112
  <tr class="alternate">
113
- <td>SiteCheck scanner</td>
114
- <td>%%SUCURI.SiteCheckScannerStatus%%</td>
115
  <td class="td-with-button">
116
- <form action="%%SUCURI.URL.Settings%%#settings-scanner" method="post">
117
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
118
- <input type="hidden" name="sucuriscan_sitecheck_scanner" value="%%SUCURI.SiteCheckScannerSwitchValue%%" />
119
- <button type="submit" class="button-primary %%SUCURI.SiteCheckScannerSwitchCssClass%%">%%SUCURI.SiteCheckScannerSwitchText%%</button>
120
  </form>
121
  </td>
122
  </tr>
123
 
124
  <tr>
125
- <td>SiteCheck counter</td>
126
- <td><span class="sucuriscan-monospace">%%SUCURI.SiteCheckCounter%% scans so far</span></td>
127
  <td class="td-with-button">
128
- <form action="%%SUCURI.URL.Scanner%%" method="post">
129
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
130
- <input type="hidden" name="sucuriscan_malware_scan" value="1" />
131
- <button type="submit" class="button-primary">Force Scan</button>
132
  </form>
133
  </td>
134
  </tr>
135
 
136
  <tr class="alternate">
137
- <td>Last Scanning</td>
138
- <td><span class="sucuriscan-monospace">%%SUCURI.ScanningRuntimeHuman%%</span></td>
139
  <td class="td-with-button">
140
- <form action="%%SUCURI.URL.Home%%" method="post">
141
  <input type="hidden" name="sucuriscan_page_nonce" value="%%SUCURI.PageNonce%%" />
142
- <button type="submit" name="sucuriscan_force_scan" class="button-primary">Force Scan</button>
 
143
  </form>
144
  </td>
145
  </tr>
146
 
147
  <tr>
148
- <td>Scanning frequency</td>
149
- <td>%%SUCURI.ScanningFrequency%%</td>
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
- <select name="sucuriscan_scan_frequency">
154
- %%SUCURI.ScanningFrequencyOptions%%
155
- </select>
156
- <button type="submit" class="button-primary">Change</button>
157
  </form>
158
  </td>
159
  </tr>
160
 
161
  <tr class="alternate">
162
- <td>Scanning interface</td>
163
- <td>%%SUCURI.ScanningInterface%%</td>
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
- <select name="sucuriscan_scan_interface">
168
- %%SUCURI.ScanningInterfaceOptions%%
169
- </select>
170
- <button type="submit" class="button-primary">Change</button>
171
  </form>
172
  </td>
173
  </tr>
174
 
175
  <tr>
176
- <td>Error logs limit</td>
177
- <td>%%SUCURI.ErrorLogsLimit%% last lines</td>
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="text" name="sucuriscan_errorlogs_limit" placeholder="Number of lines to analyze" class="input-text" />
182
- <button type="submit" class="button-primary">Change</button>
 
 
 
 
 
 
 
 
 
 
 
 
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.5
7
- Tested up to: 4.0.1
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.5
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.5');
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
- 'sucuriscan_prettify_mails' => 'Enable email alerts in HTML <em>(uncheck to get email in plain text format)</em>',
 
191
  'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
192
- 'sucuriscan_notify_user_registration' => 'Enable email alerts for new user registration',
193
- 'sucuriscan_notify_success_login' => 'Enable email alerts for successful logins',
194
- 'sucuriscan_notify_failed_login' => 'Enable email alerts for failed logins',
195
- 'sucuriscan_notify_bruteforce_attack' => 'Enable email alerts for login brute-force attack',
196
- 'sucuriscan_notify_post_publication' => 'Enable email alerts for new site content',
197
- 'sucuriscan_notify_theme_editor' => 'Enable email alerts when a file is modified via the theme/plugin editor',
198
- 'sucuriscan_notify_website_updated' => 'Enable email alerts when your website is updated',
199
- 'sucuriscan_notify_settings_updated' => 'Enable email alerts when your website settings are updated',
200
- 'sucuriscan_notify_theme_switched' => 'Enable email alerts when the website theme is switched',
201
- 'sucuriscan_notify_theme_updated' => 'Enable email alerts when a theme is updated',
202
- 'sucuriscan_notify_widget_added' => 'Enable email alerts when a widget is added to a sidebar',
203
- 'sucuriscan_notify_widget_deleted' => 'Enable email alerts when a widget is deleted from a sidebar',
204
- 'sucuriscan_notify_plugin_change' => 'Enable email alerts for Sucuri plugin changes',
205
- 'sucuriscan_notify_plugin_activated' => 'Enable email alerts when a plugin is activated',
206
- 'sucuriscan_notify_plugin_deactivated' => 'Enable email alerts when a plugin is deactivated',
207
- 'sucuriscan_notify_plugin_updated' => 'Enable email alerts when a plugin is updated',
208
- 'sucuriscan_notify_plugin_installed' => 'Enable email alerts when a plugin is installed',
209
- 'sucuriscan_notify_plugin_deleted' => 'Enable email alerts when a plugin is deleted',
 
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
- $sucuriscan_admin_notice_name = SucuriScan::is_multisite() ? 'network_admin_notices' : 'admin_notices';
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<$length; $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
- if ( function_exists('wp_upload_dir') ) {
477
- $wp_dir_array = wp_upload_dir();
478
- $wp_dir_array['basedir'] = untrailingslashit($wp_dir_array['basedir']);
479
- $datastore_path = $wp_dir_array['basedir'] . '/sucuri';
 
 
 
 
 
480
  }
481
 
482
- else {
483
- $datastore_path = rtrim(ABSPATH, '/') . '/wp-content/uploads/sucuri';
 
 
 
 
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 TRUE;
505
  }
506
 
507
- return FALSE;
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
- // Maybe the version number is in the database.
517
- $version = get_option('version');
518
- if( $version ){ return $version; }
 
 
 
 
 
 
519
 
520
- // If not, then check for a specific file.
521
- $wp_version_path = ABSPATH . WPINC . '/version.php';
522
- if( file_exists($wp_version_path) ){
523
- include($wp_version_path);
524
- if( isset($wp_version) ){ return $wp_version; }
525
  }
526
 
527
- // At last, use the checksum of the main framework class.
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 FALSE;
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 FALSE;
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=FALSE ){
597
  $remote_addr = '';
598
  $header_used = 'unknown';
599
 
600
- if( self::is_behind_cloudproxy() ){
 
 
 
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(TRUE);
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 FALSE;
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
- } else {
672
- if( !isset($_SERVER['HTTP_HOST']) ){
673
- $_SERVER['HTTP_HOST'] = 'localhost';
674
- }
675
 
676
- $site_url = $_SERVER['HTTP_HOST'];
677
  }
678
 
679
- $pattern = '/([fhtps]+:\/\/)?([^:\/]+)(:[0-9:]+)?(\/.*)?/';
680
- $domain_name = preg_replace( $pattern, '$2', $site_url );
 
 
 
 
 
 
 
 
 
681
 
682
- return $domain_name;
 
 
 
 
 
 
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=FALSE ){
692
- $http_host = self::get_domain();
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 === FALSE
704
  && SucuriScanAPI::get_cloudproxy_key()
705
  ) {
706
- $status = TRUE;
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 FALSE;
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 NULL;
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/$intervals[1][1]);
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 FALSE; }
866
  }
867
 
868
- return TRUE;
869
  }
870
  }
871
 
872
- return FALSE;
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 TRUE;
886
  }
887
  }
888
 
889
- return FALSE;
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 FALSE;
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=FALSE ){
942
  $valid_emails = array();
943
 
944
- if( strpos($email, ',') !== FALSE ){
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 === TRUE ){
962
  return $valid_emails;
963
  }
964
 
965
- return self::implode(', ', $valid_emails);
966
  }
967
 
968
- return FALSE;
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 TRUE;
1014
  }
1015
  }
1016
  }
1017
 
1018
- return FALSE;
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 FALSE;
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 = TRUE;
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 = TRUE;
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 = TRUE;
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=FALSE ){
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 FALSE;
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=NULL ){
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) !== FALSE ){
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( !$dh ){ return FALSE; }
1476
 
1477
  $files = array();
1478
- while( ($filename = readdir($dh)) !== FALSE ){
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 TRUE; }
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 TRUE;
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) !== FALSE ){
1525
- return TRUE;
1526
  }
1527
  }
1528
  }
1529
  }
1530
 
1531
- return FALSE;
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( !$this->ignore_files ){ return FALSE; }
1542
 
1543
  // Ignoring backup files from our clean ups.
1544
- if( strpos($filename, '_sucuribackup.') !== FALSE ){ return TRUE; }
1545
 
1546
  // Any file maching one of these rules WILL NOT be ignored.
1547
- if(
1548
- ( strpos($filename, '.php') !== FALSE) ||
1549
- ( strpos($filename, '.htm') !== FALSE) ||
1550
- ( strpos($filename, '.js') !== FALSE) ||
1551
- ( strcmp($filename, '.htaccess') == 0 ) ||
1552
- ( strcmp($filename, 'php.ini') == 0 )
1553
- ){ return FALSE; }
1554
 
1555
- return TRUE;
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 = TRUE;
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( !$removed ){
1605
- $all_removed = FALSE;
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 FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
  }
1695
 
1696
  }
@@ -1723,7 +1805,7 @@ class SucuriScanCache extends SucuriScan {
1723
  *
1724
  * @var null|string
1725
  */
1726
- private $datastore = NULL;
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 = FALSE;
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 FALSE;
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 FALSE;
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=FALSE ){
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 FALSE;
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=NULL ){
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=NULL ){
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 TRUE;
1986
  }
1987
  }
1988
 
1989
- return FALSE;
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=NULL, $action='', $lifetime=0, $assoc=FALSE ){
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
- !$this->data_has_expired($lifetime, $finfo)
2019
- && array_key_exists($key, $finfo['entries'])
2020
  ){
2021
- return $finfo['entries'][$key];
2022
  }
2023
  break;
2024
  case 'get_all': /* no_break */
2025
- if( !$this->data_has_expired($lifetime, $finfo) ) {
2026
  return $finfo['entries'];
2027
  }
2028
  case 'exists':
2029
- if(
2030
- !$this->data_has_expired($lifetime, $finfo)
2031
- && array_key_exists($key, $finfo['entries'])
2032
  ){
2033
- return TRUE;
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 FALSE;
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=FALSE ){
2081
- $assoc = ( $assoc == 'array' ? TRUE : $assoc );
2082
 
2083
- return $this->handle_key_data( $key, NULL, 'get', $lifetime, $assoc );
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=FALSE ){
2094
- $assoc = ( $assoc == 'array' ? TRUE : $assoc );
2095
 
2096
- return $this->handle_key_data( 'temp', NULL, 'get_all', $lifetime, $assoc );
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, NULL, 'exists' );
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, NULL, 'delete' );
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
- private static function get_default_option_values(){
2163
  $defaults = array(
2164
- 'sucuriscan_api_key' => FALSE,
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' => 'enabled',
2171
  'sucuriscan_scan_checksums' => 'enabled',
2172
- 'sucuriscan_scan_errorlogs' => 'enabled',
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
- private static function get_default_options( $settings='' ){
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( is_string($settings) ){
2231
- if( isset($default_options[$settings]) ){
2232
- return $default_options[$settings];
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 $output;
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
- $option_name = self::variable_prefix($option_name);
 
 
 
 
 
 
 
 
 
2296
 
2297
- return self::get_options($option_name);
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 FALSE;
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 FALSE;
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
- * Retrieve a single option from the database.
2370
  *
2371
- * @param string $option_name Name of the option that will be retrieved.
2372
- * @return string Value of the option stored in the database, FALSE if not found.
2373
  */
2374
- private static function get_single_option( $option_name='' ){
2375
- $option_value = FALSE;
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
- // Use framework built-in function.
2383
- elseif( function_exists('get_option') ){
2384
- $option_value = get_option($option_name);
2385
- }
 
2386
 
2387
- if( $option_value === FALSE && $is_plugin_option ){
2388
- $option_value = self::get_default_options($option_name);
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 = self::get_options_from_db('site_options');
 
 
 
 
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 TRUE;
2480
  }
2481
  }
2482
 
2483
- return FALSE;
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 = FALSE;
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, TRUE );
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 FALSE;
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 FALSE;
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 TRUE;
2575
  }
2576
 
2577
- return FALSE;
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' ) !== FALSE ){
2608
- $response['bad'][$key_name] = $key_value;
2609
  } else {
2610
- $response['good'][$key_name] = $key_value;
2611
  }
2612
  } else {
2613
- $response['missing'][$key_name] = FALSE;
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=FALSE ){
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 === FALSE
2670
  ){
2671
- return FALSE;
2672
  }
2673
 
2674
  // Check if the last runtime is too near the current time.
2675
- if( $last_run && !$force_scan ){
2676
  $runtime_diff = $current_time - $runtime;
2677
 
2678
- if( $last_run >= $runtime_diff ){
2679
- return FALSE;
2680
  }
2681
  }
2682
 
2683
  SucuriScanOption::update_option( $option_name, $current_time );
2684
 
2685
- return TRUE;
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
- SucuriScanAPI::send_log( 'WordPress version: ' . $wp_version );
2701
  SucuriScanOption::update_option( $option_name, $wp_version );
2702
 
2703
- return TRUE;
2704
  }
2705
 
2706
- return FALSE;
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=FALSE ){
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 TRUE;
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 FALSE;
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
- public static function report_event( $severity=0, $location='', $message='' ){
2758
  $user = wp_get_current_user();
2759
- $username = FALSE;
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( ' %s (%s),', $user->display_name, $user->user_login );
2776
  } else {
2777
- $username = sprintf( ' %s,', $user->user_login );
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
- $message = str_replace( array("\n", "\r"), array('', ''), $message );
 
 
 
 
 
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 .= " are getting too many of these messages, it is likely your site is under a brute";
2829
- $content .= " force attack. You can disable the notifications for failed logins from here [1].";
2830
  $content .= " More details at Password Guessing Brute Force Attacks [2].</em><br>\n<br>\n";
2831
- $content .= "[1] " . SucuriScanTemplate::get_url('settings') . " <br>\n";
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'] = TRUE;
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 FALSE;
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 ( !$remote_addr ) {
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 TRUE;
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 = FALSE;
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 TRUE;
2896
  }
2897
  }
2898
  }
2899
 
2900
- return FALSE;
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, TRUE, FALSE);
2917
 
2918
- $message .= 'The password for your user account <strong>"'. $user->display_name .'"</strong> '
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' => TRUE ); // Skip limit for emails per hour.
2925
  SucuriScanMail::send_mail( $user->user_email, 'Password changed', $message, $data_set );
2926
 
2927
- wp_set_password($new_password, $user_id);
2928
 
2929
- return TRUE;
2930
  }
2931
  }
2932
 
2933
- return FALSE;
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 FALSE;
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 advising that an attachment was added to a post.
3016
  *
3017
  * @param integer $id The post identifier.
3018
  * @return void
3019
  */
3020
- public static function hook_add_attachment( $id=0 ){
3021
- $data = ( is_int($id) ? get_post($id) : FALSE );
3022
- $title = ( $data ? $data->post_title : 'Unknown' );
 
 
 
 
 
 
3023
 
3024
- $message = 'Media file added #'.$id.' ('.$title.')';
3025
- self::report_event( 1, 'core', $message );
3026
  self::notify_event( 'post_publication', $message );
3027
  }
3028
 
3029
  /**
3030
- * Send an alert advising that a new link was added to the bookmarks.
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 = ( is_int($id) ? get_bookmark($id) : FALSE );
3037
-
3038
- if( $data ){
3039
  $title = $data->link_name;
3040
  $url = $data->link_url;
 
3041
  } else {
3042
- $title = 'Unknown';
3043
  $url = 'undefined/url';
 
3044
  }
3045
 
3046
- $message = 'New link added #'.$id.' ('.$title.': '.$url.')';
3047
- self::report_event( 2, 'core', $message );
 
 
 
3048
  self::notify_event( 'post_publication', $message );
3049
  }
3050
 
3051
  /**
3052
- * Send an alert advising that a category was created.
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 #'.$id.' ('.$title.')';
3061
- self::report_event( 1, 'core', $message );
3062
  self::notify_event( 'post_publication', $message );
3063
  }
3064
 
3065
  /**
3066
- * Send an alert advising that a post was deleted.
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::report_event( 3, 'core', 'Post deleted #'.$id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3073
  }
3074
 
3075
  /**
3076
- * Send an alert advising that a user account was deleted.
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::report_event( 3, 'core', 'User account deleted #'.$id );
3083
  }
3084
 
3085
  /**
3086
- * Send an alert advising that an attempt to reset the password
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::report_event( 3, 'core', 'Attempt to reset password by attacking WP/2.8.3 bug' );
3095
  }
3096
  }
3097
 
3098
  /**
3099
- * Send an alert advising that the state of a post was changed
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 = ( is_int($id) ? get_post($id) : FALSE );
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 = $p_type.' changed from private to published #'.$id.' ('.$title.')';
3119
- self::report_event( 2, 'core', $message );
 
 
 
3120
  self::notify_event( 'post_publication', $message );
3121
  }
3122
  }
3123
 
3124
  /**
3125
- * Send an alert advising that a post was published.
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 = ( is_int($id) ? get_post($id) : FALSE );
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 = $p_type.' was '.$action.' #'.$id.' ('.$title.')';
3144
- self::report_event( 2, 'core', $message );
 
 
 
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 advising that an attempt to retrieve the password
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 = 'Unknown'; }
3197
 
3198
- self::report_event( 3, 'core', 'Password retrieval attempt for user: '.$title );
3199
  }
3200
 
3201
  /**
3202
- * Send an alert advising that the theme of the site was changed.
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 = 'Unknown'; }
3209
 
3210
- $message = 'Theme switched to: '.$title;
3211
- self::report_event( 3, 'core', $message );
3212
- self::notify_event( 'theme_switched', $message );
3213
  }
3214
 
3215
  /**
3216
- * Send an alert advising that a new user account was created.
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 = ( is_int($id) ? get_userdata($id) : FALSE );
3223
- $title = ( $data ? $data->display_name : 'Unknown' );
 
 
 
 
 
 
 
3224
 
3225
- $message = 'New user account registered #'.$id.' ('.$title.')';
3226
- self::report_event( 3, 'core', $message );
 
 
 
3227
  self::notify_event( 'user_registration', $message );
3228
  }
3229
 
3230
  /**
3231
- * Send an alert advising that an attempt to login into the
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 logged in: '.$title;
3241
- self::report_event( 2, 'core', $message );
3242
  self::notify_event( 'success_login', $message );
3243
  }
3244
 
3245
  /**
3246
- * Send an alert advising that an attempt to login into the
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
- $password = SucuriScanRequest::post('pwd');
 
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::get('action', '(activate|deactivate)') ||
3323
- SucuriScanRequest::post('action', '(activate|deactivate)-selected')
3324
  )
3325
  ){
3326
  $plugin_list = array();
 
 
 
 
 
 
3327
 
3328
- if(
3329
- SucuriScanRequest::get('plugin', '.+')
3330
- && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== FALSE
3331
  ){
3332
- $action_d = $_GET['action'] . 'd';
3333
- $plugin_list[] = $_GET['plugin'];
3334
  }
3335
 
3336
- elseif(
3337
  isset($_POST['checked'])
3338
- && is_array($_POST['checked'])
3339
- && !empty($_POST['checked'])
3340
  ){
3341
- $action_d = str_replace('-selected', 'd', $_POST['action']);
3342
- $plugin_list = $_POST['checked'];
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
- $message = sprintf(
3353
- 'Plugin %s: %s (v%s; %s)',
3354
- $action_d,
3355
- self::escape($plugin_info['Name']),
3356
- self::escape($plugin_info['Version']),
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::get('action', '(upgrade-plugin|do-plugin-upgrade)')
3371
- || SucuriScanRequest::post('action', 'update-selected')
3372
  )
3373
  ){
3374
  $plugin_list = array();
 
3375
 
3376
- if(
3377
- SucuriScanRequest::get('plugin', '.+')
3378
- && strpos($_SERVER['REQUEST_URI'], 'wp-admin/update.php') !== FALSE
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 = $_POST['checked'];
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
- $message = sprintf(
3399
- 'Plugin request to be updated: %s (v%s; %s)',
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( !$plugin ){ $plugin = 'Unknown'; }
3422
  }
3423
 
3424
- $message = 'Plugin request to be installed: ' . self::escape($plugin);
3425
- self::report_event( 3, 'core', $message );
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 = (array) $_POST['checked'];
 
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
- $message = sprintf(
3445
- 'Plugin request to be deleted: %s (v%s; %s)',
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') !== FALSE
3464
  ){
3465
- $message = 'Plugin editor used on: ' . SucuriScanRequest::post('file');
3466
- self::report_event( 3, 'core', $message );
 
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') !== FALSE
3477
  ){
3478
- $message = 'Theme editor used on: ' . SucuriScanRequest::post('theme') . '/' . SucuriScanRequest::post('file');
3479
- self::report_event( 3, 'core', $message );
 
 
3480
  self::notify_event( 'theme_editor', $message );
3481
  }
3482
 
3483
- // Theme activation and/or deactivation (same hook for switch_theme).
3484
- // Theme installation request (hook not available).
3485
- // Theme deletion request (hook not available).
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
- $themes = SucuriScanRequest::post('checked', '_array');
3494
 
3495
- foreach( $themes as $theme ){
3496
- $theme_info = wp_get_theme($theme);
3497
- $theme_name = ucwords($theme);
3498
- $theme_version = '0.0';
3499
 
3500
- if( $theme_info->exists() ){
3501
- $theme_name = $theme_info->get('Name');
3502
- $theme_version = $theme_info->get('Version');
3503
- }
3504
 
3505
- $message = sprintf(
3506
- 'Theme updated: %s (v%s; %s)',
3507
- self::escape($theme_name),
3508
- self::escape($theme_version),
3509
- self::escape($theme)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3510
  );
 
3511
 
3512
- self::report_event( 3, 'core', $message );
 
 
 
 
 
 
 
 
 
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::report_event( 3, 'core', $message );
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') !== FALSE
3533
- && SucuriScanRequest::post('sidebar') !== FALSE
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::report_event( 3, 'core', $message );
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
- $all_options = SucuriScanOption::get_site_options();
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 = FALSE;
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
- self::report_event( 3, 'core', $message );
 
 
 
 
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( !$url ){ return FALSE; }
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' => TRUE,
3692
  'headers' => array(),
3693
  'cookies' => array(),
3694
- 'compress' => FALSE,
3695
- 'decompress' => FALSE,
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'] === TRUE ) ? TRUE : FALSE;
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'] = NULL;
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 FALSE;
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 TRUE or FALSE if the key was saved successfully or not respectively.
3759
  */
3760
- public static function set_plugin_key( $api_key='', $validate=FALSE ){
3761
- if( $validate ){
3762
- if( !preg_match('/^[a-z0-9]{32}$/', $api_key) ){
3763
  SucuriScanInterface::error( 'Invalid API key format' );
3764
- return FALSE;
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 FALSE if it does not exists.
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 FALSE;
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 FALSE if the key is invalid or not present, an array otherwise.
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( !$api_key ){
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, TRUE );
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 FALSE;
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 TRUE if the API key specified is valid, FALSE otherwise.
3833
  */
3834
- public static function is_valid_cloudproxy_key( $api_key='', $return_match=FALSE ){
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 TRUE;
3841
  }
3842
 
3843
- return FALSE;
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=TRUE, $args=array() ){
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( !$api_key ){ return FALSE; }
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 = FALSE;
3882
 
3883
- if( isset($params['k']) && isset($params['s']) ){
3884
- $send_request = TRUE;
3885
  } else {
3886
  $api_key = self::get_cloudproxy_key();
3887
 
3888
- if( $api_key ){
3889
- $send_request = TRUE;
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 FALSE;
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 TRUE or FALSE in case of success or failure of the API response (respectively).
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 TRUE;
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
- SucuriScanInterface::error( 'Unknown API content-type, it was not a JSON-encoded response.' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3935
  }
3936
  }
3937
 
3938
- return FALSE;
3939
  }
3940
 
3941
  /**
3942
  * Send a request to the API to register this site.
3943
  *
3944
- * @return boolean TRUE if the API key was generated, FALSE otherwise.
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
- ), FALSE );
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 TRUE;
3960
  }
3961
 
3962
- return FALSE;
3963
  }
3964
 
3965
  /**
3966
  * Send a request to recover a previously registered API key.
3967
  *
3968
- * @return boolean TRUE if the API key was sent to the administrator email, FALSE otherwise.
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
- ), FALSE );
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 TRUE;
3984
  }
3985
 
3986
- return FALSE;
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 TRUE if the event was logged in the monitoring service, FALSE otherwise.
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
- ), TRUE, array( 'timeout' => 20 ) );
4004
 
4005
- if( self::handle_response($response) ){
4006
- return TRUE;
4007
  }
4008
  }
4009
 
4010
- return FALSE;
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
- 'extra' => FALSE,
4038
- 'extra_total' => 0,
4039
  );
4040
 
4041
  $log_data['message'] = str_replace( ', new size', '; new size', $log_data['message'] );
 
 
 
 
 
 
4042
 
4043
- if( preg_match($extra_pattern, $log_data['message'], $log_extra) ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4044
  $log_data['message'] = $log_extra[1];
4045
- $log_data['extra'] = explode(',', $log_extra[2]);
4046
- $log_data['extra_total'] = count($log_data['extra']);
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 FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 TRUE if the hashes were stored, FALSE otherwise.
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 TRUE;
4077
  }
4078
  }
4079
 
4080
- return FALSE;
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=FALSE ){
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 FALSE;
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=FALSE ){
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 FALSE;
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=FALSE, $date='' ){
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 FALSE;
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' => TRUE
4187
- ));
4188
 
4189
- if( $response ){
4190
  return $response['body'];
4191
  }
4192
  }
4193
 
4194
- return FALSE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 FALSE;
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 FALSE;
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 !== FALSE ){
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 = FALSE;
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 = TRUE;
4302
  }
4303
 
4304
  // Retrieve the WordPress plugin page from the plugin's filename.
4305
  else {
4306
- if( strpos($plugin_path, '/') !== FALSE ){
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 = TRUE;
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'] = FALSE;
4333
 
4334
- if( is_plugin_active($plugin_path) ){
4335
- $plugins[$plugin_path]['IsPluginActive'] = TRUE;
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/', $plugin );
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 FALSE;
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, FALSE if the file was not found.
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 FALSE;
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 = FALSE;
4449
- $debug = FALSE;
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'] == TRUE
4455
  ){
4456
- $debug = TRUE;
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'] == TRUE
4464
  ){
4465
- $force = TRUE;
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 TRUE;
4491
  }
4492
  }
4493
 
4494
- return FALSE;
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 TRUE;
4589
  }
4590
  } else {
4591
  // Reset the counter of emails sent.
@@ -4593,7 +5097,7 @@ class SucuriScanMail extends SucuriScanOption {
4593
  }
4594
  }
4595
 
4596
- return FALSE;
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 FALSE;
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=FALSE ){
4665
- return ( $visible === TRUE ? 'visible' : 'hidden' );
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 = sprintf( $template_path_pattern, WP_PLUGIN_DIR, SUCURISCAN_PLUGIN_FOLDER, $template );
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
- $num = self::get( 'num', '[0-9]{1,2}' );
4914
 
4915
- return ( $num ? intval($num) : 1 );
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<=$max_pages; $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&num=%d" class="%s">%s</a></li>',
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=FALSE ){
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 FALSE;
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 = TRUE;
5124
- $sucuri_fileinfo->ignore_directories = TRUE;
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+)?\] )?' // Detect date, time, and timezone.
5145
- . '(PHP )?([a-zA-Z ]+):\s' // Detect PHP error severity.
5146
- . '(.+) in (.+)' // Detect error message, and file path.
5147
- . '(:| on line )([0-9]+)' // Detect line number.
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) !== FALSE ) {
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' ? FALSE : TRUE );
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 = FALSE;
5468
- $sucuri_fileinfo->ignore_directories = FALSE;
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
- if( @mkdir($plugin_upload_folder) ){
 
 
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
- '<!-- Attemp to prevent the directory listing. -->',
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
- create this folder manually and give it write permissions:<br><br><code>'
5520
- . $plugin_upload_folder . '</code>'
 
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( !$nonce_value || !wp_verify_nonce($nonce_value, $nonce_name) ){
5554
- wp_die(__('WordPress Nonce verification failed, try again going back and checking the form.') );
5555
 
5556
- return FALSE;
5557
  }
5558
  }
5559
 
5560
- return TRUE;
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; ?>')">&times;</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') === FALSE
5613
- && SucuriScanRequest::post(':recover_key') === FALSE
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 === FALSE ){
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 = TRUE;
5681
 
5682
  // Cache the scanning results to reduce memory lose.
5683
- if( !$cache->add( 'scan_results', $res ) ){
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']) ? TRUE : FALSE;
5704
- $blacklist_warns_exist = isset($res['BLACKLIST']['WARN']) ? TRUE : FALSE;
5705
- $outdated_warns_exist = isset($res['OUTDATEDSCAN']) ? TRUE : FALSE;
5706
- $recommendations_exist = isset($res['RECOMMENDATIONS']) ? TRUE : FALSE;
5707
 
5708
  // Check whether this WordPress installation needs an update.
5709
  global $wp_version;
5710
- $wordpress_updated = FALSE;
5711
- $updates = function_exists('get_core_updates') ? get_core_updates() : array();
5712
 
5713
- if( !is_array($updates) || empty($updates) || $updates[0]->response=='latest' ){
5714
- $wordpress_updated = TRUE;
 
 
 
 
5715
  }
5716
 
5717
- if( TRUE ){
 
 
 
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
- <div id="poststuff">
5787
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_malware) ?>">
5788
- <h3>
5789
- <?php if( $malware_warns_exist ): ?>
5790
- Site compromised (malware was identified)
5791
- <?php else: ?>
5792
- Site clean (no malware was identified)
5793
- <?php endif; ?>
5794
- </h3>
5795
-
5796
- <div class="inside">
5797
-
5798
- <?php if( !$malware_warns_exist ): ?>
5799
- <p>
5800
- <span><strong>Malware:</strong> Clean.</span><br>
5801
- <span><strong>Malicious javascript:</strong> Clean.</span><br>
5802
- <span><strong>Malicious iframes:</strong> Clean.</span><br>
5803
- <span><strong>Suspicious redirections (htaccess):</strong> Clean.</span><br>
5804
- <span><strong>Blackhat SEO Spam:</strong> Clean.</span><br>
5805
- <span><strong>Anomaly detection:</strong> Clean.</span>
5806
- </p>
5807
- <?php else: ?>
5808
- <ul>
5809
- <?php
5810
- foreach( $res['MALWARE']['WARN'] as $malres ){
5811
- if( !is_array($malres) ){
5812
- echo '<li>' . htmlspecialchars($malres) . '</li>';
5813
- } else {
5814
- $mwdetails = explode("\n", htmlspecialchars($malres[1]));
5815
- $mw_name_link = isset($mwdetails[0]) ? substr($mwdetails[0], 1) : '';
5816
-
5817
- if( preg_match('/(.*)\. Details: (.*)/', $mw_name_link, $mw_match) ){
5818
- $mw_name_link = sprintf(
5819
- '%s. Details: <a href="%s" target="_blank">%s</a>',
5820
- $mw_match[1], $mw_match[2], $mw_match[2]
5821
- );
5822
- }
5823
-
5824
- echo '<li>'. htmlspecialchars($malres[0]) . "\n<br>" . $mw_name_link . "</li>\n";
5825
- }
5826
- }
5827
- ?>
5828
- </ul>
5829
- <?php endif; ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5830
 
5831
- <p>
5832
- <i>
5833
- More details here: <a href="http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?>"
5834
- target="_blank">http://sitecheck.sucuri.net/results/<?php _e($clean_domain); ?></a>
5835
- </i>
5836
- </p>
5837
-
5838
- <hr />
5839
-
5840
- <p>
5841
- <i>
5842
- If our free scanner did not detect any issue, you may have a more complicated
5843
- and hidden problem. You can <a href="http://sucuri.net/signup" target="_blank">
5844
- sign up</a> with Sucuri for a complete and in depth scan+cleanup (not included
5845
- in the free checks).
5846
- </i>
5847
- </p>
5848
-
5849
- </div>
5850
- </div>
5851
- </div>
 
 
 
 
 
 
 
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 Information</span>
5861
- <?php if( !$wordpress_updated ): ?>
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=>$result_title ): ?>
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=>$webapp_details ): ?>
5912
- <?php if( is_array($webapp_details) ): ?>
5913
- <?php foreach( $webapp_details as $i=>$details ): ?>
5914
- <?php if( is_array($details) ){ $details = isset($details[0]) ? $details[0] : ''; } ?>
 
 
 
 
 
 
 
5915
  <tr>
5916
- <td colspan="2">
5917
- <span class="sucuriscan-monospace"><?php _e($details) ?></span>
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=>$notice ): ?>
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 printf(
 
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=>$result_url_title ): ?>
5993
 
5994
- <?php if( isset($res['LINKS'][$result_url_key]) ): ?>
5995
  <tr>
5996
  <th colspan="2">
5997
- <?php printf(
 
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
- <div id="poststuff">
6030
- <div class="postbox sucuriscan-border <?php _e($sucuriscan_css_blacklist) ?>">
6031
- <h3>
6032
- <?php if( $blacklist_warns_exist ): ?>
6033
- Site blacklisted
6034
- <?php else: ?>
6035
- Site blacklist-free
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6036
  <?php endif; ?>
6037
- </h3>
6038
-
6039
- <div class="inside">
6040
- <ul>
6041
- <?php
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 !== FALSE ){
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 FALSE;
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 = FALSE;
6382
- $filter_by_keyword = FALSE;
6383
- $filter_query = FALSE;
6384
 
6385
- if( $q = SucuriScanRequest::post(':monitoring_denial_type') ){
6386
- $filter_by_denial_type = TRUE;
6387
  $filter_query = $q;
6388
  }
6389
 
6390
- if( $q = SucuriScanRequest::post(':monitoring_log_filter') ){
6391
- $filter_by_keyword = TRUE;
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) === FALSE
6413
- && strpos($access_log->resource_path, $filter_query) === FALSE
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=TRUE ){
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=TRUE ){
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
- sucuriscan_harden_upload();
6598
- sucuriscan_harden_wpcontent();
6599
- sucuriscan_harden_wpincludes();
 
 
 
 
 
 
 
 
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=NULL, $updatemsg=NULL ){ ?>
6637
  <div class="postbox">
6638
- <h3><?php _e($title) ?></h3>
6639
 
6640
  <div class="inside">
6641
- <?php if( $desc != NULL ): ?>
6642
- <p><?php _e($desc) ?></p>
6643
  <?php endif; ?>
6644
 
6645
- <div class="sucuriscan-hstatus sucuriscan-hstatus-<?php _e($status) ?>">
6646
- <?php if( $type != NULL ): ?>
6647
- <?php if( $status == 1 ): ?>
6648
- <input type="submit" name="<?php _e($type) ?>_unharden" value="Revert hardening" class="button-secondary" />
6649
- <?php else: ?>
6650
- <input type="submit" name="<?php _e($type) ?>" value="Harden" class="button-primary" />
 
 
6651
  <?php endif; ?>
6652
- <?php endif; ?>
6653
 
6654
- <span>
6655
- <?php if( $status == 1 ): ?>
6656
- <?php _e($messageok) ?>
6657
- <?php else: ?>
6658
- <?php _e($messagewarn) ?>
6659
- <?php endif; ?>
6660
- </span>
6661
- </div>
 
 
 
6662
 
6663
- <?php if( $updatemsg != NULL ): ?>
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, NULL, $messageok, $messagewarn, $initial_msg );
6708
  }
6709
 
6710
  /**
@@ -6718,14 +7349,46 @@ function sucuriscan_harden_removegenerator(){
6718
  sucuriscan_harden_status(
6719
  'Remove WordPress version',
6720
  1,
6721
- NULL,
6722
  'WordPress version properly hidden',
6723
- NULL,
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') !== FALSE ){
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>") === FALSE ){
6761
- $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
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) ) ? TRUE : FALSE;
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
- SucuriScanInterface::info('Hardening reverted for upload directory.');
 
 
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
- $upmsg
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') !== FALSE ){
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>") === FALSE ){
6833
- $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
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) ) ? TRUE : FALSE;
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
- SucuriScanInterface::info('Hardening reverted for content directory.');
 
 
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
- $upmsg
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') !== FALSE ){
6904
  $cp = 1;
6905
  break;
6906
  }
6907
  }
6908
  }
6909
 
6910
- if( SucuriScanRequest::post(':run_hardening') ){
6911
- if( SucuriScanRequest::post(':harden_wpincludes') && $cp == 0 ){
6912
- if( @file_put_contents($htaccess_upload, "\n<Files *.php>\ndeny from all\n</Files>\n<Files wp-tinymce.php>\nallow from all\n</Files>\n")===FALSE ){
6913
- $upmsg = SucuriScanInterface::error('Unable to create <code>.htaccess</code> file, folder destination is not writable.');
 
 
 
 
 
 
 
 
 
 
 
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) ) ? TRUE : FALSE;
6922
- $htaccess_content = $htaccess_upload_writable ? file_get_contents($htaccess_upload) : '';
6923
 
6924
- if( $htaccess_upload_writable ){
6925
  $cp = 0;
6926
- if( preg_match_all('/<Files (\*|wp-tinymce|ms-files)\.php>\n(deny|allow) from all\n<\/Files>/', $htaccess_content, $match) ){
6927
- foreach($match[0] as $restriction){
6928
- $htaccess_content = str_replace($restriction, '', $htaccess_content);
 
6929
  }
6930
 
6931
- @file_put_contents($htaccess_upload, $htaccess_content, LOCK_EX);
6932
  }
6933
- SucuriScanInterface::info('Hardening reverted for library\'s directory.');
 
 
 
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
- $upmsg
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
- NULL,
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
- NULL
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 === FALSE ){
6991
  $status = 0;
6992
- $btn_string = '<a href="http://cloudproxy.sucuri.net/" target="_blank" class="button button-primary">Harden</a>';
6993
  }
6994
 
6995
  sucuriscan_harden_status(
6996
  'Website Firewall protection',
6997
  $status,
6998
- NULL,
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
- NULL
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 creating them for security reasons'
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
- NULL,
7049
  'Security keys and salts properly created',
7050
  $messageok,
7051
  $message,
7052
- NULL
7053
  );
7054
  }
7055
 
@@ -7061,29 +7746,31 @@ function sucuriscan_harden_secretkeys(){
7061
  * @return void
7062
  */
7063
  function sucuriscan_harden_readme(){
7064
- $upmsg = NULL;
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') === FALSE ){
7071
- $upmsg = SucuriScanInterface::error('Unable to remove <code>readme.html</code> file.');
7072
  } else {
7073
  $cp = 1;
7074
- $upmsg = SucuriScanInterface::info('<code>readme.html</code> file removed successfully.');
 
 
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> file by yourself.');
7080
  }
7081
  }
7082
 
7083
  sucuriscan_harden_status(
7084
  'Information leakage (readme.html)',
7085
  $cp,
7086
- ( $cp == 0 ? 'sucuriscan_harden_readme' : NULL ),
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 = NULL;
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
- NULL,
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 : FALSE;
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) ) ? TRUE : FALSE;
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
- @file_put_contents($wp_config_path, $new_wpconfig, LOCK_EX);
7153
- SucuriScanInterface::info( 'Configuration file updated successfully, the plugin and theme editor were disabled.' );
7154
- $file_editor_disabled = TRUE;
 
 
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
- SucuriScanInterface::info( 'Configuration file updated successfully, the plugin and theme editor were enabled.' );
7168
- $file_editor_disabled = FALSE;
 
 
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 &amp; Theme editor',
7187
- ( $file_editor_disabled === FALSE ? 0 : 1 ),
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
- NULL
7193
  );
7194
  }
7195
 
@@ -7208,7 +7899,7 @@ function sucuriscan_harden_dbtables(){
7208
  sucuriscan_harden_status(
7209
  'Database table prefix',
7210
  $hardened,
7211
- NULL,
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 = FALSE;
7238
- $sucuri_fileinfo->ignore_directories = FALSE;
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 removed <code>' . $removed_logs . ' out of ' . $total_log_files . '</code>' );
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' : NULL ),
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
- NULL
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(TRUE);
7327
  }
7328
 
7329
  // Restore, Remove, Mark as fixed the core files.
7330
- $allowed_actions = '(restore|remove|fixed)';
7331
- $integrity_action = SucuriScanRequest::post(':integrity_action', $allowed_actions);
7332
-
7333
- if( $integrity_action !== FALSE ){
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 'remove':
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=FALSE ){
7389
  $abs_path = rtrim( ABSPATH, '/' );
7390
 
7391
  $sucuri_fileinfo = new SucuriScanFileInfo();
7392
- $sucuri_fileinfo->ignore_files = FALSE;
7393
- $sucuri_fileinfo->ignore_directories = FALSE;
7394
  $sucuri_fileinfo->run_recursively = $recursive;
7395
  $sucuri_fileinfo->scan_interface = 'opendir';
7396
- $integrity_tree = $sucuri_fileinfo->get_directory_tree_md5( $dir, TRUE );
7397
 
7398
- if( !$integrity_tree ){
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
- $offset = 0; // The initial position to start counting the data.
 
7433
 
7434
- if( $logs_limit == $total_items ){
7435
- $offset = $logs_limit - ( $max_per_page + 1 );
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.DateTime' => SucuriScan::datetime($audit_log['timestamp']),
7448
- 'AuditLog.Account' => SucuriScan::escape($audit_log['account']),
7449
- 'AuditLog.Message' => SucuriScan::escape($audit_log['message']),
 
 
 
 
7450
  'AuditLog.Extra' => '',
7451
  );
7452
 
7453
- // Print every extra information item in a separate table.
7454
- if( $audit_log['extra'] ){
7455
- $css_scrollable = $audit_log['extra_total'] > 10 ? 'sucuriscan-list-as-table-scrollable' : '';
7456
  $snippet_data['AuditLog.Extra'] .= '<ul class="sucuriscan-list-as-table ' . $css_scrollable . '">';
7457
- foreach( $audit_log['extra'] as $log_extra ){
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
- $file_path_checksum = md5($file_path);
 
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( !$latest_hashes ){ return FALSE; }
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 TRUE;
7720
  }
7721
  }
7722
 
7723
- return FALSE;
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 = 7;
7742
 
7743
- // Set the ranges of the search to be between one and sixty days.
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
- $template_variables['ModifiedFiles.Days'] = $back_days;
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
- return SucuriScanTemplate::get_section('integrity-modifiedfiles', $template_variables);
 
 
 
 
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 !== FALSE
7828
  ){
7829
- if( $process_form === '1' ){
7830
- return TRUE;
7831
  } else {
7832
- SucuriScanInterface::error('You need to confirm that you understand the risk of this operation.');
7833
  }
7834
  }
7835
 
7836
- return FALSE;
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=FALSE ){
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'] === TRUE ){
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=FALSE ){
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 = get_users();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7936
 
7937
- if( $user_list ){
 
 
 
 
 
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.Email' => SucuriScan::escape($user->user_email),
 
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=FALSE ){
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
- SucuriScanInterface::info( 'Password changed successfully for users: ' . implode(', ',$pwd_changed) );
 
 
 
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=FALSE ){
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=FALSE ){
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 = FALSE;
8056
- $sucuri_fileinfo->ignore_directories = FALSE;
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'] === TRUE ){
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
- $plugin_directory = dirname( WP_PLUGIN_DIR . '/' . $plugin_path );
8073
- $sucuri_fileinfo->remove_directory_tree($plugin_directory);
 
 
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') !== FALSE
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 : FALSE;
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 FALSE;
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 FALSE;
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=$offset; $i<$total_lines; $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, TRUE);
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( !$is_admin_user && $current_user->user_login != $last_login['user_login'] ){
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'] = FALSE;
8426
- $user_account = get_userdata($last_login['user_id']);
8427
 
8428
- if( $user_account ){
8429
- $last_login['user_exists'] = TRUE;
8430
 
8431
- foreach( $user_account->data as $var_name=>$var_value ){
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=NULL, $user=FALSE ){
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(TRUE);
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=FALSE ){
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']==$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=FALSE ){
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 = FALSE;
8681
- $update_existing = FALSE;
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 = TRUE;
8692
  $item_index = $i;
8693
  break;
8694
  } else {
8695
- $do_nothing = TRUE;
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 FALSE;
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), TRUE );
8888
- $login_data['attempt_date'] = date('r', $login_data['attempt_time']);
8889
 
8890
- if( !$login_data['user_agent'] ){
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 FALSE;
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 FALSE;
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 TRUE;
9026
  }
9027
  }
9028
 
9029
- return FALSE;
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=NULL ){
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') !== FALSE ){
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, TRUE );
9110
  SucuriScanEvent::schedule_task();
 
9111
  }
9112
 
9113
  // Remove API key from the local storage.
9114
- if( SucuriScanRequest::post(':remove_api_key') !== FALSE ){
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
- SucuriScanOption::update_option(':fs_scanner', $action_d);
9124
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner was: ' . $action_d );
9125
- SucuriScanInterface::info( 'Filesystem scanner was <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':scan_modfiles', $action_d);
9132
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner for modified files was: ' . $action_d );
9133
- SucuriScanInterface::info( 'Filesystem scanner for modified files was <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':scan_checksums', $action_d);
9140
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner for file integrity was: ' . $action_d );
9141
- SucuriScanInterface::info( 'Filesystem scanner for file integrity was <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':ignore_scanning', $action_d);
9148
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner rules to ignore directories was: ' . $action_d );
9149
- SucuriScanInterface::info( 'Filesystem scanner rules to ignore directories was <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':scan_errorlogs', $action_d);
9156
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanner for error logs was: ' . $action_d );
9157
- SucuriScanInterface::info( 'Filesystem scanner for error logs was <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':parse_errorlogs', $action_d);
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
- if ( $errorlogs_limit == 0 ) {
9177
- SucuriScanOption::update_option(':parse_errorlogs', 'disabled');
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
- SucuriScanOption::update_option(':sitecheck_scanner', $action_d);
9186
- SucuriScanEvent::notify_event( 'plugin_change', 'SiteCheck scanner and malware scan page were: ' . $action_d );
9187
- SucuriScanInterface::info( 'SiteCheck scanner and malware scan page were <code>' . $action_d . '</code>' );
 
 
 
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
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanning frequency changed to: ' . $frequency_title );
9202
- SucuriScanInterface::info( 'Filesystem scan scheduled to run <code>'.$frequency_title.'</code>' );
 
 
 
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
- if( in_array($interface, $allowed_values) ){
9211
- SucuriScanOption::update_option(':scan_interface', $interface);
9212
- SucuriScanEvent::notify_event( 'plugin_change', 'Filesystem scanning interface changed to: ' . $interface );
9213
- SucuriScanInterface::info( 'Filesystem scan interface set to <code>'.$interface.'</code>' );
 
 
 
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::notify_event( 'plugin_change', 'Maximum email notifications per hour changed' );
9223
- SucuriScanInterface::info( 'E-mail notifications: <code>' . $per_hour_label . '</code>' );
 
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::notify_event( 'plugin_change', 'Email address to get the event notifications was changed' );
9236
- SucuriScanInterface::info( 'All the event notifications will be sent to the email specified.' );
 
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 = 'You will receive an email with a report containing information of the failed
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 = 'SSL certificates will not be verified when executing a HTTP request '
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
- SucuriScanOption::update_option(':request_timeout', $request_timeout);
9274
- SucuriScanInterface::info( 'API request timeout set to <code>' . $request_timeout . '</code> seconds.' );
 
 
 
 
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
- $collect_action = 'disabled';
9281
 
9282
  if ( $collect_wrong_passwords == 'yes' ) {
9283
  $collect_action = 'enabled';
 
 
 
 
 
 
9284
  }
9285
 
9286
- SucuriScanOption::update_option(':collect_wrong_passwords', $collect_action);
9287
- SucuriScanInterface::info( 'Option to collection wrong passwords updated to <code>' . $collect_action . '</code>' );
 
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
- SucuriScanOption::update_option(':datastore_path', $datastore_path);
9297
- SucuriScanInterface::info( 'Datastore path changed to <code>' . $datastore_path . '</code>' );
9298
 
9299
- if ( file_exists($current_datastore_path) ) {
 
 
 
 
 
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') !== FALSE ){
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 !== FALSE ){
 
9323
  $option_value = ( $option_value == '1' ) ? 'enabled' : 'disabled';
9324
- SucuriScanOption::update_option( $alert_type, $option_value );
9325
- $options_updated_counter += 1;
 
 
 
 
9326
  }
9327
  }
9328
 
9329
- if( $options_updated_counter > 0 ){
9330
- SucuriScanEvent::notify_event( 'plugin_change', 'Email notification settings changed' );
9331
- SucuriScanInterface::info( 'Notification settings updated.' );
 
 
 
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 ( $new_email_subject !== false ) {
 
 
 
 
 
9365
  SucuriScanOption::update_option( ':email_subject', $new_email_subject );
9366
- SucuriScanInterface::info( 'New email alert subject format saved successfully.' );
 
 
9367
  }
9368
  }
9369
 
9370
  // Reset all the plugin's options.
9371
- if( SucuriScanRequest::post(':reset_options') !== FALSE ){
9372
  // Notify the event before the API key is removed.
9373
- $event_msg = 'All plugin\'s options were resetted.';
9374
- SucuriScanEvent::report_event( 1, 'core', $event_msg );
9375
- SucuriScanEvent::notify_event( 'plugin_change', $event_msg );
9376
 
9377
- // Remove all plugin's options from the database.
9378
- $options = SucuriScanOption::get_options_from_db('all_plugin_options');
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 resetted successfully' );
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 path from the list.' );
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 from the list will be ignored in future scans.' );
 
 
 
 
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 from the list will not be ignored anymore.' );
 
 
 
 
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
- SucuriScanInterface::info( 'The IP address specified was trusted successfully.' );
 
 
 
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
- SucuriScanOption::update_option(':heartbeat', $heartbeat_status);
9471
- SucuriScanInterface::info( 'Heartbeat status set to <code>' . $heartbeat_status . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':heartbeat_pulse', $heartbeat_pulse);
9483
- SucuriScanInterface::info( 'Heartbeat pulse set to <code>' . $heartbeat_pulse . '</code> seconds.' );
 
 
 
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
- SucuriScanOption::update_option(':heartbeat_interval', $heartbeat_interval);
9495
- SucuriScanInterface::info( 'Heartbeat interval set to <code>' . $heartbeat_interval . '</code>' );
 
 
 
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
- SucuriScanOption::update_option(':heartbeat_autostart', $action_d);
9505
- SucuriScanInterface::info( 'Heartbeat auto-start was <code>' . $action_d . '</code>' );
 
 
 
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') !== FALSE );
9532
 
9533
- if( $page_nonce && SucuriScanRequest::post(':plugin_api_key') !== FALSE ){
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 = TRUE;
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( !$api_key ){
9555
- $clean_domain = SucuriScan::get_domain();
9556
- $domain_address = @gethostbyname($clean_domain);
9557
- $invalid_domain = ( $domain_address == $clean_domain ) ? TRUE : FALSE;
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' => ( !$api_key ? '<em>(not set)</em>' : $api_key ),
9567
- 'APIKey.RecoverVisibility' => SucuriScanTemplate::visibility( !$api_key && !$display_manual_key_form ),
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($maximum_failed_logins, $sucuriscan_maximum_failed_logins) ){
9589
- $template_variables['MaximumFailedLogins'] = $sucuriscan_maximum_failed_logins[$maximum_failed_logins];
9590
  }
9591
 
9592
- if( array_key_exists($verify_ssl_cert, $sucuriscan_verify_ssl_cert) ){
9593
- $template_variables['VerifySSLCert'] = $sucuriscan_verify_ssl_cert[$verify_ssl_cert];
 
 
 
 
 
 
 
 
 
 
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(TRUE);
 
 
 
 
 
 
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
- return SucuriScanTemplate::get_section('settings-scanner', $template_variables);
 
 
 
 
 
 
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 = FALSE;
9915
  }
9916
 
9917
  // Scan the project and get the ignored paths.
9918
- if( $ignore_scanning === TRUE ){
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 = FALSE;
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 = TRUE;
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 = TRUE;
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=FALSE){
10107
- if( $rules===FALSE ){
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=>$line){
10128
- if( $i < ($standard_lines_total-1) ){
10129
  $end_of_line = "\n";
10130
- }else{
10131
  $end_of_line = '';
10132
  }
10133
- $pattern .= sprintf("%s%s", $line, $end_of_line);
10134
  }
10135
 
10136
- if( preg_match("/{$pattern}/", $rules) ){
10137
- return TRUE;
10138
  }
10139
  }
10140
 
10141
- return FALSE;
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 === TRUE ) ? 'TRUE' : 'FALSE';
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
- switch( $cronjob_action ){
10314
- case 'runnow':
10315
- SucuriScanInterface::info( $total_tasks . ' tasks were scheduled to run in the next ten seconds.' );
10316
- foreach( $cronjobs as $task_name ){
10317
- wp_schedule_single_event( time() + 600, $task_name );
10318
- }
10319
- break;
10320
- case 'remove':
10321
- SucuriScanInterface::info( $total_tasks . ' scheduled tasks were removed.' );
10322
- foreach( $cronjobs as $task_name ){
10323
- wp_clear_scheduled_hook($task_name);
10324
- }
10325
- break;
10326
- default:
10327
- SucuriScanInterface::info( $total_tasks . ' tasks were re-scheduled to run <code>' . $cronjob_action . '</code>.' );
10328
- foreach( $cronjobs as $task_name ){
10329
- wp_clear_scheduled_hook($task_name);
10330
- $next_due = wp_next_scheduled($task_name);
10331
- wp_schedule_event( $next_due, $cronjob_action, $task_name );
10332
- }
10333
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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' => 'visible',
10353
- 'ErrorLog.DisabledVisibility' => 'visible',
 
 
10354
  'ErrorLog.FileSize' => '0B',
10355
  'ErrorLog.List' => '',
10356
  );
10357
 
10358
  $error_log_path = realpath( ABSPATH . '/error_log' );
10359
- $parse_errorlogs = ( SucuriScanOption::get_option(':parse_errorlogs') !== 'disabled' );
10360
- $errorlogs_limit = SucuriScanOption::get_option(':errorlogs_limit');
 
 
 
 
 
10361
 
10362
- if ( $error_log_path && $parse_errorlogs ) {
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
- $counter = 0;
10372
 
10373
  foreach ( $error_logs as $error_log ) {
10374
- $css_class = ( $counter % 2 == 0 ) ? '' : 'alternate';
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
- $counter += 1;
10386
  }
10387
 
10388
- if ( $counter > 0 ) {
10389
- $template_variables['ErrorLog.NoItemsVisibility'] = 'hidden';
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(TRUE),
10412
  'Using_CloudProxy' => 'Unknown',
 
 
10413
  'HTTP_Host' => 'Unknown',
10414
  'Host_Name' => 'Unknown',
10415
- 'Host_Address' => 'Unknown',
 
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(TRUE);
 
 
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; ?>')">&times;</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 &amp; 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