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 -370 5.4.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 === (int) $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' ),
@@ -253,32 +183,57 @@
253 183 esc_html__( 'Date / Time', 'defender-security' ),
254 184 esc_html__( 'Type', 'defender-security' ),
255 185 esc_html__( 'IP address', 'defender-security' ),
256 186 esc_html__( 'IP Status', 'defender-security' ),
187 + esc_html__( 'User Agent Name', 'defender-security' ),
257 188 esc_html__( 'User Agent Status', 'defender-security' ),
258 189 );
259 - fputcsv( $fp, $headers, ',', '"', '\\' );
260 190
261 - $flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE;
262 - foreach ( $logs as $key => $log ) {
263 - $item = array(
264 - $log->log,
265 - $this->format_date_time( $log->date ),
266 - $tl_component->get_type( $log->type ),
267 - $log->ip,
268 - $tl_component->get_ip_status_text( $log->ip ),
269 - $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
270 219 );
271 - fputcsv( $fp, $item, ',', '"', '\\' );
272 220
273 - if ( 0 === $key % $flush_limit ) {
274 - ob_flush();
275 - 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 + );
276 231 }
277 - }
278 232
279 - fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
280 - exit();
233 + $has_more = count( $logs ) === $per_page;
234 + ++$page;
235 + } while ( $has_more );
281 236 }
282 237
283 238 /**
284 239 * Toggles an IP address to or from a specified list.
@@ -290,104 +245,34 @@
290 245 */
291 246 public function toggle_ip_to_list( Request $request ): Response {
292 247 $data = $request->get_data(
293 248 array(
294 - 'ip' => array(
249 + 'list' => array(
295 250 'type' => 'string',
296 - 'sanitize' => 'sanitize_text_field',
251 + 'sanitize' => 'sanitize_textarea_field',
297 252 ),
298 - 'list' => array(
253 + 'type' => array(
299 254 'type' => 'string',
300 255 'sanitize' => 'sanitize_text_field',
301 256 ),
302 - 'ban_status' => array(
303 - 'type' => 'string',
304 - 'sanitize' => 'sanitize_text_field',
305 - ),
306 257 )
307 258 );
308 259
309 - $ip = $data['ip'];
310 260 $collection = $data['list'];
261 + $list_type = $data['type'];
311 262
312 263 $model = wd_di()->get( Blacklist_Lockout::class );
313 - if ( $model->is_ip_in_list( $ip, $collection ) ) {
314 - $model->remove_from_list( $ip, $collection );
315 - /* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */
316 - $message = esc_html__(
317 - 'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
318 - 'defender-security'
319 - );
320 - } else {
321 - $model->add_to_list( $ip, $collection );
322 -
323 - $global_ip_service = wd_di()->get( Global_IP::class );
324 - if ( $global_ip_service->can_blocklist_autosync() ) {
325 - $global_ip_data = array(
326 - 'block_list' => array( $ip ),
327 - );
328 - $global_ip_service->add_to_global_ip_list( $global_ip_data );
329 - }
330 -
331 - /* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */
332 - $message = esc_html__(
333 - 'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
334 - 'defender-security'
335 - );
264 + if ( 'blacklist' === $list_type ) {
265 + $model->ip_blacklist = $collection;
266 + } elseif ( 'whitelist' === $list_type ) {
267 + $model->ip_whitelist = $collection;
336 268 }
337 - $filter_data = $request->get_data(
338 - array(
339 - 'date_from' => array(
340 - 'type' => 'string',
341 - 'sanitize' => 'sanitize_text_field',
342 - ),
343 - 'date_to' => array(
344 - 'type' => 'string',
345 - 'sanitize' => 'sanitize_text_field',
346 - ),
347 - 'ip_filter' => array(
348 - 'type' => 'string',
349 - 'sanitize' => 'sanitize_text_field',
350 - ),
351 - 'type' => array(
352 - 'type' => 'string',
353 - 'sanitize' => 'sanitize_text_field',
354 - ),
355 - 'paged' => array(
356 - 'type' => 'int',
357 - 'sanitize' => 'sanitize_text_field',
358 - ),
359 - 'per_page' => array(
360 - 'type' => 'int',
361 - 'sanitize' => 'sanitize_text_field',
362 - ),
363 - )
364 - );
365 - $logs = Lockout_Log::get_logs_and_format(
366 - array(
367 - 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
368 - 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
369 - 'ip' => $filter_data['ip_filter'],
370 - // If this is all, then we set to null to exclude it from the filter.
371 - 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
372 - ),
373 - $filter_data['paged'],
374 - 'id',
375 - 'desc',
376 - $filter_data['per_page']
377 - );
269 + $model->save();
378 270
379 271 return new Response(
380 272 true,
381 273 array(
382 - 'message' => sprintf(
383 - $message,
384 - $data['ip'] ?? '-',
385 - $data['list'],
386 - $data['list'],
387 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
388 - ),
389 - 'logs' => $logs,
274 + 'banning' => $model->export(),
390 275 )
391 276 );
392 277 }
393 278
@@ -401,115 +286,34 @@
401 286 */
402 287 public function toggle_ua_to_list( Request $request ): Response {
403 288 $data = $request->get_data(
404 289 array(
405 - 'ua' => array(
290 + 'list' => array(
406 291 'type' => 'string',
407 - 'sanitize' => 'sanitize_text_field',
292 + 'sanitize' => 'sanitize_textarea_field',
408 293 ),
409 - 'list' => array(
294 + 'type' => array(
410 295 'type' => 'string',
411 296 'sanitize' => 'sanitize_text_field',
412 297 ),
413 - 'scenario' => array(
414 - 'type' => 'string',
415 - 'sanitize' => 'sanitize_text_field',
416 - ),
417 298 )
418 299 );
419 300
420 - $ua = $data['ua'];
421 301 $collection = $data['list'];
422 - $action = $data['scenario'];
302 + $list_type = $data['type'];
423 303
424 304 $model = wd_di()->get( User_Agent_Lockout::class );
425 -
426 - if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) {
427 - $model->remove_from_list( $ua, $collection );
428 - /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
429 - $message = esc_html__(
430 - 'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
431 - 'defender-security'
432 - );
433 - } elseif ( 'add' === $action ) {
434 -
435 - /**
436 - * 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`.
437 - */
438 - if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) {
439 - $model->remove_from_list( $ua, 'allowlist' );
440 - }
441 -
442 - if ( ! $model->is_ua_in_list( $ua, $collection ) ) {
443 - $model->add_to_list( $ua, $collection );
444 - }
445 - /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
446 - $message = esc_html__(
447 - 'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
448 - 'defender-security'
449 - );
450 - } else {
451 - return new Response(
452 - false,
453 - array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) )
454 - );
305 + if ( 'blacklist' === $list_type ) {
306 + $model->blacklist = $collection;
307 + } elseif ( 'whitelist' === $list_type ) {
308 + $model->whitelist = $collection;
455 309 }
310 + $model->save();
456 311
457 - $filter_data = $request->get_data(
458 - array(
459 - 'date_from' => array(
460 - 'type' => 'string',
461 - 'sanitize' => 'sanitize_text_field',
462 - ),
463 - 'date_to' => array(
464 - 'type' => 'string',
465 - 'sanitize' => 'sanitize_text_field',
466 - ),
467 - 'ip_filter' => array(
468 - 'type' => 'string',
469 - 'sanitize' => 'sanitize_text_field',
470 - ),
471 - 'type' => array(
472 - 'type' => 'string',
473 - 'sanitize' => 'sanitize_text_field',
474 - ),
475 - 'paged' => array(
476 - 'type' => 'int',
477 - 'sanitize' => 'sanitize_text_field',
478 - ),
479 - 'per_page' => array(
480 - 'type' => 'int',
481 - 'sanitize' => 'sanitize_text_field',
482 - ),
483 - )
484 - );
485 - $logs = Lockout_Log::get_logs_and_format(
486 - array(
487 - 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
488 - 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
489 - 'ip' => $filter_data['ip_filter'],
490 - // If this is all, then we set to null to exclude it from the filter.
491 - 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
492 - ),
493 - $filter_data['paged'],
494 - 'id',
495 - 'desc',
496 - $filter_data['per_page']
497 - );
498 -
499 312 return new Response(
500 313 true,
501 314 array(
502 - 'message' => sprintf(
503 - $message,
504 - '<strong>' . $data['ua'] . '</strong>',
505 - $data['list'],
506 - $data['list'],
507 - '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>'
508 - ),
509 - 'logs' => $logs,
510 - // Include blocklist preset values for the frontend.
511 - 'blocklist_preset_values' => $model->blocklist_preset_values,
315 + 'uaLockout' => $model->export(),
512 316 )
513 317 );
514 318 }
515 319
@@ -536,8 +340,12 @@
536 340 'ip' => array(
537 341 'type' => 'string',
538 342 'sanitize' => 'sanitize_text_field',
539 343 ),
344 + 'user_agent' => array(
345 + 'type' => 'string',
346 + 'sanitize' => 'sanitize_text_field',
347 + ),
540 348 'type' => array(
541 349 'type' => 'string',
542 350 'sanitize' => 'sanitize_text_field',
543 351 ),
@@ -552,57 +360,44 @@
552 360 'ban_status' => array(
553 361 'type' => 'string',
554 362 'sanitize' => 'sanitize_text_field',
555 363 ),
364 + 'per_page' => array(
365 + 'type' => 'int',
366 + 'sanitize' => 'sanitize_text_field',
367 + ),
556 368 )
557 369 );
558 370 // Validate.
559 371 $v = new Validator( $data, array() );
560 372 $v->rule( 'required', array( 'date_from', 'date_to' ) );
561 - $v->rule( 'date', array( 'date_from', 'date_to' ) );
562 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 ) {
563 380 return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) );
564 381 }
565 - $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
566 - switch ( $sort ) {
567 - case 'ip':
568 - $order = 'desc';
569 - $order_by = 'ip';
570 - break;
571 - case 'oldest':
572 - $order = 'asc';
573 - $order_by = 'id';
574 - break;
575 - case 'user_agent':
576 - $order = 'asc';
577 - $order_by = 'user_agent';
578 - break;
579 - default:
580 - $order = 'desc';
581 - $order_by = 'id';
582 - break;
583 - }
584 - // Convert date using timezone.
585 - $timezone = wp_timezone();
586 - $date_from = ( new DateTime( $data['date_from'], $timezone ) )
587 - ->setTime( 0, 0, 0 )
588 - ->getTimestamp();
589 - $date_to = ( new DateTime( $data['date_to'], $timezone ) )
590 - ->setTime( 23, 59, 59 )
591 - ->getTimestamp();
592 382
383 + $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
384 + $sort_params = wd_di()->get( Table_Lockout::class )->resolve_sort( $sort );
385 +
593 386 $result = $this->retrieve_logs(
594 387 array(
595 388 'from' => $date_from,
596 389 'to' => $date_to,
597 390 'ip' => $data['ip'],
391 + 'user_agent' => $data['user_agent'] ?? '',
598 392 // If this is all, then we set to null to exclude it from the filter.
599 393 'type' => 'all' === $data['type'] ? '' : $data['type'],
600 394 'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'],
601 395 ),
602 396 $data['paged'],
603 - $order,
604 - $order_by
397 + $sort_params['order'],
398 + $sort_params['order_by'],
399 + $data['per_page'] ?? 0
605 400 );
606 401
607 402 return new Response( true, $result );
608 403 }
@@ -614,13 +409,8 @@
614 409 public function enqueue_assets() {
615 410 if ( ! $this->is_page_active() ) {
616 411 return;
617 412 }
618 - wp_localize_script(
619 - 'def-iplockout',
620 - 'lockout_logs',
621 - array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
622 - );
623 413 }
624 414
625 415 /**
626 416 * Provides data for the frontend.
@@ -627,20 +417,34 @@
627 417 *
628 418 * @return array An array of data for the frontend.
629 419 */
630 420 public function data_frontend(): array {
631 - $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();
632 425
633 - $def_filters = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() );
634 - $init_filters = array(
635 - 'from' => strtotime( '-30 days' ),
636 - '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(),
637 429 'type' => $type,
638 - 'ip' => '',
430 + 'ip' => $ip,
431 + 'user_agent' => $user_agent,
639 432 'ban_status' => '',
640 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 + );
641 441
642 - 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 + );
643 447 }
644 448
645 449 /**
646 450 * Retrieves logs based on the given filters, paging, order, and order by.
@@ -653,8 +457,9 @@
653 457 * - 'ban_status': The ban status of the logs.
654 458 * @param int $paged The page number of the logs to retrieve. Default is 1.
655 459 * @param string $order The order of the logs. Default is 'desc'.
656 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.
657 462 *
658 463 * @return array An array containing the following keys:
659 464 * - 'count': The total count of logs.
660 465 * - 'logs': The retrieved logs.
@@ -660,13 +465,12 @@
660 465 * - 'logs': The retrieved logs.
661 466 * - 'per_page': The number of logs per page.
662 467 * - 'total_pages': The total number of pages.
663 468 */
664 - private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array {
665 - // User can set the number of logs to retrieve per page.
666 - $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;
667 471 if ( 0 === $per_page ) {
668 - $per_page = 20;
472 + $per_page = self::DEFAULT_PER_PAGE;
669 473 }
670 474 $conditions = array( 'ban_status' => $filters['ban_status'] );
671 475
672 476 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
@@ -671,9 +475,8 @@
671 475
672 476 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
673 477 $logs = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page );
674 478 return array(
675 - 'count' => $count,
676 479 'logs' => $logs,
677 480 'per_page' => $per_page,
678 481 'total_pages' => ceil( $count / $per_page ),
679 482 );
@@ -705,8 +508,9 @@
705 508 /**
706 509 * Delete all the data & the cache.
707 510 */
708 511 public function remove_data() {
512 + delete_site_transient( self::AKISMET_BLOCKED_IPS );
709 513 }
710 514
711 515 /**
712 516 * Exports strings.
@@ -720,19 +524,24 @@
720 524 /**
721 525 * Exports strings.
722 526 *
723 527 * @param array $logs Prepared logs.
528 + * @param bool $is_staging Send logs to staging.
724 529 */
725 - private function maybe_send_reports( array $logs ): void {
530 + private function maybe_send_reports( array $logs, bool $is_staging = false ): void {
726 531 $offset = 0;
727 532 $length = 1000;
728 533 $logs_chunk = array_slice( $logs, $offset, $length );
729 - while ( ! empty( $logs_chunk ) ) {
534 + while ( array() !== $logs_chunk ) {
730 535 $data = array(
731 536 'logs' => $logs_chunk,
732 537 );
733 538
734 - $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 );
735 544
736 545 if ( is_wp_error( $response ) ) {
737 546 $this->log(
738 547 sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ),
@@ -770,9 +579,10 @@
770 579 * @param bool $status Status for sending logs. Send logs to API if true.
771 580 *
772 581 * @since 4.5.0
773 582 */
774 - $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;
775 585
776 586 if (
777 587 ! $send_logs ||
778 588 ! $this->wpmudev->is_dash_activated() ||
@@ -789,10 +599,10 @@
789 599 // Log the site ID where the event is triggered.
790 600 $this->log( "{$event_name} is processing from site {$site_id}", Firewall::FIREWALL_LOG );
791 601 $from = time() - ( 7 * DAY_IN_SECONDS );
792 602
793 - $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' );
794 - 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 ) {
795 605 $time_difference = time() - $last_run_time;
796 606
797 607 if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds
798 608 $from = $last_run_time;
@@ -800,19 +610,24 @@
800 610 }
801 611 update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() );
802 612
803 613 $service = wd_di()->get( Firewall_Logs_Component::class );
804 - $logs = $service->get_compact_logs( $from );
805 614
806 - if ( ! empty( $logs ) ) {
615 + $logs = $service->get_compact_logs( $from );
616 + if ( array() !== $logs ) {
807 617 $this->maybe_send_reports( $logs );
808 618 }
809 619
810 620 $logs = $service->get_akismet_auto_spam_comment_logs();
811 - if ( ! empty( $logs ) ) {
621 + if ( array() !== $logs ) {
812 622 $this->maybe_send_reports( $logs );
813 623 }
814 624
625 + $logs = $service->get_404_intelligence_logs( $from );
626 + if ( array() !== $logs ) {
627 + $this->maybe_send_reports( $logs, true );
628 + }
629 +
815 630 // Release lock after execution.
816 631 $this->release_cron_lock( $event_name );
817 632 }
818 633
@@ -833,25 +648,31 @@
833 648
834 649 // Retrieve response body safely.
835 650 $body = wp_remote_retrieve_body( $response );
836 651 // If the body is empty or does not equal 'true' (indicating spam), return the response as is.
837 - if ( empty( $body ) || trim( $body ) !== 'true' ) {
652 + if ( ! is_string( $body ) || in_array( trim( $body ), array( '', 'true' ), true ) ) {
838 653 return $response;
839 654 }
840 655
841 656 // Ensure the request body contains data; otherwise, return the response.
842 - 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 + ) {
843 663 return $response;
844 664 }
845 665
846 - $request_data = wp_parse_args( $parsed_args['body'] );
666 + $request_data = wp_parse_args( $body_arg );
847 667 // If the comment author's IP is not present in the request data, return the response.
848 - if ( empty( $request_data['comment_author_IP'] ) ) {
668 + $author_ip = $request_data['comment_author_IP'] ?? '';
669 + if ( '' === $author_ip ) {
849 670 return $response;
850 671 }
851 672
852 673 // Validate the user IP address from the request data.
853 - $user_ip = filter_var( $request_data['comment_author_IP'], FILTER_VALIDATE_IP );
674 + $user_ip = filter_var( $author_ip, FILTER_VALIDATE_IP );
854 675 if ( false === $user_ip ) {
855 676 return $response;
856 677 }
857 678