Limit Login Attempts Reloaded - Version 2.16.0

Version Description

  • Custom Apps functionality implemented. More details: https://limitloginattempts.com/app/
Download this release

Release Info

Developer wpchefgadget
Plugin Icon 128x128 Limit Login Attempts Reloaded
Version 2.16.0
Comparing to
See all releases

Code changes from version 2.15.2 to 2.16.0

assets/css/limit-login-attempts.css CHANGED
@@ -1 +1 @@
1
- .limit-login-page-settings .field-col{display:inline-block;margin-right:20px}.limit-login-page-settings .limit-login-log table{background-color:#fff}.limit-login-page-settings .limit-login-log table th,.limit-login-page-settings .limit-login-log table td{padding:10px}.limit-login-page-settings .limit-login-log table tr:nth-child(even){background-color:rgba(0,0,0,0.09)}.limit-login-page-settings input[name="admin_notify_email"]{min-width:243px}.llar-notice-review{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px 0 !important;border-left:4px solid #333 !important}.llar-notice-review .llar-review-image img{margin-top:10px}.llar-notice-review .llar-review-info{-webkit-box-flex:1;-ms-flex:1;flex:1;margin-left:30px}.llar-notice-review .llar-review-info .llar-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.llar-notice-review .llar-review-info .llar-buttons li{margin-right:10px}.llar-notice-review .llar-review-info .llar-buttons li .dashicons{margin-right:5px}.llar-accordion .ui-accordion-header{font-weight:bold;background-color:#778899;color:#fff}.llar-accordion .ui-accordion-header.ui-accordion-header-active{background-color:#87CEFA}
1
+ .limit-login-page-settings .field-col{display:inline-block;margin-right:20px}.limit-login-page-settings .limit-login-log table{background-color:#fff}.limit-login-page-settings .limit-login-log table th,.limit-login-page-settings .limit-login-log table td{padding:10px}.limit-login-page-settings .limit-login-log table tr:nth-child(even){background-color:rgba(0,0,0,0.09)}.limit-login-page-settings #limit-login-app-setup-link{width:85%}.limit-login-page-settings .nav-tab-wrapper{position:relative}.limit-login-page-settings .nav-tab-wrapper .llar-failover-link{font-size:14px;float:right;line-height:2}.limit-login-page-settings .limit-login-app-dashboard .llar-table-scroll-wrap{max-height:400px;overflow-y:auto}.limit-login-page-settings .limit-login-app-dashboard .form-table{background-color:#fff;border:1px solid #f4f4f4;border-top:3px solid #3c8dbc;position:relative}.limit-login-page-settings .limit-login-app-dashboard .form-table.llar-preloader:before{content:"";display:block;width:100%;height:100%;background-color:rgba(255,255,255,0.7);z-index:999;position:absolute;top:0;left:0}.limit-login-page-settings .limit-login-app-dashboard .form-table th{font-weight:bold;border-bottom:1px solid #dbdbdb !important}.limit-login-page-settings .limit-login-app-dashboard .form-table th,.limit-login-page-settings .limit-login-app-dashboard .form-table td{padding:10px;border:1px solid #b9b9b9}.limit-login-page-settings .limit-login-app-dashboard .form-table th.llar-col-nowrap,.limit-login-page-settings .limit-login-app-dashboard .form-table td.llar-col-nowrap{white-space:nowrap}.limit-login-page-settings .limit-login-app-dashboard .form-table td button{line-height:1;margin-right:5px}.limit-login-page-settings .limit-login-app-dashboard .form-table td button:last-child{margin-right:0}.limit-login-page-settings .limit-login-app-dashboard .form-table td button .dashicons{vertical-align:middle}.limit-login-page-settings .limit-login-app-dashboard .form-table td.llar-app-log-actions{text-align:center}.limit-login-page-settings .limit-login-app-dashboard .form-table td.llar-app-log-actions .llar-app-log-action-btn{display:inline-block;line-height:20px;cursor:pointer}.limit-login-page-settings .limit-login-app-dashboard .form-table td.llar-app-log-actions .llar-app-log-action-btn i{vertical-align:middle}.limit-login-page-settings .limit-login-app-dashboard .form-table td.llar-app-log-actions .llar-app-log-action-btn:hover i{color:#3c8dbc}.limit-login-page-settings .limit-login-app-dashboard .form-table tr:nth-child(even){background-color:#f9f9f9}.limit-login-page-settings .limit-login-app-dashboard .llar-app-log-pagination>a{font-size:16px;line-height:1.625}.limit-login-page-settings .limit-login-app-dashboard .llar-app-log-pagination .spinner{float:none}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col{-webkit-box-flex:0;-ms-flex:0 0 49%;flex:0 0 49%}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table select{width:100%}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table .llar-app-acl-action-col{text-align:center}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table .llar-app-rule-pass{background-color:#cffbe8}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table .llar-app-rule-allow{background-color:#abdfff}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table .llar-app-rule-deny{background-color:#fd2c2c3d}.limit-login-page-settings .limit-login-app-dashboard .llar-app-acl-rules .app-rules-col .form-table .llar-app-acl-remove{color:crimson;border-color:crimson}.limit-login-page-settings .llar-app-notice{background-color:#fff;-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);padding:15px;border-radius:3px;margin-top:20px;margin-bottom:20px;font-size:14px;border-left:5px solid #ffba00}.limit-login-page-settings .llar-app-notice.success{border-color:#46b450}.limit-login-page-settings .llar-app-notice p{font-size:inherit;margin:0 0 20px}.limit-login-page-settings .llar-app-notice p:last-child{margin-bottom:0}.limit-login-page-settings input[name="admin_notify_email"]{min-width:243px}.limit-login-page-settings .llar-protect-notice{font-size:15px;color:#848484;margin-left:10px}.limit-login-page-settings .llar-protect-notice a{color:#222222;text-decoration:none;border-bottom:1px dashed}.limit-login-page-settings .llar-show-app-fields{position:absolute;right:15px;top:15px;color:#bdbdbd}.limit-login-page-settings .llar-show-app-fields:hover{color:#222}.limit-login-page-settings .llar-app-field{display:none}.limit-login-page-settings .llar-app-field.active{display:table-row}.llar-notice-review{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px 0 !important;border-left:4px solid #333 !important}.llar-notice-review .llar-review-image img{margin-top:10px}.llar-notice-review .llar-review-info{-webkit-box-flex:1;-ms-flex:1;flex:1;margin-left:30px}.llar-notice-review .llar-review-info .llar-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.llar-notice-review .llar-review-info .llar-buttons li{margin-right:10px}.llar-notice-review .llar-review-info .llar-buttons li .dashicons{margin-right:5px}.llar-accordion .ui-accordion-header{font-weight:bold;background-color:#778899;color:#fff}.llar-accordion .ui-accordion-header.ui-accordion-header-active{background-color:#87CEFA}.custom-app-tab{position:relative}.custom-app-tab .spinner{float:none}.custom-app-tab .llar-app-ajax-msg{font-size:13px;margin-top:5px;display:block}.custom-app-tab .llar-app-ajax-msg.error{color:red}.custom-app-tab .llar-app-ajax-msg.success{color:green}.custom-app-tab .llar-delete-app{color:#dc3232;position:absolute;bottom:15px;right:15px}.custom-app-tab .llar-delete-app:hover{opacity:0.8}.custom-app-tab .llar-why-use-premium-text{margin-top:20px}.custom-app-tab .llar-why-use-premium-text .title{font-weight:bold;font-size:16px;color:#4d4d4d}.custom-app-tab .llar-why-use-premium-text ul li .dashicons{color:#3ab54a;font-size:25px;width:25px;top:-2px;position:relative}#llar-progress-bar{position:fixed;top:0;height:6px;left:0;width:100%;z-index:999999;background-color:#eee}#llar-progress-bar span{height:100%;position:absolute;display:block;width:0;background-color:#00b357;-webkit-transition:width 0.4s;transition:width 0.4s}
assets/js/limit-login-attempts.js CHANGED
@@ -1,6 +1,57 @@
1
  ;(function($){
2
  "use strict";
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  $(document).ready(function(){
5
 
6
  });
1
  ;(function($){
2
  "use strict";
3
 
4
+ window.llar = {
5
+ progressbar: {
6
+ timeouts: [],
7
+ $bar: null,
8
+ $fill: null,
9
+ start: function() {
10
+
11
+ if($('body').find('#llar-progress-bar').length) {
12
+
13
+ this.$bar = $('body').find('#llar-progress-bar');
14
+
15
+ } else {
16
+
17
+ this.$bar = $('<div id="llar-progress-bar"><span></span></div>');
18
+
19
+ $('body').prepend(this.$bar);
20
+ }
21
+
22
+ this.clearTimeouts();
23
+
24
+ this.$fill = this.$bar.find('span');
25
+
26
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(35);}, 100));
27
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(60);}, 800));
28
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(75);}, 1400));
29
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(80);}, 1800));
30
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(85);}, 2200));
31
+ this.timeouts.push(setTimeout(function(){llar.progressbar.percent(95);}, 2600));
32
+
33
+ },
34
+ percent: function(val) {
35
+ this.$fill.css('width', val + '%');
36
+ },
37
+ clearTimeouts: function() {
38
+ this.timeouts.forEach(function (t) {
39
+ clearTimeout(t);
40
+ });
41
+ },
42
+ stop: function() {
43
+
44
+ this.clearTimeouts();
45
+
46
+ this.percent(100);
47
+
48
+ setTimeout(function () {
49
+ llar.progressbar.$bar.remove();
50
+ }, 500);
51
+ }
52
+ }
53
+ };
54
+
55
  $(document).ready(function(){
56
 
57
  });
assets/sass/limit-login-attempts.scss CHANGED
@@ -15,9 +15,202 @@
15
  }
16
  }
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  input[name="admin_notify_email"] {
19
  min-width: 243px;
20
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
  .llar-notice-review {
@@ -59,4 +252,72 @@
59
  background-color: #87CEFA;
60
  }
61
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
15
  }
16
  }
17
 
18
+ #limit-login-app-setup-link {
19
+ width: 85%;
20
+ }
21
+
22
+ .nav-tab-wrapper {
23
+ position: relative;
24
+
25
+ .llar-failover-link {
26
+ font-size: 14px;
27
+ float: right;
28
+ line-height: 2;
29
+ }
30
+ }
31
+
32
+ .limit-login-app-dashboard {
33
+ .llar-table-scroll-wrap {
34
+ max-height: 400px;
35
+ overflow-y: auto;
36
+ }
37
+
38
+ .form-table {
39
+ background-color: #fff;
40
+ border: 1px solid #f4f4f4;
41
+ border-top: 3px solid #3c8dbc;
42
+ position: relative;
43
+
44
+ &.llar-preloader {
45
+ &:before {
46
+ content: "";
47
+ display: block;
48
+ width: 100%;
49
+ height: 100%;
50
+ background-color: rgba(255,255,255,0.7);
51
+ z-index: 999;
52
+ position: absolute;
53
+ top: 0;
54
+ left: 0;
55
+ }
56
+ }
57
+
58
+ th {
59
+ //color: #3c8dbc;
60
+ font-weight: bold;
61
+ border-bottom: 1px solid #dbdbdb !important;
62
+ }
63
+ th, td {
64
+ padding: 10px;
65
+ border: 1px solid #b9b9b9;
66
+
67
+ &.llar-col-nowrap {
68
+ white-space: nowrap;
69
+ }
70
+ }
71
+ td {
72
+ button {
73
+ line-height: 1;
74
+ margin-right: 5px;
75
+
76
+ &:last-child {
77
+ margin-right: 0;
78
+ }
79
+
80
+ .dashicons {
81
+ vertical-align: middle;
82
+ }
83
+ }
84
+ &.llar-app-log-actions {
85
+ text-align: center;
86
+
87
+ .llar-app-log-action-btn {
88
+ display: inline-block;
89
+ line-height: 20px;
90
+ cursor: pointer;
91
+
92
+ i {
93
+ vertical-align: middle;
94
+ }
95
+
96
+ &:hover {
97
+ i {
98
+ color: #3c8dbc;
99
+ }
100
+ }
101
+ }
102
+
103
+ }
104
+ }
105
+ tr:nth-child(even) {
106
+ background-color: #f9f9f9;
107
+ }
108
+ }
109
+ .llar-app-log-pagination {
110
+ > a {
111
+ font-size: 16px;
112
+ line-height: 1.625;
113
+ }
114
+ .spinner {
115
+ float: none;
116
+ }
117
+ }
118
+ .llar-app-acl-rules {
119
+ display: flex;
120
+ justify-content: space-between;
121
+
122
+ .app-rules-col {
123
+ flex: 0 0 49%;
124
+
125
+ .form-table {
126
+ select {
127
+ width: 100%;
128
+ }
129
+
130
+ .llar-app-acl-action-col {
131
+ text-align: center;
132
+ }
133
+
134
+ .llar-app-rule-pass {
135
+ background-color: #cffbe8;
136
+ }
137
+ .llar-app-rule-allow {
138
+ background-color: #abdfff;
139
+ }
140
+ .llar-app-rule-deny {
141
+ background-color: #fd2c2c3d;
142
+ }
143
+ .llar-app-acl-remove {
144
+ color: crimson;
145
+ border-color: crimson;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ .llar-app-notice {
153
+ background-color: #fff;
154
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
155
+ padding: 15px;
156
+ border-radius: 3px;
157
+ margin-top: 20px;
158
+ margin-bottom: 20px;
159
+ font-size: 14px;
160
+ border-left: 5px solid #ffba00;
161
+
162
+ &.success {
163
+ border-color: #46b450;
164
+ }
165
+
166
+ p {
167
+ font-size: inherit;
168
+ margin: 0 0 20px;
169
+
170
+ &:last-child {
171
+ margin-bottom: 0;
172
+ }
173
+ }
174
+ }
175
+
176
  input[name="admin_notify_email"] {
177
  min-width: 243px;
178
  }
179
+
180
+ .llar-protect-notice {
181
+ font-size: 15px;
182
+ color: #848484;
183
+ margin-left: 10px;
184
+
185
+ a {
186
+ color: #222222;
187
+ //text-decoration-style: dotted;
188
+ //text-underline-offset: 3px;
189
+ text-decoration: none;
190
+ border-bottom: 1px dashed;
191
+ }
192
+ }
193
+
194
+
195
+
196
+ .llar-show-app-fields {
197
+ position: absolute;
198
+ right: 15px;
199
+ top: 15px;
200
+ color: #bdbdbd;
201
+
202
+ &:hover {
203
+ color: #222;
204
+ }
205
+ }
206
+
207
+ .llar-app-field {
208
+ display: none;
209
+
210
+ &.active {
211
+ display: table-row;
212
+ }
213
+ }
214
  }
215
 
216
  .llar-notice-review {
252
  background-color: #87CEFA;
253
  }
254
  }
255
+ }
256
+
257
+ .custom-app-tab {
258
+ position: relative;
259
+ .spinner {
260
+ float: none;
261
+ }
262
+ .llar-app-ajax-msg {
263
+ font-size: 13px;
264
+ margin-top: 5px;
265
+ display: block;
266
+
267
+ &.error {
268
+ color: red;
269
+ }
270
+ &.success {
271
+ color: green;
272
+ }
273
+ }
274
+ .llar-delete-app {
275
+ color: #dc3232;
276
+ position: absolute;
277
+ bottom: 15px;
278
+ right: 15px;
279
+
280
+ &:hover {
281
+ opacity: 0.8;
282
+ }
283
+ }
284
+ .llar-why-use-premium-text {
285
+ margin-top: 20px;
286
+
287
+ .title {
288
+ font-weight: bold;
289
+ font-size: 16px;
290
+ color: #4d4d4d;
291
+ }
292
+ ul {
293
+ li {
294
+ .dashicons {
295
+ color: #3ab54a;
296
+ font-size: 25px;
297
+ width: 25px;
298
+ top: -2px;
299
+ position: relative;
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+
306
+ #llar-progress-bar {
307
+ position: fixed;
308
+ top: 0;
309
+ height: 6px;
310
+ left: 0;
311
+ width: 100%;
312
+ z-index: 999999;
313
+ background-color: #eee;
314
+
315
+ span {
316
+ height: 100%;
317
+ position: absolute;
318
+ display: block;
319
+ width: 0;
320
+ background-color: #00b357;
321
+ transition: width 0.4s;
322
+ }
323
  }
core/App.php ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class LLAR_App {
4
+
5
+ /**
6
+ * @var null|string
7
+ */
8
+ private $id = null;
9
+
10
+ /**
11
+ * @var mixed|string
12
+ */
13
+ private $endpoint = '';
14
+
15
+ /**
16
+ * @var array
17
+ */
18
+ private $config = array();
19
+
20
+ /**
21
+ * @var array
22
+ */
23
+ private $login_errors = array();
24
+
25
+ /**
26
+ * @var null
27
+ */
28
+ public $last_response_code = null;
29
+
30
+ /**
31
+ * LLAR_App constructor.
32
+ * @param array $config
33
+ */
34
+ public function __construct( array $config ) {
35
+
36
+ if( empty( $config ) ) {
37
+ return false;
38
+ }
39
+
40
+ $this->id = 'app_' . $config['id'];
41
+ $this->api = $config['api'];
42
+ $this->config = $config;
43
+ }
44
+
45
+ /**
46
+ * @param $error
47
+ * @return bool
48
+ */
49
+ public function add_error( $error ) {
50
+
51
+ if( !$error ) return false;
52
+
53
+ $this->login_errors[] = $error;
54
+ }
55
+
56
+ /**
57
+ * @return array
58
+ */
59
+ public function get_errors() {
60
+
61
+ return $this->login_errors;
62
+ }
63
+
64
+ /**
65
+ * @return null|string
66
+ */
67
+ public function get_id() {
68
+ return $this->id;
69
+ }
70
+
71
+ /**
72
+ * @return array
73
+ */
74
+ public function get_config() {
75
+ return $this->config;
76
+ }
77
+
78
+ /**
79
+ * @param $link
80
+ * @return false[]
81
+ */
82
+ public static function setup( $link ) {
83
+
84
+ $return = array(
85
+ 'success' => false,
86
+ );
87
+
88
+ if( empty( $link ) ) {
89
+
90
+ return $return;
91
+ }
92
+
93
+ $link = add_query_arg( 'domain', $_SERVER['SERVER_NAME'], $link );
94
+
95
+ $plugin_data = get_plugin_data( LLA_PLUGIN_DIR . '/limit-login-attempts-reloaded.php' );
96
+ $link = add_query_arg( 'version', $plugin_data['Version'], $link );
97
+
98
+ $setup_response = wp_remote_get( $link );
99
+ $setup_response_body = json_decode( wp_remote_retrieve_body( $setup_response ), true );
100
+
101
+ if( is_wp_error( $setup_response ) ) {
102
+
103
+ $return['error'] = $setup_response->get_error_message();
104
+
105
+ } else if( wp_remote_retrieve_response_code( $setup_response ) === 200 ) {
106
+
107
+ $return['success'] = true;
108
+ $return['app_config'] = $setup_response_body;
109
+
110
+ } else {
111
+
112
+ $return['error'] = ( !empty( $setup_response_body['message'] ) )
113
+ ? $setup_response_body['message']
114
+ : __( 'The endpoint is not responding. Please contact your app provider to settle that.', 'limit-login-attempts-reloaded' );
115
+ $return['response_code'] = wp_remote_retrieve_response_code( $setup_response );
116
+ }
117
+
118
+ return $return;
119
+ }
120
+
121
+ /**
122
+ * @param $data
123
+ * @return bool|mixed
124
+ */
125
+ public function acl_check( $data ) {
126
+
127
+ $this->prepare_settings( 'acl', $data );
128
+
129
+ return $this->request( 'acl', 'post', $data );
130
+ }
131
+
132
+ /**
133
+ * @param $data
134
+ * @return bool|mixed
135
+ */
136
+ public function acl( $data ) {
137
+
138
+ return $this->request( 'acl', 'get', $data );
139
+ }
140
+
141
+ /**
142
+ * @param $data
143
+ * @return bool|mixed
144
+ */
145
+ public function acl_create( $data ) {
146
+
147
+ return $this->request( 'acl/create', 'post', $data );
148
+ }
149
+
150
+ /**
151
+ * @param $data
152
+ * @return bool|mixed
153
+ */
154
+ public function acl_delete( $data ) {
155
+
156
+ return $this->request( 'acl/delete', 'post', $data );
157
+ }
158
+
159
+ /**
160
+ * @param $data
161
+ * @return bool|mixed
162
+ */
163
+ public function lockout_check( $data ) {
164
+
165
+ $this->prepare_settings( 'lockout', $data );
166
+
167
+ return $this->request( 'lockout', 'post', $data );
168
+ }
169
+
170
+ /**
171
+ * @param int $limit
172
+ * @param string $offset
173
+ * @return bool|mixed
174
+ */
175
+ public function log($limit = 25, $offset = '') {
176
+
177
+ $data = array();
178
+
179
+ $data['limit'] = $limit;
180
+ $data['offset'] = $offset;
181
+
182
+ return $this->request( 'log', 'get', $data );
183
+ }
184
+
185
+ public function get_lockouts($limit = 25, $offset = '') {
186
+
187
+ $data = array();
188
+
189
+ $data['limit'] = $limit;
190
+ $data['offset'] = $offset;
191
+
192
+ return $this->request( 'lockout', 'get', $data );
193
+ }
194
+
195
+ /**
196
+ * Prepare settings for API request
197
+ *
198
+ * @param $method
199
+ */
200
+ public function prepare_settings( $method, &$data ) {
201
+
202
+ $settings = array();
203
+
204
+ if( !empty( $this->config['settings'] ) ) {
205
+
206
+ foreach ( $this->config['settings'] as $setting_name => $setting_data ) {
207
+
208
+ if( in_array( $method, $setting_data['methods'] ) ) {
209
+
210
+ $settings[$setting_name] = $setting_data['value'];
211
+ }
212
+ }
213
+ }
214
+
215
+ if( $settings )
216
+ $data['settings'] = $settings;
217
+ }
218
+
219
+ /**
220
+ * @param $method
221
+ * @param string $type
222
+ * @param null $data
223
+ * @return bool|mixed
224
+ * @throws Exception
225
+ */
226
+ public function request( $method, $type = 'get', $data = null ) {
227
+
228
+ if( !$method ) {
229
+ throw new Exception( 'You must to specify API method.' );
230
+ }
231
+
232
+ $headers = array();
233
+ $headers[$this->config['header']] = $this->config['key'];
234
+
235
+ if( $type === 'post' ) {
236
+
237
+ $headers['Content-Type'] = 'application/json; charset=utf-8';
238
+ }
239
+
240
+ $func = ( $type === 'post' ) ? 'wp_remote_post' : 'wp_remote_get';
241
+
242
+ $response = $func( $this->api.'/'.$method, array(
243
+ 'headers' => $headers,
244
+ 'body' => ( $type === 'post' ) ? json_encode( $data, JSON_FORCE_OBJECT ) : $data
245
+ ));
246
+
247
+ $this->last_response_code = wp_remote_retrieve_response_code( $response );
248
+
249
+ if( is_wp_error( $response ) || $this->last_response_code !== 200 ) {
250
+
251
+ return false;
252
+ } else {
253
+
254
+ return json_decode( sanitize_textarea_field( stripslashes( wp_remote_retrieve_body( $response ) ) ), true );
255
+ }
256
+
257
+ }
258
+
259
+ }
core/LimitLoginAttempts.php CHANGED
@@ -3,8 +3,8 @@
3
  /**
4
  * Class Limit_Login_Attempts
5
  */
6
- class Limit_Login_Attempts
7
- {
8
  public $default_options = array(
9
  'gdpr' => 0,
10
 
@@ -41,6 +41,9 @@ class Limit_Login_Attempts
41
  'whitelist_usernames' => array(),
42
  'blacklist' => array(),
43
  'blacklist_usernames' => array(),
 
 
 
44
  );
45
  /**
46
  * Admin options page slug
@@ -67,8 +70,16 @@ class Limit_Login_Attempts
67
  */
68
  private $use_local_options = null;
69
 
 
 
 
 
 
 
 
70
  public function __construct() {
71
  $this->hooks_init();
 
72
  }
73
 
74
  /**
@@ -85,6 +96,14 @@ class Limit_Login_Attempts
85
  add_filter( 'illegal_user_logins', array( $this, 'register_user_blacklist' ), 999 );
86
  add_action( 'admin_notices', array( $this, 'show_leave_review_notice' ) );
87
  add_action( 'wp_ajax_dismiss_review_notice', array( $this, 'dismiss_review_notice_callback' ));
 
 
 
 
 
 
 
 
88
 
89
  add_action( 'admin_print_scripts-settings_page_limit-login-attempts', array( $this, 'load_admin_scripts' ) );
90
  }
@@ -125,7 +144,6 @@ class Limit_Login_Attempts
125
  add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
126
 
127
  add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
128
- add_action( 'login_head', array( $this, 'add_error_message' ) );
129
  add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
130
 
131
  if ( $this->network_mode )
@@ -146,14 +164,28 @@ class Limit_Login_Attempts
146
  * later versions of WP.
147
  */
148
  add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
149
- add_action( 'authenticate', array( $this, 'authenticate_filter' ), 35, 3 );
 
 
 
 
 
150
 
151
  if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST )
152
  add_action( 'init', array( $this, 'check_xmlrpc_lock' ) );
153
 
154
  add_action('wp_ajax_limit-login-unlock', array( $this, 'ajax_unlock' ) );
 
155
  }
156
 
 
 
 
 
 
 
 
 
157
  public function load_admin_scripts() {
158
 
159
  wp_enqueue_script('jquery-ui-accordion');
@@ -314,45 +346,125 @@ class Limit_Login_Attempts
314
 
315
  if ( ! empty( $username ) && ! empty( $password ) ) {
316
 
317
- if(is_wp_error($user) && in_array('bp_account_not_activated', $user->get_error_codes()) ) {
 
 
 
 
318
 
319
- $this->other_login_errors[] = $user->get_error_message('bp_account_not_activated');
320
- }
321
 
322
- $ip = $this->get_address();
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- // Check if username is blacklisted
325
- if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
326
- ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
327
- ) {
 
 
 
328
 
329
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
330
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
331
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
332
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
333
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
334
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
335
 
336
- remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
337
- remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
 
 
338
 
339
- $user = new WP_Error();
340
- $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
 
 
341
 
342
- } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
343
 
344
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
345
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
346
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
347
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
348
 
349
- }
 
 
 
350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
352
 
353
  return $user;
354
  }
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  /**
357
  * Check if the original plugin is installed
358
  */
@@ -372,6 +484,7 @@ class Limit_Login_Attempts
372
  */
373
  public function enqueue() {
374
  wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . 'assets/css/limit-login-attempts.css' );
 
375
  }
376
 
377
  /**
@@ -388,11 +501,12 @@ class Limit_Login_Attempts
388
  }
389
 
390
  /**
391
- * Get the correct options page URI
392
- *
393
- * @return mixed
394
- */
395
- public function get_options_page_uri()
 
396
  {
397
 
398
  if ( is_network_admin() )
@@ -400,9 +514,9 @@ class Limit_Login_Attempts
400
  else
401
  $uri = menu_page_url( $this->_options_page_slug, false );
402
 
403
- if(!empty($_GET['tab'])) {
404
 
405
- $uri .= '&tab=' . sanitize_text_field( $_GET['tab'] );
406
  }
407
 
408
  return $uri;
@@ -455,6 +569,18 @@ class Limit_Login_Attempts
455
  return $func( $option, $value, '', 'no' );
456
  }
457
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  /**
459
  * Setup main options
460
  */
@@ -519,98 +645,130 @@ class Limit_Login_Attempts
519
  */
520
  public function limit_login_failed( $username ) {
521
 
522
- $ip = $this->get_address();
523
- $ipHash = $this->getHash($this->get_address());
 
 
 
524
 
525
- /* if currently locked-out, do not add to retries */
526
- $lockouts = $this->get_option( 'lockouts' );
527
 
528
- if ( ! is_array( $lockouts ) ) {
529
- $lockouts = array();
530
- }
 
531
 
532
- if ( (isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ]) || (isset( $lockouts[ $ipHash ] ) && time() < $lockouts[ $ipHash ] )) {
533
- return;
534
- }
535
 
536
- /* Get the arrays with retries and retries-valid information */
537
- $retries = $this->get_option( 'retries' );
538
- $valid = $this->get_option( 'retries_valid' );
539
 
540
- if ( ! is_array( $retries ) ) {
541
- $retries = array();
542
- $this->add_option( 'retries', $retries );
543
- }
 
 
 
544
 
545
- if ( ! is_array( $valid ) ) {
546
- $valid = array();
547
- $this->add_option( 'retries_valid', $valid );
548
- }
549
 
550
- $gdpr = $this->get_option('gdpr');
551
- $ip = ($gdpr ? $ipHash : $ip);
552
- /* Check validity and add one to retries */
553
- if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ]) {
554
- $retries[ $ip ] ++;
555
  } else {
556
- $retries[ $ip ] = 1;
557
- }
558
- $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
559
 
560
- /* lockout? */
561
- if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
562
- /*
563
- * Not lockout (yet!)
564
- * Do housecleaning (which also saves retry/valid values).
565
- */
566
- $this->cleanup( $retries, null, $valid );
567
 
568
- return;
569
- }
570
 
571
- /* lockout! */
572
- $whitelisted = $this->is_ip_whitelisted( $ip );
573
- $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
574
 
575
- /*
576
- * Note that retries and statistics are still counted and notifications
577
- * done as usual for whitelisted ips , but no lockout is done.
578
- */
579
- if ( $whitelisted ) {
580
- if ( $retries[ $ip ] >= $retries_long ) {
581
- unset( $retries[ $ip ] );
582
- unset( $valid[ $ip ] );
583
  }
584
- } else {
585
- global $limit_login_just_lockedout;
586
- $limit_login_just_lockedout = true;
587
- $gdpr = $this->get_option('gdpr');
588
- $index = ($gdpr ? $ipHash : $ip);
589
-
590
- /* setup lockout, reset retries as needed */
591
- if ( (isset($retries[ $ip ]) ? $retries[ $ip ] : 0) >= $retries_long || (isset($retries[ $ipHash ]) ? $retries[ $ipHash ] : 0) >= $retries_long ) {
592
- /* long lockout */
593
- $lockouts[ $index ] = time() + $this->get_option( 'long_duration' );
594
- unset( $retries[ $index ] );
595
- unset( $valid[ $index ] );
 
 
 
 
 
 
 
 
596
  } else {
597
- /* normal lockout */
598
- $lockouts[ $index ] = time() + $this->get_option( 'lockout_duration' );
599
  }
600
- }
 
 
 
 
 
 
 
 
601
 
602
- /* do housecleaning and save values */
603
- $this->cleanup( $retries, $lockouts, $valid );
604
 
605
- /* do any notification */
606
- $this->notify( $username );
 
607
 
608
- /* increase statistics */
609
- $total = $this->get_option( 'lockouts_total' );
610
- if ( $total === false || ! is_numeric( $total ) ) {
611
- $this->add_option( 'lockouts_total', 1 );
612
- } else {
613
- $this->update_option( 'lockouts_total', $total + 1 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  }
615
  }
616
 
@@ -760,15 +918,7 @@ class Limit_Login_Attempts
760
  $log[ $index ][ $user_login ]['counter']++;
761
  $log[ $index ][ $user_login ]['date'] = time();
762
 
763
- if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
764
- $gateway = 'WooCommerce';
765
- } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
766
- $gateway = 'XMLRPC';
767
- } else {
768
- $gateway = 'WP Login';
769
- }
770
-
771
- $log[ $index ][ $user_login ]['gateway'] = $gateway;
772
 
773
  if ( $option === false ) {
774
  $this->add_option( 'logged', $log );
@@ -777,6 +927,22 @@ class Limit_Login_Attempts
777
  }
778
  }
779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  /**
781
  * Check if IP is whitelisted.
782
  *
@@ -850,6 +1016,10 @@ class Limit_Login_Attempts
850
  */
851
  public function wp_authenticate_user( $user, $password ) {
852
 
 
 
 
 
853
  $user_login = '';
854
 
855
  if( is_a( $user, 'WP_User' ) ) {
@@ -858,8 +1028,7 @@ class Limit_Login_Attempts
858
  $user_login = $user;
859
  }
860
 
861
- if ( is_wp_error( $user ) ||
862
- $this->check_whitelist_ips( false, $this->get_address() ) ||
863
  $this->check_whitelist_usernames( false, $user_login ) ||
864
  $this->is_limit_login_ok()
865
  ) {
@@ -908,24 +1077,6 @@ class Limit_Login_Attempts
908
  $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
909
  }
910
 
911
- /**
912
- * Should we show errors and messages on this page?
913
- *
914
- * @return bool
915
- */
916
- public function login_show_msg() {
917
- if ( isset( $_GET['key'] ) ) {
918
- /* reset password */
919
- return false;
920
- }
921
-
922
- $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
923
-
924
- return ( $action != 'lostpassword' && $action != 'retrievepassword'
925
- && $action != 'resetpass' && $action != 'rp'
926
- && $action != 'register' );
927
- }
928
-
929
  /**
930
  * Construct informative error message
931
  *
@@ -991,45 +1142,19 @@ class Limit_Login_Attempts
991
  public function fixup_error_messages( $content ) {
992
  global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
993
 
994
- if ( ! $this->login_show_msg() ) {
995
- return $content;
996
- }
997
 
998
- /*
999
- * During lockout we do not want to show any other error messages (like
1000
- * unknown user or empty password).
1001
- */
1002
- if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
1003
- return $this->error_msg();
1004
- }
1005
-
1006
- /*
1007
- * We want to filter the messages 'Invalid username' and
1008
- * 'Invalid password' as that is an information leak regarding user
1009
- * account names (prior to WP 2.9?).
1010
- *
1011
- * Also, if more than one error message, put an extra <br /> tag between
1012
- * them.
1013
- */
1014
- $msgs = explode( "<br />\n", $content );
1015
-
1016
- if ( strlen( end( $msgs ) ) == 0 ) {
1017
- /* remove last entry empty string */
1018
- array_pop( $msgs );
1019
- }
1020
 
1021
- $count = count( $msgs );
1022
- $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
1023
-
1024
- if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
1025
 
1026
  if($this->other_login_errors) {
1027
 
1028
- $content = '';
1029
  foreach ($this->other_login_errors as $msg) {
1030
  $content .= $msg . "<br />\n";
1031
  }
1032
- } else {
 
1033
 
1034
  /* Replace error message, including ours if necessary */
1035
  if( !empty( $_REQUEST['log'] ) && is_email( $_REQUEST['log'] ) ) {
@@ -1039,24 +1164,14 @@ class Limit_Login_Attempts
1039
  }
1040
  }
1041
 
1042
- if ( $limit_login_my_error_shown || $this->get_message() ) {
1043
- $content .= "<br />\n" . $this->get_message() . "<br />\n";
1044
- }
1045
-
1046
- return $content;
1047
- } elseif ( $count <= 1 ) {
1048
- return $content;
1049
- }
1050
 
1051
- $new = '';
1052
- while ( $count -- > 0 ) {
1053
- $new .= array_shift( $msgs ) . "<br />\n";
1054
- if ( $count > 0 ) {
1055
- $new .= "<br />\n";
1056
  }
1057
  }
1058
 
1059
- return $new;
1060
  }
1061
 
1062
  public function fixup_error_messages_wc( \WP_Error $error ) {
@@ -1069,17 +1184,24 @@ class Limit_Login_Attempts
1069
  * @return string
1070
  */
1071
  public function get_message() {
1072
- /* Check external whitelist */
1073
- if ( $this->is_ip_whitelisted() ) {
1074
- return '';
1075
- }
1076
 
1077
- /* Is lockout in effect? */
1078
- if ( ! $this->is_limit_login_ok() ) {
1079
- return $this->error_msg();
1080
- }
1081
 
1082
- return $this->retries_remaining_msg();
 
 
 
 
 
 
 
 
 
 
 
1083
  }
1084
 
1085
  /**
@@ -1250,138 +1372,165 @@ class Limit_Login_Attempts
1250
  * Render admin options page
1251
  */
1252
  public function options_page() {
 
1253
  $this->use_local_options = !is_network_admin();
1254
  $this->cleanup();
1255
 
1256
- if( !empty( $_POST ) )
1257
- {
1258
  check_admin_referer( 'limit-login-attempts-options' );
1259
 
1260
- if ( is_network_admin() )
1261
- $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1262
 
1263
- elseif ( $this->network_mode )
1264
- $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1265
 
1266
  /* Should we clear log? */
1267
- if( isset( $_POST[ 'clear_log' ] ) )
1268
- {
1269
- $this->update_option( 'logged', '' );
1270
- $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1271
- }
1272
 
1273
- /* Should we reset counter? */
1274
- if( isset( $_POST[ 'reset_total' ] ) )
1275
- {
1276
- $this->update_option( 'lockouts_total', 0 );
1277
- $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1278
- }
1279
 
1280
- /* Should we restore current lockouts? */
1281
- if( isset( $_POST[ 'reset_current' ] ) )
1282
- {
1283
- $this->update_option( 'lockouts', array() );
1284
- $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1285
- }
1286
 
1287
- /* Should we update options? */
1288
- if( isset( $_POST[ 'llar_update_dashboard' ] ) ) {
1289
 
1290
- $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1291
 
1292
- if( !empty( $white_list_ips ) ) {
1293
- foreach( $white_list_ips as $key => $ip ) {
1294
- if( '' == $ip ) {
1295
- unset( $white_list_ips[ $key ] );
1296
- }
1297
- }
1298
- }
1299
- $this->update_option('whitelist', $white_list_ips );
1300
 
1301
- $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1302
 
1303
- if( !empty( $white_list_usernames ) ) {
1304
- foreach( $white_list_usernames as $key => $ip ) {
1305
- if( '' == $ip ) {
1306
- unset( $white_list_usernames[ $key ] );
1307
- }
1308
- }
1309
- }
1310
- $this->update_option('whitelist_usernames', $white_list_usernames );
1311
 
1312
- $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1313
 
1314
- if( !empty( $black_list_ips ) ) {
1315
- foreach( $black_list_ips as $key => $ip ) {
1316
  $range = array_map('trim', explode('-', $ip) );
1317
  if ( count( $range ) > 1 && (float)sprintf("%u",ip2long($range[0])) > (float)sprintf("%u",ip2long($range[1]))) {
1318
  $this->show_error( __( 'The "'. $ip .'" IP range is invalid', 'limit-login-attempts-reloaded' ) );
1319
  }
1320
- if( '' == $ip ) {
1321
- unset( $black_list_ips[ $key ] );
1322
- }
1323
- }
1324
- }
1325
  $this->update_option('blacklist', $black_list_ips );
1326
 
1327
- $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1328
 
1329
- if( !empty( $black_list_usernames ) ) {
1330
- foreach( $black_list_usernames as $key => $ip ) {
1331
- if( '' == $ip ) {
1332
- unset( $black_list_usernames[ $key ] );
1333
- }
1334
- }
1335
- }
1336
- $this->update_option('blacklist_usernames', $black_list_usernames );
1337
 
1338
- $this->sanitize_options();
1339
 
1340
- $this->show_error( __( 'Settings saved.', 'limit-login-attempts-reloaded' ) );
1341
- }
1342
- elseif( isset( $_POST[ 'llar_update_settings' ] ) ) {
1343
 
1344
- /* Should we support GDPR */
1345
- if( isset( $_POST[ 'gdpr' ] ) ) {
1346
 
1347
- $this->update_option( 'gdpr', 1 );
1348
- } else {
1349
 
1350
- $this->update_option( 'gdpr', 0 );
1351
- }
1352
 
1353
- $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1354
- $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1355
- $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1356
- $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1357
- $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1358
- $this->update_option('notify_email_after', (int)$_POST['email_after'] );
 
1359
 
1360
- $this->update_option('admin_notify_email', sanitize_email( $_POST['admin_notify_email'] ) );
1361
 
1362
- $trusted_ip_origins = ( !empty( $_POST['lla_trusted_ip_origins'] ) )
1363
- ? array_map( 'trim', explode( ',', sanitize_text_field( $_POST['lla_trusted_ip_origins'] ) ) )
1364
- : array();
1365
 
1366
- if( !in_array( 'REMOTE_ADDR', $trusted_ip_origins ) ) {
1367
 
1368
- $trusted_ip_origins[] = 'REMOTE_ADDR';
1369
- }
1370
 
1371
- $this->update_option('trusted_ip_origins', $trusted_ip_origins );
1372
 
1373
- $notify_methods = array();
1374
- if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1375
- $notify_methods[] = 'log';
1376
- }
1377
- if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1378
- $notify_methods[] = 'email';
1379
- }
1380
- $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
 
 
 
 
 
 
 
 
 
 
1381
 
1382
- $this->sanitize_options();
1383
 
1384
- $this->show_error( __( 'Settings saved.', 'limit-login-attempts-reloaded' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1385
  }
1386
  }
1387
 
@@ -1575,4 +1724,408 @@ class Limit_Login_Attempts
1575
 
1576
  wp_send_json_success(array());
1577
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1578
  }
3
  /**
4
  * Class Limit_Login_Attempts
5
  */
6
+ class Limit_Login_Attempts {
7
+
8
  public $default_options = array(
9
  'gdpr' => 0,
10
 
41
  'whitelist_usernames' => array(),
42
  'blacklist' => array(),
43
  'blacklist_usernames' => array(),
44
+
45
+ 'active_app' => 'local',
46
+ 'app_config' => '',
47
  );
48
  /**
49
  * Admin options page slug
70
  */
71
  private $use_local_options = null;
72
 
73
+ /**
74
+ * Current app object
75
+ *
76
+ * @var LLAR_App
77
+ */
78
+ public $app = null;
79
+
80
  public function __construct() {
81
  $this->hooks_init();
82
+ $this->app_init();
83
  }
84
 
85
  /**
96
  add_filter( 'illegal_user_logins', array( $this, 'register_user_blacklist' ), 999 );
97
  add_action( 'admin_notices', array( $this, 'show_leave_review_notice' ) );
98
  add_action( 'wp_ajax_dismiss_review_notice', array( $this, 'dismiss_review_notice_callback' ));
99
+ add_action( 'wp_ajax_app_config_save', array( $this, 'app_config_save_callback' ));
100
+ add_action( 'wp_ajax_app_setup', array( $this, 'app_setup_callback' ));
101
+ add_action( 'wp_ajax_app_log_action', array( $this, 'app_log_action_callback' ));
102
+ add_action( 'wp_ajax_app_load_log', array( $this, 'app_load_log_callback' ));
103
+ add_action( 'wp_ajax_app_load_lockouts', array( $this, 'app_load_lockouts_callback' ));
104
+ add_action( 'wp_ajax_app_load_acl_rules', array( $this, 'app_load_acl_rules_callback' ));
105
+ add_action( 'wp_ajax_app_acl_add_rule', array( $this, 'app_acl_add_rule_callback' ));
106
+ add_action( 'wp_ajax_app_acl_remove_rule', array( $this, 'app_acl_remove_rule_callback' ));
107
 
108
  add_action( 'admin_print_scripts-settings_page_limit-login-attempts', array( $this, 'load_admin_scripts' ) );
109
  }
144
  add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
145
 
146
  add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
 
147
  add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
148
 
149
  if ( $this->network_mode )
164
  * later versions of WP.
165
  */
166
  add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
167
+ add_action( 'authenticate', array( $this, 'authenticate_filter' ), 5, 3 );
168
+
169
+ /**
170
+ * BuddyPress unactivated user account message
171
+ */
172
+ add_action( 'authenticate', array( $this, 'bp_authenticate_filter' ), 35, 3 );
173
 
174
  if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST )
175
  add_action( 'init', array( $this, 'check_xmlrpc_lock' ) );
176
 
177
  add_action('wp_ajax_limit-login-unlock', array( $this, 'ajax_unlock' ) );
178
+
179
  }
180
 
181
+ public function app_init() {
182
+
183
+ if( $this->get_option( 'active_app' ) === 'custom' && $config = $this->get_custom_app_config() ) {
184
+
185
+ $this->app = new LLAR_App( $config );
186
+ }
187
+ }
188
+
189
  public function load_admin_scripts() {
190
 
191
  wp_enqueue_script('jquery-ui-accordion');
346
 
347
  if ( ! empty( $username ) && ! empty( $password ) ) {
348
 
349
+ if( $this->app && $response = $this->app->acl_check( array(
350
+ 'ip' => $this->get_all_ips(),
351
+ 'login' => $username,
352
+ 'gateway' => $this->detect_gateway()
353
+ ) ) ) {
354
 
355
+ if( $response['result'] === 'deny' ) {
 
356
 
357
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
358
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
359
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
360
+
361
+ // Remove default WP authentication filters
362
+ remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
363
+ remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
364
+
365
+ $err = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' );
366
+
367
+ $time_left = ( !empty( $response['time_left'] ) ) ? $response['time_left'] : 0;
368
+ if( $time_left ) {
369
 
370
+ if ( $time_left > 60 ) {
371
+ $time_left = ceil( $time_left / 60 );
372
+ $err .= ' ' . sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $time_left, 'limit-login-attempts-reloaded' ), $time_left );
373
+ } else {
374
+ $err .= ' ' . sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $time_left, 'limit-login-attempts-reloaded' ), $time_left );
375
+ }
376
+ }
377
 
378
+ $this->app->add_error( $err );
 
 
 
 
 
379
 
380
+ $user = new WP_Error();
381
+ $user->add( 'username_blacklisted', $err );
382
+ }
383
+ else if( $response['result'] === 'pass' ) {
384
 
385
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
386
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
387
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
388
+ }
389
 
390
+ } else {
391
 
392
+ $ip = $this->get_address();
 
 
 
393
 
394
+ // Check if username is blacklisted
395
+ if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
396
+ ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
397
+ ) {
398
 
399
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
400
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
401
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
402
+
403
+ // Remove default WP authentication filters
404
+ remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
405
+ remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
406
+
407
+ $user = new WP_Error();
408
+ $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
409
+
410
+ } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
411
+
412
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
413
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
414
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
415
+
416
+ }
417
+ }
418
  }
419
 
420
  return $user;
421
  }
422
 
423
+ /**
424
+ * BuddyPress unactivated user account message fix
425
+ *
426
+ * @param $user
427
+ * @param $username
428
+ * @param $password
429
+ * @return mixed
430
+ */
431
+ public function bp_authenticate_filter( $user, $username, $password ) {
432
+
433
+ if ( ! empty( $username ) && ! empty( $password ) ) {
434
+
435
+ if(is_wp_error($user) && in_array('bp_account_not_activated', $user->get_error_codes()) ) {
436
+
437
+ $this->other_login_errors[] = $user->get_error_message('bp_account_not_activated');
438
+ }
439
+ }
440
+ return $user;
441
+ }
442
+
443
+ /**
444
+ * @return array
445
+ */
446
+ public function get_all_ips() {
447
+
448
+ $ips = array();
449
+
450
+ foreach ( $_SERVER as $key => $value ) {
451
+
452
+ if( in_array( $key, ['SERVER_ADDR'] ) ) continue;
453
+
454
+ if( filter_var( $value, FILTER_VALIDATE_IP ) ) {
455
+
456
+ $ips[$key] = $value;
457
+ }
458
+ }
459
+
460
+ if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && !array_key_exists( 'HTTP_X_FORWARDED_FOR', $ips ) ) {
461
+
462
+ $ips['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
463
+ }
464
+
465
+ return $ips;
466
+ }
467
+
468
  /**
469
  * Check if the original plugin is installed
470
  */
484
  */
485
  public function enqueue() {
486
  wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . 'assets/css/limit-login-attempts.css' );
487
+ wp_enqueue_script( 'lla-main', LLA_PLUGIN_URL . 'assets/js/limit-login-attempts.js' );
488
  }
489
 
490
  /**
501
  }
502
 
503
  /**
504
+ * Get the correct options page URI
505
+ *
506
+ * @param bool $tab
507
+ * @return mixed
508
+ */
509
+ public function get_options_page_uri($tab = false)
510
  {
511
 
512
  if ( is_network_admin() )
514
  else
515
  $uri = menu_page_url( $this->_options_page_slug, false );
516
 
517
+ if( !empty( $tab ) ) {
518
 
519
+ $uri = add_query_arg( 'tab', $tab, $uri );
520
  }
521
 
522
  return $uri;
569
  return $func( $option, $value, '', 'no' );
570
  }
571
 
572
+ public function delete_option( $option_name, $local=null )
573
+ {
574
+ if ( is_null( $local ) )
575
+ $local = $this->use_local_options;
576
+
577
+ $option = 'limit_login_'.$option_name;
578
+
579
+ $func = $local ? 'delete_option' : 'delete_site_option';
580
+
581
+ return $func( $option );
582
+ }
583
+
584
  /**
585
  * Setup main options
586
  */
645
  */
646
  public function limit_login_failed( $username ) {
647
 
648
+ if( $this->app && $response = $this->app->lockout_check( array(
649
+ 'ip' => $this->get_all_ips(),
650
+ 'login' => $username,
651
+ 'gateway' => $this->detect_gateway()
652
+ ) ) ) {
653
 
654
+ if( $response['result'] === 'allow' ) {
 
655
 
656
+ $this->app->add_error(
657
+ sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $response['attempts_left'], 'limit-login-attempts-reloaded' ), $response['attempts_left'] )
658
+ );
659
+ } elseif( $response['result'] === 'deny' ) {
660
 
661
+ global $limit_login_just_lockedout;
662
+ $limit_login_just_lockedout = true;
 
663
 
664
+ $err = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' );
 
 
665
 
666
+ $time_left = ( !empty( $response['time_left'] ) ) ? $response['time_left'] : 0;
667
+ if ( $time_left > 60 ) {
668
+ $time_left = ceil( $time_left / 60 );
669
+ $err .= ' ' . sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $time_left, 'limit-login-attempts-reloaded' ), $time_left );
670
+ } else {
671
+ $err .= ' ' . sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $time_left, 'limit-login-attempts-reloaded' ), $time_left );
672
+ }
673
 
674
+ $this->app->add_error( $err );
675
+ }
 
 
676
 
 
 
 
 
 
677
  } else {
 
 
 
678
 
679
+ $ip = $this->get_address();
680
+ $ipHash = $this->getHash($this->get_address());
 
 
 
 
 
681
 
682
+ /* if currently locked-out, do not add to retries */
683
+ $lockouts = $this->get_option( 'lockouts' );
684
 
685
+ if ( ! is_array( $lockouts ) ) {
686
+ $lockouts = array();
687
+ }
688
 
689
+ if ( (isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ]) || (isset( $lockouts[ $ipHash ] ) && time() < $lockouts[ $ipHash ] )) {
690
+ return;
 
 
 
 
 
 
691
  }
692
+
693
+ /* Get the arrays with retries and retries-valid information */
694
+ $retries = $this->get_option( 'retries' );
695
+ $valid = $this->get_option( 'retries_valid' );
696
+
697
+ if ( ! is_array( $retries ) ) {
698
+ $retries = array();
699
+ $this->add_option( 'retries', $retries );
700
+ }
701
+
702
+ if ( ! is_array( $valid ) ) {
703
+ $valid = array();
704
+ $this->add_option( 'retries_valid', $valid );
705
+ }
706
+
707
+ $gdpr = $this->get_option('gdpr');
708
+ $ip = ($gdpr ? $ipHash : $ip);
709
+ /* Check validity and add one to retries */
710
+ if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ]) {
711
+ $retries[ $ip ] ++;
712
  } else {
713
+ $retries[ $ip ] = 1;
 
714
  }
715
+ $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
716
+
717
+ /* lockout? */
718
+ if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
719
+ /*
720
+ * Not lockout (yet!)
721
+ * Do housecleaning (which also saves retry/valid values).
722
+ */
723
+ $this->cleanup( $retries, null, $valid );
724
 
725
+ return;
726
+ }
727
 
728
+ /* lockout! */
729
+ $whitelisted = $this->is_ip_whitelisted( $ip );
730
+ $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
731
 
732
+ /*
733
+ * Note that retries and statistics are still counted and notifications
734
+ * done as usual for whitelisted ips , but no lockout is done.
735
+ */
736
+ if ( $whitelisted ) {
737
+ if ( $retries[ $ip ] >= $retries_long ) {
738
+ unset( $retries[ $ip ] );
739
+ unset( $valid[ $ip ] );
740
+ }
741
+ } else {
742
+ global $limit_login_just_lockedout;
743
+ $limit_login_just_lockedout = true;
744
+ $gdpr = $this->get_option('gdpr');
745
+ $index = ($gdpr ? $ipHash : $ip);
746
+
747
+ /* setup lockout, reset retries as needed */
748
+ if ( (isset($retries[ $ip ]) ? $retries[ $ip ] : 0) >= $retries_long || (isset($retries[ $ipHash ]) ? $retries[ $ipHash ] : 0) >= $retries_long ) {
749
+ /* long lockout */
750
+ $lockouts[ $index ] = time() + $this->get_option( 'long_duration' );
751
+ unset( $retries[ $index ] );
752
+ unset( $valid[ $index ] );
753
+ } else {
754
+ /* normal lockout */
755
+ $lockouts[ $index ] = time() + $this->get_option( 'lockout_duration' );
756
+ }
757
+ }
758
+
759
+ /* do housecleaning and save values */
760
+ $this->cleanup( $retries, $lockouts, $valid );
761
+
762
+ /* do any notification */
763
+ $this->notify( $username );
764
+
765
+ /* increase statistics */
766
+ $total = $this->get_option( 'lockouts_total' );
767
+ if ( $total === false || ! is_numeric( $total ) ) {
768
+ $this->add_option( 'lockouts_total', 1 );
769
+ } else {
770
+ $this->update_option( 'lockouts_total', $total + 1 );
771
+ }
772
  }
773
  }
774
 
918
  $log[ $index ][ $user_login ]['counter']++;
919
  $log[ $index ][ $user_login ]['date'] = time();
920
 
921
+ $log[ $index ][ $user_login ]['gateway'] = $this->detect_gateway();
 
 
 
 
 
 
 
 
922
 
923
  if ( $option === false ) {
924
  $this->add_option( 'logged', $log );
927
  }
928
  }
929
 
930
+ /**
931
+ * @return string
932
+ */
933
+ public function detect_gateway() {
934
+
935
+ $gateway = 'wp_login';
936
+
937
+ if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
938
+ $gateway = 'wp_woo_login';
939
+ } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
940
+ $gateway = 'wp_xmlrpc';
941
+ }
942
+
943
+ return $gateway;
944
+ }
945
+
946
  /**
947
  * Check if IP is whitelisted.
948
  *
1016
  */
1017
  public function wp_authenticate_user( $user, $password ) {
1018
 
1019
+ if( is_wp_error( $user ) ) {
1020
+ return $user;
1021
+ }
1022
+
1023
  $user_login = '';
1024
 
1025
  if( is_a( $user, 'WP_User' ) ) {
1028
  $user_login = $user;
1029
  }
1030
 
1031
+ if ( $this->check_whitelist_ips( false, $this->get_address() ) ||
 
1032
  $this->check_whitelist_usernames( false, $user_login ) ||
1033
  $this->is_limit_login_ok()
1034
  ) {
1077
  $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
1078
  }
1079
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  /**
1081
  * Construct informative error message
1082
  *
1142
  public function fixup_error_messages( $content ) {
1143
  global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
1144
 
1145
+ $error_msg = $this->get_message();
 
 
1146
 
1147
+ if ( $limit_login_nonempty_credentials ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1148
 
1149
+ $content = '';
 
 
 
1150
 
1151
  if($this->other_login_errors) {
1152
 
 
1153
  foreach ($this->other_login_errors as $msg) {
1154
  $content .= $msg . "<br />\n";
1155
  }
1156
+
1157
+ } else if( !$limit_login_just_lockedout ) {
1158
 
1159
  /* Replace error message, including ours if necessary */
1160
  if( !empty( $_REQUEST['log'] ) && is_email( $_REQUEST['log'] ) ) {
1164
  }
1165
  }
1166
 
1167
+ if ( $error_msg ) {
 
 
 
 
 
 
 
1168
 
1169
+ $content .= ( !empty( $content ) ) ? "<br />\n" : '';
1170
+ $content .= $error_msg . "<br />\n";
 
 
 
1171
  }
1172
  }
1173
 
1174
+ return $content;
1175
  }
1176
 
1177
  public function fixup_error_messages_wc( \WP_Error $error ) {
1184
  * @return string
1185
  */
1186
  public function get_message() {
 
 
 
 
1187
 
1188
+ if( $this->app && $app_errors = $this->app->get_errors() ) {
1189
+
1190
+ return implode( '<br>', $app_errors);
1191
+ } else {
1192
 
1193
+ /* Check external whitelist */
1194
+ if ( $this->is_ip_whitelisted() ) {
1195
+ return '';
1196
+ }
1197
+
1198
+ /* Is lockout in effect? */
1199
+ if ( ! $this->is_limit_login_ok() ) {
1200
+ return $this->error_msg();
1201
+ }
1202
+
1203
+ return $this->retries_remaining_msg();
1204
+ }
1205
  }
1206
 
1207
  /**
1372
  * Render admin options page
1373
  */
1374
  public function options_page() {
1375
+
1376
  $this->use_local_options = !is_network_admin();
1377
  $this->cleanup();
1378
 
1379
+ if( !empty( $_POST ) ) {
1380
+
1381
  check_admin_referer( 'limit-login-attempts-options' );
1382
 
1383
+ if ( is_network_admin() )
1384
+ $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1385
 
1386
+ elseif ( $this->network_mode )
1387
+ $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1388
 
1389
  /* Should we clear log? */
1390
+ if( isset( $_POST[ 'clear_log' ] ) )
1391
+ {
1392
+ $this->update_option( 'logged', '' );
1393
+ $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1394
+ }
1395
 
1396
+ /* Should we reset counter? */
1397
+ if( isset( $_POST[ 'reset_total' ] ) )
1398
+ {
1399
+ $this->update_option( 'lockouts_total', 0 );
1400
+ $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1401
+ }
1402
 
1403
+ /* Should we restore current lockouts? */
1404
+ if( isset( $_POST[ 'reset_current' ] ) )
1405
+ {
1406
+ $this->update_option( 'lockouts', array() );
1407
+ $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1408
+ }
1409
 
1410
+ /* Should we update options? */
1411
+ if( isset( $_POST[ 'llar_update_dashboard' ] ) ) {
1412
 
1413
+ $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1414
 
1415
+ if( !empty( $white_list_ips ) ) {
1416
+ foreach( $white_list_ips as $key => $ip ) {
1417
+ if( '' == $ip ) {
1418
+ unset( $white_list_ips[ $key ] );
1419
+ }
1420
+ }
1421
+ }
1422
+ $this->update_option('whitelist', $white_list_ips );
1423
 
1424
+ $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1425
 
1426
+ if( !empty( $white_list_usernames ) ) {
1427
+ foreach( $white_list_usernames as $key => $ip ) {
1428
+ if( '' == $ip ) {
1429
+ unset( $white_list_usernames[ $key ] );
1430
+ }
1431
+ }
1432
+ }
1433
+ $this->update_option('whitelist_usernames', $white_list_usernames );
1434
 
1435
+ $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1436
 
1437
+ if( !empty( $black_list_ips ) ) {
1438
+ foreach( $black_list_ips as $key => $ip ) {
1439
  $range = array_map('trim', explode('-', $ip) );
1440
  if ( count( $range ) > 1 && (float)sprintf("%u",ip2long($range[0])) > (float)sprintf("%u",ip2long($range[1]))) {
1441
  $this->show_error( __( 'The "'. $ip .'" IP range is invalid', 'limit-login-attempts-reloaded' ) );
1442
  }
1443
+ if( '' == $ip ) {
1444
+ unset( $black_list_ips[ $key ] );
1445
+ }
1446
+ }
1447
+ }
1448
  $this->update_option('blacklist', $black_list_ips );
1449
 
1450
+ $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1451
 
1452
+ if( !empty( $black_list_usernames ) ) {
1453
+ foreach( $black_list_usernames as $key => $ip ) {
1454
+ if( '' == $ip ) {
1455
+ unset( $black_list_usernames[ $key ] );
1456
+ }
1457
+ }
1458
+ }
1459
+ $this->update_option('blacklist_usernames', $black_list_usernames );
1460
 
1461
+ $this->sanitize_options();
1462
 
1463
+ $this->show_error( __( 'Settings saved.', 'limit-login-attempts-reloaded' ) );
1464
+ }
1465
+ elseif( isset( $_POST[ 'llar_update_settings' ] ) ) {
1466
 
1467
+ /* Should we support GDPR */
1468
+ if( isset( $_POST[ 'gdpr' ] ) ) {
1469
 
1470
+ $this->update_option( 'gdpr', 1 );
1471
+ } else {
1472
 
1473
+ $this->update_option( 'gdpr', 0 );
1474
+ }
1475
 
1476
+ $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1477
+ $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1478
+ $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1479
+ $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1480
+ $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1481
+ $this->update_option('notify_email_after', (int)$_POST['email_after'] );
1482
+ $this->update_option('active_app', sanitize_text_field( $_POST['active_app'] ) );
1483
 
1484
+ $this->update_option('admin_notify_email', sanitize_email( $_POST['admin_notify_email'] ) );
1485
 
1486
+ $trusted_ip_origins = ( !empty( $_POST['lla_trusted_ip_origins'] ) )
1487
+ ? array_map( 'trim', explode( ',', sanitize_text_field( $_POST['lla_trusted_ip_origins'] ) ) )
1488
+ : array();
1489
 
1490
+ if( !in_array( 'REMOTE_ADDR', $trusted_ip_origins ) ) {
1491
 
1492
+ $trusted_ip_origins[] = 'REMOTE_ADDR';
1493
+ }
1494
 
1495
+ $this->update_option('trusted_ip_origins', $trusted_ip_origins );
1496
 
1497
+ $notify_methods = array();
1498
+ if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1499
+ $notify_methods[] = 'log';
1500
+ }
1501
+ if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1502
+ $notify_methods[] = 'email';
1503
+ }
1504
+ $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1505
+
1506
+ $this->sanitize_options();
1507
+
1508
+ if( !empty( $_POST['llar_app_settings'] ) && $this->app ) {
1509
+
1510
+ if( ( $app_setup_link = $this->get_option( 'app_setup_link' ) ) && $setup_result = LLAR_App::setup( $app_setup_link ) ) {
1511
+
1512
+ if( $setup_result['success'] && $active_app_config = $setup_result['app_config'] ) {
1513
+
1514
+ foreach ( $_POST['llar_app_settings'] as $key => $value ) {
1515
 
1516
+ if( array_key_exists( $key, $active_app_config['settings'] ) ) {
1517
 
1518
+ if( !empty( $active_app_config['settings'][$key]['options'] ) &&
1519
+ !in_array( $value, $active_app_config['settings'][$key]['options'] ) ) {
1520
+
1521
+ continue;
1522
+ }
1523
+
1524
+ $active_app_config['settings'][$key]['value'] = $value;
1525
+ }
1526
+ }
1527
+
1528
+ $this->update_option( 'app_config', $active_app_config );
1529
+ }
1530
+ }
1531
+ }
1532
+
1533
+ $this->show_error( __( 'Settings saved.', 'limit-login-attempts-reloaded' ) );
1534
  }
1535
  }
1536
 
1724
 
1725
  wp_send_json_success(array());
1726
  }
1727
+
1728
+ public function app_setup_callback() {
1729
+
1730
+ if ( !current_user_can('activate_plugins') ) {
1731
+
1732
+ wp_send_json_error(array());
1733
+ }
1734
+
1735
+ check_ajax_referer('llar-action', 'sec');
1736
+
1737
+ if( !empty( $_POST['link'] ) ) {
1738
+
1739
+ $link = sanitize_text_field( $_POST['link'] );
1740
+
1741
+ if( $setup_result = LLAR_App::setup( $link ) ) {
1742
+
1743
+ if( $setup_result['success'] ) {
1744
+
1745
+ if( $setup_result['app_config'] ) {
1746
+
1747
+ $this->app_update_config( $setup_result['app_config'], true );
1748
+ $this->update_option( 'active_app', 'custom' );
1749
+
1750
+ $this->update_option( 'app_setup_link', $link );
1751
+
1752
+ wp_send_json_success(array(
1753
+ 'msg' => ( !empty( $setup_result['app_config']['messages']['setup_success'] ) )
1754
+ ? $setup_result['app_config']['messages']['setup_success']
1755
+ : __( 'The app has been successfully imported.', 'limit-login-attempts-reloaded' )
1756
+ ));
1757
+ }
1758
+
1759
+ } else {
1760
+
1761
+ wp_send_json_error(array(
1762
+ 'msg' => $setup_result['error']
1763
+ ));
1764
+ }
1765
+ }
1766
+ }
1767
+
1768
+ wp_send_json_error(array(
1769
+ 'msg' => __( 'Please specify the Setup Link', 'limit-login-attempts-reloaded' )
1770
+ ));
1771
+ }
1772
+
1773
+ public function app_update_config( $new_app_config, $update_created_at = false ) {
1774
+
1775
+ if( !$new_app_config ) return false;
1776
+
1777
+ if( $active_app_config = $this->get_custom_app_config() ) {
1778
+
1779
+ foreach ( $active_app_config['settings'] as $key => $info ) {
1780
+
1781
+ if( array_key_exists( $key, $new_app_config['settings'] ) ) {
1782
+
1783
+ if( !empty( $new_app_config['settings'][$key]['options'] ) &&
1784
+ !in_array( $info['value'], $new_app_config['settings'][$key]['options'] ) ) {
1785
+
1786
+ continue;
1787
+ }
1788
+
1789
+ $new_app_config['settings'][$key]['value'] = $info['value'];
1790
+ }
1791
+ }
1792
+
1793
+ }
1794
+
1795
+ if( $update_created_at )
1796
+ $new_app_config['created_at'] = time();
1797
+
1798
+ $this->update_option( 'app_config', $new_app_config );
1799
+ }
1800
+
1801
+ public function app_log_action_callback() {
1802
+
1803
+ if ( !current_user_can('activate_plugins') ) {
1804
+
1805
+ wp_send_json_error(array());
1806
+ }
1807
+
1808
+ check_ajax_referer('llar-action', 'sec');
1809
+
1810
+ if( !empty( $_POST['method'] ) && !empty( $_POST['params'] ) ) {
1811
+
1812
+ $method = sanitize_text_field( $_POST['method'] );
1813
+ $params = (array) $_POST['params'];
1814
+
1815
+ if( !in_array( $method, array( 'lockout/delete', 'acl/create', 'acl/delete' ) ) ) {
1816
+
1817
+ wp_send_json_error(array(
1818
+ 'msg' => 'Wrong method.'
1819
+ ));
1820
+ }
1821
+
1822
+ if( $response = $this->app->request( $method, 'post', $params ) ) {
1823
+
1824
+ wp_send_json_success(array(
1825
+ 'msg' => $response['message']
1826
+ ));
1827
+
1828
+ } else {
1829
+
1830
+ wp_send_json_error(array(
1831
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
1832
+ ));
1833
+ }
1834
+ }
1835
+
1836
+ wp_send_json_error(array(
1837
+ 'msg' => 'Wrong App id.'
1838
+ ));
1839
+ }
1840
+
1841
+ public function app_acl_add_rule_callback() {
1842
+
1843
+ if ( !current_user_can('activate_plugins') ) {
1844
+
1845
+ wp_send_json_error(array());
1846
+ }
1847
+
1848
+ check_ajax_referer('llar-action', 'sec');
1849
+
1850
+ if( !empty( $_POST['pattern'] ) && !empty( $_POST['rule'] ) && !empty( $_POST['type'] ) ) {
1851
+
1852
+ $pattern = sanitize_text_field( $_POST['pattern'] );
1853
+ $rule = sanitize_text_field( $_POST['rule'] );
1854
+ $type = sanitize_text_field( $_POST['type'] );
1855
+
1856
+ if( !in_array( $rule, array( 'pass', 'allow', 'deny' ) ) ) {
1857
+
1858
+ wp_send_json_error(array(
1859
+ 'msg' => 'Wrong rule.'
1860
+ ));
1861
+ }
1862
+
1863
+ if( $response = $this->app->acl_create( array(
1864
+ 'pattern' => $pattern,
1865
+ 'rule' => $rule,
1866
+ 'type' => ( $type === 'ip' ) ? 'ip' : 'login',
1867
+ ) ) ) {
1868
+
1869
+ wp_send_json_success(array(
1870
+ 'msg' => $response['message']
1871
+ ));
1872
+
1873
+ } else {
1874
+
1875
+ wp_send_json_error(array(
1876
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
1877
+ ));
1878
+ }
1879
+ }
1880
+
1881
+ wp_send_json_error(array(
1882
+ 'msg' => 'Wrong input data.'
1883
+ ));
1884
+ }
1885
+
1886
+ public function app_acl_remove_rule_callback() {
1887
+
1888
+ if ( !current_user_can('activate_plugins') ) {
1889
+
1890
+ wp_send_json_error(array());
1891
+ }
1892
+
1893
+ check_ajax_referer('llar-action', 'sec');
1894
+
1895
+ if( !empty( $_POST['pattern'] ) && !empty( $_POST['type'] ) ) {
1896
+
1897
+ $pattern = sanitize_text_field( $_POST['pattern'] );
1898
+ $type = sanitize_text_field( $_POST['type'] );
1899
+
1900
+ if( $response = $this->app->acl_delete( array(
1901
+ 'pattern' => $pattern,
1902
+ 'type' => ( $type === 'ip' ) ? 'ip' : 'login',
1903
+ ) ) ) {
1904
+
1905
+ wp_send_json_success(array(
1906
+ 'msg' => $response['message']
1907
+ ));
1908
+
1909
+ } else {
1910
+
1911
+ wp_send_json_error(array(
1912
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
1913
+ ));
1914
+ }
1915
+ }
1916
+
1917
+ wp_send_json_error(array(
1918
+ 'msg' => 'Wrong input data.'
1919
+ ));
1920
+ }
1921
+
1922
+ public function app_load_log_callback() {
1923
+
1924
+ if ( !current_user_can('activate_plugins') ) {
1925
+
1926
+ wp_send_json_error(array());
1927
+ }
1928
+
1929
+ check_ajax_referer('llar-action', 'sec');
1930
+
1931
+ $offset = sanitize_text_field( $_POST['offset'] );
1932
+
1933
+ $log = $this->app->log( 25, $offset );
1934
+
1935
+ if( $log ) {
1936
+
1937
+ ob_start(); ?>
1938
+
1939
+ <tr>
1940
+ <th scope="col"><?php _e( "Time", 'limit-login-attempts-reloaded' ); ?></th>
1941
+ <th scope="col"><?php _e( "IP", 'limit-login-attempts-reloaded' ); ?></th>
1942
+ <th scope="col"><?php _e( "Gateway", 'limit-login-attempts-reloaded' ); ?></th>
1943
+ <th scope="col"><?php _e( "Login", 'limit-login-attempts-reloaded' ); ?></th>
1944
+ <th scope="col"><?php _e( "Rule", 'limit-login-attempts-reloaded' ); ?></th>
1945
+ <th scope="col"><?php _e( "Reason", 'limit-login-attempts-reloaded' ); ?></th>
1946
+ <th scope="col"><?php _e( "Pattern", 'limit-login-attempts-reloaded' ); ?></th>
1947
+ <th scope="col"><?php _e( "Attempts Left", 'limit-login-attempts-reloaded' ); ?></th>
1948
+ <th scope="col"><?php _e( "Lockout Duration", 'limit-login-attempts-reloaded' ); ?></th>
1949
+ <th scope="col"><?php _e( "Actions", 'limit-login-attempts-reloaded' ); ?></th>
1950
+ </tr>
1951
+
1952
+ <?php
1953
+ $date_format = get_option('date_format') . ' ' . get_option('time_format');
1954
+ ?>
1955
+
1956
+ <?php if( $log['items'] ) : ?>
1957
+
1958
+ <?php foreach ( $log['items'] as $item ) : ?>
1959
+ <tr>
1960
+ <td class="llar-col-nowrap"><?php echo get_date_from_gmt( date( 'Y-m-d H:i:s', $item['created_at'] ), $date_format ); ?></td>
1961
+ <td><?php echo esc_html( $item['ip'] ); ?></td>
1962
+ <td><?php echo esc_html( $item['gateway'] ); ?></td>
1963
+ <td><?php echo (is_null($item['login'])) ? '-' : esc_html( $item['login'] ); ?></td>
1964
+ <td><?php echo (is_null($item['result'])) ? '-' : esc_html( $item['result'] ); ?></td>
1965
+ <td><?php echo (is_null($item['reason'])) ? '-' : esc_html( $item['reason'] ); ?></td>
1966
+ <td><?php echo (is_null($item['pattern'])) ? '-' : esc_html( $item['pattern'] ); ?></td>
1967
+ <td><?php echo (is_null($item['attempts_left'])) ? '-' : esc_html( $item['attempts_left'] ); ?></td>
1968
+ <td><?php echo (is_null($item['time_left'])) ? '-' : esc_html( $item['time_left'] ) ?></td>
1969
+ <td class="llar-app-log-actions">
1970
+ <?php
1971
+ if( $item['actions'] ) {
1972
+
1973
+ foreach ( $item['actions'] as $action ) {
1974
+
1975
+ echo '<button class="button llar-app-log-action-btn js-app-log-action" style="color:' . esc_attr( $action['color'] ) . ';border-color:' . esc_attr( $action['color'] ) . '"
1976
+ data-method="' . esc_attr( $action['method'] ) . '"
1977
+ data-params="' . esc_attr( json_encode( $action['data'], JSON_FORCE_OBJECT ) ) . '"
1978
+ href="#" title="' . $action['label'] . '"><i class="dashicons dashicons-' . esc_attr( $action['icon'] ) . '"></i></button>';
1979
+ }
1980
+ } else {
1981
+ echo '-';
1982
+ }
1983
+ ?>
1984
+ </td>
1985
+ </tr>
1986
+ <?php endforeach; ?>
1987
+ <?php else : ?>
1988
+ <tr class="empty-row"><td colspan="9" style="text-align: center"><?php _e('No events yet.', 'limit-login-attempts-reloaded' ); ?></td></tr>
1989
+ <?php endif; ?>
1990
+ <?php
1991
+
1992
+ wp_send_json_success(array(
1993
+ 'html' => ob_get_clean(),
1994
+ 'offset' => $log['offset']
1995
+ ));
1996
+
1997
+ } else {
1998
+
1999
+ wp_send_json_error(array(
2000
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
2001
+ ));
2002
+ }
2003
+ }
2004
+
2005
+ public function app_load_lockouts_callback() {
2006
+
2007
+ if ( !current_user_can('activate_plugins') ) {
2008
+
2009
+ wp_send_json_error(array());
2010
+ }
2011
+
2012
+ check_ajax_referer('llar-action', 'sec');
2013
+
2014
+ $offset = sanitize_text_field( $_POST['offset'] );
2015
+
2016
+ $lockouts = $this->app->get_lockouts( 25, $offset );
2017
+
2018
+ if( $lockouts ) {
2019
+
2020
+ ob_start(); ?>
2021
+
2022
+ <tr>
2023
+ <th scope="col"><?php _e( "IP", 'limit-login-attempts-reloaded' ); ?></th>
2024
+ <th scope="col"><?php _e( "Login", 'limit-login-attempts-reloaded' ); ?></th>
2025
+ <th scope="col"><?php _e( "Count", 'limit-login-attempts-reloaded' ); ?></th>
2026
+ <th scope="col"><?php _e( "Expires in (minutes)", 'limit-login-attempts-reloaded' ); ?></th>
2027
+ </tr>
2028
+
2029
+ <?php if( $lockouts['items'] ) : ?>
2030
+ <?php foreach ( $lockouts['items'] as $item ) : ?>
2031
+ <tr>
2032
+ <td><?php echo esc_html( $item['ip'] ); ?></td>
2033
+ <td><?php echo (is_null($item['login'])) ? '-' : esc_html( implode( ',', $item['login'] ) ); ?></td>
2034
+ <td><?php echo (is_null($item['count'])) ? '-' : esc_html( $item['count'] ); ?></td>
2035
+ <td><?php echo (is_null($item['ttl'])) ? '-' : esc_html( round( ( $item['ttl'] - time() ) / 60 ) ); ?></td>
2036
+ </tr>
2037
+ <?php endforeach; ?>
2038
+
2039
+ <?php else: ?>
2040
+ <tr class="empty-row"><td colspan="4" style="text-align: center"><?php _e('No lockouts yet.', 'limit-login-attempts-reloaded' ); ?></td></tr>
2041
+ <?php endif; ?>
2042
+ <?php
2043
+
2044
+ wp_send_json_success(array(
2045
+ 'html' => ob_get_clean(),
2046
+ 'offset' => $lockouts['offset']
2047
+ ));
2048
+
2049
+ } elseif( intval( $this->app->last_response_code ) >= 400 && intval( $this->app->last_response_code ) < 500) {
2050
+
2051
+ $app_config = $this->get_custom_app_config();
2052
+
2053
+ wp_send_json_error(array(
2054
+ 'error_notice' => '<div class="llar-app-notice">
2055
+ <p>'. $app_config['messages']['sync_error'] .'<br><br>'. sprintf( __( 'Meanwhile, the app falls over to the <a href="%s">default functionality</a>.', 'limit-login-attempts-reloaded' ), admin_url('options-general.php?page=limit-login-attempts&tab=logs-local') ) . '</p>
2056
+ </div>'
2057
+ ));
2058
+ } else {
2059
+
2060
+ wp_send_json_error(array(
2061
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
2062
+ ));
2063
+ }
2064
+ }
2065
+
2066
+ public function app_load_acl_rules_callback() {
2067
+
2068
+ if ( !current_user_can('activate_plugins') ) {
2069
+
2070
+ wp_send_json_error(array());
2071
+ }
2072
+
2073
+ check_ajax_referer('llar-action', 'sec');
2074
+
2075
+ $type = sanitize_text_field( $_POST['type'] );
2076
+
2077
+ $acl_list = $this->app->acl( array(
2078
+ 'type' => $type
2079
+ ) );
2080
+
2081
+ if( $acl_list ) {
2082
+
2083
+ ob_start(); ?>
2084
+
2085
+ <tr>
2086
+ <th scope="col"><?php _e( 'Pattern', 'limit-login-attempts-reloaded' ); ?></th>
2087
+ <th scope="col"><?php _e( 'Rule', 'limit-login-attempts-reloaded' ); ?></th>
2088
+ <th class="llar-app-acl-action-col" scope="col"><?php _e( 'Action', 'limit-login-attempts-reloaded' ); ?></th>
2089
+ </tr>
2090
+ <tr>
2091
+ <td><input class="regular-text llar-app-acl-pattern" type="text" placeholder="<?php esc_attr_e( 'Pattern', 'limit-login-attempts-reloaded' ); ?>"></td>
2092
+ <td>
2093
+ <select class="llar-app-acl-rule">
2094
+ <option value="deny" selected><?php esc_html_e( 'Deny', 'limit-login-attempts-reloaded' ); ?></option>
2095
+ <option value="allow"><?php esc_html_e( 'Allow', 'limit-login-attempts-reloaded' ); ?></option>
2096
+ <option value="pass"><?php esc_html_e( 'Pass', 'limit-login-attempts-reloaded' ); ?></option>
2097
+ </select>
2098
+ </td>
2099
+ <td class="llar-app-acl-action-col"><button class="button llar-app-acl-add-rule" data-type="<?php echo esc_attr( $type ); ?>"><?php _e( 'Add', 'limit-login-attempts-reloaded' ); ?></button></td>
2100
+ </tr>
2101
+
2102
+ <?php if( $acl_list['items'] ) : ?>
2103
+ <?php foreach ( $acl_list['items'] as $item ) : ?>
2104
+ <tr class="llar-app-rule-<?php echo esc_attr( $item['rule'] ); ?>">
2105
+ <td class="rule-pattern" scope="col"><?php echo esc_html( $item['pattern'] ); ?></td>
2106
+ <td scope="col"><?php echo esc_html( $item['rule'] ); ?></td>
2107
+ <td class="llar-app-acl-action-col" scope="col"><button class="button llar-app-acl-remove" data-type="login" data-pattern="<?php echo esc_attr( $item['pattern'] ); ?>"><span class="dashicons dashicons-no"></span></button></td>
2108
+ </tr>
2109
+ <?php endforeach; ?>
2110
+ <?php else : ?>
2111
+ <tr class="empty-row"><td colspan="3" style="text-align: center"><?php _e('No rules yet.', 'limit-login-attempts-reloaded' ); ?></td></tr>
2112
+ <?php endif; ?>
2113
+ <?php
2114
+
2115
+ wp_send_json_success(array(
2116
+ 'html' => ob_get_clean(),
2117
+ ));
2118
+
2119
+ } else {
2120
+
2121
+ wp_send_json_error(array(
2122
+ 'msg' => 'The endpoint is not responding. Please contact your app provider to settle that.'
2123
+ ));
2124
+ }
2125
+ }
2126
+
2127
+ public function get_custom_app_config() {
2128
+
2129
+ return $this->get_option( 'app_config' );
2130
+ }
2131
  }
limit-login-attempts-reloaded.php CHANGED
@@ -2,12 +2,12 @@
2
  /*
3
  Plugin Name: Limit Login Attempts Reloaded
4
  Description: Limit the rate of login attempts for each IP address.
5
- Author: WPChef
6
- Author URI: https://wpchef.org
7
  Text Domain: limit-login-attempts-reloaded
8
- Version: 2.15.2
9
 
10
- Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2020 WPChef
11
  */
12
 
13
  /***************************************************************************************
@@ -33,6 +33,7 @@ $limit_login_nonempty_credentials = false; /* user and pwd nonempty */
33
  * Include files
34
  **************************************************************************************/
35
  require_once( LLA_PLUGIN_DIR . '/core/Helpers.php' );
 
36
  require_once( LLA_PLUGIN_DIR . '/core/LimitLoginAttempts.php' );
37
 
38
  $limit_login_attempts_obj = new Limit_Login_Attempts();
2
  /*
3
  Plugin Name: Limit Login Attempts Reloaded
4
  Description: Limit the rate of login attempts for each IP address.
5
+ Author: Limit Login Attempts Reloaded
6
+ Author URI: https://limitloginattempts.com/
7
  Text Domain: limit-login-attempts-reloaded
8
+ Version: 2.16.0
9
 
10
+ Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2020 Limit Login Attempts Reloaded
11
  */
12
 
13
  /***************************************************************************************
33
  * Include files
34
  **************************************************************************************/
35
  require_once( LLA_PLUGIN_DIR . '/core/Helpers.php' );
36
+ require_once( LLA_PLUGIN_DIR . '/core/App.php' );
37
  require_once( LLA_PLUGIN_DIR . '/core/LimitLoginAttempts.php' );
38
 
39
  $limit_login_attempts_obj = new Limit_Login_Attempts();
readme.txt CHANGED
@@ -1,47 +1,68 @@
1
  === Limit Login Attempts Reloaded ===
2
  Contributors: wpchefgadget
 
3
  Tags: brute force, login, security, GDPR, protection
4
  Requires at least: 3.0
5
  Tested up to: 5.5
6
- Stable tag: 2.15.2
7
 
8
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers. GDPR compliant.
9
 
10
  == Description ==
11
 
12
- Limit the number of login attempts that possible through the normal login as well as XMLRPC, Woocommerce and custom login pages.
13
- WordPress by default allows unlimited login attempts. This allows passwords to be cracked via brute-force relatively easily.
14
- Limit Login Attempts Reloaded blocks an Internet address from making further attempts after a specified limit on retries has been reached, making a brute-force attack difficult or impossible.
15
 
16
- Features:
17
 
 
 
 
 
 
 
 
 
18
  * Limit the number of retry attempts when logging in (per each IP). This is fully customizable.
19
  * Informs the user about the remaining retries or lockout time on the login page.
20
- * Optional logging and optional email notification.
21
- * It is possible to whitelist/blacklist IPs and Usernames.
22
  * Sucuri Website Firewall compatibility.
23
  * **XMLRPC** gateway protection.
24
  * **Woocommerce** login page protection.
25
  * **Multi-site** compatibility with extra MU settings.
26
- * **GDPR** compliant. With this feature turned on, all logged IPs get obfuscated (md5-hashed).
27
  * **Custom IP origins** support (Cloudflare, Sucuri, etc.)
28
 
29
- = Upgrading from the old Limit Login Attempts plugin =
 
 
 
 
 
 
 
 
 
 
30
  1. Go to the Plugins section in your site's backend.
31
  1. Remove the Limit Login Attempts plugin.
32
  1. Install the Limit Login Attempts Reloaded plugin.
33
 
34
  All your settings will be kept in tact!
35
 
36
- Many languages are currently supported in Limit Login Attempts Reloaded plugin but we welcome any additional ones.
37
- Help us bring Limit Login Attempts Reloaded to even more cultures.
38
 
39
  Translations: Bulgarian, Brazilian Portuguese, Catalan, Chinese (Traditional), Czech, Dutch, Finnish, French, German, Hungarian, Norwegian, Persian, Romanian, Russian, Spanish, Swedish, Turkish
40
 
41
  Plugin uses standard actions and filters only.
42
 
43
- Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
44
- [](http://coderisk.com/wp/plugin/limit-login-attempts-reloaded/RIPS-M7n4uQXa-G)
 
 
 
 
45
 
46
  == Screenshots ==
47
 
@@ -49,8 +70,29 @@ Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
49
  2. Lockout loginscreen
50
  3. Administration interface in WordPress 5.2.1
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  == Changelog ==
53
 
 
 
 
54
  = 2.15.2 =
55
  * Alternative method of closing the feedback message.
56
 
1
  === Limit Login Attempts Reloaded ===
2
  Contributors: wpchefgadget
3
+ Donate link: https://www.paypal.com/donate?hosted_button_id=FKD4MYFCMNVQQ
4
  Tags: brute force, login, security, GDPR, protection
5
  Requires at least: 3.0
6
  Tested up to: 5.5
7
+ Stable tag: 2.16.0
8
 
9
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers. GDPR compliant.
10
 
11
  == Description ==
12
 
13
+ Limit the number of login attempts that are possible through the normal login as well as XMLRPC, Woocommerce and custom login pages.
 
 
14
 
15
+ WordPress by default allows unlimited login attempts. This can lead to passwords being easily cracked via brute-force.
16
 
17
+ Limit Login Attempts Reloaded blocks an Internet address (IP) from making further attempts after a specified limit on retries has been reached, making a brute-force attack difficult or impossible.
18
+
19
+ > <strong>Limit Login Attempts Reloaded Cloud App</strong><br>
20
+ > Enables cloud protection app for Limit Login Attempts Reloaded plugin. It comes with all the great features you'll need to stop hackers and bots from brute-force attacks. The cloud app <a href="https://www.limitloginattempts.com/features/">offers several features</a> including advanced protection out of the box, and the ability for site admins and agencies to sync allow/deny/pass lists across multiple domains. <a href="https://app.limitloginattempts.com/network/create">Click here to activate the cloud app for the best WordPress security plugin now!</a>
21
+
22
+ https://www.youtube.com/watch?v=IsotthPWCPA
23
+
24
+ = Features: =
25
  * Limit the number of retry attempts when logging in (per each IP). This is fully customizable.
26
  * Informs the user about the remaining retries or lockout time on the login page.
27
+ * Logging and optional email notification.
28
+ * It is possible to allow/deny IPs and Usernames.
29
  * Sucuri Website Firewall compatibility.
30
  * **XMLRPC** gateway protection.
31
  * **Woocommerce** login page protection.
32
  * **Multi-site** compatibility with extra MU settings.
33
+ * **GDPR** compliant.
34
  * **Custom IP origins** support (Cloudflare, Sucuri, etc.)
35
 
36
+ = Features (Cloud app): =
37
+ * **Outsource the site load** - All calculations and database queries are done in the cloud
38
+ * **Throttling** - Longer lockout intervals each time a hacker/bot tries to login unsuccessfully
39
+ * **Auto backups of all data**
40
+ * **Autofix diverse origin IPs (e.g. Cloudflare)** - Securely trust certain popular IP origins out of the box
41
+ * **Synced lockout & deny/pass lists check** - Lockouts can be shared between sites of the same admin
42
+ * **Synchronized allow/deny/pass lists** - Allow/Deny/Pass lists can be shared between sites of the same admin
43
+ * **Premium forum support** - Get answers within 1-2 business days.
44
+ * **Enhanced lockout logs** - A log of lockouts with extra features
45
+
46
+ = Upgrading from the old Limit Login Attempts plugin? =
47
  1. Go to the Plugins section in your site's backend.
48
  1. Remove the Limit Login Attempts plugin.
49
  1. Install the Limit Login Attempts Reloaded plugin.
50
 
51
  All your settings will be kept in tact!
52
 
53
+ Many languages are currently supported in the Limit Login Attempts Reloaded plugin but we welcome any additional ones.
54
+ Help us bring Limit Login Attempts Reloaded to even more countries.
55
 
56
  Translations: Bulgarian, Brazilian Portuguese, Catalan, Chinese (Traditional), Czech, Dutch, Finnish, French, German, Hungarian, Norwegian, Persian, Romanian, Russian, Spanish, Swedish, Turkish
57
 
58
  Plugin uses standard actions and filters only.
59
 
60
+ Based on the original code from Limit Login Attempts plugin by Johan Eenfeldt.
61
+
62
+ = Branding Guidelines =
63
+ Limit Login Attempts Reloaded™ is a trademark of Atlantic Silicon Inc. When writing about the plugin, please make sure to use Reloaded after Limit Login Attempts. Limit Login Attempts is the old plugin.
64
+ * Limit Login Attempts Reloaded (correct)
65
+ * Limit Login Attempts (incorrect)
66
 
67
  == Screenshots ==
68
 
70
  2. Lockout loginscreen
71
  3. Administration interface in WordPress 5.2.1
72
 
73
+ == Frequently Asked Questions ==
74
+
75
+ = What do I do if all users get blocked? =
76
+
77
+ If you are using contemporary hosting, it's likely your site uses a proxy domain service like CloudFlare, Sucuri, Nginx, etc. They replace your user's IP address with their own. If the server where your site runs is not configured properly (this happens a lot) all users will get the same IP address. This also applies to bots and hackers. Therefore, locking one user will lead to locking everybody else out. If the plugin is not using our <a href="https://www.limitloginattempts.com/">Cloud App</a>, this can be adjusted using the Trusted IP Origin setting. The cloud service intelligently recognizes the non-standard IP origins and handles them correctly, even if your hosting provider does not.
78
+
79
+ = What settings should I use In The Plugin? =
80
+
81
+ The settings are explained within the plugin in great detail. If you are unsure, use the default settings as they are the recommended ones.
82
+
83
+ = Can I share the allow/deny/pass lists throughout all of my sites?=
84
+
85
+ By default, you will need to copy and paste the lists to each site manually. For the <a href="https://www.limitloginattempts.com/features/">premium service</a>, sites are grouped within the same private cloud account. Each site within that group can be configured if it shares its lockouts and access lists with other group members. The setting is located in the plugin's interface. The default options are recommended.
86
+
87
+ = Where can I find answers to my Cloud App related questions? =
88
+
89
+ Please follow this link: <a href="https://www.limitloginattempts.com/resources/">https://www.limitloginattempts.com/resources/</a>
90
+
91
  == Changelog ==
92
 
93
+ = 2.16.0 =
94
+ * Custom Apps functionality implemented. More details: https://limitloginattempts.com/app/
95
+
96
  = 2.15.2 =
97
  * Alternative method of closing the feedback message.
98
 
views/options-page.php CHANGED
@@ -2,10 +2,17 @@
2
 
3
  if( !defined( 'ABSPATH' ) ) exit();
4
 
5
- $active_tab = "dashboard";
6
- if(!empty($_GET["tab"]) && in_array($_GET["tab"], ['dashboard', 'settings', 'debug'])) {
 
7
 
8
- $active_tab = $_GET["tab"];
 
 
 
 
 
 
9
  }
10
  ?>
11
 
@@ -13,9 +20,17 @@ if(!empty($_GET["tab"]) && in_array($_GET["tab"], ['dashboard', 'settings', 'deb
13
  <h2><?php echo __( 'Limit Login Attempts Reloaded', 'limit-login-attempts-reloaded' ); ?></h2>
14
 
15
  <h2 class="nav-tab-wrapper">
16
- <a href="<?php echo $this->get_options_page_uri(); ?>&tab=dashboard" class="nav-tab <?php if($active_tab == 'dashboard'){echo 'nav-tab-active';} ?> "><?php _e('Dashboard', 'limit-login-attempts-reloaded'); ?></a>
17
- <a href="<?php echo $this->get_options_page_uri(); ?>&tab=settings" class="nav-tab <?php if($active_tab == 'settings'){echo 'nav-tab-active';} ?> "><?php _e('Settings', 'limit-login-attempts-reloaded'); ?></a>
18
- <a href="<?php echo $this->get_options_page_uri(); ?>&tab=debug" class="nav-tab <?php if($active_tab == 'debug'){echo 'nav-tab-active';} ?>"><?php _e('Debug', 'limit-login-attempts-reloaded'); ?></a>
 
 
 
 
 
 
 
 
19
  </h2>
20
 
21
  <?php include_once(LLA_PLUGIN_DIR.'views/tab-'.$active_tab.'.php'); ?>
2
 
3
  if( !defined( 'ABSPATH' ) ) exit();
4
 
5
+ $active_tab = "settings";
6
+ $active_app = $this->get_option( 'active_app' );
7
+ if(!empty($_GET["tab"]) && in_array($_GET["tab"], ['logs-local', 'logs-custom', 'settings', 'debug'])) {
8
 
9
+ if(!$this->app && $_GET['tab'] === 'logs-custom') {
10
+
11
+ $active_tab = 'logs-local';
12
+ } else {
13
+
14
+ $active_tab = sanitize_text_field( $_GET["tab"] );
15
+ }
16
  }
17
  ?>
18
 
20
  <h2><?php echo __( 'Limit Login Attempts Reloaded', 'limit-login-attempts-reloaded' ); ?></h2>
21
 
22
  <h2 class="nav-tab-wrapper">
23
+ <a href="<?php echo $this->get_options_page_uri('settings'); ?>" class="nav-tab <?php if($active_tab == 'settings'){echo 'nav-tab-active';} ?> "><?php _e('Settings', 'limit-login-attempts-reloaded'); ?></a>
24
+ <?php if( $active_app === 'custom' ) : ?>
25
+ <a href="<?php echo $this->get_options_page_uri('logs-custom'); ?>" class="nav-tab <?php if($active_tab == 'logs-custom'){echo 'nav-tab-active';} ?> "><?php _e('Logs', 'limit-login-attempts-reloaded'); ?></a>
26
+ <?php else : ?>
27
+ <a href="<?php echo $this->get_options_page_uri('logs-local'); ?>" class="nav-tab <?php if($active_tab == 'logs-local'){echo 'nav-tab-active';} ?> "><?php _e('Logs', 'limit-login-attempts-reloaded'); ?></a>
28
+ <?php endif; ?>
29
+ <a href="<?php echo $this->get_options_page_uri('debug'); ?>" class="nav-tab <?php if($active_tab == 'debug'){echo 'nav-tab-active';} ?>"><?php _e('Debug', 'limit-login-attempts-reloaded'); ?></a>
30
+
31
+ <?php if($active_tab == 'logs-custom') : ?>
32
+ <a class="llar-failover-link" href="<?php echo $this->get_options_page_uri('logs-local'); ?>"><?php _e( 'Failover', 'limit-login-attempts-reloaded' ); ?></a>
33
+ <?php endif; ?>
34
  </h2>
35
 
36
  <?php include_once(LLA_PLUGIN_DIR.'views/tab-'.$active_tab.'.php'); ?>
views/tab-logs-custom.php ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if( !defined( 'ABSPATH' ) ) exit();
4
+
5
+ /**
6
+ * @var $this Limit_Login_Attempts
7
+ */
8
+ ?>
9
+
10
+ <div class="limit-login-app-dashboard">
11
+
12
+ <h3><?php _e( 'Lockouts', 'limit-login-attempts-reloaded' ); ?></h3>
13
+
14
+ <div class="llar-app-lockouts-pagination">
15
+ <a class="llar-prev-page button disabled" href="#">
16
+ <span aria-hidden="true">‹</span>
17
+ </a>
18
+ <a class="llar-next-page button disabled" href="#">
19
+ <span aria-hidden="true">›</span>
20
+ </a>
21
+ </div>
22
+
23
+ <table class="form-table llar-table-app-lockouts">
24
+ <tr>
25
+ <th scope="col"><?php _e( "IP", 'limit-login-attempts-reloaded' ); ?></th>
26
+ <th scope="col"><?php _e( "Login", 'limit-login-attempts-reloaded' ); ?></th>
27
+ <th scope="col"><?php _e( "Count", 'limit-login-attempts-reloaded' ); ?></th>
28
+ <th scope="col"><?php _e( "Expires in (minutes)", 'limit-login-attempts-reloaded' ); ?></th>
29
+ </tr>
30
+ </table>
31
+
32
+ <script type="text/javascript">
33
+ ;(function($){
34
+
35
+ $(document).ready(function () {
36
+
37
+ var $log_table = $('.llar-table-app-lockouts'),
38
+ current_page = 0,
39
+ page_offsets = [''];
40
+
41
+ load_lockouts_data();
42
+
43
+ $('.llar-app-lockouts-pagination').on('click', '.llar-prev-page:not(.disabled)', function(e){
44
+ e.preventDefault();
45
+
46
+ load_lockouts_data(page_offsets[--current_page]);
47
+
48
+ toggle_next_btn(true);
49
+ });
50
+
51
+ $('.llar-app-lockouts-pagination').on('click', '.llar-next-page:not(.disabled)', function(e){
52
+ e.preventDefault();
53
+
54
+ load_lockouts_data(page_offsets[++current_page]);
55
+ });
56
+
57
+ function toggle_prev_btn(enable) {
58
+ if(enable) {
59
+
60
+ $('.llar-app-lockouts-pagination .llar-prev-page').removeClass('disabled');
61
+ } else {
62
+
63
+ $('.llar-app-lockouts-pagination .llar-prev-page').addClass('disabled');
64
+ }
65
+ }
66
+ function toggle_next_btn(enable) {
67
+ if(enable) {
68
+
69
+ $('.llar-app-lockouts-pagination .llar-next-page').removeClass('disabled');
70
+ } else {
71
+
72
+ $('.llar-app-lockouts-pagination .llar-next-page').addClass('disabled');
73
+ }
74
+ }
75
+
76
+ function load_lockouts_data(offset) {
77
+
78
+ llar.progressbar.start();
79
+
80
+ $.post(ajaxurl, {
81
+ action: 'app_load_lockouts',
82
+ offset: offset,
83
+ sec: '<?php echo wp_create_nonce( "llar-action" ); ?>'
84
+ }, function(response){
85
+
86
+ llar.progressbar.stop();
87
+
88
+ if(response.success) {
89
+
90
+ $log_table.html(response.data.html);
91
+
92
+ if(current_page > 0) {
93
+ toggle_prev_btn(true);
94
+ } else {
95
+ toggle_prev_btn(false);
96
+
97
+ }
98
+
99
+ if(response.data.offset) {
100
+ page_offsets.push(response.data.offset);
101
+ toggle_next_btn(true);
102
+ } else {
103
+ toggle_next_btn(false);
104
+ }
105
+
106
+ } else {
107
+
108
+ if(response.data.error_notice) {
109
+ $('.limit-login-app-dashboard').find('.llar-app-notice').remove();
110
+ $('.limit-login-app-dashboard').prepend(response.data.error_notice);
111
+ }
112
+
113
+ }
114
+
115
+ });
116
+
117
+ }
118
+ });
119
+
120
+ })(jQuery);
121
+ </script>
122
+
123
+ <h3><?php _e( 'Event Log', 'limit-login-attempts-reloaded' ); ?></h3>
124
+
125
+ <div class="llar-app-log-pagination">
126
+ <a class="llar-prev-page button disabled" href="#">
127
+ <span aria-hidden="true">‹</span>
128
+ </a>
129
+ <a class="llar-next-page button disabled" href="#">
130
+ <span aria-hidden="true">›</span>
131
+ </a>
132
+ </div>
133
+
134
+ <div class="llar-table-scroll-wrap">
135
+ <table class="form-table llar-table-app-log">
136
+ <tr>
137
+ <th scope="col"><?php _e( "Time", 'limit-login-attempts-reloaded' ); ?></th>
138
+ <th scope="col"><?php _e( "IP", 'limit-login-attempts-reloaded' ); ?></th>
139
+ <th scope="col"><?php _e( "Gateway", 'limit-login-attempts-reloaded' ); ?></th>
140
+ <th scope="col"><?php _e( "Login", 'limit-login-attempts-reloaded' ); ?></th>
141
+ <th scope="col"><?php _e( "Rule", 'limit-login-attempts-reloaded' ); ?></th>
142
+ <th scope="col"><?php _e( "Reason", 'limit-login-attempts-reloaded' ); ?></th>
143
+ <th scope="col"><?php _e( "Pattern", 'limit-login-attempts-reloaded' ); ?></th>
144
+ <th scope="col"><?php _e( "Attempts Left", 'limit-login-attempts-reloaded' ); ?></th>
145
+ <th scope="col"><?php _e( "Lockout Duration", 'limit-login-attempts-reloaded' ); ?></th>
146
+ <th scope="col"><?php _e( "Actions", 'limit-login-attempts-reloaded' ); ?></th>
147
+ </tr>
148
+ </table>
149
+ </div>
150
+ <script type="text/javascript">
151
+ ;(function($){
152
+
153
+ $(document).ready(function () {
154
+
155
+ var $log_table = $('.llar-table-app-log'),
156
+ current_page = 0,
157
+ page_offsets = [''];
158
+
159
+ load_log_data();
160
+
161
+ $('.llar-app-log-pagination').on('click', '.llar-prev-page:not(.disabled)', function(e){
162
+ e.preventDefault();
163
+
164
+ load_log_data(page_offsets[--current_page]);
165
+
166
+ toggle_next_btn(true);
167
+ });
168
+
169
+ $('.llar-app-log-pagination').on('click', '.llar-next-page:not(.disabled)', function(e){
170
+ e.preventDefault();
171
+
172
+ load_log_data(page_offsets[++current_page]);
173
+ });
174
+
175
+ $log_table.on('click', '.js-app-log-action', function (e) {
176
+ e.preventDefault();
177
+
178
+ var $this = $(this),
179
+ method = $this.data('method'),
180
+ params = $this.data('params');
181
+
182
+ if(!confirm('Are you sure?')) return;
183
+
184
+ llar.progressbar.start();
185
+
186
+ $.post(ajaxurl, {
187
+ action: 'app_log_action',
188
+ method: method,
189
+ params: params,
190
+ sec: '<?php echo esc_js( wp_create_nonce( "llar-action" ) ); ?>'
191
+ }, function(response){
192
+
193
+ llar.progressbar.stop();
194
+
195
+ console.log(response);
196
+ if(response.success) {
197
+
198
+
199
+ }
200
+
201
+ });
202
+ });
203
+
204
+ function toggle_prev_btn(enable) {
205
+ if(enable) {
206
+
207
+ $('.llar-app-log-pagination .llar-prev-page').removeClass('disabled');
208
+ } else {
209
+
210
+ $('.llar-app-log-pagination .llar-prev-page').addClass('disabled');
211
+ }
212
+ }
213
+ function toggle_next_btn(enable) {
214
+ if(enable) {
215
+
216
+ $('.llar-app-log-pagination .llar-next-page').removeClass('disabled');
217
+ } else {
218
+
219
+ $('.llar-app-log-pagination .llar-next-page').addClass('disabled');
220
+ }
221
+ }
222
+
223
+ function load_log_data(offset) {
224
+
225
+ llar.progressbar.start();
226
+
227
+ $.post(ajaxurl, {
228
+ action: 'app_load_log',
229
+ offset: offset,
230
+ sec: '<?php echo wp_create_nonce( "llar-action" ); ?>'
231
+ }, function(response){
232
+
233
+ llar.progressbar.stop();
234
+
235
+ if(response.success) {
236
+
237
+ $log_table.html(response.data.html);
238
+
239
+ if(current_page > 0) {
240
+ toggle_prev_btn(true);
241
+ } else {
242
+ toggle_prev_btn(false);
243
+
244
+ }
245
+
246
+ if(response.data.offset) {
247
+ page_offsets.push(response.data.offset);
248
+ toggle_next_btn(true);
249
+ } else {
250
+ toggle_next_btn(false);
251
+ }
252
+
253
+ }
254
+
255
+ });
256
+
257
+ }
258
+ });
259
+
260
+ })(jQuery);
261
+ </script>
262
+
263
+ <div class="llar-app-acl-rules">
264
+ <div class="app-rules-col">
265
+ <h3><?php _e( 'Login Access Rules', 'limit-login-attempts-reloaded' ); ?></h3>
266
+ <table class="form-table llar-app-login-access-rules-table">
267
+ <tr>
268
+ <th scope="col"><?php _e( 'Pattern', 'limit-login-attempts-reloaded' ); ?></th>
269
+ <th scope="col"><?php _e( 'Rule', 'limit-login-attempts-reloaded' ); ?></th>
270
+ <th class="llar-app-acl-action-col" scope="col"><?php _e( 'Action', 'limit-login-attempts-reloaded' ); ?></th>
271
+ </tr>
272
+ </table>
273
+ </div>
274
+ <div class="app-rules-col">
275
+ <h3><?php _e( 'IP Access Rules', 'limit-login-attempts-reloaded' ); ?></h3>
276
+ <table class="form-table llar-app-ip-access-rules-table">
277
+ <tr>
278
+ <th scope="col"><?php _e( 'Pattern', 'limit-login-attempts-reloaded' ); ?></th>
279
+ <th scope="col"><?php _e( 'Rule', 'limit-login-attempts-reloaded' ); ?></th>
280
+ <th class="llar-app-acl-action-col" scope="col"><?php _e( 'Action', 'limit-login-attempts-reloaded' ); ?></th>
281
+ </tr>
282
+ </table>
283
+ </div>
284
+
285
+ <script type="text/javascript">
286
+ ;(function($){
287
+
288
+ $(document).ready(function () {
289
+
290
+ var $app_acl_rules = $('.llar-app-acl-rules');
291
+
292
+ load_rules_data('login');
293
+ load_rules_data('ip');
294
+
295
+ $app_acl_rules
296
+ .on('click', '.llar-app-acl-remove', function(e){
297
+ e.preventDefault();
298
+
299
+ if(!confirm('Are you sure?')) {
300
+ return false;
301
+ }
302
+
303
+ var $this = $(this),
304
+ pattern = $this.data('pattern');
305
+
306
+ if(!pattern) {
307
+
308
+ console.log('Wrong pattern');
309
+ return false;
310
+ }
311
+
312
+ llar.progressbar.start();
313
+
314
+ $.post(ajaxurl, {
315
+ action: 'app_acl_remove_rule',
316
+ pattern: pattern,
317
+ type: $this.data('type'),
318
+ sec: '<?php echo esc_js( wp_create_nonce( "llar-action" ) ); ?>'
319
+ }, function(response){
320
+
321
+ llar.progressbar.stop();
322
+
323
+ if(response.success) {
324
+
325
+ $this.closest('tr').fadeOut(300, function(){
326
+ $this.closest('tr').remove();
327
+ })
328
+
329
+ }
330
+
331
+ });
332
+
333
+ })
334
+ .on('click', '.llar-app-acl-add-rule', function(e){
335
+ e.preventDefault();
336
+
337
+ var $this = $(this),
338
+ pattern = $this.closest('tr').find('.llar-app-acl-pattern').val().trim(),
339
+ rule = $this.closest('tr').find('.llar-app-acl-rule').val(),
340
+ type = $this.data('type');
341
+
342
+ if(!pattern) {
343
+
344
+ alert('Pattern can\'t be empty!');
345
+ return false;
346
+ }
347
+
348
+ var row_exist = {};
349
+ $this.closest('table').find('.rule-pattern').each(function(i, el){
350
+ var res = el.innerText.localeCompare(pattern);
351
+ if(res === 0) {
352
+ row_exist = $(el).closest('tr');
353
+ }
354
+ });
355
+
356
+ if(row_exist.length) {
357
+
358
+ $this.closest('tr').find('.llar-app-acl-pattern').val('');
359
+ row_exist.remove();
360
+ }
361
+
362
+ llar.progressbar.start();
363
+
364
+ $.post(ajaxurl, {
365
+ action: 'app_acl_add_rule',
366
+ pattern: pattern,
367
+ rule: rule,
368
+ type: type,
369
+ sec: '<?php echo esc_js( wp_create_nonce( "llar-action" ) ); ?>'
370
+ }, function(response){
371
+
372
+ llar.progressbar.stop();
373
+
374
+ if(response.success) {
375
+
376
+ $this.closest('table').find('.empty-row').remove();
377
+
378
+ $this.closest('tr').after('<tr class="llar-app-rule-'+rule+'">' +
379
+ '<td class="rule-pattern">'+pattern+'</td>' +
380
+ '<td>'+rule+'</td>' +
381
+ '<td class="llar-app-acl-action-col" scope="col"><button class="button llar-app-acl-remove" data-type="'+type+'" data-pattern="'+pattern+'"><span class="dashicons dashicons-no"></span></button></td>' +
382
+ '</tr>');
383
+
384
+ }
385
+
386
+ });
387
+
388
+ });
389
+
390
+ });
391
+
392
+ function load_rules_data(type) {
393
+
394
+ llar.progressbar.start();
395
+
396
+ $.post(ajaxurl, {
397
+ action: 'app_load_acl_rules',
398
+ type: type,
399
+ sec: '<?php echo wp_create_nonce( "llar-action" ); ?>'
400
+ }, function(response){
401
+
402
+ llar.progressbar.stop();
403
+
404
+ if(response.success) {
405
+
406
+ $('.llar-app-'+type+'-access-rules-table').html(response.data.html);
407
+ }
408
+ });
409
+ }
410
+
411
+ })(jQuery);
412
+ </script>
413
+ </div>
414
+ </div>
views/{tab-dashboard.php → tab-logs-local.php} RENAMED
@@ -24,7 +24,7 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
24
  ?>
25
 
26
  <h3><?php echo __( 'Statistics', 'limit-login-attempts-reloaded' ); ?></h3>
27
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
28
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
29
  <table class="form-table">
30
  <tr>
@@ -54,7 +54,7 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
54
  <?php } ?>
55
  </table>
56
  </form>
57
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
58
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
59
 
60
  <table class="form-table">
@@ -100,7 +100,7 @@ $lockouts = (array)$this->get_option('lockouts');
100
 
101
  if( is_array( $log ) && ! empty( $log ) ) { ?>
102
  <h3><?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?></h3>
103
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
104
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
105
  <input type="hidden" value="true" name="clear_log"/>
106
  <p class="submit">
24
  ?>
25
 
26
  <h3><?php echo __( 'Statistics', 'limit-login-attempts-reloaded' ); ?></h3>
27
+ <form action="<?php echo $this->get_options_page_uri('logs-local'); ?>" method="post">
28
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
29
  <table class="form-table">
30
  <tr>
54
  <?php } ?>
55
  </table>
56
  </form>
57
+ <form action="<?php echo $this->get_options_page_uri('logs-local'); ?>" method="post">
58
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
59
 
60
  <table class="form-table">
100
 
101
  if( is_array( $log ) && ! empty( $log ) ) { ?>
102
  <h3><?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?></h3>
103
+ <form action="<?php echo $this->get_options_page_uri('logs-local'); ?>" method="post">
104
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
105
  <input type="hidden" value="true" name="clear_log"/>
106
  <p class="submit">
views/tab-settings.php CHANGED
@@ -17,11 +17,21 @@ $admin_email_placeholder = (!is_multisite()) ? get_option( 'admin_email' ) : get
17
 
18
  $trusted_ip_origins = $this->get_option( 'trusted_ip_origins' );
19
  $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_origins ) ) ? implode( ", ", $trusted_ip_origins ) : 'REMOTE_ADDR';
 
 
 
 
 
20
  ?>
 
 
 
 
 
21
 
22
  <h3><?php echo __( 'General Settings', 'limit-login-attempts-reloaded' ); ?></h3>
23
- <p><?php echo __( 'These settings are independent of the workers (see below).', 'limit-login-attempts-reloaded' ); ?></p>
24
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
25
 
26
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
27
 
@@ -49,6 +59,7 @@ $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_o
49
  <?php endif ?>
50
 
51
  <table class="form-table">
 
52
  <tr>
53
  <th scope="row"
54
  valign="top"><?php echo __( 'GDPR compliance', 'limit-login-attempts-reloaded' ); ?></th>
@@ -57,6 +68,8 @@ $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_o
57
  <?php echo __( 'this makes the plugin <a href="https://gdpr-info.eu/" target="_blank" >GDPR</a> compliant', 'limit-login-attempts-reloaded' ); ?> <br/>
58
  </td>
59
  </tr>
 
 
60
  <tr>
61
  <th scope="row"
62
  valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>
@@ -76,13 +89,28 @@ $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_o
76
  name="email_after"/> <?php echo __( 'lockouts', 'limit-login-attempts-reloaded' ); ?>
77
  </td>
78
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  </table>
80
 
81
- <h3><?php echo __( 'Worker Settings', 'limit-login-attempts-reloaded' ); ?></h3>
82
- <p><?php echo __( 'The workers absorb the main load caused by brute-force attacks, analyse login attempts and block unwanted visitors. They might provide other service functions as well.', 'limit-login-attempts-reloaded' ); ?></p>
83
 
84
- <div id="llar-workers-accordion" class="llar-accordion">
85
- <h3>Local Worker</h3>
86
  <div>
87
  <table class="form-table">
88
  <tr>
@@ -121,20 +149,158 @@ $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_o
121
  </table>
122
  </div>
123
 
124
- <h3>Custom Worker</h3>
125
- <div>
126
- <p>
127
- In the future versions of the plugin you will be able to create your own worker. This will allow you to share White/Black lists and lockout functionality across all your websites. Stay tuned.
128
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  </div>
130
  </div>
131
 
132
- <script>
133
  (function($){
134
 
135
  $(document).ready(function(){
136
 
137
- $( "#llar-workers-accordion" ).accordion();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  });
139
 
140
  })(jQuery);
17
 
18
  $trusted_ip_origins = $this->get_option( 'trusted_ip_origins' );
19
  $trusted_ip_origins = ( is_array( $trusted_ip_origins ) && !empty( $trusted_ip_origins ) ) ? implode( ", ", $trusted_ip_origins ) : 'REMOTE_ADDR';
20
+
21
+ $active_app = $this->get_option( 'active_app' );
22
+ $app_setup_link = $this->get_option( 'app_setup_link' );
23
+ $active_app_config = $this->get_custom_app_config();
24
+
25
  ?>
26
+ <?php if( isset( $_GET['activated'] ) ) : ?>
27
+ <div class="llar-app-notice success">
28
+ <p><?php echo $active_app_config['messages']['setup_success']; ?></p>
29
+ </div>
30
+ <?php endif; ?>
31
 
32
  <h3><?php echo __( 'General Settings', 'limit-login-attempts-reloaded' ); ?></h3>
33
+ <p><?php echo __( 'These settings are independent of the apps (see below).', 'limit-login-attempts-reloaded' ); ?></p>
34
+ <form action="<?php echo $this->get_options_page_uri('settings'); ?>" method="post">
35
 
36
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
37
 
59
  <?php endif ?>
60
 
61
  <table class="form-table">
62
+ <?php if( $active_app === 'local' ) : ?>
63
  <tr>
64
  <th scope="row"
65
  valign="top"><?php echo __( 'GDPR compliance', 'limit-login-attempts-reloaded' ); ?></th>
68
  <?php echo __( 'this makes the plugin <a href="https://gdpr-info.eu/" target="_blank" >GDPR</a> compliant', 'limit-login-attempts-reloaded' ); ?> <br/>
69
  </td>
70
  </tr>
71
+ <?php endif; ?>
72
+
73
  <tr>
74
  <th scope="row"
75
  valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>
89
  name="email_after"/> <?php echo __( 'lockouts', 'limit-login-attempts-reloaded' ); ?>
90
  </td>
91
  </tr>
92
+ <tr>
93
+ <th scope="row"
94
+ valign="top"><?php echo __( 'Active App', 'limit-login-attempts-reloaded' ); ?></th>
95
+ <td>
96
+ <select name="active_app" id="">
97
+ <option value="local" <?php selected( $active_app, 'local' ); ?>><?php echo __( 'Local', 'limit-login-attempts-reloaded' ); ?></option>
98
+ <?php if( $active_app_config ) : ?>
99
+ <option value="custom" <?php selected( $active_app, 'custom' ); ?>><?php echo esc_html( $active_app_config['name'] ); ?></option>
100
+ <?php endif; ?>
101
+ </select>
102
+ <?php if( $active_app === 'local' ) : ?>
103
+ <span class="llar-protect-notice"><?php _e( 'Get advanced protection by <a href="#" class="llar-upgrade-to-cloud">upgrading to our Cloud App.</a>', 'limit-login-attempts-reloaded' ); ?></span>
104
+ <?php endif; ?>
105
+ </td>
106
+ </tr>
107
  </table>
108
 
109
+ <h3><?php echo __( 'App Settings', 'limit-login-attempts-reloaded' ); ?></h3>
110
+ <p><?php echo __( 'The apps absorb the main load caused by brute-force attacks, analyse login attempts and block unwanted visitors. They might provide other service functions as well.', 'limit-login-attempts-reloaded' ); ?></p>
111
 
112
+ <div id="llar-apps-accordion" class="llar-accordion">
113
+ <h3><?php echo __( 'Local App', 'limit-login-attempts-reloaded' ); ?></h3>
114
  <div>
115
  <table class="form-table">
116
  <tr>
149
  </table>
150
  </div>
151
 
152
+ <h3><?php echo ($active_app_config) ? $active_app_config['name'] : __('Custom App', 'limit-login-attempts-reloaded' ); ?></h3>
153
+ <div class="custom-app-tab">
154
+
155
+ <?php if( $active_app === 'custom' ) : ?>
156
+ <a class="dashicons dashicons-admin-generic llar-show-app-fields" href="#"></a>
157
+ <?php endif; ?>
158
+
159
+ <table class="form-table">
160
+
161
+ <tr class="llar-app-field <?php echo ( $active_app === 'local' || !$active_app_config ) ? 'active' : ''; ?>">
162
+ <th scope="row"
163
+ valign="top"><?php echo __( 'Setup Link', 'limit-login-attempts-reloaded' ); ?></th>
164
+ <td>
165
+ <input type="text" class="regular-text" id="limit-login-app-setup-link" value="<?php echo ( !empty( $app_setup_link ) ) ? esc_attr( $app_setup_link ) : ''; ?>">
166
+ <button class="button" id="limit-login-app-setup"><?php echo __( 'Submit', 'limit-login-attempts-reloaded' ); ?></button>
167
+ <span class="spinner llar-app-ajax-spinner"></span><br>
168
+ <span class="llar-app-ajax-msg"></span>
169
+
170
+ <?php if( $active_app === 'local' ) : ?>
171
+ <p class="description"><?php echo sprintf( __( 'Use the <a href="%s" target="_blank">premium app</a> that we offer or follow the instructions on <a href="%s" target="_blank">how to</a> create your own one.', 'limit-login-attempts-reloaded' ), 'https://app.limitloginattempts.com/network/create', 'https://www.limitloginattempts.com/app/' ); ?></p>
172
+ <div class="llar-why-use-premium-text">
173
+ <div class="title"><?php _e( 'Why Use Our Premium Cloud App?', 'limit-login-attempts-reloaded' ); ?></div>
174
+ <ul>
175
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Absorb site load caused by attacks', 'limit-login-attempts-reloaded' ); ?></li>
176
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Use intelligent IP blocking/unblocking technology', 'limit-login-attempts-reloaded' ); ?></li>
177
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Sync the allow/deny/pass lists between multiple domains', 'limit-login-attempts-reloaded' ); ?></li>
178
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Get premium support', 'limit-login-attempts-reloaded' ); ?></li>
179
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Run auto backups of access control lists, lockouts and logs', 'limit-login-attempts-reloaded' ); ?></li>
180
+ <li><span class="dashicons dashicons-yes"></span><?php _e( 'Only pay $4.99/m per domain - cancel any time', 'limit-login-attempts-reloaded' ); ?></li>
181
+ </ul>
182
+ </div>
183
+ <?php endif; ?>
184
+ </td>
185
+ </tr>
186
+ <?php if( $active_app === 'custom' && $active_app_config ) : ?>
187
+ <tr class="llar-app-field">
188
+ <th scope="row"
189
+ valign="top"><?php echo __( 'Configuration', 'limit-login-attempts-reloaded' ); ?></th>
190
+ <td>
191
+ <div class="field-col">
192
+ <textarea id="limit-login-app-config" readonly="readonly" name="limit-login-app-config" cols="60" rows="5"><?php echo esc_textarea( json_encode( $active_app_config ) ); ?></textarea><br>
193
+ </div>
194
+ </td>
195
+ </tr>
196
+ <?php endif; ?>
197
+
198
+ <?php if( $active_app === 'custom' && !empty( $active_app_config['settings'] ) ) : ?>
199
+ <?php foreach( $active_app_config['settings'] as $setting_name => $setting_params ) : ?>
200
+ <tr>
201
+ <th scope="row" valign="top"><?php echo $setting_params['label']; ?></th>
202
+ <td>
203
+ <div class="field-col">
204
+ <?php if( !empty( $setting_params['options'] ) ) : ?>
205
+ <select name="llar_app_settings[<?php echo $setting_name; ?>]">
206
+ <?php foreach ( $setting_params['options'] as $option ) : ?>
207
+ <option value="<?php echo esc_attr( $option ); ?>" <?php selected( $option, $setting_params['value'] ); ?>><?php echo esc_html( $option ); ?></option>
208
+ <?php endforeach; ?>
209
+ </select>
210
+ <?php else : ?>
211
+ <input type="text" class="regular-text" name="llar_app_settings[<?php echo esc_attr( $setting_name ); ?>]" value="<?php echo esc_attr( $setting_params['value'] ); ?>">
212
+ <?php endif; ?>
213
+
214
+ <p class="description"><?php echo esc_html( $setting_params['description'] ); ?></p>
215
+ </div>
216
+ </td>
217
+ </tr>
218
+ <?php endforeach; ?>
219
+ <?php endif; ?>
220
+ </table>
221
  </div>
222
  </div>
223
 
224
+ <script type="text/javascript">
225
  (function($){
226
 
227
  $(document).ready(function(){
228
 
229
+ $( "#llar-apps-accordion" ).accordion({
230
+ heightStyle: "content",
231
+ active: <?php echo ( $active_app === 'local' ) ? 0 : 1; ?>
232
+ });
233
+
234
+ var $app_ajax_spinner = $('.llar-app-ajax-spinner'),
235
+ $app_ajax_msg = $('.llar-app-ajax-msg'),
236
+ $app_config_field = $('#limit-login-app-config');
237
+
238
+ if($app_config_field.val()) {
239
+ var pretty = JSON.stringify(JSON.parse($app_config_field.val()), undefined, 2);
240
+ $app_config_field.val(pretty);
241
+ }
242
+
243
+ $('#limit-login-app-setup').on('click', function(e) {
244
+ e.preventDefault();
245
+
246
+ $app_ajax_msg.text('').removeClass('success error');
247
+ $app_ajax_spinner.css('visibility', 'visible');
248
+
249
+ var setup_link = $('#limit-login-app-setup-link').val();
250
+
251
+ $.post(ajaxurl, {
252
+ action: 'app_setup',
253
+ link: setup_link,
254
+ sec: '<?php echo esc_js( wp_create_nonce( "llar-action" ) ); ?>'
255
+ }, function(response){
256
+
257
+ if(!response.success) {
258
+
259
+ $app_ajax_msg.addClass('error');
260
+ } else {
261
+
262
+ $app_ajax_msg.addClass('success');
263
+
264
+ setTimeout(function(){
265
+
266
+ window.location = window.location + '&activated';
267
+
268
+ }, 1000);
269
+ }
270
+
271
+ if(!response.success && response.data.msg) {
272
+
273
+ $app_ajax_msg.text(response.data.msg);
274
+ }
275
+
276
+ $app_ajax_spinner.css('visibility', 'hidden');
277
+
278
+ setTimeout(function(){
279
+
280
+ $app_ajax_msg.text('').removeClass('success error');
281
+
282
+ }, 5000);
283
+ });
284
+
285
+ });
286
+
287
+ $('.llar-show-app-fields').on('click', function(e){
288
+ e.preventDefault();
289
+
290
+ $('.llar-app-field').toggleClass('active');
291
+
292
+ });
293
+
294
+ $('.llar-upgrade-to-cloud').on('click', function(e){
295
+ e.preventDefault();
296
+
297
+ $("#ui-id-3").click();
298
+
299
+ $([document.documentElement, document.body]).animate({
300
+ scrollTop: $("#llar-apps-accordion").offset().top
301
+ }, 500);
302
+ });
303
+
304
  });
305
 
306
  })(jQuery);