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-audit-logging.php +297 -217 5.5.1 → 6.3.0 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Handles audit logging functionalities .
3 + * Handle Audit Logging module.
4 4 *
5 5 * @package WP_Defender\Controller
6 6 */
7 7
@@ -8,11 +8,10 @@
8 8 namespace WP_Defender\Controller;
9 9
10 10 use DateTime;
11 11 use Exception;
12 -use DateInterval;
12 +use Generator;
13 13 use WP_Defender\Event;
14 -use Calotes\Helper\HTTP;
15 14 use WP_Defender\Traits\User;
16 15 use Calotes\Component\Request;
17 16 use Calotes\Component\Response;
18 17 use Calotes\Helper\Array_Cache;
@@ -19,17 +18,21 @@
19 18 use WP_Defender\Traits\Formats;
20 19 use WP_Defender\Component\Audit;
21 20 use WP_Defender\Model\Audit_Log;
22 21 use WP_Defender\Behavior\WPMUDEV;
22 +use WP_Defender\Component\Network_Cron_Manager;
23 23 use WP_Defender\Model\Notification\Audit_Report;
24 +use WP_Defender\Component\Export\Csv;
24 25 use WP_Defender\Component\Config\Config_Hub_Helper;
25 26 use WP_Defender\Model\Setting\Audit_Logging as Model_Audit_Logging;
26 27
27 28 /**
28 - * Handles audit logging functionalities .
29 + * Handle Audit Logging module.
29 30 */
30 31 class Audit_Logging extends Event {
31 32
33 + public const DEFAULT_PER_PAGE = 10;
34 +
32 35 use User;
33 36 use Formats;
34 37
35 38 /**
@@ -53,13 +56,20 @@
53 56 */
54 57 public ?Audit $service;
55 58
56 59 /**
60 + * Indicates whether the current installation is a pro version.
61 + *
62 + * @var bool
63 + */
64 + private $is_pro;
65 +
66 + /**
57 67 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
58 68 */
59 69 public function __construct() {
60 70 $this->register_page(
61 - esc_html( Model_Audit_Logging::get_module_name() ),
71 + esc_html__( 'Audit Log', 'defender-security' ),
62 72 $this->slug,
63 73 array( $this, 'main_view' ),
64 74 $this->parent_slug
65 75 );
@@ -66,45 +76,121 @@
66 76 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
67 77 $this->model = wd_di()->get( Model_Audit_Logging::class );
68 78 $this->service = new Audit();
69 79 $this->register_routes();
70 - if ( $this->model->is_active() ) {
80 + $this->is_pro = wd_di()->get( WPMUDEV::class )->is_pro();
81 + if ( $this->is_active() ) {
71 82 $this->service->enqueue_event_listener();
72 83 add_action( 'shutdown', array( $this, 'cache_audit_logs' ) );
84 +
73 85 /**
74 - * We will schedule the time for flush data into cloud.
86 + * Network Cron Manager
87 + *
88 + * @var Network_Cron_Manager $network_cron_manager
75 89 */
76 - if ( ! wp_next_scheduled( 'audit_sync_events' ) ) {
77 - wp_schedule_event( time() + 15, 'hourly', 'audit_sync_events' );
78 - }
79 - add_action( 'audit_sync_events', array( $this, 'sync_events' ) );
90 + $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
91 + $network_cron_manager->register_callback(
92 + 'audit_clean_up_logs',
93 + array( $this->service, 'audit_clean_up_logs' ),
94 + HOUR_IN_SECONDS
95 + );
96 + }
97 + }
80 98
81 - /**
82 - * We will schedule the time to clean up old logs.
83 - */
84 - if ( is_multisite() ) {
85 - $network_cron_manager = wd_di()->get( \WP_Defender\Component\Network_Cron_Manager::class );
86 - $network_cron_manager->register_callback(
87 - 'audit_clean_up_logs',
88 - array( $this->service, 'audit_clean_up_logs' ),
89 - HOUR_IN_SECONDS
99 + /**
100 + * Is there permission to Pro feature?
101 + *
102 + * @return bool
103 + */
104 + private function is_active(): bool {
105 + return $this->model->is_active();
106 + }
107 +
108 + /**
109 + * Parses a raw request params array, resolves filters, and fetches audit logs.
110 + *
111 + * Accepted keys (all optional except date range):
112 + * date_from — date string or empty (defaults to -7 days midnight).
113 + * date_to — date string or empty (defaults to today 23:59:59).
114 + * events — array of event type strings (also accepts key 'event_type' for CSV route).
115 + * username — login name (also accepts key 'term' for CSV route).
116 + * ip_address — IP filter string.
117 + * paged — page number; pass false for unbounded export.
118 + *
119 + * Returns the resolved user_id via the $user_id out-param so callers can use it for COUNT queries.
120 + *
121 + * @param array $params Raw request data.
122 + * @param mixed $paged Page number or false for all results.
123 + * @param int|string $user_id Populated with the resolved user ID (out-param).
124 + * @param int $per_page Number of logs per page.
125 + *
126 + * @return array|\WP_Error
127 + */
128 + private function fetch_logs( array $params, $paged, &$user_id = '', $per_page = self::DEFAULT_PER_PAGE ) {
129 + $date_from_str = $params['date_from'] ?? '';
130 + $date_to_str = $params['date_to'] ?? '';
131 + $date_from = $date_from_str ? $this->date_string_to_timestamp( $date_from_str ) : strtotime( '-7 days midnight' );
132 + $date_to = $date_to_str ? $this->date_string_to_timestamp( $date_to_str, true ) : strtotime( 'today 23:59:59' );
133 + $events = $params['events'] ?? ( isset( $params['event_type'] ) && is_array( $params['event_type'] ) ? $params['event_type'] : array() );
134 + $username = $params['username'] ?? ( $params['term'] ?? '' );
135 + $ip_address = $params['ip_address'] ?? '';
136 + $user_id = '';
137 +
138 + if ( '' !== $username ) {
139 + $user = get_user_by( 'login', $username );
140 + if ( ! is_object( $user ) ) {
141 + // Try matching by display_name or email before giving up.
142 + $users = get_users(
143 + array(
144 + 'search' => '*' . $username . '*',
145 + 'search_columns' => array( 'display_name' ),
146 + 'number' => 1,
147 + )
90 148 );
149 + if ( array() === $users ) {
150 + return array();
151 + }
152 + $user_id = $users[0]->ID;
91 153 } else {
92 - if ( ! wp_next_scheduled( 'audit_clean_up_logs' ) ) {
93 - wp_schedule_event( time(), 'hourly', 'audit_clean_up_logs' );
94 - }
95 - add_action( 'audit_clean_up_logs', array( $this->service, 'audit_clean_up_logs' ) );
154 + $user_id = $user->ID;
96 155 }
97 156 }
157 +
158 + return $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged, $per_page );
98 159 }
99 160
100 161 /**
101 - * Sync all the events into cloud, this will happen per hourly basis.
162 + * Prefetches users from a result set then formats each log into an array.
102 163 *
103 - * @return void
164 + * @param array $result Array of Audit_Log objects.
165 + *
166 + * @return array
104 167 */
105 - public function sync_events(): void {
106 - $this->service->flush();
168 + private function format_log_rows( array $result ): array {
169 + $user_ids = array_filter(
170 + array_unique( array_column( $result, 'user_id' ) ),
171 + static fn ( $user_id ): bool => (int) $user_id > 0
172 + );
173 + if ( array() !== $user_ids ) {
174 + get_users( array( 'include' => $user_ids ) );
175 + }
176 +
177 + $logs = array();
178 + foreach ( $result as $item ) {
179 + $item_user_id = isset( $item->user_id ) ? $item->user_id : 0;
180 + $item_user_id = ! is_int( $item_user_id ) ? (int) $item_user_id : $item_user_id;
181 + $logs[] = array_merge(
182 + $item->export(),
183 + array(
184 + 'user' => $this->get_user_display( $item->user_id ),
185 + 'user_url' => $item_user_id > 0 ? get_edit_user_link( $item_user_id ) : '',
186 + 'log_date' => $this->get_date( $item->timestamp ),
187 + 'format_date' => $this->format_date_time( $item->timestamp ),
188 + )
189 + );
190 + }
191 +
192 + return $logs;
107 193 }
108 194
109 195 /**
110 196 * Exports audit logs as a CSV file.
@@ -113,30 +199,20 @@
113 199 * @throws Exception If there is an error during export.
114 200 * @defender_route
115 201 */
116 202 public function export_as_csv(): void {
117 - $date_from = HTTP::get(
118 - 'date_from',
119 - wp_date( 'Y-m-d H:i:s', strtotime( '-7 days', time() ) )
120 - );
121 - $date_to = HTTP::get( 'date_to', wp_date( 'Y-m-d H:i:s', time() ) );
122 - // Convert date using timezone.
123 - $timezone = wp_timezone();
124 - $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
125 - $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
126 - $username = HTTP::get( 'term', '' );
127 - $user_id = '';
128 - $user = get_user_by( 'login', $username );
129 - $events = HTTP::get( 'event_type', array() );
130 - if ( is_object( $user ) ) {
131 - $user_id = $user->ID;
203 + $params = defender_get_data_from_request( null, 'g' );
204 + $user_id = '';
205 + $result = $this->fetch_logs( $params, 1, $user_id, 1000 );
206 +
207 + if ( is_wp_error( $result ) ) {
208 + wp_send_json_error(
209 + array(
210 + 'message' => $result->get_error_message(),
211 + )
212 + );
132 213 }
133 214
134 - $handler = new Audit();
135 - $ip_address = HTTP::get( 'ip_address', '' );
136 - $result = $handler->fetch( $date_from, $date_to, $events, $user_id, $ip_address, false );
137 - // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
138 - $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
139 215 $headers = array(
140 216 esc_html__( 'Summary', 'defender-security' ),
141 217 esc_html__( 'Date / Time', 'defender-security' ),
142 218 esc_html__( 'Context', 'defender-security' ),
@@ -143,33 +219,56 @@
143 219 esc_html__( 'Type', 'defender-security' ),
144 220 esc_html__( 'IP address', 'defender-security' ),
145 221 esc_html__( 'User', 'defender-security' ),
146 222 );
147 - fputcsv( $fp, $headers, ',', '"', '\\' );
148 - foreach ( $result as $log ) {
149 - $fields = $log->export();
150 - $vars = array(
151 - $fields['msg'],
152 - is_array( $fields['timestamp'] )
153 - ? $this->format_date_time( $fields['timestamp'][0] )
154 - : $this->format_date_time( $fields['timestamp'] ),
155 - $fields['context'],
156 - $fields['action_type'],
157 - $fields['ip'],
158 - $this->get_user_display( $fields['user_id'] ),
159 - );
160 - fputcsv( $fp, $vars, ',', '"', '\\' );
223 + try {
224 + Csv::to_browser( 'audit-logs', $this->get_export_rows( $params, $result ), $headers );
225 + } catch ( \RuntimeException $e ) {
226 + $this->log( $e->getMessage(), 'audit-logging' );
161 227 }
162 - $filename = 'wdf-audit-logs-export-' . wp_date( 'ymdHis' ) . '.csv';
163 - fseek( $fp, 0 );
164 - header( 'Content-Type: text/csv' );
165 - header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
166 - // Make php send the generated csv lines to the browser.
167 - fpassthru( $fp );
168 - exit();
169 228 }
170 229
171 230 /**
231 + * Generates audit log export rows in database-backed pages.
232 + *
233 + * @param array $params Request filters.
234 + * @param array $logs First page of audit logs.
235 + *
236 + * @return Generator
237 + * @throws Exception If there is an error while fetching logs.
238 + */
239 + private function get_export_rows( array $params, array $logs ): Generator {
240 + $page = 1;
241 + $per_page = 1000;
242 + $user_id = '';
243 +
244 + do {
245 + if ( 1 < $page ) {
246 + $logs = $this->fetch_logs( $params, $page, $user_id, $per_page );
247 + if ( is_wp_error( $logs ) ) {
248 + throw new Exception( esc_html( $logs->get_error_message() ) );
249 + }
250 + }
251 +
252 + foreach ( $this->format_log_rows( $logs ) as $row ) {
253 + yield array(
254 + $row['msg'],
255 + is_array( $row['timestamp'] )
256 + ? $this->format_date_time( $row['timestamp'][0] )
257 + : $this->format_date_time( $row['timestamp'] ),
258 + $row['context'],
259 + $row['action_type'],
260 + $row['ip'],
261 + $row['user'],
262 + );
263 + }
264 +
265 + $has_more = count( $logs ) === $per_page;
266 + ++$page;
267 + } while ( $has_more );
268 + }
269 +
270 + /**
172 271 * We'll pass all the event logs into the db handler, so it writes down to db.
173 272 * Do it in shutdown runtime, so no delay time.
174 273 *
175 274 * @return void
@@ -217,66 +316,44 @@
217 316 'paged' => array(
218 317 'type' => 'int',
219 318 'sanitize' => 'sanitize_text_field',
220 319 ),
320 + 'per_page' => array(
321 + 'type' => 'int',
322 + 'sanitize' => 'sanitize_text_field',
323 + ),
221 324 )
222 325 );
223 - if ( empty( $data['date_from'] ) || empty( $data['date_to'] ) ) {
326 + if ( ! isset( $data['date_from'] ) || ! isset( $data['date_to'] ) ) {
224 327 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
225 328 }
226 - // Convert date using timezone.
227 - $timezone = wp_timezone();
228 - $date_from = ( new DateTime( $data['date_from'], $timezone ) )
229 - ->setTime( 0, 0, 0 )
230 - ->getTimestamp();
231 - $date_to = ( new DateTime( $data['date_to'], $timezone ) )
232 - ->setTime( 23, 59, 59 )
233 - ->getTimestamp();
329 + // Validate date strings before passing to fetch_logs.
330 + $date_from_check = $this->date_string_to_timestamp( $data['date_from'] );
331 + $date_to_check = $this->date_string_to_timestamp( $data['date_to'], true );
332 + if ( $date_from_check <= 0 || $date_to_check <= 0 ) {
333 + return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
334 + }
234 335
235 - $events = $data['events'] ?? array();
236 - $ip_address = $data['ip_address'] ?? '';
237 - $paged = $data['paged'] ?? 1;
238 - $username = $data['username'] ?? '';
239 - $user_id = '';
240 - if ( ! empty( $username ) ) {
241 - $user = get_user_by( 'login', $username );
242 - if ( is_object( $user ) ) {
243 - $user_id = $user->ID;
244 - // Fetch result with the specified user.
245 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
246 - } else {
247 - // A non-existent username.
248 - $result = array();
249 - }
250 - } else {
251 - // Fetch result with empty user field.
252 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
253 - }
336 + $paged = $data['paged'] ?? 1;
337 + $per_page = min( 100, max( 1, (int) ( $data['per_page'] ?? self::DEFAULT_PER_PAGE ) ) );
338 + $result = $this->fetch_logs( $data, $paged, $user_id, $per_page );
254 339
255 340 if ( is_wp_error( $result ) ) {
256 341 return new Response( false, array( 'message' => $result->get_error_message() ) );
257 342 }
258 - $logs = array();
259 - if ( ! empty( $result ) ) {
260 - foreach ( $result as $item ) {
261 - $logs[] = array_merge(
262 - $item->export(),
263 - array(
264 - 'user' => $this->get_user_display( $item->user_id ),
265 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
266 - 'log_date' => $this->get_date( $item->timestamp ),
267 - 'format_date' => $this->format_date_time( $item->timestamp ),
268 - )
269 - );
270 - }
271 - }
343 + $logs = is_array( $result ) && array() !== $result ? $this->format_log_rows( $result ) : array();
344 +
272 345 // @since 3.0.0 If no logs then $count = 0.
273 - if ( empty( $logs ) ) {
346 + // Skip the COUNT query when the result is a partial page — the fetch already told us the total is count($logs).
347 + $events = $data['events'] ?? array();
348 + $ip_address = $data['ip_address'] ?? '';
349 + if ( array() === $logs ) {
274 350 $count = 0;
351 + } elseif ( count( $logs ) < $per_page ) {
352 + $count = ( $paged - 1 ) * $per_page + count( $logs );
275 353 } else {
276 - $count = Audit_Log::count( $date_from, $date_to, $events, $user_id, $ip_address );
354 + $count = Audit_Log::count( $date_from_check, $date_to_check, $events, $user_id, $ip_address );
277 355 }
278 - $per_page = 20;
279 356
280 357 // Get the count for the submitted data.
281 358 return new Response(
282 359 true,
@@ -314,20 +391,40 @@
314 391 }
315 392
316 393 /**
317 394 * Enqueues scripts and styles for this page.
318 - * Only enqueues assets if the page is active.
319 395 */
320 - public function enqueue_assets() {
396 + public function enqueue_assets(): void {
321 397 if ( ! $this->is_page_active() ) {
322 398 return;
323 399 }
400 +
401 + $handle = 'defender-ui-audit-logging';
402 + wp_enqueue_script(
403 + $handle,
404 + WP_DEFENDER_BASE_URL . 'assets/js/audit-logging-ui.js',
405 + array( 'def-vue', 'def-manifest', 'def-core-ui', 'defender', 'wp-i18n' ),
406 + DEFENDER_VERSION,
407 + true
408 + );
409 + wp_set_script_translations( $handle, 'wpdef' );
410 +
324 411 wp_localize_script(
325 - 'def-audit',
326 - 'audit',
327 - $this->data_frontend()
412 + $handle,
413 + 'defenderUIData',
414 + array_merge(
415 + $this->get_shared_data(),
416 + $this->data_frontend()
417 + )
328 418 );
329 - wp_enqueue_script( 'def-audit' );
419 +
420 + wp_enqueue_style(
421 + $handle,
422 + WP_DEFENDER_BASE_URL . 'assets/css/core-ui.css',
423 + array(),
424 + DEFENDER_VERSION
425 + );
426 +
330 427 $this->enqueue_main_assets();
331 428 }
332 429
333 430 /**
@@ -346,9 +443,9 @@
346 443 * @throws Exception If there is an error during summary generation.
347 444 * @defender_route
348 445 */
349 446 public function summary(): void {
350 - $response = $this->model->is_active() ? $this->summary_data() : array();
447 + $response = $this->is_active() ? $this->summary_data() : array();
351 448 wp_send_json_success( $response );
352 449 }
353 450
354 451 /**
@@ -359,23 +456,25 @@
359 456 * @return array
360 457 * @throws Exception Emits Exception in case of an error.
361 458 */
362 459 public function summary_data( bool $for_hub = false ): array {
460 + $timezone = wp_timezone();
461 +
363 462 // Monthly count.
364 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )
463 + $date_from = ( new DateTime( '-30 days', $timezone ) )
365 464 ->setTime( 0, 0, 0 )
366 465 ->getTimestamp();
367 - $date_to = ( new DateTime( wp_date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp();
466 + $date_to = ( new DateTime( 'now', $timezone ) )
467 + ->setTime( 23, 59, 59 )
468 + ->getTimestamp();
368 469 $month_count = Audit_Log::count( $date_from, $date_to );
369 - // Weekly count.
370 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-7 days' ) ) ) )
371 - ->setTime( 0, 0, 0 )
372 - ->getTimestamp();
373 - $week_count = Audit_Log::count( $date_from, $date_to );
470 +
471 + // Weekly count sets in the returned array.
374 472 // Daily count. Sync data to the Hub without timezone.
375 473 $date_from = $for_hub ? new DateTime( 'now' ) : new DateTime( 'now', wp_timezone() );
376 474 $date_from = $date_from->modify( '-24 hours' )->setTime( 0, 0, 0 )->getTimestamp();
377 475 $day_count = Audit_Log::count( $date_from, $date_to );
476 +
378 477 // Get the last item.
379 478 $last = Audit_Log::get_last();
380 479 if ( is_object( $last ) ) {
381 480 $last = $for_hub
@@ -386,9 +485,9 @@
386 485 }
387 486
388 487 return array(
389 488 'monthCount' => $month_count,
390 - 'weekCount' => $week_count,
489 + 'weekCount' => Audit::get_audit_events_per_week(),
391 490 'dayCount' => $day_count,
392 491 'lastEvent' => $last,
393 492 'report' => wd_di()->get( Audit_Report::class )->to_string(),
394 493 );
@@ -403,12 +502,8 @@
403 502 * @defender_route
404 503 */
405 504 public function save_settings( Request $request ): Response {
406 505 $data = $request->get_data_by_model( $this->model );
407 - if ( false === $data['enabled'] && $data['enabled'] !== $this->model->is_active() ) {
408 - // Toggle off, so we need to flush everything to cloud.
409 - $this->service->flush();
410 - }
411 506
412 507 $this->model->import( $data );
413 508 if ( $this->model->validate() ) {
414 509 $this->model->save();
@@ -422,8 +517,9 @@
422 517 $this->data_frontend(),
423 518 array(
424 519 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
425 520 'auto_close' => true,
521 + 'model' => $this->model->export(),
426 522 )
427 523 )
428 524 );
429 525 }
@@ -428,8 +524,22 @@
428 524 );
429 525 }
430 526
431 527 /**
528 + * Provides the audit settings data used outside the Audit Logging page.
529 + *
530 + * @return array
531 + */
532 + public function settings_data_frontend(): array {
533 + return array_merge(
534 + array(
535 + 'model' => $this->model->export(),
536 + ),
537 + $this->dump_routes_and_nonces()
538 + );
539 + }
540 +
541 + /**
432 542 * Converts the current state of the object to an array.
433 543 *
434 544 * @return array Returns an associative array of object properties.
435 545 */
@@ -435,9 +545,9 @@
435 545 */
436 546 public function to_array(): array {
437 547 return array_merge(
438 548 array(
439 - 'enabled' => $this->model->is_active(),
549 + 'enabled' => $this->is_active(),
440 550 'report' => true,
441 551 ),
442 552 $this->dump_routes_and_nonces()
443 553 );
@@ -459,9 +569,9 @@
459 569 Array_Cache::remove( 'sockets', 'audit' );
460 570 Array_Cache::remove( 'logs', 'audit' );
461 571 Array_Cache::remove( 'menu_updated', 'audit' );
462 572 Array_Cache::remove( 'post_updated', 'audit' );
463 - delete_site_option( Audit::CACHE_LAST_CHECKPOINT );
573 + delete_site_option( 'wd_audit_fetch_checkpoint' );
464 574 }
465 575
466 576 /**
467 577 * Provides data for the frontend.
@@ -471,56 +581,55 @@
471 581 */
472 582 public function data_frontend(): array {
473 583 $logs = array();
474 584 $count = 0;
475 - $per_page = 20;
585 + $per_page = self::DEFAULT_PER_PAGE;
476 586 $total_page = 1;
477 - if ( $this->model->is_active() ) {
478 - $timezone = wp_timezone();
479 - $date_from = ( new DateTime() )->setTimezone( $timezone )
480 - ->sub( new DateInterval( 'P7D' ) )->setTime( 0, 0, 0 );
481 - $date_to = ( new DateTime() )->setTimezone( $timezone )->setTime( 23, 59, 59 );
482 - $result = $this->service->fetch(
587 + if ( $this->is_active() ) {
588 + $timezone = wp_timezone();
589 + $date_from = ( new DateTime( '-7 days', $timezone ) )->setTime( 0, 0, 0 );
590 + $date_to = ( new DateTime( 'now', $timezone ) )->setTime( 23, 59, 59 );
591 + $ip_address = sanitize_text_field( defender_get_data_from_request( 'ip', 'g' ) );
592 + $result = $this->service->fetch(
483 593 $date_from->getTimestamp(),
484 594 $date_to->getTimestamp(),
485 595 array(),
486 596 '',
487 - '',
488 - 1
597 + $ip_address,
598 + 1,
599 + $per_page
489 600 );
490 601 if ( ! is_wp_error( $result ) ) {
491 - foreach ( $result as $item ) {
492 - $logs[] = array_merge(
493 - $item->export(),
494 - array(
495 - 'user' => $this->get_user_display( $item->user_id ),
496 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
497 - 'log_date' => $this->get_date( $item->timestamp ),
498 - 'format_date' => $this->format_date_time( $item->timestamp ),
499 - )
500 - );
602 + $logs = $this->format_log_rows( $result );
603 + // Skip COUNT query when the first page is a partial result — it's cheaper than a second DB round-trip.
604 + if ( count( $logs ) < $per_page ) {
605 + $count = count( $logs );
606 + } else {
607 + $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp(), array(), '', $ip_address );
501 608 }
502 - $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() );
503 609 $total_page = ceil( $count / $per_page );
504 610 }
505 611 }
506 612
507 - return array_merge(
508 - array(
509 - 'model' => $this->model->export(),
510 - 'logs' => $logs,
511 - 'events_type' => Audit_Log::allowed_events(),
512 - 'summary' => array(
513 - 'count_7_days' => $count,
514 - 'report' => wd_di()->get( Audit_Report::class )->to_string(),
613 + return array(
614 + 'auditLogging' => array_merge(
615 + array(
616 + 'model' => $this->model->export(),
617 + 'logs' => $logs,
618 + 'events_type' => Audit_Log::allowed_events(),
619 + 'summary' => array(
620 + 'count_7_days' => $count,
621 + 'report' => wd_di()->get( Audit_Report::class )->to_string(),
622 + ),
623 + 'paging' => array(
624 + 'paged' => 1,
625 + 'total_pages' => $total_page,
626 + 'count' => $count,
627 + ),
515 628 ),
516 - 'paging' => array(
517 - 'paged' => 1,
518 - 'total_pages' => $total_page,
519 - 'count' => $count,
520 - ),
629 + $this->dump_routes_and_nonces()
521 630 ),
522 - $this->dump_routes_and_nonces()
631 + 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
523 632 );
524 633 }
525 634
526 635 /**
@@ -531,9 +640,9 @@
531 640 * @throws Exception If table is not defined.
532 641 */
533 642 public function import_data( array $data ) {
534 643 $model = $this->model;
535 - if ( empty( $data ) ) {
644 + if ( array() === $data ) {
536 645 $model->enabled = false;
537 646 $model->storage_days = '6 months';
538 647 $model->save();
539 648 } else {
@@ -549,67 +658,38 @@
549 658 *
550 659 * @return array An array of strings.
551 660 */
552 661 public function export_strings(): array {
553 - if ( ! ( new WPMUDEV() )->is_pro() ) {
554 - return array(
555 - sprintf(
556 - /* translators: %s: Html for Pro-tag. */
557 - esc_html__( 'Inactive %s', 'defender-security' ),
558 - '<span class="sui-tag sui-tag-pro">Pro</span>'
559 - ),
662 + $strings = $this->is_active()
663 + ? array( esc_html__( 'Active', 'defender-security' ) )
664 + : array( esc_html__( 'Inactive', 'defender-security' ) );
665 +
666 + $strings[] = sprintf(
667 + /* translators: %s: Html for Pro-tag. */
668 + esc_html__( 'Email report inactive %s', 'defender-security' ),
669 + '<span class="sui-tag sui-tag-pro">Pro</span>'
560 670 );
561 - }
562 671
563 - if ( $this->model->is_active() ) {
564 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
565 - $audit_report = new Audit_Report();
566 - if ( 'enabled' === $audit_report->status ) {
567 - $strings[] = sprintf(
568 - /* translators: %s: Frequency value. */
569 - esc_html__( 'Email reports sending %s', 'defender-security' ),
570 - $audit_report->frequency
571 - );
572 - }
573 - } else {
574 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
575 - }
576 -
577 672 return $strings;
578 673 }
579 674
580 675 /**
581 - * Generates configuration strings based on the provided configuration and
582 - * whether the product is a pro version.
676 + * Generates configuration strings based on the provided configuration.
583 677 *
584 678 * @param array $config Configuration data.
585 - * @param bool $is_pro Indicates if the product is a pro version.
586 679 *
587 680 * @return array Returns an array of configuration strings.
588 681 */
589 - public function config_strings( array $config, bool $is_pro ): array {
590 - if ( $is_pro ) {
591 - if ( $config['enabled'] ) {
592 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
593 - if ( isset( $config['report'] ) && 'enabled' === $config['report'] ) {
594 - $strings[] = sprintf(
595 - /* translators: %s: Frequency value. */
596 - esc_html__( 'Email reports sending %s', 'defender-security' ),
597 - $config['frequency']
598 - );
599 - }
600 - } else {
601 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
602 - }
603 - } else {
604 - $strings = array(
605 - sprintf(
606 - /* translators: %s: Html for Pro-tag. */
607 - esc_html__( 'Inactive %s', 'defender-security' ),
608 - '<span class="sui-tag sui-tag-pro">Pro</span>'
609 - ),
682 + public function config_strings( array $config ): array {
683 + $strings = $config['enabled']
684 + ? array( esc_html__( 'Active', 'defender-security' ) )
685 + : array( esc_html__( 'Inactive', 'defender-security' ) );
686 +
687 + $strings[] = sprintf(
688 + /* translators: %s: Html for Pro-tag. */
689 + esc_html__( 'Email report inactive %s', 'defender-security' ),
690 + '<span class="sui-tag sui-tag-pro">Pro</span>'
610 691 );
611 - }
612 692
613 693 return $strings;
614 694 }
615 695 }