PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/controller/class-firewall-logs.php +191 -372 5.5.0 → 6.3.0 View file →
@@ -8,9 +8,9 @@
8 8 namespace WP_Defender\Controller;
9 9
10 10 use DateTime;
11 11 use Exception;
12 -use Countable;
12 +use Generator;
13 13 use Valitron\Validator;
14 14 use Calotes\Helper\HTTP;
15 15 use WP_Defender\Controller;
16 16 use Calotes\Component\Request;
@@ -15,14 +15,16 @@
15 15 use WP_Defender\Controller;
16 16 use Calotes\Component\Request;
17 17 use Calotes\Component\Response;
18 18 use WP_Defender\Traits\Formats;
19 +use WP_Defender\Traits\Country;
19 20 use WP_Defender\Behavior\WPMUDEV;
20 21 use WP_Defender\Model\Lockout_Log;
21 22 use WP_Defender\Component\User_Agent;
22 -use WP_Defender\Component\IP\Global_IP;
23 23 use WP_Defender\Component\Table_Lockout;
24 +use WP_Defender\Component\Network_Cron_Manager;
24 25 use WP_Defender\Integrations\Antibot_Global_Firewall_Client;
26 +use WP_Defender\Component\Export\Csv;
25 27 use WP_Defender\Model\Setting\Blacklist_Lockout;
26 28 use WP_Defender\Model\Setting\User_Agent_Lockout;
27 29 use WP_Defender\Component\Firewall_Logs as Firewall_Logs_Component;
28 30
@@ -32,8 +34,9 @@
32 34 */
33 35 class Firewall_Logs extends Controller {
34 36
35 37 use Formats;
38 + use Country;
36 39
37 40 /**
38 41 * The slug identifier for this controller.
39 42 *
@@ -63,8 +66,13 @@
63 66 */
64 67 const AKISMET_BLOCKED_IPS = 'defender_akismet_blocked_ips';
65 68
66 69 /**
70 + * Default number of items per page.
71 + */
72 + public const DEFAULT_PER_PAGE = 10;
73 +
74 + /**
67 75 * Constructor for the class.
68 76 *
69 77 * @param Antibot_Global_Firewall_Client $antibot_client The client for interacting with the Block list API service.
70 78 */
@@ -77,13 +85,18 @@
77 85 $this->antibot_client = $antibot_client;
78 86
79 87 /**
80 88 * Send Firewall logs to AntiBot Global Firewall API.
89 + *
90 + * @var Network_Cron_Manager $network_cron_manager
81 91 */
82 - if ( ! wp_next_scheduled( 'wpdef_firewall_send_compact_logs_to_api' ) ) {
83 - wp_schedule_event( time() + 15, 'twicedaily', 'wpdef_firewall_send_compact_logs_to_api' );
84 - }
85 - add_action( 'wpdef_firewall_send_compact_logs_to_api', array( $this, 'send_compact_logs_to_api' ) );
92 + $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
93 + $network_cron_manager->register_callback(
94 + 'wpdef_firewall_send_compact_logs_to_api',
95 + array( $this, 'send_compact_logs_to_api' ),
96 + 12 * HOUR_IN_SECONDS,
97 + time() + 15
98 + );
86 99 if ( class_exists( 'Akismet' ) ) {
87 100 add_filter( 'http_response', array( $this, 'akismet_http_response' ), 10, 3 );
88 101 }
89 102 }
@@ -95,9 +108,9 @@
95 108 *
96 109 * @return Response The response object with the result of the bulk action.
97 110 * @defender_route
98 111 */
99 - public function bulk( Request $request ) {
112 + public function bulk( Request $request ): Response {
100 113 $data = $request->get_data(
101 114 array(
102 115 'action' => array(
103 116 'type' => 'string',
@@ -102,153 +115,70 @@
102 115 'action' => array(
103 116 'type' => 'string',
104 117 'sanitize' => 'sanitize_text_field',
105 118 ),
106 - 'ids' => array(
119 + 'ips' => array(
107 120 'type' => 'array',
108 121 ),
109 122 )
110 123 );
111 - $ids = $data['ids'];
112 - $ips = array();
113 - $logs = array();
114 - if ( is_array( $ids ) || $ids instanceof Countable ? count( $ids ) : 0 ) {
115 - foreach ( $ids as $id ) {
116 - $model = Lockout_Log::find_by_id( $id );
117 - if ( is_object( $model ) ) {
118 - $bl = wd_di()->get( Blacklist_Lockout::class );
119 - switch ( $data['action'] ) {
120 - case 'ban':
121 - $bl->remove_from_list( $model->ip, 'allowlist' );
122 - $bl->add_to_list( $model->ip, 'blocklist' );
123 - $ips[ $model->ip ] = $model->ip;
124 - $logs[] = $model;
125 - break;
126 - case 'allowlist':
127 - $bl->remove_from_list( $model->ip, 'blocklist' );
128 - $bl->add_to_list( $model->ip, 'allowlist' );
129 - $ips[ $model->ip ] = $model->ip;
130 - $logs[] = $model;
131 - break;
132 - case 'delete':
133 - $ips[ $model->ip ] = $model->ip;
134 - $model->delete();
135 - break;
136 - default:
137 - break;
138 - }
139 - }
140 - }
141 - }
142 124
143 - if ( count( $logs ) > 0 ) {
144 - $logs = Lockout_Log::format_logs( $logs );
145 - }
125 + $ips = implode( PHP_EOL, array_filter( array_map( 'sanitize_text_field', (array) $data['ips'] ), 'boolval' ) );
126 + $bl = wd_di()->get( Blacklist_Lockout::class );
146 127
147 128 switch ( $data['action'] ) {
148 - case 'allowlist':
149 - $messages = sprintf(
150 - /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
151 - esc_html__(
152 - 'IP %1$s has been added to your allowlist. You can control your allowlist in %2$s.',
153 - 'defender-security'
154 - ),
155 - implode( ', ', $ips ),
156 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist#tab-ip-allowlist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
157 - );
158 - break;
159 129 case 'ban':
160 - $messages = sprintf(
161 - /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
162 - esc_html__(
163 - 'IP %1$s has been added to your blocklist. You can control your blocklist in %2$s.',
164 - 'defender-security'
165 - ),
166 - implode( ', ', $ips ),
167 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
168 - );
130 + $bl->ip_blacklist = $ips;
169 131 break;
170 - case 'delete':
171 - $messages = sprintf(
172 - /* translators: %s: IP Address(es) */
173 - esc_html__( 'IP %s has been deleted', 'defender-security' ),
174 - implode( ', ', $ips )
175 - );
132 + case 'allowlist':
133 + $bl->ip_whitelist = $ips;
176 134 break;
177 135 default:
178 - $messages = '';
179 136 break;
180 137 }
181 138
139 + $bl->save();
140 +
182 141 return new Response(
183 142 true,
184 143 array(
185 - 'message' => $messages,
186 - 'logs' => $logs,
144 + 'banning' => $bl->export(),
187 145 )
188 146 );
189 147 }
190 148
191 149 /**
192 - * Export logs to CSV
150 + * Export all logs matching the current filter to CSV.
193 151 *
194 152 * @return void
195 153 * @defender_route
196 - * @throws Exception On failure.
197 154 */
198 155 public function export_as_csv(): void {
199 - $date_from = HTTP::get( 'date_from', strtotime( '-7 days midnight' ) );
200 - $date_to = HTTP::get( 'date_to', strtotime( 'tomorrow' ) );
201 - // Convert date using timezone.
202 - $timezone = wp_timezone();
203 - $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
204 - $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
205 - $filters = array(
156 + $timezone = wp_timezone();
157 + $date_from_str = sanitize_text_field( (string) HTTP::get( 'date_from', '' ) );
158 + $date_to_str = sanitize_text_field( (string) HTTP::get( 'date_to', '' ) );
159 + $date_from = $date_from_str
160 + ? $this->date_string_to_timestamp( $date_from_str )
161 + : ( new DateTime( '-30 days', $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
162 + $date_to = $date_to_str
163 + ? $this->date_string_to_timestamp( $date_to_str, true )
164 + : ( new DateTime( 'now', $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
165 + $ip = sanitize_text_field( (string) HTTP::get( 'ip', '' ) );
166 + $user_agent = sanitize_text_field( (string) HTTP::get( 'user_agent', '' ) );
167 + $type = sanitize_text_field( (string) HTTP::get( 'type', '' ) );
168 + $ban_status = sanitize_text_field( (string) HTTP::get( 'ban_status', '' ) );
169 + $sort = sanitize_text_field( (string) HTTP::get( 'sort', Table_Lockout::SORT_DESC ) );
170 + $sort_params = wd_di()->get( Table_Lockout::class )->resolve_sort( $sort );
171 +
172 + $filters = array(
206 173 'from' => $date_from,
207 174 'to' => $date_to,
208 - 'type' => HTTP::get( 'term', '' ),
209 - 'ip' => HTTP::get( 'ip', '' ),
210 - 'ban_status' => HTTP::get( 'ban_status', '' ),
175 + 'ip' => $ip,
176 + 'user_agent' => $user_agent,
177 + 'type' => 'all' === $type ? '' : $type,
178 + 'ban_status' => 'all' === $ban_status ? '' : $ban_status,
211 179 );
212 180
213 - if ( 'all' === $filters['type'] ) {
214 - $filters['type'] = '';
215 - }
216 -
217 - if ( 'all' === $filters['ban_status'] ) {
218 - $filters['ban_status'] = '';
219 - }
220 - // User can export the number of logs that are set.
221 - $per_page = (int) defender_get_data_from_request( 'per_page', 'g' );
222 - if ( 0 === $per_page ) {
223 - $per_page = 20;
224 - }
225 - if ( - 1 === $per_page ) {
226 - $per_page = false;
227 - }
228 -
229 - $paged = (int) defender_get_data_from_request( 'paged', 'g' );
230 - if ( 0 === $paged ) {
231 - $paged = 1;
232 - }
233 - $logs = Lockout_Log::query_logs( $filters, $paged, 'id', 'desc', $per_page );
234 -
235 - $tl_component = new Table_Lockout();
236 -
237 - $ua_component = wd_di()->get( User_Agent::class );
238 -
239 - $filename = 'wdf-lockout-logs-export-' . wp_date( 'ymdHis' ) . '.csv';
240 -
241 - header( 'Expires: 0' );
242 - header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
243 - header( 'Cache-Control: private', false );
244 - header( 'Content-Type: application/octet-stream' );
245 - header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
246 - header( 'Content-Transfer-Encoding: binary' );
247 -
248 - extension_loaded( 'zlib' ) ? ob_start( 'ob_gzhandler' ) : ob_start();
249 -
250 - $fp = fopen( 'php://output', 'w' );
251 181 $headers = array(
252 182 esc_html__( 'Log', 'defender-security' ),
253 183 esc_html__( 'Date / Time', 'defender-security' ),
254 184 esc_html__( 'Type', 'defender-security' ),
@@ -256,31 +186,54 @@
256 186 esc_html__( 'IP Status', 'defender-security' ),
257 187 esc_html__( 'User Agent Name', 'defender-security' ),
258 188 esc_html__( 'User Agent Status', 'defender-security' ),
259 189 );
260 - fputcsv( $fp, $headers, ',', '"', '\\' );
261 190
262 - $flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE;
263 - foreach ( $logs as $key => $log ) {
264 - $item = array(
265 - $log->log,
266 - $this->format_date_time( $log->date ),
267 - $tl_component->get_type( $log->type ),
268 - $log->ip,
269 - $tl_component->get_ip_status_text( $log->ip ),
270 - $log->user_agent,
271 - $ua_component->get_status_text( $log->type, $log->tried ),
191 + try {
192 + Csv::to_browser( 'lockout-logs', $this->get_export_rows( $filters, $sort_params ), $headers );
193 + } catch ( \RuntimeException $e ) {
194 + $this->log( $e->getMessage(), 'firewall-logs' );
195 + }
196 + }
197 +
198 + /**
199 + * Generates firewall log export rows in database-backed pages.
200 + *
201 + * @param array $filters Log filters.
202 + * @param array $sort_params Sort parameters.
203 + *
204 + * @return Generator
205 + */
206 + private function get_export_rows( array $filters, array $sort_params ): Generator {
207 + $page = 1;
208 + $per_page = 1000;
209 + $tl_component = new Table_Lockout();
210 + $ua_component = wd_di()->get( User_Agent::class );
211 +
212 + do {
213 + $logs = Lockout_Log::query_logs(
214 + $filters,
215 + $page,
216 + $sort_params['order_by'],
217 + $sort_params['order'],
218 + $per_page
272 219 );
273 - fputcsv( $fp, $item, ',', '"', '\\' );
274 220
275 - if ( 0 === $key % $flush_limit ) {
276 - ob_flush();
277 - flush();
221 + foreach ( $logs as $log ) {
222 + yield array(
223 + $log->log,
224 + $this->format_date_time( $log->date ),
225 + $tl_component->get_type( $log->type ),
226 + $log->ip,
227 + $tl_component->get_ip_status_text( $log->ip ),
228 + $log->user_agent,
229 + $ua_component->get_status_text( $log->type, $log->tried ),
230 + );
278 231 }
279 - }
280 - // WP_Filesystem is not suitable here because it abstracts to reading/writing files on disk, not to output streams.
281 - fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
282 - exit();
232 +
233 + $has_more = count( $logs ) === $per_page;
234 + ++$page;
235 + } while ( $has_more );
283 236 }
284 237
285 238 /**
286 239 * Toggles an IP address to or from a specified list.
@@ -292,104 +245,34 @@
292 245 */
293 246 public function toggle_ip_to_list( Request $request ): Response {
294 247 $data = $request->get_data(
295 248 array(
296 - 'ip' => array(
249 + 'list' => array(
297 250 'type' => 'string',
298 - 'sanitize' => 'sanitize_text_field',
251 + 'sanitize' => 'sanitize_textarea_field',
299 252 ),
300 - 'list' => array(
253 + 'type' => array(
301 254 'type' => 'string',
302 255 'sanitize' => 'sanitize_text_field',
303 256 ),
304 - 'ban_status' => array(
305 - 'type' => 'string',
306 - 'sanitize' => 'sanitize_text_field',
307 - ),
308 257 )
309 258 );
310 259
311 - $ip = $data['ip'];
312 260 $collection = $data['list'];
261 + $list_type = $data['type'];
313 262
314 263 $model = wd_di()->get( Blacklist_Lockout::class );
315 - if ( $model->is_ip_in_list( $ip, $collection ) ) {
316 - $model->remove_from_list( $ip, $collection );
317 - /* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */
318 - $message = esc_html__(
319 - 'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
320 - 'defender-security'
321 - );
322 - } else {
323 - $model->add_to_list( $ip, $collection );
324 -
325 - $global_ip_service = wd_di()->get( Global_IP::class );
326 - if ( $global_ip_service->can_blocklist_autosync() ) {
327 - $global_ip_data = array(
328 - 'block_list' => array( $ip ),
329 - );
330 - $global_ip_service->add_to_global_ip_list( $global_ip_data );
331 - }
332 -
333 - /* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */
334 - $message = esc_html__(
335 - 'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
336 - 'defender-security'
337 - );
264 + if ( 'blacklist' === $list_type ) {
265 + $model->ip_blacklist = $collection;
266 + } elseif ( 'whitelist' === $list_type ) {
267 + $model->ip_whitelist = $collection;
338 268 }
339 - $filter_data = $request->get_data(
340 - array(
341 - 'date_from' => array(
342 - 'type' => 'string',
343 - 'sanitize' => 'sanitize_text_field',
344 - ),
345 - 'date_to' => array(
346 - 'type' => 'string',
347 - 'sanitize' => 'sanitize_text_field',
348 - ),
349 - 'ip_filter' => array(
350 - 'type' => 'string',
351 - 'sanitize' => 'sanitize_text_field',
352 - ),
353 - 'type' => array(
354 - 'type' => 'string',
355 - 'sanitize' => 'sanitize_text_field',
356 - ),
357 - 'paged' => array(
358 - 'type' => 'int',
359 - 'sanitize' => 'sanitize_text_field',
360 - ),
361 - 'per_page' => array(
362 - 'type' => 'int',
363 - 'sanitize' => 'sanitize_text_field',
364 - ),
365 - )
366 - );
367 - $logs = Lockout_Log::get_logs_and_format(
368 - array(
369 - 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
370 - 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
371 - 'ip' => $filter_data['ip_filter'],
372 - // If this is all, then we set to null to exclude it from the filter.
373 - 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
374 - ),
375 - $filter_data['paged'],
376 - 'id',
377 - 'desc',
378 - $filter_data['per_page']
379 - );
269 + $model->save();
380 270
381 271 return new Response(
382 272 true,
383 273 array(
384 - 'message' => sprintf(
385 - $message,
386 - $data['ip'] ?? '-',
387 - $data['list'],
388 - $data['list'],
389 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
390 - ),
391 - 'logs' => $logs,
274 + 'banning' => $model->export(),
392 275 )
393 276 );
394 277 }
395 278
@@ -403,115 +286,34 @@
403 286 */
404 287 public function toggle_ua_to_list( Request $request ): Response {
405 288 $data = $request->get_data(
406 289 array(
407 - 'ua' => array(
290 + 'list' => array(
408 291 'type' => 'string',
409 - 'sanitize' => 'sanitize_text_field',
292 + 'sanitize' => 'sanitize_textarea_field',
410 293 ),
411 - 'list' => array(
294 + 'type' => array(
412 295 'type' => 'string',
413 296 'sanitize' => 'sanitize_text_field',
414 297 ),
415 - 'scenario' => array(
416 - 'type' => 'string',
417 - 'sanitize' => 'sanitize_text_field',
418 - ),
419 298 )
420 299 );
421 300
422 - $ua = $data['ua'];
423 301 $collection = $data['list'];
424 - $action = $data['scenario'];
302 + $list_type = $data['type'];
425 303
426 304 $model = wd_di()->get( User_Agent_Lockout::class );
427 -
428 - if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) {
429 - $model->remove_from_list( $ua, $collection );
430 - /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
431 - $message = esc_html__(
432 - 'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
433 - 'defender-security'
434 - );
435 - } elseif ( 'add' === $action ) {
436 -
437 - /**
438 - * Possible scenario on regex blocklist. For e.g. UA term `run` present in allowlist & `r.n` regex in blocklist then remove `run` to block `run` user agent using regex `r.n`.
439 - */
440 - if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) {
441 - $model->remove_from_list( $ua, 'allowlist' );
442 - }
443 -
444 - if ( ! $model->is_ua_in_list( $ua, $collection ) ) {
445 - $model->add_to_list( $ua, $collection );
446 - }
447 - /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
448 - $message = esc_html__(
449 - 'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
450 - 'defender-security'
451 - );
452 - } else {
453 - return new Response(
454 - false,
455 - array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) )
456 - );
305 + if ( 'blacklist' === $list_type ) {
306 + $model->blacklist = $collection;
307 + } elseif ( 'whitelist' === $list_type ) {
308 + $model->whitelist = $collection;
457 309 }
310 + $model->save();
458 311
459 - $filter_data = $request->get_data(
460 - array(
461 - 'date_from' => array(
462 - 'type' => 'string',
463 - 'sanitize' => 'sanitize_text_field',
464 - ),
465 - 'date_to' => array(
466 - 'type' => 'string',
467 - 'sanitize' => 'sanitize_text_field',
468 - ),
469 - 'ip_filter' => array(
470 - 'type' => 'string',
471 - 'sanitize' => 'sanitize_text_field',
472 - ),
473 - 'type' => array(
474 - 'type' => 'string',
475 - 'sanitize' => 'sanitize_text_field',
476 - ),
477 - 'paged' => array(
478 - 'type' => 'int',
479 - 'sanitize' => 'sanitize_text_field',
480 - ),
481 - 'per_page' => array(
482 - 'type' => 'int',
483 - 'sanitize' => 'sanitize_text_field',
484 - ),
485 - )
486 - );
487 - $logs = Lockout_Log::get_logs_and_format(
488 - array(
489 - 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
490 - 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
491 - 'ip' => $filter_data['ip_filter'],
492 - // If this is all, then we set to null to exclude it from the filter.
493 - 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
494 - ),
495 - $filter_data['paged'],
496 - 'id',
497 - 'desc',
498 - $filter_data['per_page']
499 - );
500 -
501 312 return new Response(
502 313 true,
503 314 array(
504 - 'message' => sprintf(
505 - $message,
506 - '<strong>' . $data['ua'] . '</strong>',
507 - $data['list'],
508 - $data['list'],
509 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>'
510 - ),
511 - 'logs' => $logs,
512 - // Include blocklist preset values for the frontend.
513 - 'blocklist_preset_values' => $model->blocklist_preset_values,
315 + 'uaLockout' => $model->export(),
514 316 )
515 317 );
516 318 }
517 319
@@ -538,8 +340,12 @@
538 340 'ip' => array(
539 341 'type' => 'string',
540 342 'sanitize' => 'sanitize_text_field',
541 343 ),
344 + 'user_agent' => array(
345 + 'type' => 'string',
346 + 'sanitize' => 'sanitize_text_field',
347 + ),
542 348 'type' => array(
543 349 'type' => 'string',
544 350 'sanitize' => 'sanitize_text_field',
545 351 ),
@@ -554,57 +360,44 @@
554 360 'ban_status' => array(
555 361 'type' => 'string',
556 362 'sanitize' => 'sanitize_text_field',
557 363 ),
364 + 'per_page' => array(
365 + 'type' => 'int',
366 + 'sanitize' => 'sanitize_text_field',
367 + ),
558 368 )
559 369 );
560 370 // Validate.
561 371 $v = new Validator( $data, array() );
562 372 $v->rule( 'required', array( 'date_from', 'date_to' ) );
563 - $v->rule( 'date', array( 'date_from', 'date_to' ) );
564 373 if ( ! $v->validate() ) {
374 + return new Response( false, array( 'message' => esc_html__( 'Start and end date are required.', 'defender-security' ) ) );
375 + }
376 +
377 + $date_from = $this->date_string_to_timestamp( $data['date_from'] );
378 + $date_to = $this->date_string_to_timestamp( $data['date_to'], true );
379 + if ( $date_from <= 0 || $date_to <= 0 ) {
565 380 return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) );
566 381 }
567 - $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
568 - switch ( $sort ) {
569 - case 'ip':
570 - $order = 'desc';
571 - $order_by = 'ip';
572 - break;
573 - case 'oldest':
574 - $order = 'asc';
575 - $order_by = 'id';
576 - break;
577 - case 'user_agent':
578 - $order = 'asc';
579 - $order_by = 'user_agent';
580 - break;
581 - default:
582 - $order = 'desc';
583 - $order_by = 'id';
584 - break;
585 - }
586 - // Convert date using timezone.
587 - $timezone = wp_timezone();
588 - $date_from = ( new DateTime( $data['date_from'], $timezone ) )
589 - ->setTime( 0, 0, 0 )
590 - ->getTimestamp();
591 - $date_to = ( new DateTime( $data['date_to'], $timezone ) )
592 - ->setTime( 23, 59, 59 )
593 - ->getTimestamp();
594 382
383 + $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
384 + $sort_params = wd_di()->get( Table_Lockout::class )->resolve_sort( $sort );
385 +
595 386 $result = $this->retrieve_logs(
596 387 array(
597 388 'from' => $date_from,
598 389 'to' => $date_to,
599 390 'ip' => $data['ip'],
391 + 'user_agent' => $data['user_agent'] ?? '',
600 392 // If this is all, then we set to null to exclude it from the filter.
601 393 'type' => 'all' === $data['type'] ? '' : $data['type'],
602 394 'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'],
603 395 ),
604 396 $data['paged'],
605 - $order,
606 - $order_by
397 + $sort_params['order'],
398 + $sort_params['order_by'],
399 + $data['per_page'] ?? 0
607 400 );
608 401
609 402 return new Response( true, $result );
610 403 }
@@ -616,13 +409,8 @@
616 409 public function enqueue_assets() {
617 410 if ( ! $this->is_page_active() ) {
618 411 return;
619 412 }
620 - wp_localize_script(
621 - 'def-iplockout',
622 - 'lockout_logs',
623 - array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
624 - );
625 413 }
626 414
627 415 /**
628 416 * Provides data for the frontend.
@@ -629,20 +417,34 @@
629 417 *
630 418 * @return array An array of data for the frontend.
631 419 */
632 420 public function data_frontend(): array {
633 - $type = defender_get_data_from_request( 'type', 'g' );
421 + $type = defender_get_data_from_request( 'type', 'g' );
422 + $ip = defender_get_data_from_request( 'ip', 'g' );
423 + $user_agent = defender_get_data_from_request( 'user_agent', 'g' );
424 + $timezone = wp_timezone();
634 425
635 - $def_filters = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() );
636 - $init_filters = array(
637 - 'from' => strtotime( '-30 days' ),
638 - 'to' => time(),
426 + $init_filters = array(
427 + 'from' => ( new DateTime( '-30 days', $timezone ) )->setTime( 0, 0, 0 )->getTimestamp(),
428 + 'to' => ( new DateTime( 'now', $timezone ) )->setTime( 23, 59, 59 )->getTimestamp(),
639 429 'type' => $type,
640 - 'ip' => '',
430 + 'ip' => $ip,
431 + 'user_agent' => $user_agent,
641 432 'ban_status' => '',
642 433 );
434 + $misc = wd_di()->get( Table_Lockout::class )->get_filters();
435 + $misc['countries'] = $this->countries_list();
436 + $def_filters = array(
437 + 'misc' => $misc,
438 + 'default_filter' => $init_filters,
439 + 'per_page' => self::DEFAULT_PER_PAGE,
440 + );
643 441
644 - return array_merge( $this->retrieve_logs( $init_filters, 1 ), $def_filters );
442 + return array_merge(
443 + $this->retrieve_logs( $init_filters ),
444 + $def_filters,
445 + $this->dump_routes_and_nonces()
446 + );
645 447 }
646 448
647 449 /**
648 450 * Retrieves logs based on the given filters, paging, order, and order by.
@@ -655,8 +457,9 @@
655 457 * - 'ban_status': The ban status of the logs.
656 458 * @param int $paged The page number of the logs to retrieve. Default is 1.
657 459 * @param string $order The order of the logs. Default is 'desc'.
658 460 * @param string $order_by The field to order the logs by. Default is 'id'.
461 + * @param int $per_page Number of logs per page. 0 falls back to the default of 10.
659 462 *
660 463 * @return array An array containing the following keys:
661 464 * - 'count': The total count of logs.
662 465 * - 'logs': The retrieved logs.
@@ -662,13 +465,12 @@
662 465 * - 'logs': The retrieved logs.
663 466 * - 'per_page': The number of logs per page.
664 467 * - 'total_pages': The total number of pages.
665 468 */
666 - private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array {
667 - // User can set the number of logs to retrieve per page.
668 - $per_page = (int) defender_get_data_from_request( 'per_page', 'p' );
469 + private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id', $per_page = 0 ): array {
470 + $per_page = (int) $per_page;
669 471 if ( 0 === $per_page ) {
670 - $per_page = 20;
472 + $per_page = self::DEFAULT_PER_PAGE;
671 473 }
672 474 $conditions = array( 'ban_status' => $filters['ban_status'] );
673 475
674 476 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
@@ -673,9 +475,8 @@
673 475
674 476 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
675 477 $logs = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page );
676 478 return array(
677 - 'count' => $count,
678 479 'logs' => $logs,
679 480 'per_page' => $per_page,
680 481 'total_pages' => ceil( $count / $per_page ),
681 482 );
@@ -707,8 +508,9 @@
707 508 /**
708 509 * Delete all the data & the cache.
709 510 */
710 511 public function remove_data() {
512 + delete_site_transient( self::AKISMET_BLOCKED_IPS );
711 513 }
712 514
713 515 /**
714 516 * Exports strings.
@@ -722,19 +524,24 @@
722 524 /**
723 525 * Exports strings.
724 526 *
725 527 * @param array $logs Prepared logs.
528 + * @param bool $is_staging Send logs to staging.
726 529 */
727 - private function maybe_send_reports( array $logs ): void {
530 + private function maybe_send_reports( array $logs, bool $is_staging = false ): void {
728 531 $offset = 0;
729 532 $length = 1000;
730 533 $logs_chunk = array_slice( $logs, $offset, $length );
731 - while ( ! empty( $logs_chunk ) ) {
534 + while ( array() !== $logs_chunk ) {
732 535 $data = array(
733 536 'logs' => $logs_chunk,
734 537 );
735 538
736 - $response = $this->antibot_client->send_reports( $data );
539 + $antibot_client = $this->antibot_client;
540 + if ( $is_staging ) {
541 + $antibot_client = new Antibot_Global_Firewall_Client( 'https://staging-api.blocklist-service.com' );
542 + }
543 + $response = $antibot_client->send_reports( $data );
737 544
738 545 if ( is_wp_error( $response ) ) {
739 546 $this->log(
740 547 sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ),
@@ -772,9 +579,10 @@
772 579 * @param bool $status Status for sending logs. Send logs to API if true.
773 580 *
774 581 * @since 4.5.0
775 582 */
776 - $send_logs = (bool) apply_filters( 'wpdef_firewall_send_logs_to_api', true );
583 + $send_logs = apply_filters( 'wpdef_firewall_send_logs_to_api', true );
584 + $send_logs = is_bool( $send_logs ) ? $send_logs : (bool) $send_logs;
777 585
778 586 if (
779 587 ! $send_logs ||
780 588 ! $this->wpmudev->is_dash_activated() ||
@@ -791,10 +599,10 @@
791 599 // Log the site ID where the event is triggered.
792 600 $this->log( "{$event_name} is processing from site {$site_id}", Firewall::FIREWALL_LOG );
793 601 $from = time() - ( 7 * DAY_IN_SECONDS );
794 602
795 - $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' );
796 - if ( $last_run_time ) {
603 + $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time', 0 );
604 + if ( 0 < $last_run_time ) {
797 605 $time_difference = time() - $last_run_time;
798 606
799 607 if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds
800 608 $from = $last_run_time;
@@ -802,19 +610,24 @@
802 610 }
803 611 update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() );
804 612
805 613 $service = wd_di()->get( Firewall_Logs_Component::class );
806 - $logs = $service->get_compact_logs( $from );
807 614
808 - if ( ! empty( $logs ) ) {
615 + $logs = $service->get_compact_logs( $from );
616 + if ( array() !== $logs ) {
809 617 $this->maybe_send_reports( $logs );
810 618 }
811 619
812 620 $logs = $service->get_akismet_auto_spam_comment_logs();
813 - if ( ! empty( $logs ) ) {
621 + if ( array() !== $logs ) {
814 622 $this->maybe_send_reports( $logs );
815 623 }
816 624
625 + $logs = $service->get_404_intelligence_logs( $from );
626 + if ( array() !== $logs ) {
627 + $this->maybe_send_reports( $logs, true );
628 + }
629 +
817 630 // Release lock after execution.
818 631 $this->release_cron_lock( $event_name );
819 632 }
820 633
@@ -835,25 +648,31 @@
835 648
836 649 // Retrieve response body safely.
837 650 $body = wp_remote_retrieve_body( $response );
838 651 // If the body is empty or does not equal 'true' (indicating spam), return the response as is.
839 - if ( empty( $body ) || trim( $body ) !== 'true' ) {
652 + if ( ! is_string( $body ) || in_array( trim( $body ), array( '', 'true' ), true ) ) {
840 653 return $response;
841 654 }
842 655
843 656 // Ensure the request body contains data; otherwise, return the response.
844 - if ( empty( $parsed_args['body'] ) ) {
657 + $body_arg = $parsed_args['body'] ?? '';
658 + if (
659 + ( is_string( $body_arg ) && '' === trim( $body_arg ) )
660 + || ( is_array( $body_arg ) && array() === $body_arg )
661 + || ( is_object( $body_arg ) && 0 === count( get_object_vars( $body_arg ) ) )
662 + ) {
845 663 return $response;
846 664 }
847 665
848 - $request_data = wp_parse_args( $parsed_args['body'] );
666 + $request_data = wp_parse_args( $body_arg );
849 667 // If the comment author's IP is not present in the request data, return the response.
850 - if ( empty( $request_data['comment_author_IP'] ) ) {
668 + $author_ip = $request_data['comment_author_IP'] ?? '';
669 + if ( '' === $author_ip ) {
851 670 return $response;
852 671 }
853 672
854 673 // Validate the user IP address from the request data.
855 - $user_ip = filter_var( $request_data['comment_author_IP'], FILTER_VALIDATE_IP );
674 + $user_ip = filter_var( $author_ip, FILTER_VALIDATE_IP );
856 675 if ( false === $user_ip ) {
857 676 return $response;
858 677 }
859 678