diff --git a/README.md b/README.md index 9a26ad1..d45312d 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,12 @@ - [Views](#visit-monitoring-views) - [Ajax Requests](#ajax-requests) - [Visit Monitoring Guest Mode](#visit-monitoring-guest-mode) + - [Visit Monitoring Custom Conditions](#visit-monitoring-custom-conditions) - [Action Monitoring](#action-monitoring) - [Views](#action-monitoring-views) - [Reverse Proxy Config](#action-monitoring-reverse-proxy-config) - [Action Monitoring Guest Mode](#action-monitoring-guest-mode) + - [Action Monitoring Custom Conditions](#action-monitoring-custom-conditions) - [Authentication Monitoring](#authentication-monitoring) - [Views](#authentication-monitoring-views) - [How to use in big projects](#how-to-use-in-big-projects) @@ -366,6 +368,34 @@ When set to `false`, only authenticated user visits will be recorded. ], ``` + +### Visit Monitoring Custom Conditions + +The `Laravel User Monitoring` package allows you to define custom conditions for visit monitoring. +Conditions give you full control over when a visit should be logged. + +#### 🔧 How It Works + +- Conditions are checked before a visit is stored. +- If any condition returns false, the visit will be skipped. +- You can define conditions as closures or as class-based rules. + +```php +'visit_monitoring' => [ + 'conditions' => [ + // Class-based condition (must implement MonitoringCondition interface) + \App\Monitoring\YourCustomCondition::class, + + // Closure-based condition (receives the Request and authenticated User) + function (Illuminate\Http\Request $request) { + $user = $request->user(); + + return $user && $user->isAdmin(); + }, + ], +], +``` + ## Action Monitoring @@ -457,6 +487,34 @@ When set to `false`, only authenticated user visits will be recorded. ], ``` + +### Action Monitoring Custom Conditions + +The `Laravel User Monitoring` package lets you define custom conditions for action monitoring (create, update, delete events on models). +Conditions give you control over when model actions should be logged. + +#### 🔧 How It Works + +- Conditions are checked before an action is stored. +- If any condition returns false, the action will be skipped. +- You can define conditions as closures or class-based rules. + +```php +'action_monitoring' => [ + 'conditions' => [ + // Class-based condition (must implement MonitoringCondition interface) + \App\Monitoring\YourCustomCondition::class, + + // Closure-based condition (receives the Request and authenticated User) + function (Illuminate\Http\Request $request) { + $user = $request->user(); + + return $user && $user->isAdmin(); + }, + ], +], +``` + ## Authentication Monitoring diff --git a/config/user-monitoring.php b/config/user-monitoring.php index 2c9fae4..158cfc3 100644 --- a/config/user-monitoring.php +++ b/config/user-monitoring.php @@ -98,6 +98,16 @@ * Determines whether to store `visits` even when the user is not logged in. */ 'guest_mode' => true, + + /* + | Here you can define one or more conditions that determine whether a visit + | should be logged. Each condition must return a boolean (true = log visit, + | false = skip logging). + | + | All conditions are evaluated before monitoring. If any condition returns + | false, the visit will NOT be recorded. + */ + 'conditions' => [], ], /* @@ -136,6 +146,13 @@ * Determines whether to store `actions` even when the user is not logged in. */ 'guest_mode' => true, + + /* + * Here you can define one or more conditions that determine whether an action + * should be logged. Each condition must return a boolean (true = log action, + * false = skip logging). + */ + 'conditions' => [], ], /* diff --git a/src/Contracts/MonitoringCondition.php b/src/Contracts/MonitoringCondition.php new file mode 100644 index 0000000..a516925 --- /dev/null +++ b/src/Contracts/MonitoringCondition.php @@ -0,0 +1,10 @@ +shouldMonitor($request)) { + return $next($request); + } + + $detector = new Detector; $exceptPages = config('user-monitoring.visit_monitoring.except_pages', []); if (empty($exceptPages) || !$this->checkIsExceptPages($request->path(), $exceptPages)) { @@ -53,4 +59,30 @@ protected function checkIsExceptPages(string $page, array $exceptPages): bool { return collect($exceptPages)->contains($page); } + + /** + * Determine if monitoring should be performed for the given request and user. + */ + protected function shouldMonitor(Request $request): bool + { + $config = config('user-monitoring.visit_monitoring.conditions', []); + + foreach ($config as $condition) { + if (is_callable($condition)) { + if (! $condition($request)) { + return false; + } + } elseif (is_string($condition)) { + $instance = new $condition; + if (! $instance instanceof MonitoringCondition) { + continue; + } + if (! $instance->shouldMonitor($request)) { + return false; + } + } + } + + return true; + } } diff --git a/src/Traits/Actionable.php b/src/Traits/Actionable.php index be0a177..8a5f7dc 100644 --- a/src/Traits/Actionable.php +++ b/src/Traits/Actionable.php @@ -2,9 +2,11 @@ namespace Binafy\LaravelUserMonitoring\Traits; +use Binafy\LaravelUserMonitoring\Contracts\MonitoringCondition; use Binafy\LaravelUserMonitoring\Utills\ActionType; use Binafy\LaravelUserMonitoring\Utills\Detector; use Binafy\LaravelUserMonitoring\Utills\UserUtils; +use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; trait Actionable @@ -20,6 +22,11 @@ protected static function boot(): void return; } + // Custom conditions from config + if (! static::shouldMonitor(request())) { + return; + } + if (config('user-monitoring.action_monitoring.on_store', false)) { static::created(function (mixed $model) { static::insertActionMonitoring($model, ActionType::ACTION_STORE); @@ -94,4 +101,30 @@ private static function getRealIP(): string ? request()->header(config('user-monitoring.real_ip_header')) : request()->ip(); } + + /** + * Determine if monitoring should be performed for the given request and user. + */ + protected static function shouldMonitor(Request $request): bool + { + $config = config('user-monitoring.action_monitoring.conditions', []); + + foreach ($config as $condition) { + if (is_callable($condition)) { + if (! $condition($request)) { + return false; + } + } elseif (is_string($condition)) { + $instance = new $condition; + if (! $instance instanceof MonitoringCondition) { + continue; + } + if (! $instance->shouldMonitor($request)) { + return false; + } + } + } + + return true; + } } diff --git a/tests/Feature/ActionMonitoringTest.php b/tests/Feature/ActionMonitoringTest.php index d7f9092..ada2dac 100644 --- a/tests/Feature/ActionMonitoringTest.php +++ b/tests/Feature/ActionMonitoringTest.php @@ -4,6 +4,7 @@ use Binafy\LaravelUserMonitoring\Utills\ActionType; use Binafy\LaravelUserMonitoring\Utills\UserUtils; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Tests\SetUp\Models\Product; use Tests\SetUp\Models\ProductSoftDelete; @@ -361,3 +362,35 @@ // DB Assertions assertDatabaseCount(config('user-monitoring.action_monitoring.table'), 2); }); + +test('action is stored when config conditions are true', function () { + config()->set('user-monitoring.action_monitoring.conditions', [ + function (Request $request) { + return true; + }, + ]); + + Product::query()->create([ + 'title' => 'milwad', + 'description' => 'WE ARE HELPING TO OPEN-SOURCE WORLD' + ]); + + // DB Assertions + assertDatabaseCount(config('user-monitoring.action_monitoring.table'), 1); +}); + +test('action is not stored when config conditions are false', function () { + config()->set('user-monitoring.action_monitoring.conditions', [ + function (Request $request) { + return false; + }, + ]); + + Product::query()->create([ + 'title' => 'milwad', + 'description' => 'WE ARE HELPING TO OPEN-SOURCE WORLD' + ]); + + // DB Assertions + assertDatabaseCount(config('user-monitoring.action_monitoring.table'), 0); +}); diff --git a/tests/Feature/VisitMonitoringTest.php b/tests/Feature/VisitMonitoringTest.php index 5e8f532..35018ea 100644 --- a/tests/Feature/VisitMonitoringTest.php +++ b/tests/Feature/VisitMonitoringTest.php @@ -2,6 +2,7 @@ use Binafy\LaravelUserMonitoring\Models\VisitMonitoring; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Request; use Tests\SetUp\Models\User; use function Pest\Laravel\{actingAs, get}; use function Pest\Laravel\{assertDatabaseCount, assertDatabaseHas, assertDatabaseMissing}; @@ -122,6 +123,34 @@ assertDatabaseCount(config('user-monitoring.visit_monitoring.table'), 1); }); +test('visit monitoring is store when config conditions are true', function () { + config()->set('user-monitoring.visit_monitoring.conditions', [ + function (Request $request) { + return true; + }, + ]); + + $response = get('/'); + $response->assertContent('milwad'); + + // DB Assertions + assertDatabaseCount(config('user-monitoring.visit_monitoring.table'), 1); +}); + +test('visit monitoring is not store when config conditions are false', function () { + config()->set('user-monitoring.visit_monitoring.conditions', [ + function (Request $request) { + return false; + }, + ]); + + $response = get('/'); + $response->assertContent('milwad'); + + // DB Assertions + assertDatabaseCount(config('user-monitoring.visit_monitoring.table'), 0); +}); + /** * Create user and return it. */