PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
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 3.1.1 All 140 releases
defender-security / src / controller / class-blacklist.php

class-blacklist.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at src/controller/class-blacklist.php

773 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles IP and country blacklisting functionalities.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use PharData;
11 use Exception;
12 use Countable;
13 use WP_Defender\Traits\IP;
14 use WP_Defender\Controller;
15 use Calotes\Component\Request;
16 use Calotes\Component\Response;
17 use WP_Defender\Traits\Country;
18 use WP_Defender\Behavior\WPMUDEV;
19 use WP_Defender\Model\Lockout_Ip;
20 use WP_Defender\Traits\Continent;
21 use WP_Defender\Component\Blacklist_Lockout;
22 use MaxMind\Db\Reader\InvalidDatabaseException;
23 use WP_Defender\Component\Network_Cron_Manager;
24 use WP_Defender\Integrations\MaxMind_Geolocation;
25 use WP_Defender\Component\Config\Config_Hub_Helper;
26 use WP_Defender\Model\Setting\Blacklist_Lockout as Model_Blacklist_Lockout;
27
28 /**
29 * Handles IP and country blacklisting functionalities.
30 */
31 class Blacklist extends Controller {
32
33 use IP;
34 use Country;
35 use Continent;
36
37 /**
38 * The slug identifier for this controller.
39 *
40 * @var string
41 */
42 protected $slug = 'wdf-ip-lockout';
43
44 /**
45 * The model for handling the data.
46 *
47 * @var Model_Blacklist_Lockout
48 */
49 protected $model;
50
51 /**
52 * Service for handling logic.
53 *
54 * @var Blacklist_Lockout
55 */
56 protected $service;
57
58 /**
59 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
60 */
61 public function __construct() {
62 $this->register_routes();
63 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
64 $this->model = wd_di()->get( Model_Blacklist_Lockout::class );
65 $this->service = wd_di()->get( Blacklist_Lockout::class );
66 add_action( 'wd_blacklist_this_ip', array( $this, 'blacklist_an_ip' ) );
67 // Update MaxMind's DB.
68 if ( '' !== $this->model->maxmind_license_key ) {
69 // @since 2.8.0 Allows update or remove the database of MaxMind automatic and periodically (MaxMind's TOS).
70 $bind_updater = apply_filters( 'wd_update_maxmind_database', true );
71 $bind_updater = is_bool( $bind_updater ) ? $bind_updater : (bool) $bind_updater;
72
73 if ( $bind_updater ) {
74 /**
75 * Network Cron Manager
76 *
77 * @var Network_Cron_Manager $network_cron_manager
78 */
79 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
80 $network_cron_manager->register_callback(
81 'wpdef_update_geoip',
82 array( $this, 'update_database' ),
83 WEEK_IN_SECONDS,
84 'next Thursday'
85 );
86 }
87 }
88 }
89
90 /**
91 * Add an IP into blacklist.
92 *
93 * @param string $ip IP address to be blacklisted.
94 *
95 * @return void
96 */
97 public function blacklist_an_ip( string $ip ): void {
98 $this->model->add_to_list( $ip, 'blocklist' );
99 if ( defender_is_wp_org_version() ) {
100 \WP_Defender\Component\Rate::run_counter_of_ip_lockouts();
101 }
102 }
103
104 /**
105 * Enqueues scripts and styles for this page.
106 * Only enqueues assets if the page is active.
107 *
108 * @throws InvalidDatabaseException|Exception When unexpected data is found in the database.
109 */
110 public function enqueue_assets() {
111 if ( ! $this->is_page_active() ) {
112 return;
113 }
114 }
115
116 /**
117 * Provides data for the frontend.
118 *
119 * @return array An array of data for the frontend.
120 * @throws InvalidDatabaseException|Exception When unexpected data is found in the database.
121 */
122 public function data_frontend(): array {
123 $user_ip = $this->get_user_ip();
124 $arr_model = $this->model->export();
125 $exist_geodb = $this->service->is_geodb_downloaded();
126 // If MaxMind GeoIP DB is downloaded then display the required data.
127 if ( $exist_geodb ) {
128 $country_list = $this->countries_list();
129 $blacklist_countries = array_merge(
130 array( 'all' => esc_html__( 'Block all', 'defender-security' ) ),
131 $country_list
132 );
133 $whitelist_countries = array_merge(
134 array( 'all' => esc_html__( 'Allow all', 'defender-security' ) ),
135 $country_list
136 );
137 $countries_with_continents_list = $this->get_countries_with_continents();
138 } else {
139 $blacklist_countries = array();
140 $whitelist_countries = array();
141 $countries_with_continents_list = array();
142 }
143
144 $current_country = array();
145 foreach ( $user_ip as $ip ) {
146 $current_country[] = $this->get_current_country( $ip );
147 }
148
149 $misc = array(
150 'user_ip' => implode( ', ', $user_ip ),
151 'is_geodb_downloaded' => $exist_geodb,
152 'blacklist_countries' => $blacklist_countries,
153 'whitelist_countries' => $whitelist_countries,
154 'current_country' => $current_country,
155 'no_ips' => '' === $arr_model['ip_blacklist'] && '' === $arr_model['ip_whitelist'],
156 'countries_with_continents_list' => $countries_with_continents_list,
157 'geodb_license_key' => $this->mask_license_key( $this->model->maxmind_license_key ),
158 'module_name' => Model_Blacklist_Lockout::get_module_name(),
159 );
160
161 return array_merge(
162 array(
163 'model' => $arr_model,
164 'misc' => $misc,
165 ),
166 $this->dump_routes_and_nonces()
167 );
168 }
169
170 /**
171 * Masks a license key with asterisks.
172 *
173 * @param mixed $maxmind_license_key The license key to be masked.
174 *
175 * @return string The masked license key.
176 */
177 private function mask_license_key( $maxmind_license_key ): string {
178 if ( ! is_string( $maxmind_license_key ) || '' === $maxmind_license_key ) {
179 return $maxmind_license_key;
180 }
181 // Get the length of the license key.
182 $key_length = strlen( $maxmind_license_key );
183 // Decide how many characters to reveal. Revealing at least 4 characters or 25% of the key, whichever is greater.
184 $reveal_chars = max( 4, intval( $key_length / 5 ) );
185 // Calculate the number of asterisks to replace the hidden characters.
186 $num_asterisks = $key_length - $reveal_chars;
187 // Generate masked key.
188 return substr( $maxmind_license_key, 0, $reveal_chars ) . str_repeat( '*', $num_asterisks );
189 }
190
191 /**
192 * Save settings.
193 *
194 * @param Request $request The request object containing new settings data.
195 *
196 * @return Response
197 * @defender_route
198 */
199 public function save_settings( Request $request ) {
200 $curr_blacklist = $this->model->get_list( 'blocklist' );
201 $curr_allowlist = $this->model->get_list( 'allowlist' );
202 $data = $request->get_data(
203 array(
204 'country_blacklist' => array(
205 'type' => 'array',
206 ),
207 'country_whitelist' => array(
208 'type' => 'array',
209 ),
210 'ip_blacklist' => array(
211 'type' => 'string',
212 'sanitize' => 'sanitize_textarea_field',
213 ),
214 'ip_whitelist' => array(
215 'type' => 'string',
216 'sanitize' => 'sanitize_textarea_field',
217 ),
218 'ip_lockout_message' => array(
219 'type' => 'string',
220 'sanitize' => 'sanitize_textarea_field',
221 ),
222 'http_ip_header' => array(
223 'type' => 'string',
224 'sanitize' => 'sanitize_text_field',
225 ),
226 'trusted_proxies_ip' => array(
227 'type' => 'string',
228 'sanitize' => 'sanitize_textarea_field',
229 ),
230 )
231 );
232 $data = $this->remove_country_list_conflicts( $data );
233
234 $this->model->import( $data );
235 if ( $this->model->validate() ) {
236 $this->model->save();
237 Config_Hub_Helper::set_clear_active_flag();
238
239 return new Response(
240 true,
241 array_merge(
242 array(
243 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
244 'auto_close' => true,
245 ),
246 $this->data_frontend()
247 )
248 );
249 }
250
251 $after_validate_blacklist = $this->model->get_list( 'blocklist' );
252 $after_validate_allowlist = $this->model->get_list( 'allowlist' );
253 if (
254 ! defender_are_arrays_equal( $curr_blacklist, $after_validate_blacklist ) ||
255 ! defender_are_arrays_equal( $curr_allowlist, $after_validate_allowlist )
256 ) {
257 $this->model->save();
258 Config_Hub_Helper::set_clear_active_flag();
259 }
260
261 return new Response(
262 false,
263 array_merge(
264 array( 'message' => $this->model->get_formatted_errors() ),
265 $this->data_frontend()
266 )
267 );
268 }
269
270 /**
271 * Prevent the same country from being saved in both blocked and allowed lists.
272 *
273 * @param array $data Settings data.
274 *
275 * @return array
276 */
277 private function remove_country_list_conflicts( array $data ): array {
278 if ( ! isset( $data['country_blacklist'], $data['country_whitelist'] ) ) {
279 return $data;
280 }
281
282 $country_blacklist = array_values( array_unique( $data['country_blacklist'] ) );
283 $country_whitelist = array_values( array_unique( $data['country_whitelist'] ) );
284 $added_blacklist = array_diff( $country_blacklist, $this->model->country_blacklist );
285 $added_whitelist = array_diff( $country_whitelist, $this->model->country_whitelist );
286
287 if ( array() !== $added_blacklist ) {
288 $country_whitelist = array_values( array_diff( $country_whitelist, $added_blacklist ) );
289 }
290
291 if ( array() !== $added_whitelist ) {
292 $country_blacklist = array_values( array_diff( $country_blacklist, $added_whitelist ) );
293 }
294
295 $remaining_conflicts = array_intersect( $country_blacklist, $country_whitelist );
296 if ( array() !== $remaining_conflicts ) {
297 $country_whitelist = array_values( array_diff( $country_whitelist, $remaining_conflicts ) );
298 }
299
300 $data['country_blacklist'] = $country_blacklist;
301 $data['country_whitelist'] = $country_whitelist;
302
303 return $data;
304 }
305
306 /**
307 * Download the GEODB IP from Maxmind.
308 *
309 * @param Request $request The request object containing the license key.
310 *
311 * @return Response
312 * @defender_route
313 * @throws InvalidDatabaseException|Exception When unexpected data is found in the database.
314 */
315 public function download_geodb( Request $request ) {
316 $data = $request->get_data(
317 array(
318 'license_key' => array(
319 'type' => 'string',
320 'sanitize' => 'sanitize_text_field',
321 ),
322 )
323 );
324 $license_key = $data['license_key'];
325 $service_geo = wd_di()->get( MaxMind_Geolocation::class );
326 $tmp = $service_geo->get_downloaded_url( $license_key );
327 if ( ! is_wp_error( $tmp ) ) {
328 $phar = new PharData( $tmp );
329 $path = $service_geo->get_db_base_path();
330 if ( ! is_dir( $path ) ) {
331 wp_mkdir_p( $path );
332 }
333 $phar->extractTo( $path, null, true );
334 // Todo: move logic for the path to MaxMind_Geolocation class.
335 $this->model->geodb_path = $path . DIRECTORY_SEPARATOR . $phar->current()->getFileName() . DIRECTORY_SEPARATOR . $service_geo->get_db_full_name();
336 // Save because we'll check for a saved path.
337 $this->model->save();
338
339 if ( file_exists( $tmp ) ) {
340 wp_delete_file( $tmp );
341 }
342
343 foreach ( $this->get_user_ip() as $ip ) {
344 $country = $this->get_current_country( $ip );
345 if ( isset( $country['iso'] ) && '' !== (string) $country['iso'] ) {
346 $this->model = $this->service->add_default_whitelisted_country( $this->model, $country['iso'] );
347 }
348 }
349 $this->model->maxmind_license_key = $license_key;
350 $this->model->save();
351
352 return new Response(
353 true,
354 array(
355 'message' => esc_html__(
356 'You have successfully downloaded Geo IP Database. You can now use this feature to ban any countries to access any area of your website.',
357 'defender-security'
358 ),
359 'is_geodb_downloaded' => $this->service->is_geodb_downloaded(),
360 )
361 );
362 } else {
363 $this->log( 'Error from MaxMind: ' . $tmp->get_error_message(), Firewall::FIREWALL_LOG );
364 $string = esc_html__(
365 'The license key you entered isn\'t valid. If you recently created it, allow up to 5 minutes for activation before trying again.',
366 'defender-security'
367 );
368
369 if ( ( new WPMUDEV() )->show_support_links() ) {
370 $string = sprintf(
371 /* translators: 1. Opening support link, 2. Closing support link. */
372 esc_html__(
373 'The license key you entered isn\'t valid. If you recently created it, allow up to 5 minutes for activation before trying again. Need help? %1$sContact support%2$s.',
374 'defender-security'
375 ),
376 '<a target="_blank" href="' . WP_DEFENDER_SUPPORT_LINK . '">',
377 '</a>'
378 );
379 }
380
381 return new Response( false, array( 'invalid_text' => $string ) );
382 }
383 }
384
385 /**
386 * Delete the Maxmind License key from the settings.
387 *
388 * @return Response
389 * @defender_route
390 * @throws InvalidDatabaseException When unexpected data is found in the database.
391 */
392 public function delete_geodb(): Response {
393 $this->model->maxmind_license_key = '';
394 $this->model->geodb_path = '';
395 $this->model->save();
396
397 return new Response(
398 true,
399 array(
400 'message' => esc_html__(
401 'Maxmind GeoLite2 database license successfully disconnected.',
402 'defender-security'
403 ),
404 'is_geodb_deleted' => $this->service->is_geodb_downloaded(),
405 )
406 );
407 }
408
409 /**
410 * Export IPs
411 * This method exports the IP addresses from the blocklist and allowlist
412 * and generates a CSV file for download.
413 *
414 * @defender_route
415 */
416 public function export_ips(): void {
417 $data = array();
418
419 foreach ( $this->model->get_list( 'blocklist' ) as $ip ) {
420 $data[] = array(
421 'ip' => $ip,
422 'type' => 'blocklist',
423 );
424 }
425 foreach ( $this->model->get_list( 'allowlist' ) as $ip ) {
426 $data[] = array(
427 'ip' => $ip,
428 'type' => 'allowlist',
429 );
430 }
431 // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
432 $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
433 foreach ( $data as $fields ) {
434 fputcsv( $fp, $fields, ',', '"', '\\' );
435 }
436 $filename = 'wdf-ips-export-' . wp_date( 'ymdHis' ) . '.csv';
437 fseek( $fp, 0 );
438 header( 'Content-Type: text/csv' );
439 header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
440 // Make php send the generated csv lines to the browser.
441 fpassthru( $fp );
442 exit();
443 }
444
445 /**
446 * Perform IP blocking or unblocking actions.
447 *
448 * @param Request $request Request object.
449 *
450 * @return void
451 * @throws Exception If table is not defined.
452 * @defender_route
453 */
454 public function ip_action( Request $request ): void {
455 $data = $request->get_data(
456 array(
457 'ip' => array(
458 'type' => 'string',
459 'sanitize' => 'sanitize_text_field',
460 ),
461 'behavior' => array(
462 'type' => 'string',
463 'sanitize' => 'sanitize_text_field',
464 ),
465 )
466 );
467
468 $ip = $data['ip'];
469 $action = $data['behavior'];
470 $models = Lockout_Ip::get( $ip, $action, true );
471
472 foreach ( $models as $model ) {
473 if ( 'unban' === $action ) {
474 $model->status = Lockout_Ip::STATUS_NORMAL;
475 $model->save();
476 } elseif ( 'ban' === $action ) {
477 $model->status = Lockout_Ip::STATUS_BLOCKED;
478 $model->save();
479 }
480 }
481
482 $this->query_locked_ips( $request );
483 }
484
485 /**
486 * Bulk ban or unban IPs.
487 *
488 * @param Request $request Request object.
489 *
490 * @return Response
491 * @throws Exception If table is not defined.
492 * @defender_route
493 */
494 public function bulk_ip_action( Request $request ) {
495 $data = $request->get_data(
496 array(
497 'behavior' => array(
498 'type' => 'string',
499 'sanitize' => 'sanitize_text_field',
500 ),
501 'ips' => array(
502 'type' => 'string',
503 'sanitize' => 'sanitize_text_field',
504 ),
505 )
506 );
507
508 $status = 'unban' === $data['behavior'] ? Lockout_Ip::STATUS_BLOCKED : Lockout_Ip::STATUS_NORMAL;
509 $ips = null;
510 $bulk_ips = null;
511 $limit = 50;
512
513 if ( isset( $data['ips'] ) && is_string( $data['ips'] ) && '' !== $data['ips'] ) {
514 $ips = json_decode( $data['ips'] );
515 $first_nth_ips = array_slice( $ips, 0, $limit );
516 $bulk_ips = wp_list_pluck( $first_nth_ips, 'ip' );
517 }
518
519 try {
520 $models = Lockout_Ip::get_bulk( $status, $bulk_ips, $limit );
521 foreach ( $models as $model ) {
522 $model->status = ( 'unban' === $data['behavior'] ) ? Lockout_Ip::STATUS_NORMAL : Lockout_Ip::STATUS_BLOCKED;
523 $model->save();
524 }
525 // While bulk banning the IPs, needs to slice the IPs array for next iteration.
526 if ( 'ban' === $data['behavior'] ) {
527 $ips = array_slice( $ips, $limit );
528 }
529 // If the queried models are less than the limit it means we are on the last set of IPs.
530 if ( ( is_array( $models ) || $models instanceof Countable ? count( $models ) : 0 ) < $limit ) {
531 return new Response(
532 true,
533 array(
534 'status' => 'done',
535 )
536 );
537 }
538 } catch ( Exception $e ) {
539 return new Response(
540 true,
541 array(
542 'status' => 'error',
543 )
544 );
545 }
546
547 return new Response(
548 true,
549 array(
550 'status' => 'continue',
551 'ips' => $ips,
552 )
553 );
554 }
555
556 /**
557 * Query locked IPs and return the results as a Response object.
558 *
559 * @return Response
560 * @defender_route
561 */
562 public function query_locked_ips() {
563 $results = Lockout_Ip::query_locked_ip();
564 $locked_ips = array();
565 if ( array() !== $results ) {
566 foreach ( $results as $key => $locked_ip ) {
567 $locked_ips[] = array(
568 'id' => $locked_ip['id'],
569 'ip' => $locked_ip['ip'],
570 'status' => $locked_ip['status'],
571 );
572 }
573 }
574
575 return new Response(
576 true,
577 array(
578 'ips' => $locked_ips,
579 )
580 );
581 }
582
583 /**
584 * Get Listed IPs.
585 *
586 * @return Response
587 * @defender_route
588 * @throws Exception If table is not defined.
589 */
590 public function get_listed_ips(): Response {
591 return new Response( true, $this->model->export() );
592 }
593
594 /**
595 * Converts the current state of the object to an array.
596 *
597 * @return array Returns an associative array of object properties.
598 */
599 public function to_array(): array {
600 return array();
601 }
602
603 /**
604 * Adapt the given data array by adding additional fields if necessary.
605 *
606 * @param array $data The data array to adapt.
607 *
608 * @return array The adapted data array.
609 */
610 private function adapt_data( array $data ): array {
611 $adapted_data = array(
612 'ip_blacklist' => $data['ip_blacklist'],
613 'ip_whitelist' => $data['ip_whitelist'],
614 'ip_lockout_message' => $data['ip_lockout_message'],
615 );
616 if ( isset( $data['geoIP_db'] ) && file_exists( $data['geoIP_db'] ) ) {
617 $adapted_data['geodb_path'] = $data['geoIP_db'];
618 if ( isset( $data['country_blacklist'] ) ) {
619 $adapted_data['country_blacklist'] = $data['country_blacklist'];
620 }
621 if ( isset( $data['country_whitelist'] ) ) {
622 $adapted_data['country_whitelist'] = $data['country_whitelist'];
623 }
624 }
625
626 return array_merge( $data, $adapted_data );
627 }
628
629 /**
630 * Imports data into the model.
631 *
632 * @param array $data Data to be imported into the model.
633 *
634 * @throws Exception If table is not defined.
635 */
636 public function import_data( array $data ) {
637 if ( array() !== $data ) {
638 // Upgrade for old versions.
639 $data = $this->adapt_data( $data );
640 $model = $this->model;
641 $model->import( $data );
642 if ( $model->validate() ) {
643 $model->save();
644 }
645 }
646 }
647
648 /**
649 * Removes settings for all submodules.
650 */
651 public function remove_settings() {
652 }
653
654 /**
655 * Delete all the data & the cache.
656 */
657 public function remove_data() {
658 }
659
660 /**
661 * Exports strings.
662 *
663 * @return array An array of strings.
664 */
665 public function export_strings() {
666 return array();
667 }
668
669 /**
670 * Importing IPs from exporter.
671 *
672 * @param Request $request The request object containing the data.
673 *
674 * @defender_route
675 * @return Response
676 */
677 public function import_ips( Request $request ) {
678 $data = $request->get_data(
679 array(
680 'id' => array(
681 'type' => 'int',
682 ),
683 )
684 );
685 $file = '';
686 $attached_id = $data['id'] ?? 0;
687 if ( 0 < $attached_id ) {
688 if ( ! is_object( get_post( $attached_id ) ) ) {
689 return new Response(
690 false,
691 array(
692 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ),
693 )
694 );
695 }
696
697 $file = get_attached_file( $attached_id );
698 } else {
699 $file_data = defender_get_data_from_request( 'file', 'f' );
700 if ( is_array( $file_data ) && isset( $file_data['tmp_name'] ) && is_uploaded_file( $file_data['tmp_name'] ) ) {
701 $file = $file_data['tmp_name'];
702 }
703 }
704
705 if ( ! is_string( $file ) || '' === $file || ! is_file( $file ) ) {
706 return new Response(
707 false,
708 array(
709 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ),
710 )
711 );
712 }
713
714 $data = $this->service->verify_import_file( $file );
715 if ( ! $data ) {
716 return new Response(
717 false,
718 array(
719 'message' => esc_html__( 'Your file content is invalid!', 'defender-security' ),
720 )
721 );
722 }
723
724 // All good, start to import.
725 foreach ( $data as $line ) {
726 $this->model->add_to_list( $line[0], $line[1] );
727 }
728
729 return new Response(
730 true,
731 array_merge(
732 array(
733 'message' => esc_html__( 'Your allowlist/blocklist has been successfully imported.', 'defender-security' ),
734 'auto_close' => true,
735 ),
736 $this->data_frontend()
737 )
738 );
739 }
740
741 /**
742 * Update the geolocation database.
743 *
744 * @return void
745 * @throws Exception If table is not defined.
746 * @since 2.8.0
747 */
748 public function update_database() {
749 if ( '' === $this->model->maxmind_license_key ) {
750 return;
751 }
752
753 $service_geo = wd_di()->get( MaxMind_Geolocation::class );
754 $service_geo->delete_database();
755
756 $tmp = $service_geo->get_downloaded_url( $this->model->maxmind_license_key );
757 if ( is_wp_error( $tmp ) ) {
758 $this->log( 'CRON error downloading from MaxMind: ' . $tmp->get_error_message(), Firewall::FIREWALL_LOG );
759
760 return;
761 }
762
763 $geodb_path = $service_geo->extract_db_archive( $tmp );
764 if ( is_wp_error( $geodb_path ) ) {
765 $this->log( 'CRON error extracting MaxMind archive: ' . $geodb_path->get_error_message(), Firewall::FIREWALL_LOG );
766
767 return;
768 }
769 $this->model->geodb_path = $geodb_path;
770 $this->model->save();
771 }
772 }
773