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 +291 -220 5.3.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,49 +76,124 @@
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' ) );
73 - /**
74 - * We will schedule the time for flush data into cloud.
75 - */
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' ) );
80 84
81 85 /**
82 - * We will schedule the time to clean up old logs.
86 + * Network Cron Manager
87 + *
88 + * @var Network_Cron_Manager $network_cron_manager
83 89 */
84 - if ( ! wp_next_scheduled( 'audit_clean_up_logs' ) ) {
85 - wp_schedule_event( time(), 'hourly', 'audit_clean_up_logs' );
86 - }
87 - add_action( 'audit_clean_up_logs', array( $this, 'clean_up_audit_logs' ) );
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 + );
88 96 }
89 97 }
90 98
91 99 /**
92 - * Sync all the events into cloud, this will happen per hourly basis.
100 + * Is there permission to Pro feature?
93 101 *
94 - * @return void
102 + * @return bool
95 103 */
96 - public function sync_events(): void {
97 - $this->service->flush();
104 + private function is_active(): bool {
105 + return $this->model->is_active();
98 106 }
99 107
100 108 /**
101 - * Clean up all the old logs from the local storage, this will happen per hourly basis.
109 + * Parses a raw request params array, resolves filters, and fetches audit logs.
102 110 *
103 - * @return void
104 - * @throws Exception When the $duration cannot be parsed as an interval.
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
105 127 */
106 - public function clean_up_audit_logs(): void {
107 - $this->service->audit_clean_up_logs();
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 );
108 159 }
109 160
110 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 + )
189 + );
190 + }
191 +
192 + return $logs;
193 + }
194 +
195 + /**
111 196 * Exports audit logs as a CSV file.
112 197 *
113 198 * @return void
114 199 * @throws Exception If there is an error during export.
@@ -114,30 +199,20 @@
114 199 * @throws Exception If there is an error during export.
115 200 * @defender_route
116 201 */
117 202 public function export_as_csv(): void {
118 - $date_from = HTTP::get(
119 - 'date_from',
120 - wp_date( 'Y-m-d H:i:s', strtotime( '-7 days', time() ) )
121 - );
122 - $date_to = HTTP::get( 'date_to', wp_date( 'Y-m-d H:i:s', time() ) );
123 - // Convert date using timezone.
124 - $timezone = wp_timezone();
125 - $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
126 - $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
127 - $username = HTTP::get( 'term', '' );
128 - $user_id = '';
129 - $user = get_user_by( 'login', $username );
130 - $events = HTTP::get( 'event_type', array() );
131 - if ( is_object( $user ) ) {
132 - $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 + );
133 213 }
134 214
135 - $handler = new Audit();
136 - $ip_address = HTTP::get( 'ip_address', '' );
137 - $result = $handler->fetch( $date_from, $date_to, $events, $user_id, $ip_address, false );
138 - // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
139 - $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
140 215 $headers = array(
141 216 esc_html__( 'Summary', 'defender-security' ),
142 217 esc_html__( 'Date / Time', 'defender-security' ),
143 218 esc_html__( 'Context', 'defender-security' ),
@@ -144,33 +219,56 @@
144 219 esc_html__( 'Type', 'defender-security' ),
145 220 esc_html__( 'IP address', 'defender-security' ),
146 221 esc_html__( 'User', 'defender-security' ),
147 222 );
148 - fputcsv( $fp, $headers, ',', '"', '\\' );
149 - foreach ( $result as $log ) {
150 - $fields = $log->export();
151 - $vars = array(
152 - $fields['msg'],
153 - is_array( $fields['timestamp'] )
154 - ? $this->format_date_time( $fields['timestamp'][0] )
155 - : $this->format_date_time( $fields['timestamp'] ),
156 - $fields['context'],
157 - $fields['action_type'],
158 - $fields['ip'],
159 - $this->get_user_display( $fields['user_id'] ),
160 - );
161 - 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' );
162 227 }
163 - $filename = 'wdf-audit-logs-export-' . wp_date( 'ymdHis' ) . '.csv';
164 - fseek( $fp, 0 );
165 - header( 'Content-Type: text/csv' );
166 - header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
167 - // Make php send the generated csv lines to the browser.
168 - fpassthru( $fp );
169 - exit();
170 228 }
171 229
172 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 + /**
173 271 * We'll pass all the event logs into the db handler, so it writes down to db.
174 272 * Do it in shutdown runtime, so no delay time.
175 273 *
176 274 * @return void
@@ -218,66 +316,44 @@
218 316 'paged' => array(
219 317 'type' => 'int',
220 318 'sanitize' => 'sanitize_text_field',
221 319 ),
320 + 'per_page' => array(
321 + 'type' => 'int',
322 + 'sanitize' => 'sanitize_text_field',
323 + ),
222 324 )
223 325 );
224 - if ( empty( $data['date_from'] ) || empty( $data['date_to'] ) ) {
326 + if ( ! isset( $data['date_from'] ) || ! isset( $data['date_to'] ) ) {
225 327 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
226 328 }
227 - // Convert date using timezone.
228 - $timezone = wp_timezone();
229 - $date_from = ( new DateTime( $data['date_from'], $timezone ) )
230 - ->setTime( 0, 0, 0 )
231 - ->getTimestamp();
232 - $date_to = ( new DateTime( $data['date_to'], $timezone ) )
233 - ->setTime( 23, 59, 59 )
234 - ->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 + }
235 335
236 - $events = $data['events'] ?? array();
237 - $ip_address = $data['ip_address'] ?? '';
238 - $paged = $data['paged'] ?? 1;
239 - $username = $data['username'] ?? '';
240 - $user_id = '';
241 - if ( ! empty( $username ) ) {
242 - $user = get_user_by( 'login', $username );
243 - if ( is_object( $user ) ) {
244 - $user_id = $user->ID;
245 - // Fetch result with the specified user.
246 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
247 - } else {
248 - // A non-existent username.
249 - $result = array();
250 - }
251 - } else {
252 - // Fetch result with empty user field.
253 - $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged );
254 - }
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 );
255 339
256 340 if ( is_wp_error( $result ) ) {
257 341 return new Response( false, array( 'message' => $result->get_error_message() ) );
258 342 }
259 - $logs = array();
260 - if ( ! empty( $result ) ) {
261 - foreach ( $result as $item ) {
262 - $logs[] = array_merge(
263 - $item->export(),
264 - array(
265 - 'user' => $this->get_user_display( $item->user_id ),
266 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
267 - 'log_date' => $this->get_date( $item->timestamp ),
268 - 'format_date' => $this->format_date_time( $item->timestamp ),
269 - )
270 - );
271 - }
272 - }
343 + $logs = is_array( $result ) && array() !== $result ? $this->format_log_rows( $result ) : array();
344 +
273 345 // @since 3.0.0 If no logs then $count = 0.
274 - 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 ) {
275 350 $count = 0;
351 + } elseif ( count( $logs ) < $per_page ) {
352 + $count = ( $paged - 1 ) * $per_page + count( $logs );
276 353 } else {
277 - $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 );
278 355 }
279 - $per_page = 20;
280 356
281 357 // Get the count for the submitted data.
282 358 return new Response(
283 359 true,
@@ -315,29 +391,40 @@
315 391 }
316 392
317 393 /**
318 394 * Enqueues scripts and styles for this page.
319 - * Only enqueues assets if the page is active.
320 395 */
321 - public function enqueue_assets() {
396 + public function enqueue_assets(): void {
322 397 if ( ! $this->is_page_active() ) {
323 398 return;
324 399 }
325 400
326 - wp_enqueue_script( 'def-moment', defender_asset_url( '/assets/js/vendor/moment/moment.min.js' ), array(), DEFENDER_VERSION, true );
401 + $handle = 'defender-ui-audit-logging';
327 402 wp_enqueue_script(
328 - 'def-daterangepicker',
329 - defender_asset_url( '/assets/js/vendor/daterangepicker/daterangepicker.js' ),
330 - array(),
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' ),
331 406 DEFENDER_VERSION,
332 407 true
333 408 );
409 + wp_set_script_translations( $handle, 'wpdef' );
410 +
334 411 wp_localize_script(
335 - 'def-audit',
336 - 'audit',
337 - $this->data_frontend()
412 + $handle,
413 + 'defenderUIData',
414 + array_merge(
415 + $this->get_shared_data(),
416 + $this->data_frontend()
417 + )
338 418 );
339 - 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 +
340 427 $this->enqueue_main_assets();
341 428 }
342 429
343 430 /**
@@ -356,9 +443,9 @@
356 443 * @throws Exception If there is an error during summary generation.
357 444 * @defender_route
358 445 */
359 446 public function summary(): void {
360 - $response = $this->model->is_active() ? $this->summary_data() : array();
447 + $response = $this->is_active() ? $this->summary_data() : array();
361 448 wp_send_json_success( $response );
362 449 }
363 450
364 451 /**
@@ -369,23 +456,25 @@
369 456 * @return array
370 457 * @throws Exception Emits Exception in case of an error.
371 458 */
372 459 public function summary_data( bool $for_hub = false ): array {
460 + $timezone = wp_timezone();
461 +
373 462 // Monthly count.
374 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )
463 + $date_from = ( new DateTime( '-30 days', $timezone ) )
375 464 ->setTime( 0, 0, 0 )
376 465 ->getTimestamp();
377 - $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();
378 469 $month_count = Audit_Log::count( $date_from, $date_to );
379 - // Weekly count.
380 - $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-7 days' ) ) ) )
381 - ->setTime( 0, 0, 0 )
382 - ->getTimestamp();
383 - $week_count = Audit_Log::count( $date_from, $date_to );
470 +
471 + // Weekly count sets in the returned array.
384 472 // Daily count. Sync data to the Hub without timezone.
385 473 $date_from = $for_hub ? new DateTime( 'now' ) : new DateTime( 'now', wp_timezone() );
386 474 $date_from = $date_from->modify( '-24 hours' )->setTime( 0, 0, 0 )->getTimestamp();
387 475 $day_count = Audit_Log::count( $date_from, $date_to );
476 +
388 477 // Get the last item.
389 478 $last = Audit_Log::get_last();
390 479 if ( is_object( $last ) ) {
391 480 $last = $for_hub
@@ -396,11 +485,12 @@
396 485 }
397 486
398 487 return array(
399 488 'monthCount' => $month_count,
400 - 'weekCount' => $week_count,
489 + 'weekCount' => Audit::get_audit_events_per_week(),
401 490 'dayCount' => $day_count,
402 491 'lastEvent' => $last,
492 + 'report' => wd_di()->get( Audit_Report::class )->to_string(),
403 493 );
404 494 }
405 495
406 496 /**
@@ -412,12 +502,8 @@
412 502 * @defender_route
413 503 */
414 504 public function save_settings( Request $request ): Response {
415 505 $data = $request->get_data_by_model( $this->model );
416 - if ( false === $data['enabled'] && $data['enabled'] !== $this->model->is_active() ) {
417 - // Toggle off, so we need to flush everything to cloud.
418 - $this->service->flush();
419 - }
420 506
421 507 $this->model->import( $data );
422 508 if ( $this->model->validate() ) {
423 509 $this->model->save();
@@ -431,8 +517,9 @@
431 517 $this->data_frontend(),
432 518 array(
433 519 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
434 520 'auto_close' => true,
521 + 'model' => $this->model->export(),
435 522 )
436 523 )
437 524 );
438 525 }
@@ -437,8 +524,22 @@
437 524 );
438 525 }
439 526
440 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 + /**
441 542 * Converts the current state of the object to an array.
442 543 *
443 544 * @return array Returns an associative array of object properties.
444 545 */
@@ -444,9 +545,9 @@
444 545 */
445 546 public function to_array(): array {
446 547 return array_merge(
447 548 array(
448 - 'enabled' => $this->model->is_active(),
549 + 'enabled' => $this->is_active(),
449 550 'report' => true,
450 551 ),
451 552 $this->dump_routes_and_nonces()
452 553 );
@@ -468,9 +569,9 @@
468 569 Array_Cache::remove( 'sockets', 'audit' );
469 570 Array_Cache::remove( 'logs', 'audit' );
470 571 Array_Cache::remove( 'menu_updated', 'audit' );
471 572 Array_Cache::remove( 'post_updated', 'audit' );
472 - delete_site_option( Audit::CACHE_LAST_CHECKPOINT );
573 + delete_site_option( 'wd_audit_fetch_checkpoint' );
473 574 }
474 575
475 576 /**
476 577 * Provides data for the frontend.
@@ -480,56 +581,55 @@
480 581 */
481 582 public function data_frontend(): array {
482 583 $logs = array();
483 584 $count = 0;
484 - $per_page = 20;
585 + $per_page = self::DEFAULT_PER_PAGE;
485 586 $total_page = 1;
486 - if ( $this->model->is_active() ) {
487 - $timezone = wp_timezone();
488 - $date_from = ( new DateTime() )->setTimezone( $timezone )
489 - ->sub( new DateInterval( 'P7D' ) )->setTime( 0, 0, 0 );
490 - $date_to = ( new DateTime() )->setTimezone( $timezone )->setTime( 23, 59, 59 );
491 - $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(
492 593 $date_from->getTimestamp(),
493 594 $date_to->getTimestamp(),
494 595 array(),
495 596 '',
496 - '',
497 - 1
597 + $ip_address,
598 + 1,
599 + $per_page
498 600 );
499 601 if ( ! is_wp_error( $result ) ) {
500 - foreach ( $result as $item ) {
501 - $logs[] = array_merge(
502 - $item->export(),
503 - array(
504 - 'user' => $this->get_user_display( $item->user_id ),
505 - 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '',
506 - 'log_date' => $this->get_date( $item->timestamp ),
507 - 'format_date' => $this->format_date_time( $item->timestamp ),
508 - )
509 - );
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 );
510 608 }
511 - $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() );
512 609 $total_page = ceil( $count / $per_page );
513 610 }
514 611 }
515 612
516 - return array_merge(
517 - array(
518 - 'model' => $this->model->export(),
519 - 'logs' => $logs,
520 - 'events_type' => Audit_Log::allowed_events(),
521 - 'summary' => array(
522 - 'count_7_days' => $count,
523 - '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 + ),
524 628 ),
525 - 'paging' => array(
526 - 'paged' => 1,
527 - 'total_pages' => $total_page,
528 - 'count' => $count,
529 - ),
629 + $this->dump_routes_and_nonces()
530 630 ),
531 - $this->dump_routes_and_nonces()
631 + 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
532 632 );
533 633 }
534 634
535 635 /**
@@ -540,9 +640,9 @@
540 640 * @throws Exception If table is not defined.
541 641 */
542 642 public function import_data( array $data ) {
543 643 $model = $this->model;
544 - if ( empty( $data ) ) {
644 + if ( array() === $data ) {
545 645 $model->enabled = false;
546 646 $model->storage_days = '6 months';
547 647 $model->save();
548 648 } else {
@@ -558,67 +658,38 @@
558 658 *
559 659 * @return array An array of strings.
560 660 */
561 661 public function export_strings(): array {
562 - if ( ! ( new WPMUDEV() )->is_pro() ) {
563 - return array(
564 - sprintf(
565 - /* translators: %s: Html for Pro-tag. */
566 - esc_html__( 'Inactive %s', 'defender-security' ),
567 - '<span class="sui-tag sui-tag-pro">Pro</span>'
568 - ),
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>'
569 670 );
570 - }
571 671
572 - if ( $this->model->is_active() ) {
573 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
574 - $audit_report = new Audit_Report();
575 - if ( 'enabled' === $audit_report->status ) {
576 - $strings[] = sprintf(
577 - /* translators: %s: Frequency value. */
578 - esc_html__( 'Email reports sending %s', 'defender-security' ),
579 - $audit_report->frequency
580 - );
581 - }
582 - } else {
583 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
584 - }
585 -
586 672 return $strings;
587 673 }
588 674
589 675 /**
590 - * Generates configuration strings based on the provided configuration and
591 - * whether the product is a pro version.
676 + * Generates configuration strings based on the provided configuration.
592 677 *
593 678 * @param array $config Configuration data.
594 - * @param bool $is_pro Indicates if the product is a pro version.
595 679 *
596 680 * @return array Returns an array of configuration strings.
597 681 */
598 - public function config_strings( array $config, bool $is_pro ): array {
599 - if ( $is_pro ) {
600 - if ( $config['enabled'] ) {
601 - $strings = array( esc_html__( 'Active', 'defender-security' ) );
602 - if ( isset( $config['report'] ) && 'enabled' === $config['report'] ) {
603 - $strings[] = sprintf(
604 - /* translators: %s: Frequency value. */
605 - esc_html__( 'Email reports sending %s', 'defender-security' ),
606 - $config['frequency']
607 - );
608 - }
609 - } else {
610 - $strings = array( esc_html__( 'Inactive', 'defender-security' ) );
611 - }
612 - } else {
613 - $strings = array(
614 - sprintf(
615 - /* translators: %s: Html for Pro-tag. */
616 - esc_html__( 'Inactive %s', 'defender-security' ),
617 - '<span class="sui-tag sui-tag-pro">Pro</span>'
618 - ),
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>'
619 691 );
620 - }
621 692
622 693 return $strings;
623 694 }
624 695 }