HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux WebLive 5.15.0-79-generic #86-Ubuntu SMP Mon Jul 10 16:07:21 UTC 2023 x86_64
User: ubuntu (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/html/wptoho/wp-content/plugins/defender-security/src/controller/class-firewall-logs.php
<?php
/**
 * Handles firewall logs and interactions with Block list API service.
 *
 * @package WP_Defender\Controller
 */

namespace WP_Defender\Controller;

use DateTime;
use Exception;
use Countable;
use Valitron\Validator;
use Calotes\Helper\HTTP;
use WP_Defender\Controller;
use Calotes\Component\Request;
use Calotes\Component\Response;
use WP_Defender\Traits\Formats;
use WP_Defender\Behavior\WPMUDEV;
use WP_Defender\Model\Lockout_Log;
use WP_Defender\Component\User_Agent;
use WP_Defender\Component\IP\Global_IP;
use WP_Defender\Component\Table_Lockout;
use WP_Defender\Integrations\Antibot_Global_Firewall_Client;
use WP_Defender\Model\Setting\Blacklist_Lockout;
use WP_Defender\Model\Setting\User_Agent_Lockout;
use WP_Defender\Component\Firewall_Logs as Firewall_Logs_Component;

/**
 * Responsible for managing firewall logs, including bulk actions, exporting logs to CSV,
 *  toggling IP addresses and user agents, querying logs, and sending logs to the Block list API.
 */
class Firewall_Logs extends Controller {

	use Formats;

	/**
	 * The slug identifier for this controller.
	 *
	 * @var string
	 */
	protected $slug = 'wdf-ip-lockout';

	/**
	 * The WPMUDEV instance used for interacting with WPMUDEV services.
	 *
	 * @var WPMUDEV
	 */
	private $wpmudev;

	/**
	 * The client for interacting with the AntiBot Global Firewall API.
	 *
	 * @var Antibot_Global_Firewall_Client
	 */
	private $antibot_client;

	/**
	 * The transient key used to store the list of IP addresses from the
	 * Akismet service that are blocked in the firewall.
	 *
	 * @var string
	 */
	const AKISMET_BLOCKED_IPS = 'defender_akismet_blocked_ips';

	/**
	 * Constructor for the class.
	 *
	 * @param  Antibot_Global_Firewall_Client $antibot_client  The client for interacting with the Block list API service.
	 */
	public function __construct( Antibot_Global_Firewall_Client $antibot_client ) {
		$this->register_routes();
		add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );

		$this->wpmudev = wd_di()->get( WPMUDEV::class );

		$this->antibot_client = $antibot_client;

		/**
		 * Send Firewall logs to AntiBot Global Firewall API.
		 */
		if ( ! wp_next_scheduled( 'wpdef_firewall_send_compact_logs_to_api' ) ) {
			wp_schedule_event( time() + 15, 'twicedaily', 'wpdef_firewall_send_compact_logs_to_api' );
		}
		add_action( 'wpdef_firewall_send_compact_logs_to_api', array( $this, 'send_compact_logs_to_api' ) );
		if ( class_exists( 'Akismet' ) ) {
			add_filter( 'http_response', array( $this, 'akismet_http_response' ), 10, 3 );
		}
	}

	/**
	 * Bulk action handler for lockout logs.
	 *
	 * @param  Request $request  The request object containing the data.
	 *
	 * @return Response The response object with the result of the bulk action.
	 * @defender_route
	 */
	public function bulk( Request $request ) {
		$data = $request->get_data(
			array(
				'action' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ids'    => array(
					'type' => 'array',
				),
			)
		);
		$ids  = $data['ids'];
		$ips  = array();
		$logs = array();
		if ( is_array( $ids ) || $ids instanceof Countable ? count( $ids ) : 0 ) {
			foreach ( $ids as $id ) {
				$model = Lockout_Log::find_by_id( $id );
				if ( is_object( $model ) ) {
					$bl = wd_di()->get( Blacklist_Lockout::class );
					switch ( $data['action'] ) {
						case 'ban':
							$bl->remove_from_list( $model->ip, 'allowlist' );
							$bl->add_to_list( $model->ip, 'blocklist' );
							$ips[ $model->ip ] = $model->ip;
							$logs[]            = $model;
							break;
						case 'allowlist':
							$bl->remove_from_list( $model->ip, 'blocklist' );
							$bl->add_to_list( $model->ip, 'allowlist' );
							$ips[ $model->ip ] = $model->ip;
							$logs[]            = $model;
							break;
						case 'delete':
							$ips[ $model->ip ] = $model->ip;
							$model->delete();
							break;
						default:
							break;
					}
				}
			}
		}

		if ( count( $logs ) > 0 ) {
			$logs = Lockout_Log::format_logs( $logs );
		}

		switch ( $data['action'] ) {
			case 'allowlist':
				$messages = sprintf(
				/* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
					esc_html__(
						'IP %1$s has been added to your allowlist. You can control your allowlist in %2$s.',
						'defender-security'
					),
					implode( ', ', $ips ),
					'<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist#tab-ip-allowlist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
				);
				break;
			case 'ban':
				$messages = sprintf(
				/* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
					esc_html__(
						'IP %1$s has been added to your blocklist. You can control your blocklist in %2$s.',
						'defender-security'
					),
					implode( ', ', $ips ),
					'<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
				);
				break;
			case 'delete':
				$messages = sprintf(
				/* translators: %s: IP Address(es) */
					esc_html__( 'IP %s has been deleted', 'defender-security' ),
					implode( ', ', $ips )
				);
				break;
			default:
				$messages = '';
				break;
		}

		return new Response(
			true,
			array(
				'message' => $messages,
				'logs'    => $logs,
			)
		);
	}

	/**
	 * Export logs to CSV
	 *
	 * @return void
	 * @defender_route
	 * @throws Exception On failure.
	 */
	public function export_as_csv(): void {
		$date_from = HTTP::get( 'date_from', strtotime( '-7 days midnight' ) );
		$date_to   = HTTP::get( 'date_to', strtotime( 'tomorrow' ) );
		// Convert date using timezone.
		$timezone  = wp_timezone();
		$date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
		$date_to   = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
		$filters   = array(
			'from'       => $date_from,
			'to'         => $date_to,
			'type'       => HTTP::get( 'term', '' ),
			'ip'         => HTTP::get( 'ip', '' ),
			'ban_status' => HTTP::get( 'ban_status', '' ),
		);

		if ( 'all' === $filters['type'] ) {
			$filters['type'] = '';
		}

		if ( 'all' === $filters['ban_status'] ) {
			$filters['ban_status'] = '';
		}
		// User can export the number of logs that are set.
		$per_page = (int) defender_get_data_from_request( 'per_page', 'g' );
		if ( 0 === $per_page ) {
			$per_page = 20;
		}
		if ( - 1 === (int) $per_page ) {
			$per_page = false;
		}

		$paged = (int) defender_get_data_from_request( 'paged', 'g' );
		if ( 0 === $paged ) {
			$paged = 1;
		}
		$logs = Lockout_Log::query_logs( $filters, $paged, 'id', 'desc', $per_page );

		$tl_component = new Table_Lockout();

		$ua_component = wd_di()->get( User_Agent::class );

		$filename = 'wdf-lockout-logs-export-' . wp_date( 'ymdHis' ) . '.csv';

		header( 'Expires: 0' );
		header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
		header( 'Cache-Control: private', false );
		header( 'Content-Type: application/octet-stream' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
		header( 'Content-Transfer-Encoding: binary' );

		extension_loaded( 'zlib' ) ? ob_start( 'ob_gzhandler' ) : ob_start();

		$fp      = fopen( 'php://output', 'w' );
		$headers = array(
			esc_html__( 'Log', 'defender-security' ),
			esc_html__( 'Date / Time', 'defender-security' ),
			esc_html__( 'Type', 'defender-security' ),
			esc_html__( 'IP address', 'defender-security' ),
			esc_html__( 'IP Status', 'defender-security' ),
			esc_html__( 'User Agent Status', 'defender-security' ),
		);
		fputcsv( $fp, $headers, ',', '"', '\\' );

		$flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE;
		foreach ( $logs as $key => $log ) {
			$item = array(
				$log->log,
				$this->format_date_time( $log->date ),
				$tl_component->get_type( $log->type ),
				$log->ip,
				$tl_component->get_ip_status_text( $log->ip ),
				$ua_component->get_status_text( $log->type, $log->tried ),
			);
			fputcsv( $fp, $item, ',', '"', '\\' );

			if ( 0 === $key % $flush_limit ) {
				ob_flush();
				flush();
			}
		}

		fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		exit();
	}

	/**
	 * Toggles an IP address to or from a specified list.
	 *
	 * @param  Request $request  The HTTP request object.
	 *
	 * @return Response The HTTP response object.
	 * @defender_route
	 */
	public function toggle_ip_to_list( Request $request ): Response {
		$data = $request->get_data(
			array(
				'ip'         => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'list'       => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ban_status' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
			)
		);

		$ip         = $data['ip'];
		$collection = $data['list'];

		$model = wd_di()->get( Blacklist_Lockout::class );
		if ( $model->is_ip_in_list( $ip, $collection ) ) {
			$model->remove_from_list( $ip, $collection );
			/* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */
			$message = esc_html__(
				'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
				'defender-security'
			);
		} else {
			$model->add_to_list( $ip, $collection );

			$global_ip_service = wd_di()->get( Global_IP::class );
			if ( $global_ip_service->can_blocklist_autosync() ) {
				$global_ip_data = array(
					'block_list' => array( $ip ),
				);
				$global_ip_service->add_to_global_ip_list( $global_ip_data );
			}

			/* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */
			$message = esc_html__(
				'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
				'defender-security'
			);
		}
		$filter_data = $request->get_data(
			array(
				'date_from' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'date_to'   => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ip_filter' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'type'      => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'paged'     => array(
					'type'     => 'int',
					'sanitize' => 'sanitize_text_field',
				),
				'per_page'  => array(
					'type'     => 'int',
					'sanitize' => 'sanitize_text_field',
				),
			)
		);
		$logs        = Lockout_Log::get_logs_and_format(
			array(
				'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
				'to'   => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
				'ip'   => $filter_data['ip_filter'],
				// If this is all, then we set to null to exclude it from the filter.
				'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
			),
			$filter_data['paged'],
			'id',
			'desc',
			$filter_data['per_page']
		);

		return new Response(
			true,
			array(
				'message' => sprintf(
					$message,
					$data['ip'] ?? '-',
					$data['list'],
					$data['list'],
					'<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
				),
				'logs'    => $logs,
			)
		);
	}

	/**
	 * Toggles a user agent to/from a specified list based on the given request data.
	 *
	 * @param Request $request  The request object containing the data for toggling the user agent.
	 *
	 * @return Response The response object indicating the success or failure of the toggle operation.
	 * @defender_route
	 */
	public function toggle_ua_to_list( Request $request ): Response {
		$data = $request->get_data(
			array(
				'ua'       => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'list'     => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'scenario' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
			)
		);

		$ua         = $data['ua'];
		$collection = $data['list'];
		$action     = $data['scenario'];

		$model = wd_di()->get( User_Agent_Lockout::class );

		if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) {
			$model->remove_from_list( $ua, $collection );
			/* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
			$message = esc_html__(
				'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
				'defender-security'
			);
		} elseif ( 'add' === $action ) {

			/**
			 * 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`.
			 */
			if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) {
				$model->remove_from_list( $ua, 'allowlist' );
			}

			if ( ! $model->is_ua_in_list( $ua, $collection ) ) {
				$model->add_to_list( $ua, $collection );
			}
			/* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
			$message = esc_html__(
				'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
				'defender-security'
			);
		} else {
			return new Response(
				false,
				array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) )
			);
		}

		$filter_data = $request->get_data(
			array(
				'date_from' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'date_to'   => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ip_filter' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'type'      => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'paged'     => array(
					'type'     => 'int',
					'sanitize' => 'sanitize_text_field',
				),
				'per_page'  => array(
					'type'     => 'int',
					'sanitize' => 'sanitize_text_field',
				),
			)
		);
		$logs        = Lockout_Log::get_logs_and_format(
			array(
				'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
				'to'   => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
				'ip'   => $filter_data['ip_filter'],
				// If this is all, then we set to null to exclude it from the filter.
				'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
			),
			$filter_data['paged'],
			'id',
			'desc',
			$filter_data['per_page']
		);

		return new Response(
			true,
			array(
				'message'                 => sprintf(
					$message,
					'<strong>' . $data['ua'] . '</strong>',
					$data['list'],
					$data['list'],
					'<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>'
				),
				'logs'                    => $logs,
				// Include blocklist preset values for the frontend.
				'blocklist_preset_values' => $model->blocklist_preset_values,
			)
		);
	}

	/**
	 * Query the logs and display on frontend.
	 *
	 * @param Request $request  The request object containing filter parameters.
	 *
	 * @return Response
	 * @defender_route
	 * @throws Exception If an argument is not of the expected type.
	 */
	public function query_logs( Request $request ): Response {
		$data = $request->get_data(
			array(
				'date_from'  => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'date_to'    => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ip'         => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'type'       => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'paged'      => array(
					'type'     => 'int',
					'sanitize' => 'sanitize_text_field',
				),
				'sort'       => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
				'ban_status' => array(
					'type'     => 'string',
					'sanitize' => 'sanitize_text_field',
				),
			)
		);
		// Validate.
		$v = new Validator( $data, array() );
		$v->rule( 'required', array( 'date_from', 'date_to' ) );
		$v->rule( 'date', array( 'date_from', 'date_to' ) );
		if ( ! $v->validate() ) {
			return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) );
		}
		$sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
		switch ( $sort ) {
			case 'ip':
				$order    = 'desc';
				$order_by = 'ip';
				break;
			case 'oldest':
				$order    = 'asc';
				$order_by = 'id';
				break;
			case 'user_agent':
				$order    = 'asc';
				$order_by = 'user_agent';
				break;
			default:
				$order    = 'desc';
				$order_by = 'id';
				break;
		}
		// Convert date using timezone.
		$timezone  = wp_timezone();
		$date_from = ( new DateTime( $data['date_from'], $timezone ) )
			->setTime( 0, 0, 0 )
			->getTimestamp();
		$date_to   = ( new DateTime( $data['date_to'], $timezone ) )
			->setTime( 23, 59, 59 )
			->getTimestamp();

		$result = $this->retrieve_logs(
			array(
				'from'       => $date_from,
				'to'         => $date_to,
				'ip'         => $data['ip'],
				// If this is all, then we set to null to exclude it from the filter.
				'type'       => 'all' === $data['type'] ? '' : $data['type'],
				'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'],
			),
			$data['paged'],
			$order,
			$order_by
		);

		return new Response( true, $result );
	}

	/**
	 * Enqueues scripts and styles for this page.
	 * Only enqueues assets if the page is active.
	 */
	public function enqueue_assets() {
		if ( ! $this->is_page_active() ) {
			return;
		}
		wp_localize_script(
			'def-iplockout',
			'lockout_logs',
			array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
		);
	}

	/**
	 * Provides data for the frontend.
	 *
	 * @return array An array of data for the frontend.
	 */
	public function data_frontend(): array {
		$type = defender_get_data_from_request( 'type', 'g' );

		$def_filters  = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() );
		$init_filters = array(
			'from'       => strtotime( '-30 days' ),
			'to'         => time(),
			'type'       => $type,
			'ip'         => '',
			'ban_status' => '',
		);

		return array_merge( $this->retrieve_logs( $init_filters, 1 ), $def_filters );
	}

	/**
	 * Retrieves logs based on the given filters, paging, order, and order by.
	 *
	 * @param  array  $filters  An array containing the following keys:
	 *                               - 'from': The start date of the logs.
	 *                               - 'to': The end date of the logs.
	 *                               - 'type': The type of logs.
	 *                               - 'ip': The IP address of the logs.
	 *                               - 'ban_status': The ban status of the logs.
	 * @param  int    $paged  The page number of the logs to retrieve. Default is 1.
	 * @param  string $order  The order of the logs. Default is 'desc'.
	 * @param  string $order_by  The field to order the logs by. Default is 'id'.
	 *
	 * @return array An array containing the following keys:
	 *               - 'count': The total count of logs.
	 *               - 'logs': The retrieved logs.
	 *               - 'per_page': The number of logs per page.
	 *               - 'total_pages': The total number of pages.
	 */
	private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array {
		// User can set the number of logs to retrieve per page.
		$per_page = (int) defender_get_data_from_request( 'per_page', 'p' );
		if ( 0 === $per_page ) {
			$per_page = 20;
		}
		$conditions = array( 'ban_status' => $filters['ban_status'] );

		$count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
		$logs  = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page );
		return array(
			'count'       => $count,
			'logs'        => $logs,
			'per_page'    => $per_page,
			'total_pages' => ceil( $count / $per_page ),
		);
	}

	/**
	 * Converts the current object state to an array.
	 *
	 * @return array The array representation of the object.
	 */
	public function to_array(): array {
		return array();
	}

	/**
	 * Imports data into the model.
	 *
	 * @param  array $data  Data to be imported into the model.
	 */
	public function import_data( array $data ) {
	}

	/**
	 * Removes settings for all submodules.
	 */
	public function remove_settings() {
	}

	/**
	 * Delete all the data & the cache.
	 */
	public function remove_data() {
	}

	/**
	 * Exports strings.
	 *
	 * @return array An array of strings.
	 */
	public function export_strings(): array {
		return array();
	}

	/**
	 * Exports strings.
	 *
	 * @param array $logs Prepared logs.
	 */
	private function maybe_send_reports( array $logs ): void {
		$offset     = 0;
		$length     = 1000;
		$logs_chunk = array_slice( $logs, $offset, $length );
		while ( ! empty( $logs_chunk ) ) {
			$data = array(
				'logs' => $logs_chunk,
			);

			$response = $this->antibot_client->send_reports( $data );

			if ( is_wp_error( $response ) ) {
				$this->log(
					sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ),
					Firewall::FIREWALL_LOG
				);
			} elseif ( isset( $response['status'] ) && 'error' === $response['status'] ) {
				$this->log(
					sprintf( 'AntiBot Global Firewall Error: %s', $response['message'] ),
					Firewall::FIREWALL_LOG
				);
			}

			$offset    += $length;
			$logs_chunk = array_slice( $logs, $offset, $length );
		}

		$this->log( 'AntiBot Global Firewall: Process for sending logs completed.', Firewall::FIREWALL_LOG );
	}

	/**
	 * Send last 12 hours logs to AntiBot Global Firewall API.
	 * If running for first time then grab 7 days of logs.
	 * If last run difference is greater than 12 hours then grab 12+ hours of log but at most grab 7 days of logs.
	 *
	 * @return void
	 */
	public function send_compact_logs_to_api(): void {
		$site_id    = get_current_blog_id();
		$event_name = 'wpdef_firewall_send_compact_logs_to_api';
		$this->log( "Cron job {$event_name} triggered at site {$site_id}", Firewall::FIREWALL_LOG );

		/**
		 * Enable/disable sending Firewall logs to API.
		 *
		 * @param bool  $status  Status for sending logs. Send logs to API if true.
		 *
		 * @since 4.5.0
		 */
		$send_logs = (bool) apply_filters( 'wpdef_firewall_send_logs_to_api', true );

		if (
			! $send_logs ||
			! $this->wpmudev->is_dash_activated() ||
			! $this->wpmudev->is_site_connected_to_hub()
		) {
			return;
		}

		// Acquire lock before executing.
		if ( ! $this->acquire_cron_lock( $event_name, 'twicedaily' ) ) {
			$this->log( "{$event_name} is skipped running from site {$site_id}", Firewall::FIREWALL_LOG );
			return;
		}
		// Log the site ID where the event is triggered.
		$this->log( "{$event_name} is processing from site {$site_id}", Firewall::FIREWALL_LOG );
		$from = time() - ( 7 * DAY_IN_SECONDS );

		$last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' );
		if ( $last_run_time ) {
			$time_difference = time() - $last_run_time;

			if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds
				$from = $last_run_time;
			}
		}
		update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() );

		$service = wd_di()->get( Firewall_Logs_Component::class );
		$logs    = $service->get_compact_logs( $from );

		if ( ! empty( $logs ) ) {
			$this->maybe_send_reports( $logs );
		}

		$logs = $service->get_akismet_auto_spam_comment_logs();
		if ( ! empty( $logs ) ) {
			$this->maybe_send_reports( $logs );
		}

		// Release lock after execution.
		$this->release_cron_lock( $event_name );
	}

	/**
	 * Filters a successful HTTP API response before returning it.
	 *
	 * @param array  $response    HTTP response.
	 * @param array  $parsed_args HTTP request arguments.
	 * @param string $url         The request URL.
	 *
	 * @return array HTTP response.
	 */
	public function akismet_http_response( $response, $parsed_args, $url ) {
		// If the URL is not the Akismet comment-check endpoint, return the response as is.
		if ( 'https://rest.akismet.com/1.1/comment-check' !== $url ) {
			return $response;
		}

		// Retrieve response body safely.
		$body = wp_remote_retrieve_body( $response );
		// If the body is empty or does not equal 'true' (indicating spam), return the response as is.
		if ( empty( $body ) || trim( $body ) !== 'true' ) {
			return $response;
		}

		// Ensure the request body contains data; otherwise, return the response.
		if ( empty( $parsed_args['body'] ) ) {
			return $response;
		}

		$request_data = wp_parse_args( $parsed_args['body'] );
		// If the comment author's IP is not present in the request data, return the response.
		if ( empty( $request_data['comment_author_IP'] ) ) {
			return $response;
		}

		// Validate the user IP address from the request data.
		$user_ip = filter_var( $request_data['comment_author_IP'], FILTER_VALIDATE_IP );
		if ( false === $user_ip ) {
			return $response;
		}

		// Retrieve the current list of blocked IPs from the site transient.
		$option = get_site_transient( self::AKISMET_BLOCKED_IPS );
		// Ensure the retrieved data is an array; if not, initialize it as an empty array.
		if ( ! is_array( $option ) ) {
			$option = array();
		}

		// Increment the count of how many times this IP has been associated with spam.
		$option[ $user_ip ] = isset( $option[ $user_ip ] ) ? (int) $option[ $user_ip ] + 1 : 1;
		// Update the site transient with the new list of blocked IPs.
		set_site_transient( self::AKISMET_BLOCKED_IPS, $option );

		// Return the original HTTP response.
		return $response;
	}
}