| |
| <?php
|
|
|
|
|
|
|
|
|
| define( 'FAL_LOG_FILE', WP_CONTENT_DIR . '/activity.log' );
|
| define( 'FAL_MAX_ENTRIES', 500 );
|
|
|
|
|
| function fal_log( $action, $meta = array() ) {
|
| $user = wp_get_current_user();
|
|
|
| $entry = array(
|
|
|
| 'time' => current_time( 'mysql' ),
|
| 'user' => $user->ID ? $user->user_login : 'guest',
|
| 'user_id' => $user->ID,
|
| 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
| 'action' => $action,
|
| 'meta' => $meta,
|
| );
|
|
|
| file_put_contents( FAL_LOG_FILE, json_encode( $entry ) . "\n", FILE_APPEND | LOCK_EX );
|
|
|
|
|
| $lines = file( FAL_LOG_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
|
| if ( count( $lines ) > FAL_MAX_ENTRIES ) {
|
| $lines = array_slice( $lines, -FAL_MAX_ENTRIES );
|
| file_put_contents( FAL_LOG_FILE, implode( "\n", $lines ) . "\n", LOCK_EX );
|
| }
|
| }
|
|
|
|
|
|
|
| add_action( 'wp_login', function( $user_login, $user ) {
|
| fal_log( 'login', array(
|
| 'user' => $user_login,
|
| 'user_id' => $user->ID,
|
| 'email' => $user->user_email,
|
| 'roles' => $user->roles,
|
| 'remember_me' => ! empty( $_POST['rememberme'] ),
|
| 'ua' => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 200 ),
|
| ) );
|
| }, 10, 2 );
|
|
|
|
|
| add_action( 'wp_login_failed', function( $username ) {
|
| fal_log( 'login_failed', array( 'attempted_user' => $username ) );
|
| });
|
|
|
|
|
| add_action( 'wp_logout', function() {
|
| $user = wp_get_current_user();
|
| fal_log( 'logout', array(
|
| 'user' => $user->user_login,
|
| 'user_id' => $user->ID,
|
| 'ua' => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 200 ),
|
| ) );
|
| });
|
|
|
|
|
| add_action( 'user_register', function( $user_id ) {
|
| $user = get_userdata( $user_id );
|
| fal_log( 'user_registered', array(
|
| 'user' => $user->user_login,
|
| 'user_id' => $user_id,
|
| 'email' => $user->user_email,
|
| 'roles' => $user->roles,
|
| 'ua' => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 200 ),
|
| 'registration' => isset( $_POST['reg_source'] ) ? sanitize_text_field( $_POST['reg_source'] ) : 'default',
|
| ) );
|
| });
|
|
|
|
|
| add_action( 'profile_update', function( $user_id, $old_data ) {
|
| $user = get_userdata( $user_id );
|
| $changes = array();
|
| if ( $old_data->user_email !== $user->user_email ) {
|
| $changes['email'] = array( 'old' => $old_data->user_email, 'new' => $user->user_email );
|
| }
|
| if ( $changes ) {
|
| fal_log( 'profile_updated', $changes );
|
| }
|
| }, 10, 2 );
|
|
|
|
|
| add_action( 'set_user_role', function( $user_id, $role, $old_roles ) {
|
| $user = get_userdata( $user_id );
|
| fal_log( 'role_changed', array(
|
| 'user' => $user->user_login,
|
| 'user_id' => $user_id,
|
| 'new_role' => $role,
|
| 'old_roles' => $old_roles,
|
| ) );
|
| }, 10, 3 );
|
|
|
|
|
| add_action( 'save_post', function( $post_id, $post, $is_update ) {
|
| if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) return;
|
| fal_log( $is_update ? 'post_updated' : 'post_created', array(
|
| 'post_id' => $post_id,
|
| 'post_type' => $post->post_type,
|
| 'title' => $post->post_title,
|
| 'status' => $post->post_status,
|
| ) );
|
| }, 10, 3 );
|
|
|
|
|
| add_action( 'before_delete_post', function( $post_id ) {
|
| $post = get_post( $post_id );
|
| if ( ! $post ) return;
|
| fal_log( 'post_deleted', array(
|
| 'post_id' => $post_id,
|
| 'post_type' => $post->post_type,
|
| 'title' => $post->post_title,
|
| 'status' => $post->post_status,
|
| 'author' => get_the_author_meta( 'display_name', $post->post_author ),
|
| 'permalink' => get_permalink( $post_id ),
|
| ) );
|
| });
|
|
|
|
|
| add_action( 'comment_post', function( $comment_id, $approved ) {
|
| fal_log( 'comment_posted', array(
|
| 'comment_id' => $comment_id,
|
| 'approved' => (bool) $approved,
|
| ) );
|
| }, 10, 2 );
|
|
|
|
|
| add_action( 'activate_plugin', function( $plugin ) {
|
| fal_log( 'plugin_activated', array( 'plugin' => $plugin ) );
|
| });
|
| add_action( 'deactivate_plugin', function( $plugin ) {
|
| fal_log( 'plugin_deactivated', array( 'plugin' => $plugin ) );
|
| });
|
|
|
|
|
| add_action( 'switch_theme', function( $name, $theme ) {
|
| fal_log( 'theme_changed', array( 'theme' => $theme->get_stylesheet() ) );
|
| }, 10, 2 );
|
|
|
|
|
| add_action( 'admin_menu', function() {
|
| add_menu_page(
|
| 'Activity Logs',
|
| 'Activity Logs',
|
| 'manage_options',
|
| 'fal-logs',
|
| 'fal_render_admin',
|
| 'dashicons-clipboard',
|
| 80
|
| );
|
| });
|
|
|
|
|
| function fal_render_admin() {
|
| if ( ! current_user_can( 'manage_options' ) ) {
|
| wp_die( 'Permission denied.' );
|
| }
|
|
|
| $per_page = 50;
|
| $paged = max( 1, isset( $_GET['paged'] ) ? (int) $_GET['paged'] : 1 );
|
| $action = isset( $_GET['action_filter'] ) ? sanitize_text_field( $_GET['action_filter'] ) : '';
|
| $search = isset( $_GET['search'] ) ? sanitize_text_field( $_GET['search'] ) : '';
|
|
|
|
|
| $lines = array();
|
| if ( file_exists( FAL_LOG_FILE ) ) {
|
| $raw = file( FAL_LOG_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
|
| foreach ( $raw as $line ) {
|
| $entry = json_decode( $line, true );
|
| if ( ! $entry ) continue;
|
| if ( $action && $entry['action'] !== $action ) continue;
|
| if ( $search ) {
|
| $haystack = wp_json_encode( $entry );
|
| if ( stripos( $haystack, $search ) === false ) continue;
|
| }
|
| $lines[] = $entry;
|
| }
|
| }
|
|
|
| $lines = array_reverse( $lines );
|
|
|
|
|
| $total = count( $lines );
|
| $pages = max( 1, (int) ceil( $total / $per_page ) );
|
| $paged = min( $paged, $pages );
|
| $offset = ( $paged - 1 ) * $per_page;
|
| $page = array_slice( $lines, $offset, $per_page );
|
|
|
|
|
| $all_actions = array();
|
| foreach ( $lines as $e ) {
|
| $all_actions[ $e['action'] ] = true;
|
| }
|
| $all_actions = array_keys( $all_actions );
|
| sort( $all_actions );
|
|
|
| ?>
|
|
|
| <div class="wrap">
|
| <h1>Activity Logs</h1>
|
|
|
| <form method="get" style="margin-bottom:1em; display:flex; gap:0.5em; align-items:center;">
|
| <input type="hidden" name="page" value="fal-logs">
|
| <input style="min-width:40%;" type="search" name="search" value="<?php echo esc_attr( $search ); ?>" placeholder="Search (user, IP, action, details)…">
|
| <label>Action:
|
| <select name="action_filter">
|
| <option value="">All</option>
|
| <?php foreach ( $all_actions as $a ) : ?>
|
| <option value="<?php echo esc_attr( $a ); ?>" <?php selected( $action, $a ); ?>><?php echo esc_html( $a ); ?></option>
|
| <?php endforeach; ?>
|
| </select>
|
| </label>
|
| <input type="submit" class="button" value="Filter">
|
| </form>
|
|
|
| <p>Found <strong><?php echo $total; ?></strong> entries</p>
|
|
|
| <table class="wp-list-table widefat striped" style="margin-top:1em;">
|
| <thead>
|
| <tr>
|
| <th style="width:150px;">Time</th>
|
| <th style="width:120px;">User</th>
|
| <th style="width:140px;">Action</th>
|
| <th style="width:130px;">IP</th>
|
| <th>Details</th>
|
| </tr>
|
| </thead>
|
| <tbody>
|
| <?php if ( empty( $page ) ) : ?>
|
| <tr><td colspan="5">No entries found.</td></tr>
|
| <?php else : ?>
|
| <?php foreach ( $page as $entry ) : ?>
|
| <tr>
|
| <td><?php echo esc_html( $entry['time'] ); ?></td>
|
| <td><?php echo esc_html( $entry['user'] ); ?></td>
|
| <td><code><?php echo esc_html( $entry['action'] ); ?></code></td>
|
| <td><?php echo esc_html( $entry['ip'] ); ?></td>
|
| <td>
|
| <?php if ( ! empty( $entry['meta'] ) ) : ?>
|
| <details>
|
| <summary>Meta</summary>
|
| <pre><?php echo esc_html( wp_json_encode( $entry['meta'], JSON_PRETTY_PRINT ) ); ?></pre>
|
| </details>
|
| <?php else : ?>
|
| —
|
| <?php endif; ?>
|
| </td>
|
| </tr>
|
| <?php endforeach; ?>
|
| <?php endif; ?>
|
| </tbody>
|
| </table>
|
|
|
| <?php if ( $pages > 1 ) : ?>
|
| <div class="tablenav" style="margin-top:1em;">
|
| <div class="tablenav-pages">
|
| <?php for ( $i = 1; $i <= $pages; $i++ ) : ?>
|
| <?php
|
| $url = add_query_arg( array(
|
| 'page' => 'fal-logs',
|
| 'paged' => $i,
|
| 'action_filter' => $action,
|
| 'search' => $search,
|
| ) );
|
| $class = ( $i === $paged ) ? 'page-numbers current' : 'page-numbers';
|
| ?>
|
| <a class="<?php echo $class; ?>" href="<?php echo esc_url( $url ); ?>"><?php echo $i; ?></a>
|
| <?php endfor; ?>
|
| </div>
|
| </div>
|
| <?php endif; ?>
|
|
|
| </div>
|
| <?php
|
| }
|
| |
| |
Comments