Activity Log in Its Own Database Table
Records logins, failed logins, content changes, plugin, theme and user changes and edits to key settings in a dedicated database table, with a searchable, paginated log under Tools > Activity Log and automatic cleanup.
Opens WPIDE on
Code
// Days to keep log entries, and settings whose changes are logged.
$wpide_log_days = 90;
$wpide_log_options = array( 'siteurl', 'home', 'admin_email', 'users_can_register', 'default_role', 'blog_public', 'permalink_structure' );
$wpide_log_table = function () {
global $wpdb;
return $wpdb->prefix . 'wpide_activity';
};
$wpide_log = function ( $action, $object = '', $details = '', $user_id = null ) use ( $wpide_log_table ) {
global $wpdb;
if ( '1' !== get_option( 'wpide_activity_db' ) ) {
return;
}
$user_id = null === $user_id ? get_current_user_id() : (int) $user_id;
$user = $user_id ? get_userdata( $user_id ) : false;
$wpdb->insert( $wpide_log_table(), array(
'created' => current_time( 'mysql', true ),
'user_id' => $user_id,
'user_login' => $user ? $user->user_login : '',
'ip' => isset( $_SERVER['REMOTE_ADDR'] ) ? substr( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ), 0, 45 ) : '',
'action' => substr( $action, 0, 40 ),
'object' => substr( (string) $object, 0, 190 ),
'details' => substr( (string) $details, 0, 1000 ),
) );
};
add_action( 'admin_init', function () use ( $wpide_log_table ) {
global $wpdb;
if ( '1' === get_option( 'wpide_activity_db' ) || ! current_user_can( 'manage_options' ) ) {
return;
}
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( 'CREATE TABLE ' . $wpide_log_table() . " (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
created datetime NOT NULL,
user_id bigint(20) unsigned NOT NULL DEFAULT 0,
user_login varchar(60) NOT NULL DEFAULT '',
ip varchar(45) NOT NULL DEFAULT '',
action varchar(40) NOT NULL,
object varchar(190) NOT NULL DEFAULT '',
details text NOT NULL,
PRIMARY KEY (id),
KEY created (created),
KEY action (action)
) " . $wpdb->get_charset_collate() . ';' );
update_option( 'wpide_activity_db', '1' );
} );
add_action( 'wp_login', function ( $login, $user ) use ( $wpide_log ) {
$wpide_log( 'login', $login, '', $user->ID );
}, 10, 2 );
add_action( 'wp_login_failed', function ( $login ) use ( $wpide_log ) {
$wpide_log( 'login_failed', sanitize_user( $login ), '', 0 );
} );
add_action( 'transition_post_status', function ( $new, $old, $post ) use ( $wpide_log ) {
if ( $new === $old || wp_is_post_revision( $post ) || in_array( $new, array( 'auto-draft', 'inherit' ), true ) || ! post_type_exists( $post->post_type ) || ! is_post_type_viewable( $post->post_type ) ) {
return;
}
$wpide_log( 'post_' . $new, $post->post_type . ' #' . $post->ID, $post->post_title );
}, 10, 3 );
add_action( 'before_delete_post', function ( $post_id, $post ) use ( $wpide_log ) {
if ( $post && is_post_type_viewable( $post->post_type ) && 'revision' !== $post->post_type ) {
$wpide_log( 'post_deleted', $post->post_type . ' #' . $post_id, $post->post_title );
}
}, 10, 2 );
foreach ( array( 'activated_plugin' => 'plugin_activated', 'deactivated_plugin' => 'plugin_deactivated', 'deleted_plugin' => 'plugin_deleted' ) as $wpide_hook => $wpide_action ) {
add_action( $wpide_hook, function ( $plugin ) use ( $wpide_log, $wpide_action ) {
$wpide_log( $wpide_action, $plugin );
} );
}
add_action( 'switch_theme', function ( $name ) use ( $wpide_log ) {
$wpide_log( 'theme_switched', $name );
} );
add_action( 'upgrader_process_complete', function ( $upgrader, $extra ) use ( $wpide_log ) {
if ( ! empty( $extra['type'] ) && ! empty( $extra['action'] ) ) {
$items = isset( $extra['plugins'] ) ? $extra['plugins'] : ( isset( $extra['themes'] ) ? $extra['themes'] : array() );
$wpide_log( $extra['type'] . '_' . $extra['action'], implode( ', ', (array) $items ) );
}
}, 10, 2 );
add_action( 'user_register', function ( $user_id ) use ( $wpide_log ) {
$user = get_userdata( $user_id );
$wpide_log( 'user_created', $user ? $user->user_login : '#' . $user_id, $user ? implode( ',', $user->roles ) : '' );
} );
add_action( 'delete_user', function ( $user_id ) use ( $wpide_log ) {
$user = get_userdata( $user_id );
$wpide_log( 'user_deleted', $user ? $user->user_login : '#' . $user_id );
} );
add_action( 'set_user_role', function ( $user_id, $role, $old ) use ( $wpide_log ) {
if ( $old ) {
$user = get_userdata( $user_id );
$wpide_log( 'role_changed', $user ? $user->user_login : '#' . $user_id, implode( ',', (array) $old ) . ' -> ' . $role );
}
}, 10, 3 );
add_action( 'updated_option', function ( $option, $old, $new ) use ( $wpide_log, $wpide_log_options ) {
if ( in_array( $option, $wpide_log_options, true ) ) {
$wpide_log( 'setting_changed', $option, wp_json_encode( $old ) . ' -> ' . wp_json_encode( $new ) );
}
}, 10, 3 );
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'wpide_activity_cleanup' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpide_activity_cleanup' );
}
} );
add_action( 'wpide_activity_cleanup', function () use ( $wpide_log_table, $wpide_log_days ) {
global $wpdb;
if ( '1' === get_option( 'wpide_activity_db' ) ) {
$wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpide_log_table() . ' WHERE created < %s LIMIT 10000', gmdate( 'Y-m-d H:i:s', time() - $wpide_log_days * DAY_IN_SECONDS ) ) );
}
} );
add_action( 'admin_menu', function () use ( $wpide_log_table ) {
add_management_page( 'Activity Log', 'Activity Log', 'manage_options', 'wpide-activity-table', function () use ( $wpide_log_table ) {
global $wpdb;
$per = 50;
$paged = max( 1, isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 );
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
$rows = array();
$total = 0;
if ( '1' === get_option( 'wpide_activity_db' ) ) {
$where = '1=1';
$args = array();
if ( '' !== $search ) {
$like = '%' . $wpdb->esc_like( $search ) . '%';
$where = '(user_login LIKE %s OR action LIKE %s OR object LIKE %s OR details LIKE %s OR ip LIKE %s)';
$args = array( $like, $like, $like, $like, $like );
}
$table = $wpide_log_table();
$total = (int) $wpdb->get_var( $args ? $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE $where", $args ) : "SELECT COUNT(*) FROM $table" );
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table WHERE $where ORDER BY id DESC LIMIT %d OFFSET %d", array_merge( $args, array( $per, ( $paged - 1 ) * $per ) ) ) );
}
echo '<div class="wrap"><h1>Activity Log</h1><form method="get"><input type="hidden" name="page" value="wpide-activity-table">';
echo '<p class="search-box"><label class="screen-reader-text" for="wpide-log-search">Search the log</label><input type="search" id="wpide-log-search" name="s" value="' . esc_attr( $search ) . '"> <button class="button">Search</button></p></form>';
echo '<table class="widefat striped"><thead><tr><th>Time (UTC)</th><th>User</th><th>Action</th><th>Object</th><th>Details</th><th>IP</th></tr></thead><tbody>';
if ( ! $rows ) {
echo '<tr><td colspan="6">No entries.</td></tr>';
}
foreach ( $rows as $row ) {
printf( '<tr><td>%s</td><td>%s</td><td><code>%s</code></td><td>%s</td><td>%s</td><td>%s</td></tr>', esc_html( $row->created ), esc_html( $row->user_login ? $row->user_login : '-' ), esc_html( $row->action ), esc_html( $row->object ), esc_html( wp_html_excerpt( $row->details, 120, '...' ) ), esc_html( $row->ip ) );
}
echo '</tbody></table>';
$links = paginate_links( array( 'base' => add_query_arg( 'paged', '%#%' ), 'format' => '', 'current' => $paged, 'total' => max( 1, (int) ceil( $total / $per ) ) ) );
if ( $links ) {
echo '<div class="tablenav"><div class="tablenav-pages">' . wp_kses_post( $links ) . '</div></div>';
}
echo '</div>';
} );
} );
Notes
Records are kept for $wpide_log_days days; the table is created the first time an administrator opens the dashboard.
How to add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.