register_page( esc_html__( 'Audit Logging', 'wpdef' ), $this->slug, array( &$this, 'main_view', ), $this->parent_slug ); add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); $this->model = wd_di()->get( \WP_Defender\Model\Setting\Audit_Logging::class ); $this->service = new Audit(); $this->register_routes(); if ( $this->model->is_active() ) { $this->service->enqueue_event_listener(); add_action( 'shutdown', array( &$this, 'cache_audit_logs' ) ); /** * We will schedule the time for flush data into cloud. */ if ( ! wp_next_scheduled( 'audit_sync_events' ) ) { wp_schedule_event( time() + 15, 'hourly', 'audit_sync_events' ); } add_action( 'audit_sync_events', array( &$this, 'sync_events' ) ); /** * We will schedule the time to clean up old logs. */ if ( ! wp_next_scheduled( 'audit_clean_up_logs' ) ) { wp_schedule_event( time(), 'hourly', 'audit_clean_up_logs' ); } add_action( 'audit_clean_up_logs', array( &$this, 'clean_up_audit_logs' ) ); } } /** * Sync all the events into cloud, this will happen per hourly basis. */ public function sync_events() { $this->service->flush(); } /** * Clean up all the old logs from the local storage, this will happen per hourly basis. */ public function clean_up_audit_logs() { $this->service->audit_clean_up_logs(); } /** * @throws \Exception * @defender_route */ public function export_as_csv() { $date_from = HTTP::get( 'date_from', date( 'Y-m-d H:i:s', strtotime( '-7 days', current_time( 'timestamp' ) ) ) ); $date_to = HTTP::get( 'date_to', date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) ); $date_from = ( new \DateTime( $date_from ) )->setTime( 0, 0, 0 )->getTimestamp(); $date_to = ( new \DateTime( $date_to ) )->setTime( 23, 59, 59 )->getTimestamp(); $username = HTTP::get( 'term', '' ); $user_id = ''; $user = get_user_by( 'login', $username ); $events = HTTP::get( 'event_type', array() ); if ( is_object( $user ) ) { $user_id = $user->ID; } $handler = new Audit(); $ip_address = HTTP::get( 'ip_address', '' ); $result = $handler->fetch( $date_from, $date_to, $events, $user_id, $ip_address, false ); // Have data, now prepare to flush. $fp = fopen( 'php://memory', 'w' ); $headers = array( __( 'Summary', 'wpdef' ), __( 'Date / Time', 'wpdef' ), __( 'Context', 'wpdef' ), __( 'Type', 'wpdef' ), __( 'IP address', 'wpdef' ), __( 'User', 'wpdef' ), ); fputcsv( $fp, $headers ); foreach ( $result as $log ) { $fields = $log->export(); $vars = array( $fields['msg'], is_array( $fields['timestamp'] ) ? $this->format_date_time( date( 'Y-m-d H:i:s', $fields['timestamp'][0] ) ) : $this->format_date_time( date( 'Y-m-d H:i:s', $fields['timestamp'] ) ), $fields['context'], $fields['action_type'], $fields['ip'], $this->get_user_display( $fields['user_id'] ), ); fputcsv( $fp, $vars ); } $filename = 'wdf-audit-logs-export-' . date( 'ymdHis' ) . '.csv'; fseek( $fp, 0 ); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="' . $filename . '";' ); // Make php send the generated csv lines to the browser. fpassthru( $fp ); exit(); } /** * We'll pass all the event logs into the db handler, so it writes down to db. * Do it in shutdown runtime, so no delay time. * */ public function cache_audit_logs() { $audit = new Audit(); $audit->log_audit_events(); } /** * Pull the logs from db cached: * - date_from: the start of the date we will run the query, as mysql time format, * - date_to: similar to the above, * others will refer to Audit. * * @param Request $request * * @return Response * @throws \Exception * @defender_route */ public function pull_logs( Request $request ) { $data = $request->get_data( array( 'date_from' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'date_to' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'username' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'events' => array( 'type' => 'array', 'sanitize' => 'sanitize_text_field', ), 'ip_address' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), ) ); $date_from = $data['date_from']; $date_from = new \DateTime( $date_from ); $date_to = $data['date_to']; $date_to = new \DateTime( $date_to ); if ( ! is_object( $date_from ) || ! is_object( $date_to ) ) { return new Response( false, array( 'message' => __( 'Invalid data', 'wpdef' ), ) ); } $username = $data['username'] ?? ''; $user_id = ''; $user = get_user_by( 'login', $username ); $events = $data['events'] ?? array(); if ( is_object( $user ) ) { $user_id = $user->ID; } $ip_address = $data['ip_address'] ?? ''; $paged = $data['paged'] ?? 1; $result = $this->service->fetch( $date_from->setTime( 0, 0, 0 )->getTimestamp(), $date_to->setTime( 23, 59, 59 )->getTimestamp(), $events, $user_id, $ip_address, $paged ); if ( is_wp_error( $result ) ) { return new Response( false, array( 'message' => $result->get_error_message(), ) ); } $logs = array(); foreach ( $result as $item ) { $logs[] = array_merge( $item->export(), array( 'user' => $this->get_user_display( $item->user_id ), 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '', 'log_date' => $this->get_date( $item->timestamp ), 'format_date' => $this->format_date_time( gmdate( 'Y-m-d H:i:s', $item->timestamp ) ), ) ); } // @since 3.0.0 If no logs then $count = 0. if ( empty( $logs ) ) { $count = 0; } else { $count = Audit_Log::count( $date_from->setTime( 0, 0, 0 )->getTimestamp(), $date_to->setTime( 23, 59, 59 )->getTimestamp(), $events, $user_id, $ip_address ); } $per_page = 20; // Get the week count. return new Response( true, array( 'logs' => $logs, 'total_items' => $count, 'total_pages' => ceil( $count / $per_page ), 'per_page' => $per_page, ) ); } public function get_frequency_text( Audit_Report $audit_report ) { $text = ''; switch ( $audit_report->frequency ) { case 'daily': $text = ucfirst( $audit_report->day ) . 's at ' . $audit_report->time; break; case 'weekly': case 'monthly': $text = ucfirst( $audit_report->frequency ) . ' on ' . ucfirst( $audit_report->day ) . 's at ' . $audit_report->time; break; default: break; } return $text; } public function enqueue_assets() { if ( ! $this->is_page_active() ) { return; } wp_enqueue_script( 'def-moment', defender_asset_url( '/assets/js/vendor/moment/moment.min.js' ) ); wp_enqueue_script( 'def-daterangepicker', defender_asset_url( '/assets/js/vendor/daterangepicker/daterangepicker.js' ) ); wp_localize_script( 'def-audit', 'audit', $this->data_frontend() ); wp_enqueue_script( 'def-audit' ); $this->enqueue_main_assets(); } /** * Render the root element for frontend. */ public function main_view() { $this->render( 'main' ); } /** * @throws \Exception * @defender_route */ public function summary() { $response = $this->model->is_active() ? $this->summary_data() : array(); wp_send_json_success( $response ); } /** * @param bool $for_hub. Default 'false' because it's displayed on site summary sections. * * @return array */ public function summary_data( $for_hub = false) { $date_from = ( new \DateTime( date( 'Y-m-d', strtotime( '-30 days' ) ) ) )->setTime( 0, 0, 0 )->getTimestamp(); $date_to = ( new \DateTime( date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp(); $month_count = Audit_Log::count( $date_from, $date_to ); $date_from = ( new \DateTime( date( 'Y-m-d', strtotime( '-7 days' ) ) ) )->setTime( 0, 0, 0 )->getTimestamp(); $week_count = Audit_Log::count( $date_from, $date_to ); $last = Audit_Log::get_last(); $date_from = ( new \DateTime( 'now', wp_timezone() ) )->modify( '-24 hours' )->setTime( 0, 0, 0 )->getTimestamp(); $day_count = Audit_Log::count( $date_from, $date_to ); if ( is_object( $last ) ) { $last = $for_hub ? $this->persistent_hub_datetime_format( $last->timestamp ) : $this->format_date_time( $last->timestamp ); } else { $last = 'n/a'; } return array( 'monthCount' => $month_count, 'weekCount' => $week_count, 'dayCount' => $day_count, 'lastEvent' => $last, ); } /** * Save settings. * @param Request $request * * @return Response * @defender_route */ public function save_settings( Request $request ) { $data = $request->get_data_by_model( $this->model ); if ( false === $data['enabled'] && $data['enabled'] !== $this->model->is_active() ) { // Toggle off, so we need to flush everything to cloud. $this->service->flush(); } $this->model->import( $data ); if ( $this->model->validate() ) { $this->model->save(); } Config_Hub_Helper::set_clear_active_flag(); return new Response( true, array_merge( $this->data_frontend(), array( 'message' => __( 'Your settings have been updated.', 'wpdef' ), ) ) ); } /** * @return array */ public function to_array() { return array_merge( array( 'enabled' => $this->model->is_active(), 'report' => true, ), $this->dump_routes_and_nonces() ); } public function remove_settings() { ( new \WP_Defender\Model\Setting\Audit_Logging() )->delete(); } /** * Delete all the data & the cache. */ public function remove_data() { Audit_Log::truncate(); delete_site_option( Audit::CACHE_LAST_CHECKPOINT ); } /** * Setup config for audit. * Todo: need? */ public function optimize_configs() { $settings = new \WP_Defender\Model\Setting\Audit_Logging(); $settings->enabled = true; $settings->save(); $report = new Audit_Report(); $report->save(); } /** * All the variables that we will show on frontend, both in the main page, or dashboard widget. * * @return array */ public function data_frontend() { $logs = array(); $count = 0; $per_page = 20; $total_page = 1; if ( $this->model->is_active() ) { $date_from = ( new \DateTime() ) ->setTimezone( wp_timezone() ) ->sub( new \DateInterval( 'P7D' ) )->setTime( 0, 0, 0 ); $date_to = ( new \DateTime() )->setTimezone( wp_timezone() )->setTime( 23, 59, 59 ); $result = $this->service->fetch( $date_from->getTimestamp(), $date_to->getTimestamp(), array(), '', '', 1 ); if ( ! is_wp_error( $result ) ) { foreach ( $result as $item ) { $logs[] = array_merge( $item->export(), array( 'user' => $this->get_user_display( $item->user_id ), 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '', 'log_date' => $this->get_date( $item->timestamp ), 'format_date' => $this->format_date_time( gmdate( 'Y-m-d H:i:s', $item->timestamp ) ), ) ); } $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() ); $total_page = ceil( $count / $per_page ); } } return array_merge( array( 'model' => $this->model->export(), 'logs' => $logs, 'events_type' => Audit_Log::allowed_events(), 'summary' => array( 'count_7_days' => $count, 'report' => wd_di()->get( Audit_Report::class )->to_string(), ), 'paging' => array( 'paged' => 1, 'total_pages' => $total_page, 'count' => $count, ), ), $this->dump_routes_and_nonces() ); } /** * @param array $data */ public function import_data( $data ) { $model = $this->model; $model->import( $data ); if ( $model->validate() ) { $model->save(); } } /** * @return array */ public function export_strings() { if ( ! ( new \WP_Defender\Behavior\WPMUDEV() )->is_pro() ) { return array( sprintf( __( 'Inactive %s', 'wpdef' ), 'Pro' ), ); } if ( $this->model->is_active() ) { $strings = array( __( 'Active', 'wpdef' ) ); $audit_report = new \WP_Defender\Model\Notification\Audit_Report(); if ( 'enabled' === $audit_report->status ) { $strings[] = sprintf( __( 'Email reports sending %s', 'wpdef' ), $audit_report->frequency ); } } else { $strings = array( __( 'Inactive', 'wpdef' ) ); } return $strings; } /** * @param array $config * @param bool $is_pro * * @return array */ public function config_strings( $config, $is_pro ) { if ( $is_pro ) { if ( $config['enabled'] ) { $strings = array( __( 'Active', 'wpdef' ) ); if ( isset( $config['report'] ) && 'enabled' === $config['report'] ) { $strings[] = sprintf( /* translators: %s - option frequency */ __( 'Email reports sending %s', 'wpdef' ), $config['frequency'] ); } } else { $strings = array( __( 'Inactive', 'wpdef' ) ); } } else { $strings = array( sprintf( /* translators: ... */ __( 'Inactive %s', 'wpdef' ), 'Pro' ) ); } return $strings; } }