PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.0.1
Defender Security – Malware Scanner, Login Security & Firewall v6.0.1
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
defender-security / src / model / class-lockout-log.php

class-lockout-log.php in Defender Security – Malware Scanner, Login Security & Firewall 6.0.1, at src/model/class-lockout-log.php

729 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles interactions with the database table for lockout logs.
4 *
5 * @package WP_Defender\Model
6 */
7
8 namespace WP_Defender\Model;
9
10 use WP_Defender\DB;
11 use Calotes\Base\Model;
12 use WP_Defender\Traits\Formats;
13 use WP_Defender\Component\User_Agent;
14 use WP_Defender\Component\Table_Lockout;
15 use WP_Defender\Model\Setting\Blacklist_Lockout;
16 use WP_Defender\Model\Setting\User_Agent_Lockout;
17
18 /**
19 * Model for the lockout log table.
20 */
21 class Lockout_Log extends DB {
22
23 use Formats;
24
25 // The Firewall log hierarchy. Parent items: 'login', '404', 'bots', 'unlock' and 'custom'.
26 // Group of 'login' cases.
27 public const AUTH_FAIL = 'auth_fail', AUTH_LOCK = 'auth_lock';
28 // Group of '404' cases.
29 public const ERROR_404 = '404_error', LOCKOUT_404 = '404_lockout', ERROR_404_IGNORE = '404_error_ignore';
30 // New log cases since v5.7.0.
31 public const ERROR_404_XSS = 'xss_attempt', LOCKOUT_404_XSS = 'xss_lockout';
32 public const ERROR_404_NON_EXISTENT_PLUGIN = 'plugin_attempt', LOCKOUT_404_NON_EXISTENT_PLUGIN = 'plugin_lockout';
33 public const ERROR_404_NON_EXISTENT_THEME = 'theme_attempt', LOCKOUT_404_NON_EXISTENT_THEME = 'theme_lockout';
34 // Group of 'bots' cases.
35 // 'ua_lockout' includes 'bad_user_agent' due to attempted login with banned user agent && 'bad_post' due to empty User-Agent and Referer headers).
36 public const LOCKOUT_MALICIOUS_BOT = 'malicious_bot', LOCKOUT_FAKE_BOT = 'fake_bot', LOCKOUT_UA = 'ua_lockout';
37 // Group of 'unlock' cases.
38 public const IP_UNLOCK = 'ip_unlock';
39 // Group of 'custom' cases.
40 public const LOCKOUT_IP_CUSTOM = 'custom_lockout';
41
42 public const INFINITE_SCROLL_SIZE = 50;
43
44 /**
45 * Table name.
46 *
47 * @var string
48 */
49 protected $table = 'defender_lockout_log';
50
51 /**
52 * Primary key column.
53 *
54 * @var int
55 * @defender_property
56 */
57 public $id;
58 /**
59 * Table column for log.
60 *
61 * @var string
62 * @defender_property
63 */
64 public $log;
65 /**
66 * Table column for IP address.
67 *
68 * @var string
69 * @defender_property
70 */
71 public $ip;
72 /**
73 * Table column for date.
74 *
75 * @var int
76 * @defender_property
77 */
78 public $date;
79 /**
80 * Table column for user agent.
81 *
82 * @var string
83 * @defender_property
84 */
85 public $user_agent;
86 /**
87 * Table column for type.
88 *
89 * @var string
90 * @defender_property
91 */
92 public $type;
93 /**
94 * Table column for blog id.
95 *
96 * @var int
97 * @defender_property
98 */
99 public $blog_id;
100 /**
101 * Table column for tried.
102 *
103 * @var string
104 * @defender_property
105 */
106 public $tried;
107 /**
108 * Table column for country iso code.
109 *
110 * @var string
111 * @defender_property
112 */
113 public $country_iso_code;
114
115 /**
116 * Query the logs based on the provided filters and pagination settings.
117 *
118 * @param array $filters An array of filters to apply to the query. Default is an empty array.
119 * The following filters are supported:
120 * - from: The start date for the date range filter.
121 * - to: The end date for the date range filter.
122 * - ip: The IP address to filter by.
123 * - type: The type of log to filter by.
124 * - ban_status: The ban status to filter by.
125 * @param int $paged The page number of the results to retrieve. Default is 1.
126 * @param string $order_by The field to order the results by. Default is 'id'.
127 * @param string $order The order direction of the results. Default is 'desc'.
128 * @param int $page_size The number of results to retrieve per page. Default is 50.
129 * Set to -1 to retrieve all results.
130 *
131 * @return array An array of Lockout_Log objects representing the queried logs.
132 */
133 public static function query_logs(
134 $filters = array(),
135 $paged = 1,
136 $order_by = 'id',
137 $order = 'desc',
138 $page_size = 50
139 ): array {
140 $orm = self::get_orm();
141 $orm->get_repository( self::class )
142 ->where(
143 'date',
144 'between',
145 array(
146 $filters['from'],
147 $filters['to'],
148 )
149 );
150
151 if ( isset( $filters['ip'] ) && '' !== $filters['ip'] ) {
152 $orm->where( 'ip', 'like', '%' . $filters['ip'] . '%' );
153 }
154 if ( isset( $filters['user_agent'] ) && '' !== $filters['user_agent'] ) {
155 $orm->where( 'user_agent', 'like', '%' . $filters['user_agent'] . '%' );
156 }
157 if ( isset( $filters['type'] ) && '' !== $filters['type'] ) {
158 $orm->where( 'type', $filters['type'] );
159 }
160
161 if ( isset( $filters['ban_status'] ) && '' !== $filters['ban_status'] ) {
162 self::apply_ban_status_filter( $orm, $filters );
163 }
164
165 if ( '' !== $order_by && '' !== $order ) {
166 $orm->order_by( $order_by, $order );
167 }
168 if ( $page_size > 0 ) {
169 $offset = ( $paged - 1 ) * $page_size;
170 $orm->limit( $page_size, $offset );
171 }
172
173 return $orm->get();
174 }
175
176 /**
177 * Count the number of records in the database table based on the provided filters.
178 *
179 * @param mixed $date_from The start date for the date range filter.
180 * @param mixed $date_to The end date for the date range filter.
181 * @param mixed $type The type of log to filter by.
182 * @param string $ip The IP address to filter by. Default is an empty string.
183 * @param array $filters An array of additional filters to apply to the query. Default is an empty array.
184 * The following filters are supported:
185 * - ban_status: The ban status to filter by.
186 *
187 * @return string|null The number of records matching the provided filters, or null if an error occurred.
188 */
189 public static function count( $date_from, $date_to, $type, $ip = '', $filters = array() ): ?string {
190 $orm = self::get_orm();
191 $orm->get_repository( self::class )
192 ->where(
193 'date',
194 'between',
195 array(
196 $date_from,
197 $date_to,
198 )
199 );
200
201 if ( is_array( $type ) && array() !== $type ) {
202 $orm->where( 'type', 'in', $type );
203 } elseif ( is_string( $type ) && '' !== trim( $type ) ) {
204 $orm->where( 'type', $type );
205 }
206
207 if ( is_string( $ip ) && '' !== trim( $ip ) ) {
208 $orm->where( 'ip', 'like', "%$ip%" );
209 }
210
211 if ( isset( $filters['user_agent'] ) && '' !== trim( $filters['user_agent'] ) ) {
212 $orm->where( 'user_agent', 'like', '%' . $filters['user_agent'] . '%' );
213 }
214
215 if ( isset( $filters['ban_status'] ) && '' !== trim( $filters['ban_status'] ) ) {
216 self::apply_ban_status_filter( $orm, array_merge( $filters, array( 'type' => $type ) ) );
217 }
218
219 return $orm->count();
220 }
221
222 /**
223 * Get 404 lockout types.
224 */
225 public static function get_404_lockout_types(): array {
226 return array(
227 self::LOCKOUT_404,
228 self::LOCKOUT_404_XSS,
229 self::LOCKOUT_404_NON_EXISTENT_PLUGIN,
230 self::LOCKOUT_404_NON_EXISTENT_THEME,
231 );
232 }
233
234 /**
235 * Get all lockout types.
236 */
237 public static function get_all_lockout_types(): array {
238 $arr = self::get_404_lockout_types();
239 $arr[] = self::AUTH_LOCK;
240 $arr[] = self::LOCKOUT_UA;
241
242 return $arr;
243 }
244
245 /**
246 * Count login lockout in the last 7 days.
247 *
248 * @return string|null
249 */
250 public static function count_login_lockout_last_7_days(): ?string {
251 $start = strtotime( '-7 days' );
252 $end = time();
253
254 return self::count( $start, $end, self::AUTH_LOCK );
255 }
256
257 /**
258 * Count 404 lockout in the last 7 days.
259 *
260 * @return string|null
261 */
262 public static function count_404_lockout_last_7_days(): ?string {
263 $start = strtotime( '-7 days' );
264 $end = time();
265
266 return self::count( $start, $end, self::get_404_lockout_types() );
267 }
268
269 /**
270 * Count UA lockout in the last 7 days.
271 *
272 * @return string|null
273 */
274 public static function count_ua_lockout_last_7_days(): ?string {
275 $start = strtotime( '-7 days' );
276 $end = time();
277
278 return self::count( $start, $end, self::LOCKOUT_UA );
279 }
280
281 /**
282 * A shortcut for quickly count lockout in last 24 hours.
283 *
284 * @return string|null
285 */
286 public static function count_lockout_in_24_hours(): ?string {
287 $start = strtotime( '-24 hours' );
288 $end = time();
289
290 return self::count(
291 $start,
292 $end,
293 self::get_all_lockout_types()
294 );
295 }
296
297 /**
298 * A shortcut for quickly count lockout in last 7 days.
299 *
300 * @return string|null
301 */
302 public static function count_lockout_in_7_days(): ?string {
303 $start = strtotime( '-7 days' );
304 $end = time();
305
306 return self::count(
307 $start,
308 $end,
309 self::get_all_lockout_types()
310 );
311 }
312
313 /**
314 * A shortcut for count lockout in 30 days.
315 *
316 * @return string|null
317 */
318 public static function count_lockout_in_30_days(): ?string {
319 $start = strtotime( '-30 days' );
320 $end = time();
321
322 return self::count(
323 $start,
324 $end,
325 // LOCKOUT_IP_CUSTOM is not taken into account.
326 self::get_all_lockout_types()
327 );
328 }
329
330 /**
331 * Retrieves the date of the last lockout that occurred within the last 30 days.
332 *
333 * @param bool $for_hub (optional) Whether to format the date for the persistent hub. Default is false.
334 *
335 * @return string The formatted date of the last lockout, or 'n/a' if no lockouts were found.
336 */
337 public static function get_last_lockout_date( $for_hub = false ) {
338 $data = self::query_logs(
339 array(
340 'from' => strtotime( '-30 days' ),
341 'to' => time(),
342 ),
343 1,
344 'id',
345 'desc',
346 1
347 );
348 $last = array_shift( $data );
349 if ( ! is_object( $last ) ) {
350 return 'n/a';
351 }
352
353 return $for_hub
354 ? $last->persistent_hub_datetime_format( $last->date )
355 : $last->format_date_time( $last->date );
356 }
357
358 /**
359 * Remove all data.
360 *
361 * @return bool|int
362 */
363 public static function truncate() {
364 $orm = self::get_orm();
365
366 return $orm->get_repository( self::class )
367 ->truncate();
368 }
369
370 /**
371 * Remove logs based on timestamp and limit.
372 *
373 * @param int $timestamp The timestamp to filter logs by.
374 * @param int $limit The maximum number of logs to delete.
375 *
376 * @return void
377 */
378 public static function remove_logs( $timestamp, $limit ) {
379 $orm = self::get_orm();
380 $orm->get_repository( self::class )
381 ->where( 'date', '<=', $timestamp )
382 ->order_by( 'id' )
383 ->limit( $limit )
384 ->delete_by_limit();
385 }
386
387 /**
388 * Get log summary.
389 *
390 * @return array
391 */
392 public static function get_summary(): array {
393 // Time.
394 $current_time = time();
395 $today_midnight = strtotime( '-24 hours', $current_time );
396 $first_this_week = strtotime( '-7 days', $current_time );
397 $first_this_month = strtotime( '-30 days', $current_time );
398
399 // Prepare columns.
400 $select = array(
401 'MAX(date) as lockout_last',
402 'COUNT(*) as lockout_this_month',
403 // 24 hours
404 "COUNT(IF(date > {$today_midnight}, 1, NULL)) as lockout_today",
405 "COUNT(IF(date > {$today_midnight} AND type IN ('" . self::LOCKOUT_404 . "', '" . self::LOCKOUT_404_XSS . "', '" . self::LOCKOUT_404_NON_EXISTENT_THEME . "', '" . self::LOCKOUT_404_NON_EXISTENT_PLUGIN . "'), 1, NULL)) as lockout_404_today",
406 "COUNT(IF(date > {$today_midnight} AND type = '" . self::AUTH_LOCK . "', 1, NULL)) as lockout_login_today",
407 "COUNT(IF(date > {$today_midnight} AND type = '" . self::LOCKOUT_UA . "', 1, NULL)) as lockout_ua_today",
408 // 7 days
409 "COUNT(IF(date > {$first_this_week} AND type IN ('" . self::LOCKOUT_404 . "', '" . self::LOCKOUT_404_XSS . "', '" . self::LOCKOUT_404_NON_EXISTENT_THEME . "', '" . self::LOCKOUT_404_NON_EXISTENT_PLUGIN . "'), 1, NULL)) as lockout_404_this_week",
410 "COUNT(IF(date > {$first_this_week} AND type = '" . self::AUTH_LOCK . "', 1, NULL)) as lockout_login_this_week",
411 "COUNT(IF(date > {$first_this_week} AND type = '" . self::LOCKOUT_UA . "', 1, NULL)) as lockout_ua_this_week",
412 // 30 days
413 "COUNT(IF(date > {$first_this_month} AND type IN ('" . self::LOCKOUT_404 . "', '" . self::LOCKOUT_404_XSS . "', '" . self::LOCKOUT_404_NON_EXISTENT_THEME . "', '" . self::LOCKOUT_404_NON_EXISTENT_PLUGIN . "'), 1, NULL)) as lockout_404_this_month",
414 "COUNT(IF(date > {$first_this_month} AND type = '" . self::AUTH_LOCK . "', 1, NULL)) as lockout_login_this_month",
415 "COUNT(IF(date > {$first_this_month} AND type = '" . self::LOCKOUT_UA . "', 1, NULL)) as lockout_ua_this_month",
416 );
417 $select = implode( ',', $select );
418
419 $orm = self::get_orm();
420 $result = $orm->get_repository( self::class )
421 ->select( $select )
422 // LOCKOUT_IP_CUSTOM is not taken into account.
423 ->where( 'type', 'in', self::get_all_lockout_types() )
424 ->where( 'date', '>=', strtotime( '-30 days', $current_time ) )
425 ->get_results();
426
427 return $result[0] ?? array();
428 }
429
430 /**
431 * Returns the log tag based on the given type.
432 *
433 * @param string $type The type of the log.
434 *
435 * @return string The log tag.
436 */
437 protected static function get_log_tag( $type ): string {
438 switch ( $type ) {
439 case self::LOCKOUT_404:
440 case self::LOCKOUT_404_XSS:
441 case self::LOCKOUT_404_NON_EXISTENT_THEME:
442 case self::LOCKOUT_404_NON_EXISTENT_PLUGIN:
443 case self::ERROR_404:
444 case self::ERROR_404_IGNORE:
445 case self::ERROR_404_XSS:
446 case self::ERROR_404_NON_EXISTENT_THEME:
447 case self::ERROR_404_NON_EXISTENT_PLUGIN:
448 $tag = '404';
449 break;
450 case self::AUTH_FAIL:
451 case self::AUTH_LOCK:
452 $tag = 'login';
453 break;
454 case self::LOCKOUT_IP_CUSTOM:
455 $tag = 'Custom';
456 break;
457 case self::IP_UNLOCK:
458 $tag = 'Unlock';
459 break;
460 case self::LOCKOUT_UA:
461 case self::LOCKOUT_MALICIOUS_BOT:
462 case self::LOCKOUT_FAKE_BOT:
463 default:
464 $tag = 'bots';
465 break;
466 }
467
468 return $tag;
469 }
470
471 /**
472 * Returns the CSS class for the log container based on the given type.
473 *
474 * @param string $type The type of the log.
475 *
476 * @return string The CSS class for the log container.
477 */
478 protected static function get_log_container_class( $type ): string {
479 switch ( $type ) {
480 case self::AUTH_LOCK:
481 case self::LOCKOUT_404:
482 case self::LOCKOUT_404_XSS:
483 case self::LOCKOUT_404_NON_EXISTENT_THEME:
484 case self::LOCKOUT_404_NON_EXISTENT_PLUGIN:
485 case self::LOCKOUT_UA:
486 case self::LOCKOUT_MALICIOUS_BOT:
487 case self::LOCKOUT_FAKE_BOT:
488 $class = 'sui-error';
489 break;
490 case self::AUTH_FAIL:
491 case self::ERROR_404:
492 case self::ERROR_404_IGNORE:
493 case self::ERROR_404_XSS:
494 case self::ERROR_404_NON_EXISTENT_THEME:
495 case self::ERROR_404_NON_EXISTENT_PLUGIN:
496 default:
497 $class = 'sui-warning';
498 break;
499 }
500
501 return $class;
502 }
503
504 /**
505 * Retrieves logs from the database and formats them for display on the frontend.
506 *
507 * @param array $filters An array of filters to apply to the query. Default is an empty array.
508 * The following filters are supported:
509 * - from: The start date for the date range filter.
510 * - to: The end date for the date range filter.
511 * - ip: The IP address to filter by.
512 * - type: The type of log to filter by.
513 * - ban_status: The ban status to filter by.
514 * @param int $paged The page number of the results to retrieve. Default is 1.
515 * @param string $order_by The field to order the results by. Default is 'id'.
516 * @param string $order The order direction of the results. Default is 'desc'.
517 * @param int $page_size The number of results to retrieve per page. Default is 50.
518 * Set to -1 to retrieve all results.
519 *
520 * @return array An array of formatted log entries.
521 */
522 public static function get_logs_and_format(
523 $filters = array(),
524 $paged = 1,
525 $order_by = 'id',
526 $order = 'desc',
527 $page_size = 50
528 ): array {
529 $logs = self::query_logs( $filters, $paged, $order_by, $order, $page_size );
530
531 return self::format_logs( $logs );
532 }
533
534 /**
535 * Get the first log by ID.
536 *
537 * @param int $id The ID of the log.
538 *
539 * @return null|Model
540 */
541 public static function find_by_id( $id ): ?Model {
542 $orm = self::get_orm();
543
544 return $orm->get_repository( self::class )
545 ->where( 'id', $id )
546 ->first();
547 }
548
549 /**
550 * Get logs by an array of IDs.
551 *
552 * @param int[] $ids Array of log IDs.
553 *
554 * @return array
555 */
556 public static function find_by_ids( array $ids ): array {
557 if ( array() === $ids ) {
558 return array();
559 }
560
561 $orm = self::get_orm();
562
563 return $orm->get_repository( self::class )
564 ->where( 'id', 'in', $ids )
565 ->get();
566 }
567
568 /**
569 * Delete current log.
570 */
571 public function delete() {
572 $orm = self::get_orm();
573 $orm->get_repository( self::class )->delete(
574 array(
575 'id' => $this->id,
576 )
577 );
578 }
579
580 /**
581 * Apply ban status filtering based on lockout type.
582 *
583 * @param object $orm The ORM query builder.
584 * @param array $filters The filters array.
585 */
586 private static function apply_ban_status_filter( $orm, $filters ) {
587 $ban_status = $filters['ban_status'];
588 $type = $filters['type'] ?? '';
589 $ua_types = self::get_ua_lockout_types();
590
591 if ( in_array( $type, $ua_types, true ) ) {
592 $where = self::ban_status_where( $ban_status );
593 if ( 3 === count( $where ) ) {
594 $orm->where( ...$where );
595 }
596 return;
597 }
598
599 $table_lockout = wd_di()->get( Table_Lockout::class );
600 $bl_model = wd_di()->get( Blacklist_Lockout::class );
601
602 if ( $table_lockout::STATUS_BAN === $ban_status ) {
603 $list = $bl_model->get_list( 'blocklist' );
604 // Empty blocklist means no IPs are banned — force zero results.
605 $orm->where( 'ip', 'in', array() === $list ? array( '' ) : $list );
606 } elseif ( $table_lockout::STATUS_NOT_BAN === $ban_status ) {
607 $list = $bl_model->get_list( 'blocklist' );
608 // Empty blocklist means all IPs are "not banned" — no filter needed.
609 if ( array() !== $list ) {
610 $orm->where( 'ip', 'not in', $list );
611 }
612 } elseif ( $table_lockout::STATUS_ALLOWLIST === $ban_status ) {
613 $list = $bl_model->get_list( 'allowlist' );
614 // Empty allowlist means no IPs are allowlisted — force zero results.
615 $orm->where( 'ip', 'in', array() === $list ? array( '' ) : $list );
616 }
617 }
618
619 /**
620 * Prepare user-agent where condition based on the ban status variant.
621 *
622 * @param string $ban_status_type Ban status type.
623 *
624 * @return array Where condition arguments or empty array.
625 */
626 private static function ban_status_where( $ban_status_type ): array {
627 $table_lockout = wd_di()->get( Table_Lockout::class );
628 $ua_model = wd_di()->get( User_Agent_Lockout::class );
629
630 if ( $table_lockout::STATUS_NOT_BAN === $ban_status_type ) {
631 $blocklist = $ua_model->get_all_selected_blocklist_ua();
632 if ( array() === $blocklist ) {
633 return array();
634 }
635 $escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '' ) );
636 return array( 'user_agent', 'not regexp', '(?i)' . implode( '|', $escaped ) );
637 } elseif ( $table_lockout::STATUS_BAN === $ban_status_type ) {
638 $blocklist = $ua_model->get_all_selected_blocklist_ua();
639 if ( array() === $blocklist ) {
640 return array();
641 }
642 $escaped = array_map( 'preg_quote', $blocklist, array_fill( 0, count( $blocklist ), '' ) );
643 return array( 'user_agent', 'regexp', '(?i)' . implode( '|', $escaped ) );
644 } elseif ( $table_lockout::STATUS_ALLOWLIST === $ban_status_type ) {
645 $allowlist = $ua_model->get_lockout_list( 'allowlist' );
646 if ( array() === $allowlist ) {
647 return array();
648 }
649 $escaped = array_map( 'preg_quote', $allowlist, array_fill( 0, count( $allowlist ), '' ) );
650 return array( 'user_agent', 'regexp', '(?i)' . implode( '|', $escaped ) );
651 }
652
653 return array();
654 }
655
656 /**
657 * Format logs for ready to use on frontend.
658 *
659 * @param array $logs An array of log entries.
660 *
661 * @return array
662 * @since 3.11.0
663 */
664 public static function format_logs( array $logs ): array {
665 $data = array();
666 foreach ( $logs as $item ) {
667 // Escape object properties received from end user.
668 $item->log = sanitize_textarea_field( $item->log );
669 $item->tried = sanitize_textarea_field( $item->tried );
670 $log = $item->export();
671
672 // Escape array keys received from end user.
673 $log['log'] = sanitize_textarea_field( $log['log'] );
674 $log['tried'] = sanitize_textarea_field( $log['tried'] );
675
676 $log['date'] = $item->format_date_time( $item->date );
677 $log['format_date'] = $item->get_date( $item->date );
678 $log['tag'] = self::get_log_tag( $item->type );
679 $log['container_class'] = self::get_log_container_class( $item->type );
680 if ( self::LOCKOUT_UA !== $item->type ) {
681 $log['description'] = sanitize_textarea_field( $item->log );
682 }
683 $data[] = $log;
684 }
685
686 return $data;
687 }
688
689 /**
690 * Determine if the IP should be added to the database based on the timeframe.
691 *
692 * @return bool True if the IP should be added to the database, false otherwise.
693 */
694 public function has_recent_ip_log(): bool {
695 // Ensure IP is set before proceeding.
696 if ( ! is_string( $this->ip ) || '' === trim( $this->ip ) ) {
697 return false;
698 }
699
700 $orm = self::get_orm();
701 // Query the latest log for the current IP.
702 $latest_log = $orm->get_repository( self::class )
703 ->select( 'date' )
704 ->where( 'ip', $this->ip )
705 ->order_by( 'date', 'desc' )
706 ->first();
707
708 if ( null !== $latest_log ) {
709 // Return true if the log is within the 5-minute timeframe.
710 return ( time() - $latest_log->date ) <= 300;
711 }
712
713 return false;
714 }
715
716 /**
717 * Get UA lockout types.
718 *
719 * @return array
720 */
721 public static function get_ua_lockout_types(): array {
722 return array(
723 self::LOCKOUT_UA,
724 self::LOCKOUT_MALICIOUS_BOT,
725 self::LOCKOUT_FAKE_BOT,
726 );
727 }
728 }
729