Home / Admin / Admin Activity Log
Duplicate Snippet

Embed Snippet on Your Site

Admin Activity Log

File based log of user actions and file modifications. Also add to admin menu for review. Not intended for use on production sites.

Code Preview
php
<?php
// File Activity Log / Kiri Vadivelu / 2026
// Log user actions and file modifications
// ─── Configuration ──────────────────────────────────────────────
define( 'FAL_LOG_FILE', WP_CONTENT_DIR . '/activity.log' );
define( 'FAL_MAX_ENTRIES', 500 );
// ─── Core Logger ────────────────────────────────────────────────
function fal_log( $action, $meta = array() ) {
    $user = wp_get_current_user();
    $entry = array(
        // 'time'    => gmdate( 'Y-m-d H:i:s' ),
        '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 );
    // Trim to last 500 entries
    $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 );
    }
}
// ─── Event Hooks ────────────────────────────────────────────────
// Login
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 );
// Failed Login
add_action( 'wp_login_failed', function( $username ) {
    fal_log( 'login_failed', array( 'attempted_user' => $username ) );
});
// Logout
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 ),
    ) );
});
// User Registration
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',
    ) );
});
// Profile Update
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 );
// Role Changed
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 );
// Post Created / Updated
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 );
// Post / Page Deleted
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 ),
    ) );
});
// Comment Posted
add_action( 'comment_post', function( $comment_id, $approved ) {
    fal_log( 'comment_posted', array(
        'comment_id' => $comment_id,
        'approved'   => (bool) $approved,
    ) );
}, 10, 2 );
// Plugin Activated / Deactivated
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 ) );
});
// Theme Changed
add_action( 'switch_theme', function( $name, $theme ) {
    fal_log( 'theme_changed', array( 'theme' => $theme->get_stylesheet() ) );
}, 10, 2 );
// ─── Admin Menu ─────────────────────────────────────────────────
add_action( 'admin_menu', function() {
    add_menu_page(
        'Activity Logs',
        'Activity Logs',
        'manage_options',
        'fal-logs',
        'fal_render_admin',
        'dashicons-clipboard',
        80
    );
});
// ─── Admin Page ─────────────────────────────────────────────────
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'] ) : '';
    // Read entries
    $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 );
    // Paginate
    $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 );
    // Unique actions for filter
    $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

Add a Comment