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 +294 -202 5.7.0 → 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;
@@ -21,16 +20,19 @@
21 20 use WP_Defender\Model\Audit_Log;
22 21 use WP_Defender\Behavior\WPMUDEV;
23 22 use WP_Defender\Component\Network_Cron_Manager;
24 23 use WP_Defender\Model\Notification\Audit_Report;
24 +use WP_Defender\Component\Export\Csv;
25 25 use WP_Defender\Component\Config\Config_Hub_Helper;
26 26 use WP_Defender\Model\Setting\Audit_Logging as Model_Audit_Logging;
27 27
28 28 /**
29 - * Handles audit logging functionalities .
29 + * Handle Audit Logging module.
30 30 */
31 31 class Audit_Logging extends Event {
32 32
33 + public const DEFAULT_PER_PAGE = 10;
34 +
33 35 use User;
34 36 use Formats;
35 37
36 38 /**
@@ -54,13 +56,20 @@
54 56 */
55 57 public ?Audit $service;
56 58
57 59 /**
60 + * Indicates whether the current installation is a pro version.
61 + *
62 + * @var bool
63 + */
64 + private $is_pro;
65 +
66 + /**
58 67 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
59 68 */
60 69 public function __construct() {
61 70 $this->register_page(
62 - esc_html( Model_Audit_Logging::get_module_name() ),
71 + esc_html__( 'Audit Log', 'defender-security' ),
63 72 $this->slug,
64 73 array( $this, 'main_view' ),
65 74 $this->parent_slug
66 75 );
@@ -67,9 +76,10 @@
67 76 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
68 77 $this->model = wd_di()->get( Model_Audit_Logging::class );
69 78 $this->service = new Audit();
70 79 $this->register_routes();
71 - if ( $this->model->is_active() ) {
80 + $this->is_pro = wd_di()->get( WPMUDEV::class )->is_pro();
81 + if ( $this->is_active() ) {
72 82 $this->service->enqueue_event_listener();
73 83 add_action( 'shutdown', array( $this, 'cache_audit_logs' ) );
74 84
75 85 /**
@@ -82,15 +92,105 @@
82 92 'audit_clean_up_logs',
83 93 array( $this->service, 'audit_clean_up_logs' ),
84 94 HOUR_IN_SECONDS
85 95 );
86 - $network_cron_manager->register_callback(
87 - 'audit_sync_events',
88 - array( $this->service, 'flush' ),
89 - HOUR_IN_SECONDS,
90 - time() + 15
96 + }
97 + }
98 +
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 + )
148 + );
149 + if ( array() === $users ) {
150 + return array();
151 + }
152 + $user_id = $users[0]->ID;
153 + } else {
154 + $user_id = $user->ID;
155 + }
156 + }
157 +
158 + return $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged, $per_page );
159 + }
160 +
161 + /**
162 + * Prefetches users from a result set then formats each log into an array.
163 + *
164 + * @param array $result Array of Audit_Log objects.
165 + *
166 + * @return array
167 + */
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 + )
91 189 );
92 190 }
191 +
192 + return $logs;
93 193 }
94 194
95 195 /**
96 196 * Exports audit logs as a CSV file.
@@ -99,30 +199,20 @@
99 199 * @throws Exception If there is an error during export.
100 200 * @defender_route
101 201 */
102 202 public function export_as_csv(): void {
103 - $date_from = HTTP::get(
104 - 'date_from',
105 - wp_date( 'Y-m-d H:i:s', strtotime( '-7 days', time() ) )
106 - );
107 - $date_to = HTTP::get( 'date_to', wp_date( 'Y-m-d H:i:s', time() ) );
108 - // Convert date using timezone.
109 - $timezone = wp_timezone();
110 - $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
111 - $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
112 - $username = HTTP::get( 'term', '' );
113 - $user_id = '';
114 - $user = get_user_by( 'login', $username );
115 - $events = HTTP::get( 'event_type', array() );
116 - if ( is_object( $user ) ) {
117 - $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 + );
118 213 }
119 214
120 - $handler = new Audit();
121 - $ip_address = HTTP::get( 'ip_address', '' );
122 - $result = $handler->fetch( $date_from, $date_to, $events, $user_id, $ip_address, false );
123 - // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
124 - $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
125 215 $headers = array(
126 216 esc_html__( 'Summary', 'defender-security' ),
127 217 esc_html__( 'Date / Time', 'defender-security' ),
128 218 esc_html__( 'Context', 'defender-security' ),
@@ -129,33 +219,56 @@
129 219 esc_html__( 'Type', 'defender-security' ),
130 220 esc_html__( 'IP address', 'defender-security' ),
131 221 esc_html__( 'User', 'defender-security' ),
132 222 );
133 - fputcsv( $fp, $headers, ',', '"', '\\' );
134 - foreach ( $result as $log ) {
135 - $fields = $log->export();
136 - $vars = array(
137 - $fields['msg'],
138 - is_array( $fields['timestamp'] )
139 - ? $this->format_date_time( $fields['timestamp'][0] )
140 - : $this->format_date_time( $fields['timestamp'] ),
141 - $fields['context'],
142 - $fields['action_type'],
143 - $fields['ip'],
144 - $this->get_user_display( $fields['user_id'] ),
145 - );
146 - 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' );
147 227 }
148 - $filename = 'wdf-audit-logs-export-' . wp_date( 'ymdHis' ) . '.csv';
149 - fseek( $fp, 0 );
150 - header( 'Content-Type: text/csv' );
151 - header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
152 - // Make php send the generated csv lines to the browser.
153 - fpassthru( $fp );
154 - exit();
155 228 }
156 229
157 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 + /**
158 271 * We'll pass all the event logs into the db handler, so it writes down to db.
159 272 * Do it in shutdown runtime, so no delay time.
160 273 *
161 274 * @return void
@@ -203,66 +316,44 @@
203 316 'paged' => array(
204 317 'type' => 'int',
205 318 'sanitize' => 'sanitize_text_field',
206 319 ),
320 + 'per_page' => array(
321 + 'type' => 'int',
322 + 'sanitize' => 'sanitize_text_field',
323 + ),
207 324 )
208 325 );
209 - if ( empty( $data['date_from'] ) || empty( $data['date_to'] ) ) {
326 + if ( ! isset( $data['date_from'] ) || ! isset( $data['date_to'] ) ) {
210 327 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
211 328 }
212 - // Convert date using timezone.
213 - $timezone = wp_timezone();
214 - $date_from = ( new DateTime( $data['date_from'], $timezone ) )
215 - ->setTime( 0, 0, 0 )
216 - ->getTimestamp();
217 - $date_to = ( new DateTime( $data['date_to'], $timezone ) )
218 - ->setTime( 23, 59, 59 )
219 - ->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 + }
220 335
221 - $events = $data['events'] ?? array();
222 - $ip_address = $data['ip_address'] ?? '';
223 - $paged = $data['paged'] ?? 1;
224 - $username = $data['username'] ?? '';
225 - $user_id = '';
226 - if ( ! empty( $username ) ) {
227 - $user = get_user_by( 'login', $username );
228 - if ( is_object( $user ) ) {
229 - $user_id = $user->ID;
230 - // Fetch result with the specified user.
231 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
232 - } else {
233 - // A non-existent username.
234 - $result = array();
235 - }
236 - } else {
237 - // Fetch result with empty user field.
238 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
239 - }
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 );
240 339
241 340 if ( is_wp_error( $result ) ) {
242 341 return new Response( false, array( 'message' => $result->get_error_message() ) );
243 342 }
244 - $logs = array();
245 - if ( ! empty( $result ) ) {
246 - foreach ( $result as $item ) {
247 - $logs[] = array_merge(
248 - $item->export(),
249 - array(
250 - 'user' => $this->get_user_display( $item->user_id ),
251 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
252 - 'log_date' => $this->get_date( $item->timestamp ),
253 - 'format_date' => $this->format_date_time( $item->timestamp ),
254 - )
255 - );
256 - }
257 - }
343 + $logs = is_array( $result ) && array() !== $result ? $this->format_log_rows( $result ) : array();
344 +
258 345 // @since 3.0.0 If no logs then $count = 0.
259 - 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 ) {
260 350 $count = 0;
351 + } elseif ( count( $logs ) < $per_page ) {
352 + $count = ( $paged - 1 ) * $per_page + count( $logs );
261 353 } else {
262 - $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 );
263 355 }
264 - $per_page = 20;
265 356
266 357 // Get the count for the submitted data.
267 358 return new Response(
268 359 true,
@@ -300,20 +391,40 @@
300 391 }
301 392
302 393 /**
303 394 * Enqueues scripts and styles for this page.
304 - * Only enqueues assets if the page is active.
305 395 */
306 - public function enqueue_assets() {
396 + public function enqueue_assets(): void {
307 397 if ( ! $this->is_page_active() ) {
308 398 return;
309 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 +
310 411 wp_localize_script(
311 - 'def-audit',
312 - 'audit',
313 - $this->data_frontend()
412 + $handle,
413 + 'defenderUIData',
414 + array_merge(
415 + $this->get_shared_data(),
416 + $this->data_frontend()
417 + )
314 418 );
315 - 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 +
316 427 $this->enqueue_main_assets();
317 428 }
318 429
319 430 /**
@@ -332,9 +443,9 @@
332 443 * @throws Exception If there is an error during summary generation.
333 444 * @defender_route
334 445 */
335 446 public function summary(): void {
336 - $response = $this->model->is_active() ? $this->summary_data() : array();
447 + $response = $this->is_active() ? $this->summary_data() : array();
337 448 wp_send_json_success( $response );
338 449 }
339 450
340 451 /**
@@ -345,23 +456,25 @@
345 456 * @return array
346 457 * @throws Exception Emits Exception in case of an error.
347 458 */
348 459 public function summary_data( bool $for_hub = false ): array {
460 + $timezone = wp_timezone();
461 +
349 462 // Monthly count.
350 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )
463 + $date_from = ( new DateTime( '-30 days', $timezone ) )
351 464 ->setTime( 0, 0, 0 )
352 465 ->getTimestamp();
353 - $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();
354 469 $month_count = Audit_Log::count( $date_from, $date_to );
355 - // Weekly count.
356 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-7 days' ) ) ) )
357 - ->setTime( 0, 0, 0 )
358 - ->getTimestamp();
359 - $week_count = Audit_Log::count( $date_from, $date_to );
470 +
471 + // Weekly count sets in the returned array.
360 472 // Daily count. Sync data to the Hub without timezone.
361 473 $date_from = $for_hub ? new DateTime( 'now' ) : new DateTime( 'now', wp_timezone() );
362 474 $date_from = $date_from->modify( '-24 hours' )->setTime( 0, 0, 0 )->getTimestamp();
363 475 $day_count = Audit_Log::count( $date_from, $date_to );
476 +
364 477 // Get the last item.
365 478 $last = Audit_Log::get_last();
366 479 if ( is_object( $last ) ) {
367 480 $last = $for_hub
@@ -372,9 +485,9 @@
372 485 }
373 486
374 487 return array(
375 488 'monthCount' => $month_count,
376 - 'weekCount' => $week_count,
489 + 'weekCount' => Audit::get_audit_events_per_week(),
377 490 'dayCount' => $day_count,
378 491 'lastEvent' => $last,
379 492 'report' => wd_di()->get( Audit_Report::class )->to_string(),
380 493 );
@@ -389,12 +502,8 @@
389 502 * @defender_route
390 503 */
391 504 public function save_settings( Request $request ): Response {
392 505 $data = $request->get_data_by_model( $this->model );
393 - if ( false === $data['enabled'] && $data['enabled'] !== $this->model->is_active() ) {
394 - // Toggle off, so we need to flush everything to cloud.
395 - $this->service->flush();
396 - }
397 506
398 507 $this->model->import( $data );
399 508 if ( $this->model->validate() ) {
400 509 $this->model->save();
@@ -408,8 +517,9 @@
408 517 $this->data_frontend(),
409 518 array(
410 519 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
411 520 'auto_close' => true,
521 + 'model' => $this->model->export(),
412 522 )
413 523 )
414 524 );
415 525 }
@@ -414,8 +524,22 @@
414 524 );
415 525 }
416 526
417 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 + /**
418 542 * Converts the current state of the object to an array.
419 543 *
420 544 * @return array Returns an associative array of object properties.
421 545 */
@@ -421,9 +545,9 @@
421 545 */
422 546 public function to_array(): array {
423 547 return array_merge(
424 548 array(
425 - 'enabled' => $this->model->is_active(),
549 + 'enabled' => $this->is_active(),
426 550 'report' => true,
427 551 ),
428 552 $this->dump_routes_and_nonces()
429 553 );
@@ -445,9 +569,9 @@
445 569 Array_Cache::remove( 'sockets', 'audit' );
446 570 Array_Cache::remove( 'logs', 'audit' );
447 571 Array_Cache::remove( 'menu_updated', 'audit' );
448 572 Array_Cache::remove( 'post_updated', 'audit' );
449 - delete_site_option( Audit::CACHE_LAST_CHECKPOINT );
573 + delete_site_option( 'wd_audit_fetch_checkpoint' );
450 574 }
451 575
452 576 /**
453 577 * Provides data for the frontend.
@@ -457,58 +581,55 @@
457 581 */
458 582 public function data_frontend(): array {
459 583 $logs = array();
460 584 $count = 0;
461 - $per_page = 20;
585 + $per_page = self::DEFAULT_PER_PAGE;
462 586 $total_page = 1;
463 - if ( $this->model->is_active() ) {
464 - $timezone = wp_timezone();
465 - $date_from = ( new DateTime() )->setTimezone( $timezone )
466 - ->sub( new DateInterval( 'P7D' ) )->setTime( 0, 0, 0 );
467 - $date_to = ( new DateTime() )->setTimezone( $timezone )->setTime( 23, 59, 59 );
468 - $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(
469 593 $date_from->getTimestamp(),
470 594 $date_to->getTimestamp(),
471 595 array(),
472 596 '',
473 - '',
474 - 1
597 + $ip_address,
598 + 1,
599 + $per_page
475 600 );
476 601 if ( ! is_wp_error( $result ) ) {
477 - foreach ( $result as $item ) {
478 - $logs[] = array_merge(
479 - $item->export(),
480 - array(
481 - 'user' => $this->get_user_display( $item->user_id ),
482 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
483 - 'log_date' => $this->get_date( $item->timestamp ),
484 - 'format_date' => $this->format_date_time( $item->timestamp ),
485 - )
486 - );
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 );
487 608 }
488 - $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() );
489 609 $total_page = ceil( $count / $per_page );
490 610 }
491 611 }
492 612
493 - return array_merge(
494 - array(
495 - 'model' => $this->model->export(),
496 - 'logs' => $logs,
497 - 'events_type' => Audit_Log::allowed_events(),
498 - 'summary' => array(
499 - 'count_7_days' => $count,
500 - '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 + ),
501 628 ),
502 - 'paging' => array(
503 - 'paged' => 1,
504 - 'total_pages' => $total_page,
505 - 'count' => $count,
506 - ),
507 - 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
508 - 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
629 + $this->dump_routes_and_nonces()
509 630 ),
510 - $this->dump_routes_and_nonces()
631 + 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
511 632 );
512 633 }
513 634
514 635 /**
@@ -519,9 +640,9 @@
519 640 * @throws Exception If table is not defined.
520 641 */
521 642 public function import_data( array $data ) {
522 643 $model = $this->model;
523 - if ( empty( $data ) ) {
644 + if ( array() === $data ) {
524 645 $model->enabled = false;
525 646 $model->storage_days = '6 months';
526 647 $model->save();
527 648 } else {
@@ -537,67 +658,38 @@
537 658 *
538 659 * @return array An array of strings.
539 660 */
540 661 public function export_strings(): array {
541 - if ( ! ( new WPMUDEV() )->is_pro() ) {
542 - return array(
543 - sprintf(
544 - /* translators: %s: Html for Pro-tag. */
545 - esc_html__( 'Inactive %s', 'defender-security' ),
546 - '<span class="sui-tag sui-tag-pro">Pro</span>'
547 - ),
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>'
548 670 );
549 - }
550 671
551 - if ( $this->model->is_active() ) {
552 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
553 - $audit_report = new Audit_Report();
554 - if ( 'enabled' === $audit_report->status ) {
555 - $strings[] = sprintf(
556 - /* translators: %s: Frequency value. */
557 - esc_html__( 'Email reports sending %s', 'defender-security' ),
558 - $audit_report->frequency
559 - );
560 - }
561 - } else {
562 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
563 - }
564 -
565 672 return $strings;
566 673 }
567 674
568 675 /**
569 - * Generates configuration strings based on the provided configuration and
570 - * whether the product is a pro version.
676 + * Generates configuration strings based on the provided configuration.
571 677 *
572 678 * @param array $config Configuration data.
573 - * @param bool $is_pro Indicates if the product is a pro version.
574 679 *
575 680 * @return array Returns an array of configuration strings.
576 681 */
577 - public function config_strings( array $config, bool $is_pro ): array {
578 - if ( $is_pro ) {
579 - if ( $config['enabled'] ) {
580 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
581 - if ( isset( $config['report'] ) && 'enabled' === $config['report'] ) {
582 - $strings[] = sprintf(
583 - /* translators: %s: Frequency value. */
584 - esc_html__( 'Email reports sending %s', 'defender-security' ),
585 - $config['frequency']
586 - );
587 - }
588 - } else {
589 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
590 - }
591 - } else {
592 - $strings = array(
593 - sprintf(
594 - /* translators: %s: Html for Pro-tag. */
595 - esc_html__( 'Inactive %s', 'defender-security' ),
596 - '<span class="sui-tag sui-tag-pro">Pro</span>'
597 - ),
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>'
598 691 );
599 - }
600 692
601 693 return $strings;
602 694 }
603 695 }