| @@ -39,15 +39,19 @@ | ||
| 39 | 39 | * @param array $events Specific events to fetch. |
| 40 | 40 | * @param string $user_id User ID to filter logs. |
| 41 | 41 | * @param string $ip IP address to filter logs. |
| 42 | 42 | * @param int $paged Pagination page number. |
| 43 | + * @param int $per_page Number of logs per page. | |
| 43 | 44 | * |
| 44 | 45 | * @return Audit_Log[]|WP_Error Returns an array of Audit_Log objects or WP_Error on failure. |
| 45 | 46 | * @throws Exception Throws exception on failure. |
| 46 | 47 | */ |
| 47 | - public function fetch( $date_from, $date_to, $events = array(), $user_id = '', $ip = '', $paged = 1 ) { | |
| 48 | - $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged ); | |
| 48 | + public function fetch( $date_from, $date_to, $events = array(), $user_id = '', $ip = '', $paged = 1, $per_page = 10 ) { | |
| 49 | + $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged, $per_page ); | |
| 49 | 50 | $this->log( sprintf( 'Found %s from local', count( $internal ) ), self::AUDIT_LOG ); |
| 51 | + if ( ! wd_di()->get( WPMUDEV::class )->is_pro() ) { | |
| 52 | + return $internal; | |
| 53 | + } | |
| 50 | 54 | $checkpoint = get_site_option( self::CACHE_LAST_CHECKPOINT ); |
| 51 | 55 | if ( false === $checkpoint ) { |
| 52 | 56 | // This case where user install the plugin, have some local data but never reach to Logs page, then check point will be today. |
| 53 | 57 | $checkpoint = time(); |
| @@ -52,9 +56,9 @@ | ||
| 52 | 56 | // This case where user install the plugin, have some local data but never reach to Logs page, then check point will be today. |
| 53 | 57 | $checkpoint = time(); |
| 54 | 58 | } |
| 55 | 59 | $checkpoint = (int) $checkpoint; |
| 56 | - $date_from = (int) $date_from; | |
| 60 | + $date_from = ! is_int( $date_from ) ? (int) $date_from : $date_from; | |
| 57 | 61 | if ( 0 === count( $internal ) && $checkpoint > $date_from ) { |
| 58 | 62 | // Have to fetch from API. |
| 59 | 63 | $this->log( 'fetch from cloud', self::AUDIT_LOG ); |
| 60 | 64 | // Todo:need $paged as 'nopaging'-arg? |
| @@ -67,9 +71,9 @@ | ||
| 67 | 71 | if ( count( $cloud ) ) { |
| 68 | 72 | // No data from cloud too. |
| 69 | 73 | Audit_Log::mass_insert( $cloud ); |
| 70 | 74 | // Because this is roughly fetch, so we have to filter out again using the local data. |
| 71 | - $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged ); | |
| 75 | + $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged, $per_page ); | |
| 72 | 76 | } |
| 73 | 77 | // Cache the last time fetch, this will be useful in case of mixed data. |
| 74 | 78 | update_site_option( self::CACHE_LAST_CHECKPOINT, $date_from ); |
| 75 | 79 | // This case we have the data, however, maybe it can be out of the cached range, so we have to check. |
| @@ -92,9 +96,9 @@ | ||
| 92 | 96 | } |
| 93 | 97 | if ( is_array( $cloud ) ) { |
| 94 | 98 | // Silence the error here, as we actually have data. |
| 95 | 99 | Audit_Log::mass_insert( $cloud ); |
| 96 | - $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged ); | |
| 100 | + $internal = Audit_Log::query( $date_from, $date_to, $events, $user_id, $ip, $paged, $per_page ); | |
| 97 | 101 | // Cache the last time fetch, this will be useful in case of mixed data. |
| 98 | 102 | update_site_option( self::CACHE_LAST_CHECKPOINT, $date_from ); |
| 99 | 103 | } |
| 100 | 104 | } |
| @@ -148,33 +152,17 @@ | ||
| 148 | 152 | |
| 149 | 153 | return $data['data']; |
| 150 | 154 | } |
| 151 | 155 | |
| 156 | + | |
| 152 | 157 | /** |
| 153 | - * Flushes logs that need to be synced with the cloud. | |
| 158 | + * Delete all local audit log entries from database and reset checkpoint. | |
| 159 | + * | |
| 160 | + * @return void | |
| 154 | 161 | */ |
| 155 | - public function flush() { | |
| 156 | - $logs = Audit_Log::get_logs_need_flush(); | |
| 157 | - // Build the data. | |
| 158 | - $data = array(); | |
| 159 | - foreach ( $logs as $log ) { | |
| 160 | - $item = $log->export(); | |
| 161 | - unset( $item['synced'] ); | |
| 162 | - unset( $item['safe'] ); | |
| 163 | - unset( $item['id'] ); | |
| 164 | - $item['msg'] = addslashes( $item['msg'] ); | |
| 165 | - $data[] = $item; | |
| 166 | - } | |
| 167 | - | |
| 168 | - if ( count( $data ) ) { | |
| 169 | - $ret = $this->curl_to_api( $data ); | |
| 170 | - if ( ! is_wp_error( $ret ) ) { | |
| 171 | - foreach ( $logs as $log ) { | |
| 172 | - $log->synced = 1; | |
| 173 | - $log->save(); | |
| 174 | - } | |
| 175 | - } | |
| 176 | - } | |
| 162 | + public function reset(): void { | |
| 163 | + Audit_Log::truncate(); | |
| 164 | + delete_site_option( self::CACHE_LAST_CHECKPOINT ); | |
| 177 | 165 | } |
| 178 | 166 | |
| 179 | 167 | /** |
| 180 | 168 | * Cleans up old logs based on storage settings. |
| @@ -183,23 +171,36 @@ | ||
| 183 | 171 | */ |
| 184 | 172 | public function audit_clean_up_logs() { |
| 185 | 173 | $audit_settings = wd_di()->get( Audit_Logging::class ); |
| 186 | 174 | $interval = $this->calculate_date_interval( $audit_settings->storage_days ); |
| 187 | - $date_from = ( new DateTime() )->setTimezone( wp_timezone() ) | |
| 188 | - ->sub( new DateInterval( 'P1Y' ) ) | |
| 189 | - ->setTime( 0, 0, 0 ); | |
| 190 | - $date_to = ( new DateTime() )->setTimezone( wp_timezone() ) | |
| 191 | - ->sub( new DateInterval( $interval ) ); | |
| 175 | + // Since v5.7.0. | |
| 176 | + $interval = apply_filters( 'wpdef_audit_logs_store_backward', $interval ); | |
| 177 | + $interval = is_string( $interval ) ? $interval : (string) $interval; | |
| 192 | 178 | |
| 179 | + try { | |
| 180 | + $interval_obj = new DateInterval( $interval ); | |
| 181 | + } catch ( Exception ) { | |
| 182 | + // Fallback if the filter supplied an incorrect value. | |
| 183 | + $interval_obj = new DateInterval( 'P6M' ); | |
| 184 | + } | |
| 185 | + | |
| 186 | + $date_from = ( new DateTime() )->setTimezone( wp_timezone() ) | |
| 187 | + ->sub( new DateInterval( 'P1Y' ) ) | |
| 188 | + ->setTime( 0, 0, 0 ); | |
| 189 | + $date_to = ( new DateTime() )->setTimezone( wp_timezone() ) | |
| 190 | + ->sub( $interval_obj ); | |
| 191 | + | |
| 193 | 192 | if ( $date_from < $date_to ) { |
| 194 | 193 | // Count the logs that should be deleted. |
| 195 | 194 | $logs_count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() ); |
| 196 | 195 | if ( $logs_count > 0 ) { |
| 196 | + $this->log( 'Cleaning up old logs from ' . $date_from->format( 'Y-m-d H:i:s' ) . ' to ' . $date_to->format( 'Y-m-d H:i:s' ), self::AUDIT_LOG ); | |
| 197 | + // Since v5.0.0. | |
| 198 | + $delete_count = apply_filters( 'wpdef_audit_limit_deleted_logs', 50 ); | |
| 197 | 199 | Audit_Log::delete_old_logs( |
| 198 | 200 | $date_from->getTimestamp(), |
| 199 | 201 | $date_to->getTimestamp(), |
| 200 | - // Since v5.0.0. | |
| 201 | - (int) apply_filters( 'wpdef_audit_limit_deleted_logs', 50 ) | |
| 202 | + is_int( $delete_count ) ? $delete_count : (int) $delete_count, | |
| 202 | 203 | ); |
| 203 | 204 | } |
| 204 | 205 | } |
| 205 | 206 | } |
| @@ -228,84 +229,8 @@ | ||
| 228 | 229 | return $ret; |
| 229 | 230 | } |
| 230 | 231 | |
| 231 | 232 | /** |
| 232 | - * Sends data to the API using a socket connection. | |
| 233 | - * | |
| 234 | - * @param array $data Data to be sent to the API. | |
| 235 | - * | |
| 236 | - * @return bool Returns true on success, false on failure. | |
| 237 | - */ | |
| 238 | - public function socket_to_api( $data ) { | |
| 239 | - $sockets = Array_Cache::get( 'sockets', 'audit', array() ); | |
| 240 | - // We will need to wait a bit. | |
| 241 | - if ( 0 === ( is_array( $sockets ) || $sockets instanceof Countable ? count( $sockets ) : 0 ) ) { | |
| 242 | - // Fall back. | |
| 243 | - return false; | |
| 244 | - } | |
| 245 | - $this->log( | |
| 246 | - sprintf( 'Flush %s to cloud', is_array( $data ) || $data instanceof Countable ? count( $data ) : 0 ), | |
| 247 | - self::AUDIT_LOG | |
| 248 | - ); | |
| 249 | - $start_time = microtime( true ); | |
| 250 | - $sks = $sockets; | |
| 251 | - $r = null; | |
| 252 | - $e = null; | |
| 253 | - if ( ( false === stream_select( $r, $sks, $e, 1 ) ) ) { | |
| 254 | - // This case error happen. | |
| 255 | - return false; | |
| 256 | - } | |
| 257 | - | |
| 258 | - $fp = array_shift( $sockets ); | |
| 259 | - | |
| 260 | - $uri = '/logs/add_multiple'; | |
| 261 | - $vars = http_build_query( $data ); | |
| 262 | - $this->attach_behavior( WPMUDEV::class, WPMUDEV::class ); | |
| 263 | - // We're sending data to the server, so we're not manipulating files. Ignore the error. | |
| 264 | - // @codingStandardsIgnoreStart | |
| 265 | - fwrite( $fp, 'POST ' . $uri . " HTTP/1.1\r\n" ); | |
| 266 | - fwrite( $fp, 'Host: ' . $this->strip_protocol( $this->get_endpoint() ) . "\r\n" ); | |
| 267 | - fwrite( $fp, "Content-Type: application/x-www-form-urlencoded\r\n" ); | |
| 268 | - fwrite( $fp, 'Content-Length: ' . strlen( $vars ) . "\r\n" ); | |
| 269 | - fwrite( $fp, 'apikey: ' . $this->get_apikey() . "\r\n" ); | |
| 270 | - fwrite( $fp, "Connection: close\r\n" ); | |
| 271 | - fwrite( $fp, "\r\n" ); | |
| 272 | - fwrite( $fp, $vars ); | |
| 273 | - stream_set_timeout( $fp, 5 ); | |
| 274 | - $res = ''; | |
| 275 | - while ( ! feof( $fp ) ) { | |
| 276 | - $res .= fgets( $fp, 1024 ); | |
| 277 | - // Check if the transfer has taken too long. | |
| 278 | - $end_time = microtime( true ); | |
| 279 | - if ( $end_time - $start_time > 3 ) { | |
| 280 | - fclose( $fp ); | |
| 281 | - break; | |
| 282 | - } | |
| 283 | - } | |
| 284 | - // @codingStandardsIgnoreEnd | |
| 285 | - return true; | |
| 286 | - } | |
| 287 | - | |
| 288 | - /** | |
| 289 | - * Open a socket to API for faster transmit. | |
| 290 | - */ | |
| 291 | - public function open_socket() { | |
| 292 | - $sockets = Array_Cache::get( 'sockets', 'audit', array() ); | |
| 293 | - $endpoint = $this->strip_protocol( $this->get_endpoint() ); | |
| 294 | - if ( empty( $sockets ) ) { | |
| 295 | - $fp = stream_socket_client( | |
| 296 | - 'ssl://' . $endpoint . ':443', | |
| 297 | - $errno, | |
| 298 | - $errstr, | |
| 299 | - 5 | |
| 300 | - ); | |
| 301 | - if ( is_resource( $fp ) ) { | |
| 302 | - Array_Cache::set( 'sockets', array( $fp ), 'audit' ); | |
| 303 | - } | |
| 304 | - } | |
| 305 | - } | |
| 306 | - | |
| 307 | - /** | |
| 308 | 233 | * Strips the protocol from a URL. |
| 309 | 234 | * |
| 310 | 235 | * @param string $url URL to process. |
| 311 | 236 | * |
| @@ -339,11 +264,15 @@ | ||
| 339 | 264 | new Component\Audit\Comment_Audit(), |
| 340 | 265 | new Component\Audit\Core_Audit(), |
| 341 | 266 | new Component\Audit\Media_Audit(), |
| 342 | 267 | new Component\Audit\Post_Audit(), |
| 343 | - new Component\Audit\Users_Audit(), | |
| 268 | + new Component\Audit\User_Audit(), | |
| 344 | 269 | new Component\Audit\Options_Audit(), |
| 345 | 270 | new Component\Audit\Menu_Audit(), |
| 271 | + new Component\Audit\Theme_Audit(), | |
| 272 | + new Component\Audit\Feature_Audit(), | |
| 273 | + new Component\Audit\Password_Audit(), | |
| 274 | + new Component\Audit\Application_Password_Audit(), | |
| 346 | 275 | ); |
| 347 | 276 | |
| 348 | 277 | foreach ( $events_class as $class ) { |
| 349 | 278 | // since 2.4.7. |
| @@ -393,9 +322,9 @@ | ||
| 393 | 322 | $model = new Audit_Log(); |
| 394 | 323 | |
| 395 | 324 | if ( ( is_array( $events ) || $events instanceof Countable ? count( $events ) : 0 ) > 1 ) { |
| 396 | 325 | if ( $model->has_method( 'mass_insert' ) ) { |
| 397 | - $model->mass_insert( $events ); | |
| 326 | + $model->mass_insert( $events, 0 ); | |
| 398 | 327 | |
| 399 | 328 | return; |
| 400 | 329 | } |
| 401 | 330 | } |
| @@ -404,6 +333,21 @@ | ||
| 404 | 333 | $model->import( $event ); |
| 405 | 334 | $model->synced = 0; |
| 406 | 335 | $model->save(); |
| 407 | 336 | } |
| 337 | + } | |
| 338 | + | |
| 339 | + /** | |
| 340 | + * Get audit events per week. | |
| 341 | + */ | |
| 342 | + public static function get_audit_events_per_week(): int { | |
| 343 | + $timezone = wp_timezone(); | |
| 344 | + $date_to = ( new DateTime( 'now', $timezone ) ) | |
| 345 | + ->setTime( 23, 59, 59 ) | |
| 346 | + ->getTimestamp(); | |
| 347 | + $date_from = ( new DateTime( '-7 days', $timezone ) ) | |
| 348 | + ->setTime( 0, 0, 0 ) | |
| 349 | + ->getTimestamp(); | |
| 350 | + | |
| 351 | + return Audit_Log::count( $date_from, $date_to ); | |
| 408 | 352 | } |
| 409 | 353 | } |