├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── laravisit.php ├── database └── migrations │ └── create_laravisits_table.php.stub └── src ├── Concerns ├── CanVisit.php ├── FilterByPopularityTimeFrame.php ├── HasVisits.php └── SetsPendingIntervals.php ├── Exceptions ├── InvalidDataException.php └── VisitException.php ├── LaravisitServiceProvider.php ├── Models ├── User.php └── Visit.php ├── PendingVisit.php └── Presenters └── VisitPresenter.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravisit` will be documented in this file. 4 | 5 | ## v1.2.1 - 2022-08-22 6 | 7 | ### What's Changed 8 | 9 | - Larastan Fix Errors by @ousid in https://github.com/coderflexx/laravisit/pull/17 10 | - Enhancements by @ousid in https://github.com/coderflexx/laravisit/pull/16 11 | 12 | **Full Changelog**: https://github.com/coderflexx/laravisit/compare/v1.2.0...v1.2.1 13 | 14 | ## v1.2.0 - 2022-08-21 15 | 16 | ### What's Changed 17 | 18 | - Enhancement by @ousid in https://github.com/coderflexx/laravisit/pull/13 19 | - FEAT: Added with session function by @juliangarcess in https://github.com/coderflexx/laravisit/pull/15 20 | 21 | **Full Changelog**: https://github.com/coderflexx/laravisit/compare/v1.1.0...v1.2.0 22 | 23 | ## v1.1.0 - 2022-08-14 24 | 25 | ### What's Changed 26 | 27 | - FEAT: Added `CrawlerDetect` by @juliangarcess in https://github.com/coderflexx/laravisit/pull/12 28 | 29 | ### New Contributors 30 | 31 | - @juliangarcess made their first contribution in https://github.com/coderflexx/laravisit/pull/12 32 | 33 | **Full Changelog**: https://github.com/coderflexx/laravisit/compare/v1.0.3...v1.1.0 34 | 35 | ## v1.0.3 - 2022-02-15 36 | 37 | ## Update 38 | 39 | - Update `README.md` 40 | 41 | ## v1.0.2 - 2022-02-14 42 | 43 | Update: 44 | 45 | - update `composer.json` 46 | 47 | ## v1.0.1 - 2022-02-14 48 | 49 | ## Add 50 | 51 | - add `CanVisit` Interface 52 | 53 | ## v1.0.0 - 2022-02-14 54 | 55 | - Initial release 56 | 57 | ## 1.0.0 - 202X-XX-XX 58 | 59 | - initial release 60 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) coderflex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Laravisit Logo 3 |

4 |

5 | 6 | [![The Latest Version on Packagist](https://img.shields.io/packagist/v/coderflexx/laravisit.svg?style=flat-square)](https://packagist.org/packages/coderflexx/laravisit) 7 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravisit/run-tests.yml?branch=main&label=tests)](https://github.com/coderflexx/laravisit/actions?query=workflow%3Arun-tests+branch%3Amain) 8 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravisit/phpstan.yml?branch=main&label=code%20style)](https://github.com/coderflexx/laravisit/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/coderflexx/laravisit.svg?style=flat-square)](https://packagist.org/packages/coderflexx/laravisit) 10 | 11 | A clean way to track your pages & understand your user's behavior 12 | 13 | ## Installation 14 | 15 | You can install the package via composer: 16 | 17 | ```bash 18 | composer require coderflexx/laravisit 19 | ``` 20 | 21 | You can publish the config file with: 22 | 23 | ```bash 24 | # linux 25 | php artisan vendor:publish --provider="Coderflex\\Laravisit\\LaravisitServiceProvider" 26 | 27 | # windows 28 | php artisan vendor:publish --provider="Coderflex\Laravisit\LaravisitServiceProvider" 29 | ``` 30 | 31 | then, run database migration 32 | ```bash 33 | php artisan migrate 34 | ``` 35 | 36 | This is the contents of the published config file: 37 | ```php 38 | return [ 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | User Namespace 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This value informs Laravisit which namespace you will be 45 | | selecting to get the user model instance 46 | | If this value equals to null, "\Coderflex\Laravisit\Models\User" will be used 47 | | by default. 48 | | 49 | */ 50 | 'user_namespace' => "\Coderflex\Laravisit\Models\User", 51 | ]; 52 | ``` 53 | 54 | 55 | ## Usage 56 | 57 | ### Use `HasVisits` Trait 58 | 59 | The first thing you need to do is, to use `HasVisits` trait, and implement `CanVisit` interface. 60 | 61 | ```php 62 | namespace App\Models\Post; 63 | 64 | use Coderflex\Laravisit\Concerns\CanVisit; 65 | use Coderflex\Laravisit\Concerns\HasVisits; 66 | use Illuminate\Database\Eloquent\Factories\HasFactory; 67 | use Illuminate\Database\Eloquent\Model; 68 | 69 | class Post extends Model implements CanVisit 70 | { 71 | ... 72 | use HasFactory; 73 | use HasVisits; 74 | ... 75 | } 76 | ``` 77 | After this step, you are all set, you can now count visits by using `visit` method 78 | 79 | ```php 80 | $post->visit(); 81 | ``` 82 | 83 | You can chain methods to the `visit` method. Here are a list of the available methods: 84 | 85 | | METHOD | SYNTAX | DESCRIPTION | EXAMPLE | 86 | | ----------- | ----------- | ----------- | ----------- | 87 | | `withIp()` | string `$ip = null` | Set an Ip address (default `request()->ip()`) | `$post->visit()->withIp()` | 88 | | `withSession()` | string `$session = null` | Set an Session ID (default `session()->getId()`) | `$post->visit()->withSession()` | 89 | |`withData()` | array `$data` | Set custom data | `$post->visit()->withData(['region' => 'USA'])` | 90 | | `withUser()` | Model `$user = null` | Set a user model (default `auth()->user()`) | `$user->visit()->withUser()` | 91 | 92 | --- 93 | 94 | By default, you will have unique visits __each day__ using `dailyInterval()` method. Meaning, when the users access the page multiple times in a day time frame, you will see just `one record` related to them. 95 | 96 | If you want to log users access to a page with different __timeframes__, here are a bunch of useful methods: 97 | 98 | | METHOD | SYNTAX | DESCRIPTION | EXAMPLE | 99 | | ----------- | ----------- | ----------- | ----------- | 100 | | `hourlyInterval()` | `void` | Log visits each hour | `$post->visit()->hourlyIntervals()->withIp();` | 101 | | `dailyInterval()` | `void` | Log visits each day | `$post->visit()->dailyIntervals()->withIp();` | 102 | | `weeklyInterval()` | `void` | Log visits each week | `$post->visit()->weeklyIntervals()->withIp();` | 103 | | `monthlyInterval()` | `void` | Log visits each month | `$post->visit()->monthlyIntervals()->withIp();` | 104 | | `yearlyInterval()` | `void` | Log visits each year | `$post->visit()->yearlyIntervals()->withIp();` | 105 | | `customInterval()` | mixed `$interval` | Log visits within a custom interval | `$post->visit()->customInterval( now()->subYear() )->withIp();` | 106 | 107 | ### Get The Records With Popular Time Frames 108 | After the visits get logged, you can retrieve the data by the following method: 109 | 110 | | METHOD | SYNTAX | DESCRIPTION | EXAMPLE | 111 | | ----------- | ----------- | ----------- | ----------- | 112 | | `withTotalVisitCount()` | `void` | get total visit count | `Post::withTotalVisitCount()->first()->visit_count_total` | 113 | | `popularAllTime()` | `void` | get popular visits all time | `Post::popularAllTime()->get()` | 114 | | `popularToday()` | `void` | get popular visits in the current day | `Post::popularToday()->get()` | 115 | | `popularLastDays()` | int `$days` | get popular visits last given days | `Post::popularLastDays(10)->get()` | 116 | | `popularThisWeek()` | `void` | get popular visits this week | `Post::popularThisWeek()->get()` | 117 | | `popularLastWeek()` | `void` | get popular visits last week | `Post::popularLastWeek()->get()` | 118 | | `popularThisMonth()` | `void` | get popular visits this month | `Post::popularThisMonth()->get()` | 119 | | `popularLastMonth()` | `void` | get popular visits last month | `Post::popularLastMonth()->get()` | 120 | | `popularThisYear()` | `void` | get popular visits this year | `Post::popularThisYear()->get()` | 121 | | `popularLastYear()` | `void` | get popular visits last year | `Post::popularLastYear()->get()` | 122 | | `popularBetween()` | Carbon `$from`, Carbon `$to` | get popular visits between custom two dates | `Post::popularBetween(Carbon::createFromDate(2019, 1, 9), Carbon::createFromDat(2022, 1, 3))->get();` | 123 | 124 | ## Visit Presenter 125 | This package is coming with helpful decorate model properties, and it uses [Laravel Presenter](https://github.com/coderflexx/laravel-presenter) package under the hood. 126 | 127 | | METHOD | SYNTAX | DESCRIPTION | EXAMPLE | 128 | | ----------- | ----------- | ----------- | ----------- | 129 | | `ip()` | `void` | Get the associated IP from the model instance | `$post->visits->first()->present()->ip`| 130 | | `user()` | `void` | Get the associated User from the model instance | `$post->visits->first()->present()->user->name`| 131 | 132 | ## Testing 133 | 134 | ```bash 135 | composer test 136 | ``` 137 | 138 | ## Changelog 139 | 140 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 141 | 142 | ## Contributing 143 | 144 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 145 | 146 | ## Security Vulnerabilities 147 | 148 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 149 | 150 | ## Inspiration 151 | - [Codecourse Laravel Model Popularity](https://codecourse.com/courses/laravel-model-popularity) 152 | 153 | ## Credits 154 | 155 | - [Oussama Sid](https://github.com/ousid) 156 | - [All Contributors](../../contributors) 157 | 158 | ## License 159 | 160 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 161 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coderflexx/laravisit", 3 | "description": "A package to keep track of your pages & understand your audience", 4 | "keywords": [ 5 | "coderflex", 6 | "laravel", 7 | "laravisit" 8 | ], 9 | "homepage": "https://github.com/coderflexx/laravisit", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "oussama sid", 14 | "email": "oussama@coderflex.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "coderflexx/laravel-presenter": "^2.0", 21 | "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0", 22 | "jaybizzle/crawler-detect": "^1.2", 23 | "spatie/laravel-package-tools": "^1.9.2" 24 | }, 25 | "require-dev": { 26 | "laravel/pint": "^1.0", 27 | "nunomaduro/collision": "^7.0|^8.0", 28 | "nunomaduro/larastan": "^2.0.1|^3.0", 29 | "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", 30 | "phpstan/extension-installer": "^1.1", 31 | "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", 32 | "phpstan/phpstan-phpunit": "^1.0|^2.0", 33 | "phpunit/phpunit": "^9.5|^10.0|^11.0", 34 | "pestphp/pest": "^1.21|^2.0|^3.7" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Coderflex\\Laravisit\\": "src", 39 | "Coderflex\\Laravisit\\Database\\Factories\\": "database/factories" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Coderflex\\Laravisit\\Tests\\": "tests" 45 | } 46 | }, 47 | "scripts": { 48 | "analyse": "vendor/bin/phpstan analyse", 49 | "test": "vendor/bin/pest", 50 | "test-coverage": "vendor/bin/pest --coverage" 51 | }, 52 | "config": { 53 | "sort-packages": true, 54 | "allow-plugins": { 55 | "phpstan/extension-installer": true, 56 | "pestphp/pest-plugin": true 57 | } 58 | }, 59 | "extra": { 60 | "laravel": { 61 | "providers": [ 62 | "Coderflex\\Laravisit\\LaravisitServiceProvider" 63 | ] 64 | } 65 | }, 66 | "minimum-stability": "dev", 67 | "prefer-stable": true 68 | } 69 | -------------------------------------------------------------------------------- /config/laravisit.php: -------------------------------------------------------------------------------- 1 | "\Coderflex\Laravisit\Models\User", 16 | ]; 17 | -------------------------------------------------------------------------------- /database/migrations/create_laravisits_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->morphs('visitable'); 19 | $table->json('data'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('laravisits'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/Concerns/CanVisit.php: -------------------------------------------------------------------------------- 1 | withCount('visits as visit_count_total'); 20 | } 21 | 22 | /** 23 | * Get the popular visits all time 24 | */ 25 | public function scopePopularAllTime(Builder $builder): Builder 26 | { 27 | return $builder->withTotalVisitCount() 28 | ->orderBy('visit_count_total', 'desc'); 29 | } 30 | 31 | /** 32 | * Get the popular visits today 33 | */ 34 | public function scopePopularToday(Builder $builder): Builder 35 | { 36 | return $builder->popularBetween( 37 | now()->startOfDay(), 38 | now()->endOfDay() 39 | ); 40 | } 41 | 42 | /** 43 | * Get the popular visits last given days 44 | */ 45 | public function scopePopularLastDays(Builder $builder, int $days): Builder 46 | { 47 | return $builder->popularBetween( 48 | now()->subDays($days), 49 | now() 50 | ); 51 | } 52 | 53 | /** 54 | * Get the popular visits this week 55 | */ 56 | public function scopePopularThisWeek(Builder $builder): Builder 57 | { 58 | return $builder->popularBetween( 59 | now()->startOfWeek(), 60 | now()->endOfWeek(), 61 | ); 62 | } 63 | 64 | /** 65 | * Get the popular visits last week 66 | */ 67 | public function scopePopularLastWeek(Builder $builder): Builder 68 | { 69 | return $builder->popularBetween( 70 | $startOfLastWeek = now()->subDay(7)->startOfWeek(), 71 | $startOfLastWeek->copy()->endOfWeek() 72 | ); 73 | } 74 | 75 | /** 76 | * Get the popular visits this month 77 | */ 78 | public function scopePopularThisMonth(Builder $builder): Builder 79 | { 80 | return $builder->popularBetween( 81 | now()->startOfMonth(), 82 | now()->endOfMonth(), 83 | ); 84 | } 85 | 86 | /** 87 | * Get the popular visits last month 88 | */ 89 | public function scopePopularLastMonth(Builder $builder): Builder 90 | { 91 | return $builder->popularBetween( 92 | now()->startOfMonth()->subMonthWithoutOverflow(), 93 | now()->subMonthWithoutOverflow()->endOfMonth() 94 | ); 95 | } 96 | 97 | /** 98 | * Get the popular visits this year 99 | */ 100 | public function scopePopularThisYear(Builder $builder): Builder 101 | { 102 | return $builder->popularBetween( 103 | now()->startOfYear(), 104 | now()->endOfYear(), 105 | ); 106 | } 107 | 108 | /** 109 | * Get the popular visits last year 110 | */ 111 | public function scopePopularLastYear(Builder $builder): Builder 112 | { 113 | return $builder->popularBetween( 114 | now()->startOfYear()->subYearWithoutOverflow(), 115 | now()->subYearWithoutOverflow()->endOfYear() 116 | ); 117 | } 118 | 119 | /** 120 | * Get the popular visits between two dates 121 | */ 122 | public function scopePopularBetween(Builder $builder, Carbon $from, Carbon $to): Builder 123 | { 124 | return $builder->whereHas('visits', $this->betweenScope($from, $to)) 125 | ->withCount([ 126 | 'visits as visit_count_total' => $this->betweenScope($from, $to), 127 | ]); 128 | } 129 | 130 | /** 131 | * Get the popular visits between two dates 132 | */ 133 | protected function betweenScope(Carbon $from, Carbon $to): Closure 134 | { 135 | return fn ($query) => $query->whereBetween('created_at', [$from, $to]); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Concerns/HasVisits.php: -------------------------------------------------------------------------------- 1 | morphMany(Visit::class, 'visitable'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Concerns/SetsPendingIntervals.php: -------------------------------------------------------------------------------- 1 | the name of carbon interval method 20 | * 21 | * @var array 22 | */ 23 | protected static $intervalsFunc = [ 24 | 'hourlyIntervals' => 'subHour', 25 | 'dailyIntervals' => 'subDay', 26 | 'weeklyIntervals' => 'subWeek', 27 | 'monthlyIntervals' => 'subMonth', 28 | 'yearlyIntervals' => 'subYear', 29 | ]; 30 | 31 | /** 32 | * Set Time Intervals 33 | */ 34 | public function __call($name, $arguments): mixed 35 | { 36 | try { 37 | $timezone = count($arguments) ? $arguments[0] : null; 38 | $method = self::$intervalsFunc[$name]; 39 | 40 | $this->interval = Carbon::now($timezone)->$method(); 41 | 42 | return $this; 43 | } catch (\Throwable $th) { 44 | return __('Error: Method :name does not exists, :message', [ 45 | 'name' => $name, 46 | 'message' => $th->getMessage(), 47 | ]); 48 | } 49 | } 50 | 51 | /** 52 | * Set Custom Interval 53 | */ 54 | public function customInterval(mixed $interval): self 55 | { 56 | $this->interval = $interval instanceof Carbon 57 | ? $interval 58 | : Carbon::parse($interval); 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidDataException.php: -------------------------------------------------------------------------------- 1 | get_class($model), 18 | 'interface' => '\Coderflex\Laravisit\Concerns\CanVisit', 19 | ]) 20 | )); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaravisitServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravisit') 19 | ->hasConfigFile() 20 | ->hasMigration('create_laravisits_table'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Models/User.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | protected $fillable = [ 20 | 'name', 21 | 'email', 22 | 'password', 23 | ]; 24 | 25 | /** 26 | * The attributes that should be hidden for serialization. 27 | * 28 | * @var array 29 | */ 30 | protected $hidden = [ 31 | 'password', 32 | 'remember_token', 33 | ]; 34 | 35 | /** 36 | * The attributes that should be cast. 37 | * 38 | * @var array 39 | */ 40 | protected $casts = [ 41 | 'email_verified_at' => 'datetime', 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /src/Models/Visit.php: -------------------------------------------------------------------------------- 1 | 'json', 43 | ]; 44 | 45 | /** 46 | * The classes that should be present 47 | * 48 | * @var array 49 | */ 50 | protected $presenters = [ 51 | 'default' => VisitPresenter::class, 52 | ]; 53 | 54 | public function visitable() 55 | { 56 | return $this->morphTo(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/PendingVisit.php: -------------------------------------------------------------------------------- 1 | header('User-Agent')); 30 | 31 | $this->isCrawler = $crawlerDetect->isCrawler(); 32 | 33 | // set daily intervals by default 34 | $this->dailyIntervals(); // @phpstan-ignore-line 35 | } 36 | 37 | /** 38 | * Set IP attribute 39 | */ 40 | public function withIP(?string $ip = null): self 41 | { 42 | $this->attributes['ip'] = $ip ?? request()->ip(); 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Set Session attribute 49 | */ 50 | public function withSession(?string $session = null): self 51 | { 52 | $this->attributes['session'] = $session ?? session()->getId(); 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Set Custom Data attribute 59 | */ 60 | public function withData(array $data): self 61 | { 62 | if (! count($data)) { 63 | throw new InvalidDataException('The data argument cannot be empty'); 64 | } 65 | 66 | $this->attributes = array_merge($this->attributes, $data); 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Set User attribute 73 | */ 74 | public function withUser(?Model $user = null): self 75 | { 76 | $this->attributes['user_id'] = $user?->id ?? auth()->id(); 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Build Json Columns from the given attribues 83 | */ 84 | protected function buildJsonColumns(): array 85 | { 86 | return collect($this->attributes) 87 | ->mapWithKeys( 88 | fn ($value, $index) => ['data->'.$index => $value] 89 | ) 90 | ->toArray(); 91 | } 92 | 93 | /** 94 | * Make sure that we need to log the current record or not 95 | * based on the creation 96 | */ 97 | protected function shouldBeLoggedAgain(Visit $visit): bool 98 | { 99 | // Using wasRecentlyCreate model attribute 100 | // to check if the visit model was created 101 | // already or found. 102 | 103 | return ! $visit->wasRecentlyCreated && 104 | $visit->created_at->lt($this->interval); 105 | } 106 | 107 | public function __destruct() 108 | { 109 | // if the current request is a crawler, 110 | // we don't need to log the visit 111 | // because it's not a real visitor 112 | if ($this->isCrawler) { 113 | return; 114 | } 115 | 116 | // @phpstan-ignore-next-line 117 | $visit = $this->model 118 | ->visits() 119 | ->where('created_at', '>=', $this->interval) 120 | ->latest() 121 | ->firstOrCreate($this->buildJsonColumns(), [ 122 | 'data' => $this->attributes, 123 | ]); 124 | 125 | $visit->when( 126 | $this->shouldBeLoggedAgain($visit), 127 | function () use ($visit) { 128 | $visit->replicate()->save(); 129 | } 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Presenters/VisitPresenter.php: -------------------------------------------------------------------------------- 1 | model->data['ip']; // @phpstan-ignore-line 16 | } 17 | 18 | /** 19 | * Get the associated User from the model instance 20 | */ 21 | public function user(): Model 22 | { 23 | $userId = $this->model->data['user_id']; // @phpstan-ignore-line 24 | $userNamespace = config('laravisit.user_namespace'); 25 | 26 | $user = is_null($userNamespace) || empty($userNamespace) 27 | ? '\Coderflex\Laravisit\Models\User' 28 | : $userNamespace; 29 | 30 | return (new $user())->find($userId); 31 | } 32 | } 33 | --------------------------------------------------------------------------------