Monitor file changes in a specific directory

Monitor file changes in a specific directory

For certain security-critical services or older projects, it can be useful to monitor file changes. Therefore, I took the time to write a small script that is stored directly in the home directory. The subdirectory, sender email, and recipient still need to be configured in the script. An email will be sent when new, deleted, or modified files are added. This only applies to the files (not folders) in a specific folder (not subfolders). This allows you to specifically monitor a specific directory (without constantly receiving emails from the modified cache).

Simply create a monitor.php and an empty file_state.json file, then enter the following in monitor.php:

<?php

$directory = __DIR__ . '/SUBFOLDER'; // Change to your target directory
$stateFile = __DIR__ . '/file_state.json';
$recipientEmail = '[email protected]';

function getFileStates($dir) {
    $files = scandir($dir);
    $fileStates = [];

    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;

        $filePath = $dir . DIRECTORY_SEPARATOR . $file;

        if (is_file($filePath)) {
            $fileStates[$file] = [
                'mtime' => filemtime($filePath),
                'size'  => filesize($filePath)
            ];
        }
    }

    return $fileStates;
}

$previousState = [];
if (file_exists($stateFile)) {
    $json = file_get_contents($stateFile);
    $previousState = json_decode($json, true) ?: [];
}

$currentState = getFileStates($directory);

$added = array_diff_key($currentState, $previousState);
$deleted = array_diff_key($previousState, $currentState);
$modified = [];

foreach ($currentState as $file => $info) {
    if (isset($previousState[$file])) {
        if ($info['mtime'] !== $previousState[$file]['mtime'] || $info['size'] !== $previousState[$file]['size']) {
            $modified[$file] = $info;
        }
    }
}

if (!empty($added) || !empty($deleted) || !empty($modified)) {
    $subject = "File Change Detected in '$directory'";
    $message = "Changes detected in directory: $directory\n\n";

    if (!empty($added)) {
        $message .= "New files:\n" . implode("\n", array_keys($added)) . "\n\n";
    }

    if (!empty($deleted)) {
        $message .= "Deleted files:\n" . implode("\n", array_keys($deleted)) . "\n\n";
    }

    if (!empty($modified)) {
        $message .= "Modified files:\n" . implode("\n", array_keys($modified)) . "\n\n";
    }

    $headers = 'From: [email protected]' . "\r\n";

    mail($recipientEmail, $subject, $message, $headers);
}

file_put_contents($stateFile, json_encode($currentState, JSON_PRETTY_PRINT));

Danach z. B. als stündlichen Cronjob einrichten.

Multi-folder version

The following version can monitor multiple folders simultaneously, the list can be expanded as required:

<?php
// Configuration
$directories = [
    __DIR__ . '/httpdocs',
    __DIR__ . '/httpdocs/api'
    // Add more directories as needed
];

$stateFile = __DIR__ . '/file_state.json';
$recipientEmail = '[email protected]';
$senderEmail = '[email protected]';

// Get current file states in a directory
function getFileStates($dir) {
    $files = scandir($dir);
    $fileStates = [];

    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;

        $filePath = $dir . DIRECTORY_SEPARATOR . $file;

        if (is_file($filePath)) {
            $fileStates[$file] = [
                'mtime' => filemtime($filePath),
                'size'  => filesize($filePath)
            ];
        }
    }

    return $fileStates;
}

// Load previous state
$previousStates = [];
if (file_exists($stateFile)) {
    $json = file_get_contents($stateFile);
    $previousStates = json_decode($json, true) ?: [];
}

// Initialize change log
$allChanges = [];
$currentStates = [];

foreach ($directories as $dir) {
    $prevState = $previousStates[$dir] ?? [];
    $currState = getFileStates($dir);
    $currentStates[$dir] = $currState;

    $added = array_diff_key($currState, $prevState);
    $deleted = array_diff_key($prevState, $currState);
    $modified = [];

    foreach ($currState as $file => $info) {
        if (isset($prevState[$file])) {
            if ($info['mtime'] !== $prevState[$file]['mtime'] || $info['size'] !== $prevState[$file]['size']) {
                $modified[$file] = $info;
            }
        }
    }

    if (!empty($added) || !empty($deleted) || !empty($modified)) {
        $allChanges[$dir] = [
            'added' => array_keys($added),
            'deleted' => array_keys($deleted),
            'modified' => array_keys($modified)
        ];
    }
}

// Save updated state
file_put_contents($stateFile, json_encode($currentStates, JSON_PRETTY_PRINT));

// Send email if any changes
if (!empty($allChanges)) {
    $subject = "File Change Detected in Monitored Directories";
    $message = "The following changes were detected:\n\n";

    foreach ($allChanges as $dir => $changes) {
        $message .= "Directory: $dir\n";

        if (!empty($changes['added'])) {
            $message .= "  New files:\n    - " . implode("\n    - ", $changes['added']) . "\n";
        }

        if (!empty($changes['deleted'])) {
            $message .= "  Deleted files:\n    - " . implode("\n    - ", $changes['deleted']) . "\n";
        }

        if (!empty($changes['modified'])) {
            $message .= "  Modified files:\n    - " . implode("\n    - ", $changes['modified']) . "\n";
        }

        $message .= "\n";
    }

    $headers = "From: $senderEmail\r\n";
    mail($recipientEmail, $subject, $message, $headers);
}

Version for multiple folders and with exclusion list

<?php
// Configuration
$directories = [
    __DIR__ . '/httpdocs',
    __DIR__ . '/httpdocs/api'
    // Add more directories as needed
];

// Array of files to exclude from change notifications
$excludeFiles = [
    'test.cache',
    'sitemap.xml'
];

$stateFile = __DIR__ . '/file_state.json';
$recipientEmail = '[email protected]';
$senderEmail = '[email protected]';

// Get current file states in a directory
function getFileStates($dir) {
    $files = scandir($dir);
    $fileStates = [];

    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;

        $filePath = $dir . DIRECTORY_SEPARATOR . $file;

        if (is_file($filePath)) {
            $fileStates[$file] = [
                'mtime' => filemtime($filePath),
                'size'  => filesize($filePath)
            ];
        }
    }

    return $fileStates;
}

// Load previous state
$previousStates = [];
if (file_exists($stateFile)) {
    $json = file_get_contents($stateFile);
    $previousStates = json_decode($json, true) ?: [];
}

// Initialize change log
$allChanges = [];
$currentStates = [];

foreach ($directories as $dir) {
    $prevState = $previousStates[$dir] ?? [];
    $currState = getFileStates($dir);
    $currentStates[$dir] = $currState;

    $added = array_diff_key($currState, $prevState);
    $deleted = array_diff_key($prevState, $currState);
    $modified = [];

    foreach ($currState as $file => $info) {
        if (isset($prevState[$file])) {
            if ($info['mtime'] !== $prevState[$file]['mtime'] || $info['size'] !== $prevState[$file]['size']) {
                $modified[$file] = $info;
            }
        }
    }

    if (!empty($added) || !empty($deleted) || !empty($modified)) {
        $allChanges[$dir] = [
            'added' => array_keys($added),
            'deleted' => array_keys($deleted),
            'modified' => array_keys($modified)
        ];
    }
}

// Save updated state
file_put_contents($stateFile, json_encode($currentStates, JSON_PRETTY_PRINT));

// Check if changes only involve excluded files
$onlyExcludedFilesChanged = true;
foreach ($allChanges as $dir => $changes) {
    foreach (['added', 'deleted', 'modified'] as $changeType) {
        if (!empty($changes[$changeType])) {
            foreach ($changes[$changeType] as $file) {
                if (!in_array($file, $excludeFiles)) {
                    $onlyExcludedFilesChanged = false;
                    break 2; // Break out of both loops
                }
            }
        }
    }
}

// Send email if any changes, but only if non-excluded files changed
if (!empty($allChanges) && !$onlyExcludedFilesChanged) {
    $subject = "File Change Detected in Monitored Directories";
    $message = "The following changes were detected:\n\n";

    foreach ($allChanges as $dir => $changes) {
        $message .= "Directory: $dir\n";

        if (!empty($changes['added'])) {
            $message .= "  New files:\n    - " . implode("\n    - ", $changes['added']) . "\n";
        }

        if (!empty($changes['deleted'])) {
            $message .= "  Deleted files:\n    - " . implode("\n    - ", $changes['deleted']) . "\n";
        }

        if (!empty($changes['modified'])) {
            $message .= "  Modified files:\n    - " . implode("\n    - ", $changes['modified']) . "\n";
        }

        $message .= "\n";
    }

    $headers = "From: $senderEmail\r\n";
     mail($recipientEmail, $subject, $message, $headers);
}
?>

Leave a Reply

Your email address will not be published. Required fields are marked *