# Daily Plugin and Theme File Change Monitor

> Fingerprints every PHP file in your plugins and themes once a day and emails the administrator a list of files that were added, changed or removed outside of normal updates.

- Type: PHP
- Runs: Run everywhere
- Category: [Security](https://wpide.com/library/category/security/)
- Tags: integrity, files, cron
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/sec-file-change-monitor/

## Code

```php
// Folders to watch and the maximum number of files to fingerprint.
$wpide_watch_dirs  = array( WP_PLUGIN_DIR, get_theme_root() );
$wpide_watch_limit = 30000;

$wpide_fingerprint = function () use ( $wpide_watch_dirs, $wpide_watch_limit ) {
    $map = array();

    foreach ( $wpide_watch_dirs as $dir ) {
        if ( ! is_dir( $dir ) ) {
            continue;
        }
        $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ) );
        foreach ( $files as $file ) {
            if ( count( $map ) >= $wpide_watch_limit ) {
                break 2;
            }
            if ( $file->isFile() && preg_match( '/\.(php|phtml|phar)$/i', $file->getFilename() ) && false === strpos( $file->getPathname(), '/node_modules/' ) ) {
                $map[ str_replace( WP_CONTENT_DIR, '', $file->getPathname() ) ] = substr( md5_file( $file->getPathname() ), 0, 12 );
            }
        }
    }

    return $map;
};

$wpide_save_baseline = function ( $map ) {
    update_option( 'wpide_file_baseline', base64_encode( gzcompress( wp_json_encode( $map ), 6 ) ), false );
};

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'wpide_file_change_scan' ) ) {
        wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpide_file_change_scan' );
    }
} );

add_action( 'upgrader_process_complete', function () use ( $wpide_fingerprint, $wpide_save_baseline ) {
    $wpide_save_baseline( $wpide_fingerprint() );
}, 99 );

add_action( 'wpide_file_change_scan', function () use ( $wpide_fingerprint, $wpide_save_baseline ) {
    $stored = get_option( 'wpide_file_baseline' );
    $now    = $wpide_fingerprint();
    $wpide_save_baseline( $now );

    if ( ! $stored ) {
        return;
    }

    $old = json_decode( (string) gzuncompress( base64_decode( $stored ) ), true );
    if ( ! is_array( $old ) ) {
        return;
    }

    $added   = array_keys( array_diff_key( $now, $old ) );
    $removed = array_keys( array_diff_key( $old, $now ) );
    $changed = array_keys( array_diff_assoc( array_intersect_key( $now, $old ), $old ) );

    if ( ! $added && ! $removed && ! $changed ) {
        return;
    }

    $lines = array();
    foreach ( array( 'Added' => $added, 'Changed' => $changed, 'Removed' => $removed ) as $label => $list ) {
        if ( $list ) {
            $lines[] = $label . ' (' . count( $list ) . "):\n  " . implode( "\n  ", array_slice( $list, 0, 50 ) );
        }
    }

    wp_mail(
        get_option( 'admin_email' ),
        sprintf( '[%s] Plugin or theme files changed', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ),
        "These PHP files changed since yesterday without a dashboard update:\n\n" . implode( "\n\n", $lines ) . "\n\nIf you did not make these changes, check the files for malware."
    );
} );
```

## Notes

The baseline is refreshed automatically after updates done from the dashboard; changes made over FTP or by malware are reported.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Daily Plugin and Theme File Change Monitor" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
