├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── config └── config.php ├── database └── migrations │ ├── 2020_01_01_000001_create_statistics_data_table.php │ ├── 2020_01_01_000002_create_statistics_routes_table.php │ ├── 2020_01_01_000003_create_statistics_paths_table.php │ ├── 2020_01_01_000004_create_statistics_agents_table.php │ ├── 2020_01_01_000005_create_statistics_devices_table.php │ ├── 2020_01_01_000006_create_statistics_platforms_table.php │ ├── 2020_01_01_000007_create_statistics_geoips_table.php │ └── 2020_01_01_000008_create_statistics_requests_table.php ├── phpstan.neon.dist └── src ├── Console └── Commands │ ├── MigrateCommand.php │ ├── PublishCommand.php │ └── RollbackCommand.php ├── Http └── Middleware │ └── TrackStatistics.php ├── Jobs ├── CleanStatisticsRequests.php └── CrunchStatistics.php ├── Models ├── Agent.php ├── Datum.php ├── Device.php ├── Geoip.php ├── Path.php ├── Platform.php ├── Request.php └── Route.php └── Providers └── StatisticsServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Rinvex Statistics Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](CONTRIBUTING.md). 6 | 7 | 8 | ## [v6.1.0] - 2022-02-14 9 | - Update composer dependencies to Laravel v9 10 | 11 | ## [v6.0.0] - 2021-08-22 12 | - Drop PHP v7 support, and upgrade rinvex package dependencies to next major version 13 | - Update composer dependencies 14 | - Merge rules instead of resetting, to allow adequate model override 15 | - Fix constructor initialization order (fill attributes should come next after merging fillables & rules) 16 | - Set validation rules in constructor for consistency & flexibility 17 | - Upgrade to GitHub-native Dependabot 18 | - Drop old MySQL versions support that doesn't support json columns 19 | - Use `request->input()` instead of `request->get()` 20 | - Simplify service provider model registration into IoC 3baa106 21 | - Add exclude option for form inputs 22 | - Update CrunchStatistics.php 23 | - Enable StyleCI risky mode 24 | 25 | ## [v5.0.1] - 2020-12-25 26 | - Add support for PHP v8 27 | 28 | ## [v5.0.0] - 2020-12-22 29 | - Upgrade to Laravel v8 30 | - Update validation rules 31 | 32 | ## [v4.1.0] - 2020-06-15 33 | - Update validation rules 34 | - Drop using rinvex/laravel-cacheable from core packages for more flexibility 35 | - Caching should be handled on the application layer, not enforced from the core packages 36 | - Drop PHP 7.2 & 7.3 support from travis 37 | 38 | ## [v4.0.6] - 2020-05-30 39 | - Remove default indent size config 40 | - Add strip_tags validation rule to string fields 41 | - Specify events queue 42 | - Explicitly specify relationship attributes 43 | - Add strip_tags validation rule 44 | - Explicitly define relationship name 45 | 46 | ## [v4.0.5] - 2020-04-12 47 | - Fix ServiceProvider registerCommands method compatibility 48 | 49 | ## [v4.0.4] - 2020-04-09 50 | - Tweak artisan command registration 51 | - Reverse commit "Convert database int fields into bigInteger" 52 | - Refactor publish command and allow multiple resource values 53 | 54 | ## [v4.0.3] - 2020-04-04 55 | - Fix namespace issue 56 | 57 | ## [v4.0.2] - 2020-04-04 58 | - Enforce consistent artisan command tag namespacing 59 | - Enforce consistent package namespace 60 | - Drop laravel/helpers usage as it's no longer used 61 | 62 | ## [v4.0.1] - 2020-03-20 63 | - Convert into bigInteger database fields 64 | - Add shortcut -f (force) for artisan publish commands 65 | - Fix migrations path 66 | 67 | ## [v4.0.0] - 2020-03-15 68 | - Upgrade to Laravel v7.1.x & PHP v7.4.x 69 | 70 | ## [v3.0.2] - 2020-03-13 71 | - Tweak TravisCI config 72 | - Add migrations autoload option to the package 73 | - Tweak service provider `publishesResources` 74 | - Remove indirect composer dependency 75 | - Drop using global helpers 76 | - Update StyleCI config 77 | 78 | ## [v3.0.1] - 2019-12-18 79 | - Fix `migrate:reset` args as it doesn't accept --step 80 | 81 | ## [v3.0.0] - 2019-09-23 82 | - Upgrade to Laravel v6 and update dependencies 83 | 84 | ## [v2.1.1] - 2019-06-03 85 | - Enforce latest composer package versions 86 | 87 | ## [v2.1.0] - 2019-06-02 88 | - Update composer deps 89 | - Drop PHP 7.1 travis test 90 | - Refactor migrations and artisan commands, and tweak service provider publishes functionality 91 | 92 | ## [v2.0.0] - 2019-03-03 93 | - Rename environment variable QUEUE_DRIVER to QUEUE_CONNECTION 94 | - Require PHP 7.2 & Laravel 5.8 95 | - Apply PHPUnit 8 updates 96 | 97 | ## [v1.0.2] - 2018-12-22 98 | - Update composer dependencies 99 | - Add PHP 7.3 support to travis 100 | - Fix MySQL / PostgreSQL json column compatibility 101 | 102 | ## [v1.0.1] - 2018-10-05 103 | - Fix wrong composer package version constraints 104 | 105 | ## [v1.0.0] - 2018-10-01 106 | - Enforce Consistency 107 | - Support Laravel 5.7+ 108 | - Rename package to rinvex/laravel-statistics 109 | 110 | ## [v0.0.2] - 2018-09-21 111 | - Update travis php versions 112 | - Define polymorphic relationship parameters explicitly 113 | - Require import package rinvex/countries 114 | - Update timezone validation rule 115 | - Drop StyleCI multi-language support (paid feature now!) 116 | - Update composer dependencies 117 | - Prepare and tweak testing configuration 118 | - Update StyleCI options 119 | - Highlight variables in strings explicitly 120 | - Update PHPUnit options 121 | 122 | ## v0.0.1 - 2018-02-18 123 | - Tag first release 124 | 125 | [v6.1.0]: https://github.com/rinvex/laravel-statistics/compare/v6.0.0...v6.1.0 126 | [v6.0.0]: https://github.com/rinvex/laravel-statistics/compare/v5.0.1...v6.0.0 127 | [v5.0.1]: https://github.com/rinvex/laravel-statistics/compare/v5.0.0...v5.0.1 128 | [v5.0.0]: https://github.com/rinvex/laravel-statistics/compare/v4.1.0...v5.0.0 129 | [v4.1.0]: https://github.com/rinvex/laravel-statistics/compare/v4.0.6...v4.1.0 130 | [v4.0.6]: https://github.com/rinvex/laravel-statistics/compare/v4.0.5...v4.0.6 131 | [v4.0.5]: https://github.com/rinvex/laravel-statistics/compare/v4.0.4...v4.0.5 132 | [v4.0.4]: https://github.com/rinvex/laravel-statistics/compare/v4.0.3...v4.0.4 133 | [v4.0.3]: https://github.com/rinvex/laravel-statistics/compare/v4.0.2...v4.0.3 134 | [v4.0.2]: https://github.com/rinvex/laravel-statistics/compare/v4.0.1...v4.0.2 135 | [v4.0.1]: https://github.com/rinvex/laravel-statistics/compare/v4.0.0...v4.0.1 136 | [v4.0.0]: https://github.com/rinvex/laravel-statistics/compare/v3.0.2...v4.0.0 137 | [v3.0.2]: https://github.com/rinvex/laravel-statistics/compare/v3.0.1...v3.0.2 138 | [v3.0.1]: https://github.com/rinvex/laravel-statistics/compare/v3.0.0...v3.0.1 139 | [v3.0.0]: https://github.com/rinvex/laravel-statistics/compare/v2.1.1...v3.0.0 140 | [v2.1.1]: https://github.com/rinvex/laravel-statistics/compare/v2.1.0...v2.1.1 141 | [v2.1.0]: https://github.com/rinvex/laravel-statistics/compare/v2.0.0...v2.1.0 142 | [v2.0.0]: https://github.com/rinvex/laravel-statistics/compare/v1.0.2...v2.0.0 143 | [v1.0.2]: https://github.com/rinvex/laravel-statistics/compare/v1.0.1...v1.0.2 144 | [v1.0.1]: https://github.com/rinvex/laravel-statistics/compare/v1.0.0...v1.0.1 145 | [v1.0.0]: https://github.com/rinvex/laravel-statistics/compare/v0.0.2...v1.0.0 146 | [v0.0.2]: https://github.com/rinvex/laravel-statistics/compare/v0.0.1...v0.0.2 147 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [help@rinvex.com](mailto:help@rinvex.com). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | This project adheres to the following standards and practices. 4 | 5 | 6 | ## Versioning 7 | 8 | This project is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible. 9 | 10 | Releases will be numbered with the following format: 11 | 12 | - `..` 13 | - `..` 14 | 15 | And constructed with the following guidelines: 16 | 17 | - Breaking backward compatibility bumps the major and resets the minor and patch. 18 | - New additions without breaking backward compatibility bump the minor and reset the patch. 19 | - Bug fixes and misc changes bump the patch. 20 | 21 | 22 | ## Pull Requests 23 | 24 | The pull request process differs for new features and bugs. 25 | 26 | Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed, please leave a comment detailing your proposed fix or create a pull request with the fix mentioning that issue id. 27 | 28 | 29 | ## Coding Standards 30 | 31 | This project follows the FIG PHP Standards Recommendations compliant with the [PSR-1: Basic Coding Standard](http://www.php-fig.org/psr/psr-1/), [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/) and [PSR-4: Autoloader](http://www.php-fig.org/psr/psr-4/) to ensure a high level of interoperability between shared PHP code. If you notice any compliance oversights, please send a patch via pull request. 32 | 33 | 34 | ## Feature Requests 35 | 36 | If you have a proposal or a feature request, you may create an issue with `[Proposal]` in the title. 37 | 38 | The proposal should also describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. 39 | 40 | 41 | ## Git Flow 42 | 43 | This project follows [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/), and as such has `master` (latest stable releases), `develop` (latest WIP development) and X.Y support branches (when there's multiple major versions). 44 | 45 | Accordingly all pull requests MUST be sent to the `develop` branch. 46 | 47 | > **Note:** Pull requests which do not follow these guidelines will be closed without any further notice. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021, Rinvex LLC, 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 | # Rinvex Statistics 2 | 3 | ⚠️ This package is abandoned and no longer maintained. No replacement package was suggested. ⚠️ 4 | 5 | 👉 If you are interested to step on as the main maintainer of this package, please [reach out to me](https://twitter.com/omranic)! 6 | 7 | --- 8 | 9 | **Rinvex Statistics** is a lightweight, yet detailed package for tracking and recording user visits across your Laravel application. With only one simple query per request, important data is being stored, and later a cronjob crush numbers to extract meaningful stories from within the haystack. 10 | 11 | Unlike other tracking packages that seriously damage your project's performance (yes, I mean that package you know 😅), our package takes a different approach by just executing only one query at the end of each request after the response is being served to the user, through the `terminate` method of an automatically attached middleware, and then later on it uses the raw data previously inserted in the database to extract meaningfull numbers. This is done based on a random lottery request, or through a scheduled job (recommended) that could be queued to offload the heavy crunching work. 12 | 13 | **Rinvex Statistics** tracks each -valid- request, meaning only requests that goes through routing pipeline, which also means that any wrong URL that results in `NotFoundHttpException` will not be tracked. If requested page has uncaught exceptions, it won't be tracked as well. It track user's logged in account (if any), session of all users and guests (if any), device (family, model, brand), platform (family, version), browser (agent, kind, family, version), path, route (action, middleware, parameters), host, protocol, ip address, language, status codes, and many more, and still we've plenty of awesome features planned for the future. 14 | 15 | With such a huge collected data, the `statistics_requests` database table will noticeably increase in size specially if you've a lot of visits, that's why it's recommended to clean it periodically. Other important data will stay still in their respective tables, normalized and without any performance issues, so only this table need to be cleaned. By default that will be done automatically every month. 16 | 17 | The default implementation of **Rinvex Statistics** comes with zero configuration out-of-the-box, which means it just works once installed. But it's recommended to change the defaults and disable the "Statistics Crunching Lottery" from config file, and replace it with a [Scheduled Tasks](https://laravel.com/docs/master/scheduling) for even better performance if you've large number of visits. See [Usage](#usage) for details. 18 | 19 | [![Packagist](https://img.shields.io/packagist/v/rinvex/laravel-statistics.svg?label=Packagist&style=flat-square)](https://packagist.org/packages/rinvex/laravel-statistics) 20 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/rinvex/laravel-statistics.svg?label=Scrutinizer&style=flat-square)](https://scrutinizer-ci.com/g/rinvex/laravel-statistics/) 21 | [![Travis](https://img.shields.io/travis/rinvex/laravel-statistics.svg?label=TravisCI&style=flat-square)](https://travis-ci.org/rinvex/laravel-statistics) 22 | [![StyleCI](https://styleci.io/repos/118045101/shield)](https://styleci.io/repos/118045101) 23 | [![License](https://img.shields.io/packagist/l/rinvex/laravel-statistics.svg?label=License&style=flat-square)](https://github.com/rinvex/laravel-statistics/blob/develop/LICENSE) 24 | 25 | 26 | ## Installation 27 | 28 | 1. Install the package via composer: 29 | ```shell 30 | composer require rinvex/laravel-statistics 31 | ``` 32 | 33 | 2. Publish resources (migrations and config files): 34 | ```shell 35 | php artisan rinvex:publish:statistics 36 | ``` 37 | 38 | 3. Execute migrations via the following command: 39 | ```shell 40 | php artisan rinvex:migrate:statistics 41 | ``` 42 | 43 | 4. Done! 44 | 45 | 46 | ## Usage 47 | 48 | Well, this is the fun part! **Rinvex Statistics** has no usage instructions, because it just works! You install it and you are done! Seriously!! 49 | 50 | Anyway, as a recommended performance tweak go ahead and do the following (optionally): 51 | 52 | 1. Publish config file via the following command: 53 | ``` 54 | php artisan rinvex:publish:statistics 55 | ``` 56 | 57 | 2. Disable the "Statistics Crunching Lottery" from config file. 58 | 59 | 3. Follow the default Laravel documentation about [Scheduled Tasks](https://laravel.com/docs/master/scheduling), then schedule both `\Rinvex\Statistics\Jobs\CrunchStatistics` and `\Rinvex\Statistics\Jobs\CleanStatisticsRequests` jobs at whatever intervals you see appropriate. 60 | 61 | 4. Enjoy! 62 | 63 | > **Note:** **Rinvex Statistics** has a `\Rinvex\Statistics\Http\Middleware\TrackStatistics` middleware that attach itself automatically to the `web` middleware group, that's how it works out-of-the-box with zero configuration. 64 | 65 | ### Data retrieval 66 | 67 | You may need to build your own frontend interface to browse statistics, and for that you can utilize any of the included eloquent models as you normally do with [Laravel Eloquent](https://laravel.com/docs/master/eloquent). 68 | 69 | All eloquent models are self explainatory: 70 | 71 | - `\Rinvex\Statistics\Models\Agent` browser agent model 72 | - `\Rinvex\Statistics\Models\Datum` raw statistics data (to be crunched) 73 | - `\Rinvex\Statistics\Models\Device` user device model 74 | - `\Rinvex\Statistics\Models\Path` request path model 75 | - `\Rinvex\Statistics\Models\Platform` user platform model 76 | - `\Rinvex\Statistics\Models\Request` request data model (to be cleaned periodically) 77 | - `\Rinvex\Statistics\Models\Route` request route details model 78 | 79 | All models are bound to the [Service Container](https://laravel.com/docs/master/container) so you can swap easily from anywhere in your application. In addition to the default normal way of using these models explicitely, you can use their respective service names as in the following example: 80 | 81 | ```php 82 | // Find first browser agent (any of these methods are valid and equivalent) 83 | app('rinvex.statistics.agent')->first(); 84 | new \Rinvex\Statistics\Models\Agent::first(); 85 | app(\Rinvex\Statistics\Contracts\AgentContract::class)->first(); 86 | ``` 87 | 88 | Same for all other eloquent models. 89 | 90 | ### Counts that matters 91 | 92 | All agent, device, path, platform, route models have a `count` attribute, which gets updated automatically whenever a new request has been tracked. 93 | 94 | This `count` attribute reflects number of hits. To make it clear let's explain through data samples: 95 | 96 | #### Agents 97 | 98 | | id | kind | family | version | count | name | 99 | | --- | --- | --- | --- | --- | --- | 100 | | 1 | desktop | Chrome | 63.0.3239 | 734 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 | 101 | 102 | This means there's 734 visit to our project through **Chrome** browser, version **63.0.3239**, with agent (**Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36**) 103 | 104 | #### Devices 105 | 106 | | id | family | model | brand | count | 107 | | --- | --- | --- | --- | --- | 108 | | 1 | iPhone | iPhone | Apple | 83 | 109 | 110 | This means there's 83 visits to our project through **iPhone** device. 111 | 112 | #### Platforms 113 | 114 | | id | family | version | count | 115 | | --- | --- | --- | --- | 116 | | 1 | Mac OS X | 10.12.6 | 615 | 117 | 118 | This means there's 615 visits to our project through **Mac OS X** operating system, with version **10.12.6**. 119 | 120 | #### Paths 121 | 122 | | id | host | locale | path | parameters | count | 123 | | --- | --- | --- | --- | --- | --- | 124 | | 1 | test.homestead.local | en | en/adminarea/roles/admin | {"role": "admin", "locale": "en"} | 12 | 125 | 126 | This means there's 12 visits to the admin dashboard roles management of the **test.homestead.local** host (in case you have multiple hosts or wildcard subdomains enabled on the same project, you can track all of them correctly here). The english interface was used, and the accessed route had two parameters, one for locale (english in this case), and updated role record (admin in this case). 127 | 128 | This table could be used as a visit counter for all your pages. To retrieve and display page views you can use the following code for example: 129 | 130 | ```php 131 | $pageViews = app('rinvex.statistics.path')->where('path', request()->decodedPath())->first()->count; 132 | ``` 133 | 134 | And simply use the `$pageViews` variable anywhere in your views or controllers, or anywhere else. That way you have automatic visit counter for all your project's pages, very useful and performant, ready at your fingertips. You can add `host` contraint in case you have wildcard subdomains enabled. 135 | 136 | #### Routes 137 | 138 | | id | name | path | action | middleware | parameters | count | 139 | | --- | --- | --- | --- | --- | --- | --- | 140 | | 1 | adminarea.roles.edit | {locale}/adminarea/roles/{role} | App\Http\Controllers\Adminarea\RolesController@form | ["web","nohttpcache","can:access-adminarea","auth","can:update-roles,roles"] | {"role": "[a-z0-9-]+", "locale": "[a-z]{2}"} | 41 | 141 | 142 | This means there's 41 visits to the `adminarea.roles.edit` route, which has the `{locale}/adminarea/roles/{role}` raw path, and served through the `App\Http\Controllers\Adminarea\RolesController@form` controller action, and has the following middleware applied `["web","nohttpcache","can:access-adminarea","auth","can:update-roles,roles"]`, knowing the route accepts two parameters with the following regex requirements `{"role": "[a-z0-9-]+", "locale": "[a-z]{2}"}`. 143 | 144 | As you can see, this `statistics_routes` table beside the `statistics_paths` table are both complimentary, and could be used together to track which paths and routs are being accessed, how many times, and what controller actions serve it, and what parameters are required, with the actual parameter replacements used to access it. Think of routes as your raw links blueprint map, and of paths as the executed and actually used links by users. 145 | 146 | #### Geoips 147 | 148 | | id | client_ip | latitude | longitude | country_code | client_ips | is_from_trusted_proxy | division_code | postal_code | timezone | city | count | 149 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 150 | | 1 | 127.0.0.0 | 41.31 | -72.92 | US | NULL | 0 | CT | 06510 | America/New_York | New Haven | 57 | 151 | 152 | This means there's 57 visits to the project from IP address `127.0.0.0` with the latitude, longitude and timezone mentioned above coming from `New Haven` city, `Connecticut` state. 153 | 154 | #### Requests 155 | 156 | | id | route_id | agent_id | device_id | platform_id | path_id | geoip_id | user_id | user_type | session_id | method | status_code | protocol_version | referer | language | is_no_cache | wants_json | is_secure | is_json | is_ajax | is_pjax | created_at | 157 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 158 | | 1 | 123 | 123 | 123 | 123 | 123 | 123 | 123 | user | MU22QcrzDIdj0gY27yJmUPJHNFy9Hlqvkel1KBZ1 | GET|POST | 200 | HTTP/1.1 | https://google.com | en_US | 0 | 0 | 1 | 0 | 0 | 0 | 2018-01-10 09:42:39 | 159 | 160 | This is the most comprehensive table that records every single request made to the project, with access details as seen in the sample above. Through `session_id`, `user_id` and `user_type` you can track guests (logged out) and users (logged in) and extract unique visits/visitors with the criteria you see appropriate for you. 161 | 162 | > **Notes:** 163 | > - As a final note, this package is a data hord, and it doesn't actually do much of the math that could be done on such a valuable gathered data, so it's up to your imagination to utilize it however you see fits your goals. Implementation details is up to you. 164 | > - We didn't explain the `statistics_data` table since it's used for temporary raw data storage until it's being crunched and processed by the package, so you should **NOT** care or mess with that table. It's used internally by the package and has no real end-user usage. 165 | > - The `\Rinvex\Statistics\Models\Request` model has relationships to all related data such as `agent`, `device`, `path`, `platform`, and `route`. So once you grab a request instance you can access any of it's relationships as you normaly do with [Eloquent Relationships](https://laravel.com/docs/master/eloquent-relationships) like so: `$statisticsRequest->agent->version` or `$statisticsRequest->platform->family`. 166 | 167 | 168 | ## Changelog 169 | 170 | Refer to the [Changelog](CHANGELOG.md) for a full history of the project. 171 | 172 | 173 | ## Support 174 | 175 | The following support channels are available at your fingertips: 176 | 177 | - [Chat on Slack](https://bit.ly/rinvex-slack) 178 | - [Help on Email](mailto:help@rinvex.com) 179 | - [Follow on Twitter](https://twitter.com/rinvex) 180 | 181 | 182 | ## Contributing & Protocols 183 | 184 | Thank you for considering contributing to this project! The contribution guide can be found in [CONTRIBUTING.md](CONTRIBUTING.md). 185 | 186 | Bug reports, feature requests, and pull requests are very welcome. 187 | 188 | - [Versioning](CONTRIBUTING.md#versioning) 189 | - [Pull Requests](CONTRIBUTING.md#pull-requests) 190 | - [Coding Standards](CONTRIBUTING.md#coding-standards) 191 | - [Feature Requests](CONTRIBUTING.md#feature-requests) 192 | - [Git Flow](CONTRIBUTING.md#git-flow) 193 | 194 | 195 | ## Security Vulnerabilities 196 | 197 | If you discover a security vulnerability within this project, please send an e-mail to [help@rinvex.com](help@rinvex.com). All security vulnerabilities will be promptly impressioned. 198 | 199 | 200 | ## About Rinvex 201 | 202 | Rinvex is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That’s how we do our share of advancing humanity. 203 | 204 | 205 | ## License 206 | 207 | This software is released under [The MIT License (MIT)](LICENSE). 208 | 209 | (c) 2016-2022 Rinvex LLC, Some rights reserved. 210 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rinvex/laravel-statistics", 3 | "description": "Rinvex Statistics is a lightweight, yet detailed package for tracking and recording user visits across your Laravel application. With only one simple query per request, important data is being stored, and later a cronjob crush numbers to extract meaningful stories from within the haystack.", 4 | "type": "library", 5 | "keywords": [ 6 | "user", 7 | "model", 8 | "rinvex", 9 | "laravel", 10 | "eloquent", 11 | "statistics", 12 | "tracking", 13 | "visitor", 14 | "agents", 15 | "client", 16 | "views", 17 | "device", 18 | "logging", 19 | "platform", 20 | "browser" 21 | ], 22 | "license": "MIT", 23 | "homepage": "https://rinvex.com", 24 | "support": { 25 | "email": "help@rinvex.com", 26 | "issues": "https://github.com/rinvex/laravel-statistics/issues", 27 | "source": "https://github.com/rinvex/laravel-statistics", 28 | "docs": "https://github.com/rinvex/laravel-statistics/blob/master/README.md" 29 | }, 30 | "authors": [ 31 | { 32 | "name": "Rinvex LLC", 33 | "homepage": "https://rinvex.com", 34 | "email": "help@rinvex.com" 35 | }, 36 | { 37 | "name": "Abdelrahman Omran", 38 | "homepage": "https://omranic.com", 39 | "email": "me@omranic.com", 40 | "role": "Project Lead" 41 | }, 42 | { 43 | "name": "The Generous Laravel Community", 44 | "homepage": "https://github.com/rinvex/laravel-statistics/contributors" 45 | } 46 | ], 47 | "require": { 48 | "php": "^8.0.0", 49 | "geoip2/geoip2": "^2.12.0", 50 | "illuminate/bus": "^9.0.0 || ^10.0.0", 51 | "illuminate/console": "^9.0.0 || ^10.0.0", 52 | "illuminate/contracts": "^9.0.0 || ^10.0.0", 53 | "illuminate/database": "^9.0.0 || ^10.0.0", 54 | "illuminate/http": "^9.0.0 || ^10.0.0", 55 | "illuminate/queue": "^9.0.0 || ^10.0.0", 56 | "illuminate/routing": "^9.0.0 || ^10.0.0", 57 | "illuminate/support": "^9.0.0 || ^10.0.0", 58 | "jenssegers/agent": "^2.6.0", 59 | "rinvex/countries": "^8.0.0", 60 | "rinvex/laravel-support": "^6.0.0", 61 | "symfony/http-foundation": "^6.0.0", 62 | "torann/geoip": "^3.0.0", 63 | "ua-parser/uap-php": "^3.9.0" 64 | }, 65 | "require-dev": { 66 | "codedungeon/phpunit-result-printer": "^0.31.0", 67 | "illuminate/container": "^9.0.0 || ^10.0.0", 68 | "phpunit/phpunit": "^9.5.0" 69 | }, 70 | "autoload": { 71 | "psr-4": { 72 | "Rinvex\\Statistics\\": "src/" 73 | } 74 | }, 75 | "autoload-dev": { 76 | "psr-4": { 77 | "Rinvex\\Statistics\\Tests\\": "tests" 78 | } 79 | }, 80 | "scripts": { 81 | "test": "vendor/bin/phpunit" 82 | }, 83 | "config": { 84 | "sort-packages": true, 85 | "preferred-install": "dist", 86 | "optimize-autoloader": true 87 | }, 88 | "extra": { 89 | "laravel": { 90 | "providers": [ 91 | "Rinvex\\Statistics\\Providers\\StatisticsServiceProvider" 92 | ] 93 | } 94 | }, 95 | "minimum-stability": "dev", 96 | "prefer-stable": true 97 | } 98 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | true, 9 | 10 | // Statistics Database Tables 11 | 'tables' => [ 12 | 'data' => 'statistics_data', 13 | 'paths' => 'statistics_paths', 14 | 'geoips' => 'statistics_geoips', 15 | 'routes' => 'statistics_routes', 16 | 'agents' => 'statistics_agents', 17 | 'devices' => 'statistics_devices', 18 | 'requests' => 'statistics_requests', 19 | 'platforms' => 'statistics_platforms', 20 | ], 21 | 22 | // Statistics Models 23 | 'models' => [ 24 | 'path' => \Rinvex\Statistics\Models\Path::class, 25 | 'datum' => \Rinvex\Statistics\Models\Datum::class, 26 | 'geoip' => \Rinvex\Statistics\Models\Geoip::class, 27 | 'route' => \Rinvex\Statistics\Models\Route::class, 28 | 'agent' => \Rinvex\Statistics\Models\Agent::class, 29 | 'device' => \Rinvex\Statistics\Models\Device::class, 30 | 'request' => \Rinvex\Statistics\Models\Request::class, 31 | 'platform' => \Rinvex\Statistics\Models\Platform::class, 32 | ], 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Statistics Crunching Lottery 37 | |-------------------------------------------------------------------------- 38 | | 39 | | Raw statistical data needs to be crunched to extract meaningful stories. 40 | | Here the chances that it will happen on a given request. By default, 41 | | the odds are 2 out of 100. For better performance consider using 42 | | task scheduling and set this lottery option to "FALSE" then. 43 | | 44 | */ 45 | 46 | 'lottery' => [2, 100], 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Statistics Cleaning Period 51 | |-------------------------------------------------------------------------- 52 | | 53 | | If you would like to clean old statistics automatically, you may specify 54 | | the number of days after which the it will be wiped automatically. 55 | | Any records older than this period (in days) will be cleaned. 56 | | 57 | | Note that this cleaning process just affects `statistics_requests` 58 | | only! Other database tables are kept safely untouched anyway. 59 | | 60 | */ 61 | 62 | 'lifetime' => false, 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Exclude input fields 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Some fields must/should/might be hidden. And you can decide which area 70 | | would be this is. For example sensitive informations, credit card info, 71 | | passwords, pins, secret keys and others. Also some fields are may not 72 | | important or may unecessary informations for statistics. For example csrf tokens, 73 | | nested inputs and other fields. This option excludes given form inputs. 74 | | 75 | | This option prevents possible data leaks because form inputs storing as raw. 76 | | 77 | */ 78 | 79 | 'exclude_input_fields' => [ 80 | 'email', 81 | 'password', 82 | 'password_confirmation', 83 | 'secret', 84 | 'secret_key', 85 | 'pin', 86 | '_csrf', 87 | 'card_number', 88 | 'card_owner', 89 | ], 90 | ]; 91 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000001_create_statistics_data_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('session_id'); 22 | $table->nullableMorphs('user'); 23 | $table->integer('status_code'); 24 | $table->text('uri'); 25 | $table->string('method'); 26 | $table->json('server'); 27 | $table->json('input')->nullable(); 28 | $table->timestamp('created_at')->nullable(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down(): void 38 | { 39 | Schema::dropIfExists(config('rinvex.statistics.tables.data')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000002_create_statistics_routes_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('name'); 22 | $table->string('path'); 23 | $table->string('action'); 24 | $table->string('middleware')->nullable(); 25 | $table->json('parameters')->nullable(); 26 | $table->integer('count')->unsigned()->default(0); 27 | 28 | // Indexes 29 | $table->unique('name'); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down(): void 39 | { 40 | Schema::dropIfExists(config('rinvex.statistics.tables.routes')); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000003_create_statistics_paths_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('host'); 22 | $table->string('locale'); 23 | $table->string('path'); 24 | $table->string('method'); 25 | $table->json('parameters')->nullable(); 26 | $table->integer('count')->unsigned()->default(0); 27 | 28 | // Indexes 29 | $table->unique(['host', 'path', 'method', 'locale']); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down(): void 39 | { 40 | Schema::dropIfExists(config('rinvex.statistics.tables.paths')); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000004_create_statistics_agents_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('name'); 22 | $table->string('kind'); 23 | $table->string('family'); 24 | $table->string('version')->nullable(); 25 | $table->integer('count')->unsigned()->default(0); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down(): void 35 | { 36 | Schema::dropIfExists(config('rinvex.statistics.tables.agents')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000005_create_statistics_devices_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('family'); 22 | $table->string('model')->nullable(); 23 | $table->string('brand')->nullable(); 24 | $table->integer('count')->unsigned()->default(0); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists(config('rinvex.statistics.tables.devices')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000006_create_statistics_platforms_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('family'); 22 | $table->string('version')->nullable(); 23 | $table->integer('count')->unsigned()->default(0); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists(config('rinvex.statistics.tables.platforms')); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000007_create_statistics_geoips_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->string('client_ip'); 22 | $table->string('latitude'); 23 | $table->string('longitude'); 24 | $table->char('country_code', 2); 25 | $table->json('client_ips')->nullable(); 26 | $table->boolean('is_from_trusted_proxy')->default(0); 27 | $table->string('division_code')->nullable(); 28 | $table->string('postal_code')->nullable(); 29 | $table->string('timezone')->nullable(); 30 | $table->string('city')->nullable(); 31 | $table->integer('count')->unsigned()->default(0); 32 | 33 | // Indexes 34 | $table->unique(['client_ip', 'latitude', 'longitude']); 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists(config('rinvex.statistics.tables.geoips')); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_000008_create_statistics_requests_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 21 | $table->integer('route_id')->unsigned(); 22 | $table->integer('agent_id')->unsigned(); 23 | $table->integer('device_id')->unsigned(); 24 | $table->integer('platform_id')->unsigned(); 25 | $table->integer('path_id')->unsigned(); 26 | $table->integer('geoip_id')->unsigned(); 27 | $table->nullableMorphs('user'); 28 | $table->string('session_id'); 29 | $table->integer('status_code'); 30 | $table->string('protocol_version')->nullable(); 31 | $table->text('referer')->nullable(); 32 | $table->string('language'); 33 | $table->boolean('is_no_cache')->default(0); 34 | $table->boolean('wants_json')->default(0); 35 | $table->boolean('is_secure')->default(0); 36 | $table->boolean('is_json')->default(0); 37 | $table->boolean('is_ajax')->default(0); 38 | $table->boolean('is_pjax')->default(0); 39 | $table->timestamp('created_at')->nullable(); 40 | 41 | // Indexes 42 | $table->foreign('route_id')->references('id')->on(config('rinvex.statistics.tables.routes')) 43 | ->onDelete('cascade')->onUpdate('cascade'); 44 | $table->foreign('agent_id')->references('id')->on(config('rinvex.statistics.tables.agents')) 45 | ->onDelete('cascade')->onUpdate('cascade'); 46 | $table->foreign('device_id')->references('id')->on(config('rinvex.statistics.tables.devices')) 47 | ->onDelete('cascade')->onUpdate('cascade'); 48 | $table->foreign('platform_id')->references('id')->on(config('rinvex.statistics.tables.platforms')) 49 | ->onDelete('cascade')->onUpdate('cascade'); 50 | $table->foreign('path_id')->references('id')->on(config('rinvex.statistics.tables.paths')) 51 | ->onDelete('cascade')->onUpdate('cascade'); 52 | $table->foreign('geoip_id')->references('id')->on(config('rinvex.statistics.tables.geoips')) 53 | ->onDelete('cascade')->onUpdate('cascade'); 54 | }); 55 | } 56 | 57 | /** 58 | * Reverse the migrations. 59 | * 60 | * @return void 61 | */ 62 | public function down(): void 63 | { 64 | Schema::dropIfExists(config('rinvex.statistics.tables.requests')); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | parameters: 4 | level: 5 5 | paths: 6 | - src 7 | -------------------------------------------------------------------------------- /src/Console/Commands/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | alert($this->description); 33 | 34 | $path = config('rinvex.statistics.autoload_migrations') ? 35 | 'vendor/rinvex/laravel-statistics/database/migrations' : 36 | 'database/migrations/rinvex/laravel-statistics'; 37 | 38 | if (file_exists($path)) { 39 | $this->call('migrate', [ 40 | '--step' => true, 41 | '--path' => $path, 42 | '--force' => $this->option('force'), 43 | ]); 44 | } else { 45 | $this->warn('No migrations found! Consider publish them first: php artisan rinvex:publish:statistics'); 46 | } 47 | 48 | $this->line(''); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/Commands/PublishCommand.php: -------------------------------------------------------------------------------- 1 | alert($this->description); 33 | 34 | collect($this->option('resource') ?: ['config', 'migrations'])->each(function ($resource) { 35 | $this->call('vendor:publish', ['--tag' => "rinvex/statistics::{$resource}", '--force' => $this->option('force')]); 36 | }); 37 | 38 | $this->line(''); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Console/Commands/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | alert($this->description); 33 | 34 | $path = config('rinvex.statistics.autoload_migrations') ? 35 | 'vendor/rinvex/laravel-statistics/database/migrations' : 36 | 'database/migrations/rinvex/laravel-statistics'; 37 | 38 | if (file_exists($path)) { 39 | $this->call('migrate:reset', [ 40 | '--path' => $path, 41 | '--force' => $this->option('force'), 42 | ]); 43 | } else { 44 | $this->warn('No migrations found! Consider publish them first: php artisan rinvex:publish:statistics'); 45 | } 46 | 47 | $this->line(''); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Http/Middleware/TrackStatistics.php: -------------------------------------------------------------------------------- 1 | user(); 41 | 42 | app('rinvex.statistics.datum')->fill([ 43 | 'session_id' => $request->session()->getId(), 44 | 'user_id' => $currentUser?->getKey(), 45 | 'user_type' => $currentUser?->getMorphClass(), 46 | 'status_code' => $response->getStatusCode(), 47 | 'uri' => $request->getUri(), 48 | 'method' => $request->getMethod(), 49 | 'server' => $request->server() ?: null, 50 | 'input' => $request->input() ? $request->except(config('rinvex.statistics.exclude_input_fields')) : null, 51 | 'created_at' => Carbon::now(), 52 | ])->save(); 53 | 54 | // Here we will see if this request hits the statistics crunching lottery by hitting 55 | // the odds needed to perform statistics crunching on any given request. If we do 56 | // hit it, we'll call this handler to let it crunch numbers and the hard work. 57 | if ($this->configHitsLottery()) { 58 | CrunchStatistics::dispatch(); 59 | 60 | // Now let's do some garbage collection and clean old statistics requests 61 | CleanStatisticsRequests::dispatch(); 62 | } 63 | } 64 | 65 | /** 66 | * Determine if the configuration odds hit the lottery. 67 | * 68 | * @return bool 69 | */ 70 | protected function configHitsLottery(): bool 71 | { 72 | $config = config('rinvex.statistics.lottery'); 73 | 74 | return $config ? random_int(1, $config[1]) <= $config[0] : false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Jobs/CleanStatisticsRequests.php: -------------------------------------------------------------------------------- 1 | where('created_at', '<=', Carbon::now()->subDays(config('rinvex.statistics.lifetime')))->delete(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Jobs/CrunchStatistics.php: -------------------------------------------------------------------------------- 1 | each(function ($item) { 33 | try { 34 | $symfonyRequest = SymfonyRequest::create($item['uri'], $item['server']['REQUEST_METHOD'], $item['input'] ?? [], [], [], $item['server']); 35 | $symfonyRequest->overrideGlobals(); 36 | 37 | LaravelRequest::enableHttpMethodParameterOverride(); 38 | $laravelRequest = LaravelRequest::createFromBase($symfonyRequest); 39 | $laravelRoute = app('router')->getRoutes()->match($laravelRequest); 40 | $laravelRequest->setRouteResolver(function () use ($laravelRoute) { 41 | return $laravelRoute; 42 | }); 43 | 44 | $tokens = []; 45 | $agent = new Agent($item['server']); 46 | $UAParser = Parser::create()->parse($agent->getUserAgent()); 47 | $kind = $agent->isDesktop() ? 'desktop' : ($agent->isTablet() ? 'tablet' : ($agent->isPhone() ? 'phone' : ($agent->isRobot() ? 'robot' : 'unknown'))); 48 | 49 | collect($laravelRequest->route()->getCompiled()->getTokens())->map(function ($item) use (&$tokens) { 50 | return ($item = collect($item)) && $item->contains('variable') ? $tokens[$item[3]] = $item[2] : null; 51 | }); 52 | 53 | $route = app('rinvex.statistics.route')->firstOrCreate([ 54 | 'name' => $laravelRoute->getName() ?: $laravelRoute->uri(), 55 | ], [ 56 | 'path' => $laravelRoute->uri(), 57 | 'action' => $laravelRoute->getActionName(), 58 | 'middleware' => $laravelRoute->gatherMiddleware() ?: null, 59 | 'parameters' => $tokens ?: null, 60 | ]); 61 | 62 | $agent = app('rinvex.statistics.agent')->firstOrCreate([ 63 | 'name' => $agent->getUserAgent(), 64 | 'kind' => $kind, 65 | 'family' => $UAParser->ua->family, 66 | 'version' => $UAParser->ua->toVersion(), 67 | ]); 68 | 69 | $device = app('rinvex.statistics.device')->firstOrCreate([ 70 | 'family' => $UAParser->device->family, 71 | 'model' => $UAParser->device->model, 72 | 'brand' => $UAParser->device->brand, 73 | ]); 74 | 75 | $platform = app('rinvex.statistics.platform')->firstOrCreate([ 76 | 'family' => $UAParser->os->family, 77 | 'version' => $UAParser->os->toVersion(), 78 | ]); 79 | 80 | $path = app('rinvex.statistics.path')->firstOrCreate([ 81 | 'host' => $laravelRequest->getHost(), 82 | 'path' => $laravelRequest->decodedPath(), 83 | 'method' => $laravelRequest->getMethod(), 84 | 'locale' => $laravelRequest->route('locale') ?? app()->getLocale(), 85 | ], [ 86 | 'accessarea' => $laravelRequest->input('accessarea'), 87 | 'parameters' => $laravelRoute->parameters() ?: null, 88 | ]); 89 | 90 | $geoip = app('rinvex.statistics.geoip')->firstOrCreate([ 91 | 'client_ip' => $ip = $laravelRequest->getClientIp(), 92 | 'latitude' => geoip($ip)->getAttribute('lat'), 93 | 'longitude' => geoip($ip)->getAttribute('lon'), 94 | ], [ 95 | 'client_ips' => $laravelRequest->getClientIps() ?: null, 96 | 'country_code' => mb_strtoupper(geoip($ip)->getAttribute('iso_code')), 97 | 'is_from_trusted_proxy' => $laravelRequest->isFromTrustedProxy(), 98 | 'division_code' => geoip($ip)->getAttribute('state'), 99 | 'postal_code' => geoip($ip)->getAttribute('postal_code'), 100 | 'timezone' => geoip($ip)->getAttribute('timezone'), 101 | 'city' => geoip($ip)->getAttribute('city'), 102 | ]); 103 | 104 | $requestDetails = [ 105 | 'route_id' => $route->getKey(), 106 | 'agent_id' => $agent->getKey(), 107 | 'device_id' => $device->getKey(), 108 | 'platform_id' => $platform->getKey(), 109 | 'path_id' => $path->getKey(), 110 | 'geoip_id' => $geoip->getKey(), 111 | 'user_id' => $item['user_id'], 112 | 'user_type' => $item['user_type'], 113 | 'session_id' => $item['session_id'], 114 | 'status_code' => $item['status_code'], 115 | 'referer' => $laravelRequest->header('referer') ?: $laravelRequest->input('utm_source'), 116 | 'protocol_version' => $laravelRequest->getProtocolVersion(), 117 | 'language' => $laravelRequest->getPreferredLanguage(), 118 | 'is_no_cache' => $laravelRequest->isNoCache(), 119 | 'wants_json' => $laravelRequest->wantsJson(), 120 | 'is_secure' => $laravelRequest->isSecure(), 121 | 'is_json' => $laravelRequest->isJson(), 122 | 'is_ajax' => $laravelRequest->ajax(), 123 | 'is_pjax' => $laravelRequest->pjax(), 124 | 'created_at' => $item['created_at'], 125 | ]; 126 | 127 | app('rinvex.statistics.request')->create($requestDetails); 128 | $item->delete(); 129 | } catch (Exception $exception) { 130 | } 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Models/Agent.php: -------------------------------------------------------------------------------- 1 | 'string', 32 | 'kind' => 'string', 33 | 'family' => 'string', 34 | 'version' => 'string', 35 | ]; 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public $timestamps = false; 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | protected $observables = [ 46 | 'validating', 47 | 'validated', 48 | ]; 49 | 50 | /** 51 | * The default rules that the model will validate against. 52 | * 53 | * @var array 54 | */ 55 | protected $rules = []; 56 | 57 | /** 58 | * Whether the model should throw a 59 | * ValidationException if it fails validation. 60 | * 61 | * @var bool 62 | */ 63 | protected $throwValidationExceptions = true; 64 | 65 | /** 66 | * Create a new Eloquent model instance. 67 | * 68 | * @param array $attributes 69 | */ 70 | public function __construct(array $attributes = []) 71 | { 72 | $this->setTable(config('rinvex.statistics.tables.agents')); 73 | $this->mergeRules([ 74 | 'name' => 'required|string|strip_tags|max:150', 75 | 'kind' => 'required|string|strip_tags|max:150', 76 | 'family' => 'required|string|strip_tags|max:150', 77 | 'version' => 'nullable|string|strip_tags|max:150', 78 | ]); 79 | 80 | parent::__construct($attributes); 81 | } 82 | 83 | /** 84 | * The agent may have many requests. 85 | * 86 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 87 | */ 88 | public function requests(): HasMany 89 | { 90 | return $this->hasMany(config('rinvex.statistics.models.request'), 'agent_id', 'id'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Models/Datum.php: -------------------------------------------------------------------------------- 1 | 'string', 38 | 'user_id' => 'integer', 39 | 'user_type' => 'string', 40 | 'status_code' => 'integer', 41 | 'uri' => 'string', 42 | 'method' => 'string', 43 | 'server' => 'json', 44 | 'input' => 'json', 45 | 'created_at' => 'datetime', 46 | ]; 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public $timestamps = false; 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | protected $observables = [ 57 | 'validating', 58 | 'validated', 59 | ]; 60 | 61 | /** 62 | * The default rules that the model will validate against. 63 | * 64 | * @var array 65 | */ 66 | protected $rules = []; 67 | 68 | /** 69 | * Whether the model should throw a 70 | * ValidationException if it fails validation. 71 | * 72 | * @var bool 73 | */ 74 | protected $throwValidationExceptions = true; 75 | 76 | /** 77 | * Create a new Eloquent model instance. 78 | * 79 | * @param array $attributes 80 | */ 81 | public function __construct(array $attributes = []) 82 | { 83 | $this->setTable(config('rinvex.statistics.tables.data')); 84 | $this->mergeRules([ 85 | 'session_id' => 'required|string', 86 | 'user_id' => 'nullable|integer', 87 | 'user_type' => 'nullable|string|strip_tags|max:150', 88 | 'status_code' => 'required|integer', 89 | 'uri' => 'required|string', 90 | 'method' => 'required|string', 91 | 'server' => 'required|array', 92 | 'input' => 'nullable|array', 93 | 'created_at' => 'required|date', 94 | ]); 95 | 96 | parent::__construct($attributes); 97 | } 98 | 99 | /** 100 | * Get the owning user. 101 | * 102 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 103 | */ 104 | public function user(): MorphTo 105 | { 106 | return $this->morphTo('user', 'user_type', 'user_id', 'id'); 107 | } 108 | 109 | /** 110 | * Get bookings of the given user. 111 | * 112 | * @param \Illuminate\Database\Eloquent\Builder $builder 113 | * @param \Illuminate\Database\Eloquent\Model $user 114 | * 115 | * @return \Illuminate\Database\Eloquent\Builder 116 | */ 117 | public function scopeOfUser(Builder $builder, Model $user): Builder 118 | { 119 | return $builder->where('user_type', $user->getMorphClass())->where('user_id', $user->getKey()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Models/Device.php: -------------------------------------------------------------------------------- 1 | 'string', 31 | 'model' => 'string', 32 | 'brand' => 'string', 33 | ]; 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public $timestamps = false; 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected $observables = [ 44 | 'validating', 45 | 'validated', 46 | ]; 47 | 48 | /** 49 | * The default rules that the model will validate against. 50 | * 51 | * @var array 52 | */ 53 | protected $rules = []; 54 | 55 | /** 56 | * Whether the model should throw a 57 | * ValidationException if it fails validation. 58 | * 59 | * @var bool 60 | */ 61 | protected $throwValidationExceptions = true; 62 | 63 | /** 64 | * Create a new Eloquent model instance. 65 | * 66 | * @param array $attributes 67 | */ 68 | public function __construct(array $attributes = []) 69 | { 70 | $this->setTable(config('rinvex.statistics.tables.devices')); 71 | $this->mergeRules([ 72 | 'family' => 'required|string', 73 | 'model' => 'nullable|string', 74 | 'brand' => 'nullable|string', 75 | ]); 76 | 77 | parent::__construct($attributes); 78 | } 79 | 80 | /** 81 | * The device may have many requests. 82 | * 83 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 84 | */ 85 | public function requests(): HasMany 86 | { 87 | return $this->hasMany(config('rinvex.statistics.models.request'), 'device_id', 'id'); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Models/Geoip.php: -------------------------------------------------------------------------------- 1 | 'string', 38 | 'latitude' => 'string', 39 | 'longitude' => 'string', 40 | 'country_code' => 'string', 41 | 'client_ips' => 'json', 42 | 'is_from_trusted_proxy' => 'boolean', 43 | 'division_code' => 'string', 44 | 'postal_code' => 'string', 45 | 'timezone' => 'string', 46 | 'city' => 'string', 47 | ]; 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public $timestamps = false; 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | protected $observables = [ 58 | 'validating', 59 | 'validated', 60 | ]; 61 | 62 | /** 63 | * The default rules that the model will validate against. 64 | * 65 | * @var array 66 | */ 67 | protected $rules = []; 68 | 69 | /** 70 | * Whether the model should throw a 71 | * ValidationException if it fails validation. 72 | * 73 | * @var bool 74 | */ 75 | protected $throwValidationExceptions = true; 76 | 77 | /** 78 | * Create a new Eloquent model instance. 79 | * 80 | * @param array $attributes 81 | */ 82 | public function __construct(array $attributes = []) 83 | { 84 | $this->setTable(config('rinvex.statistics.tables.geoips')); 85 | $this->mergeRules([ 86 | 'client_ip' => 'required|string', 87 | 'latitude' => 'required|string', 88 | 'longitude' => 'required|string', 89 | 'country_code' => 'required|alpha|size:2|country', 90 | 'client_ips' => 'nullable|array', 91 | 'is_from_trusted_proxy' => 'sometimes|boolean', 92 | 'division_code' => 'nullable|string', 93 | 'postal_code' => 'nullable|string', 94 | 'timezone' => 'nullable|string|max:64|timezone', 95 | 'city' => 'nullable|string', 96 | ]); 97 | 98 | parent::__construct($attributes); 99 | } 100 | 101 | /** 102 | * The geoip may have many requests. 103 | * 104 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 105 | */ 106 | public function requests(): HasMany 107 | { 108 | return $this->hasMany(config('rinvex.statistics.models.request'), 'geoip_id', 'id'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Models/Path.php: -------------------------------------------------------------------------------- 1 | 'string', 33 | 'locale' => 'string', 34 | 'path' => 'string', 35 | 'method' => 'string', 36 | 'parameters' => 'json', 37 | ]; 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public $timestamps = false; 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected $observables = [ 48 | 'validating', 49 | 'validated', 50 | ]; 51 | 52 | /** 53 | * The default rules that the model will validate against. 54 | * 55 | * @var array 56 | */ 57 | protected $rules = []; 58 | 59 | /** 60 | * Whether the model should throw a 61 | * ValidationException if it fails validation. 62 | * 63 | * @var bool 64 | */ 65 | protected $throwValidationExceptions = true; 66 | 67 | /** 68 | * Create a new Eloquent model instance. 69 | * 70 | * @param array $attributes 71 | */ 72 | public function __construct(array $attributes = []) 73 | { 74 | $this->setTable(config('rinvex.statistics.tables.paths')); 75 | $this->mergeRules([ 76 | 'host' => 'required|string', 77 | 'locale' => 'required|string', 78 | 'path' => 'required|string', 79 | 'method' => 'required|string', 80 | 'parameters' => 'nullable|array', 81 | ]); 82 | 83 | parent::__construct($attributes); 84 | } 85 | 86 | /** 87 | * The path may have many requests. 88 | * 89 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 90 | */ 91 | public function requests(): HasMany 92 | { 93 | return $this->hasMany(config('rinvex.statistics.models.request'), 'path_id', 'id'); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Models/Platform.php: -------------------------------------------------------------------------------- 1 | 'string', 30 | 'version' => 'string', 31 | ]; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public $timestamps = false; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | protected $observables = [ 42 | 'validating', 43 | 'validated', 44 | ]; 45 | 46 | /** 47 | * The default rules that the model will validate against. 48 | * 49 | * @var array 50 | */ 51 | protected $rules = []; 52 | 53 | /** 54 | * Whether the model should throw a 55 | * ValidationException if it fails validation. 56 | * 57 | * @var bool 58 | */ 59 | protected $throwValidationExceptions = true; 60 | 61 | /** 62 | * Create a new Eloquent model instance. 63 | * 64 | * @param array $attributes 65 | */ 66 | public function __construct(array $attributes = []) 67 | { 68 | $this->setTable(config('rinvex.statistics.tables.platforms')); 69 | $this->mergeRules([ 70 | 'family' => 'required|string', 71 | 'version' => 'nullable|string', 72 | ]); 73 | 74 | parent::__construct($attributes); 75 | } 76 | 77 | /** 78 | * The platform may have many requests. 79 | * 80 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 81 | */ 82 | public function requests(): HasMany 83 | { 84 | return $this->hasMany(config('rinvex.statistics.models.request'), 'platform_id', 'id'); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Models/Request.php: -------------------------------------------------------------------------------- 1 | 'integer', 50 | 'agent_id' => 'integer', 51 | 'device_id' => 'integer', 52 | 'platform_id' => 'integer', 53 | 'path_id' => 'integer', 54 | 'geoip_id' => 'integer', 55 | 'user_id' => 'integer', 56 | 'user_type' => 'string', 57 | 'session_id' => 'string', 58 | 'status_code' => 'integer', 59 | 'protocol_version' => 'string', 60 | 'referer' => 'string', 61 | 'language' => 'string', 62 | 'is_no_cache' => 'boolean', 63 | 'wants_json' => 'boolean', 64 | 'is_secure' => 'boolean', 65 | 'is_json' => 'boolean', 66 | 'is_ajax' => 'boolean', 67 | 'is_pjax' => 'boolean', 68 | 'created_at' => 'datetime', 69 | ]; 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public $timestamps = false; 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | protected $observables = [ 80 | 'validating', 81 | 'validated', 82 | ]; 83 | 84 | /** 85 | * The default rules that the model will validate against. 86 | * 87 | * @var array 88 | */ 89 | protected $rules = []; 90 | 91 | /** 92 | * Whether the model should throw a 93 | * ValidationException if it fails validation. 94 | * 95 | * @var bool 96 | */ 97 | protected $throwValidationExceptions = true; 98 | 99 | /** 100 | * Create a new Eloquent model instance. 101 | * 102 | * @param array $attributes 103 | */ 104 | public function __construct(array $attributes = []) 105 | { 106 | $this->setTable(config('rinvex.statistics.tables.requests')); 107 | $this->mergeRules([ 108 | 'route_id' => 'required|integer', 109 | 'agent_id' => 'required|integer', 110 | 'device_id' => 'required|integer', 111 | 'platform_id' => 'required|integer', 112 | 'path_id' => 'required|integer', 113 | 'geoip_id' => 'required|integer', 114 | 'user_id' => 'nullable|integer', 115 | 'user_type' => 'nullable|string|strip_tags|max:150', 116 | 'session_id' => 'required|string', 117 | 'status_code' => 'required|integer', 118 | 'protocol_version' => 'nullable|string', 119 | 'referer' => 'nullable|string', 120 | 'language' => 'required|string', 121 | 'is_no_cache' => 'sometimes|boolean', 122 | 'wants_json' => 'sometimes|boolean', 123 | 'is_secure' => 'sometimes|boolean', 124 | 'is_json' => 'sometimes|boolean', 125 | 'is_ajax' => 'sometimes|boolean', 126 | 'is_pjax' => 'sometimes|boolean', 127 | 'created_at' => 'required|date', 128 | ]); 129 | 130 | parent::__construct($attributes); 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | */ 136 | protected static function boot() 137 | { 138 | parent::boot(); 139 | 140 | static::saved(function (self $request) { 141 | $request->path()->increment('count'); 142 | $request->route()->increment('count'); 143 | $request->geoip()->increment('count'); 144 | $request->agent()->increment('count'); 145 | $request->device()->increment('count'); 146 | $request->platform()->increment('count'); 147 | }); 148 | } 149 | 150 | /** 151 | * The request always belongs to a route. 152 | * 153 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 154 | */ 155 | public function route(): BelongsTo 156 | { 157 | return $this->belongsTo(config('rinvex.statistics.models.route'), 'route_id', 'id', 'route'); 158 | } 159 | 160 | /** 161 | * The request always belongs to a path. 162 | * 163 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 164 | */ 165 | public function path(): BelongsTo 166 | { 167 | return $this->belongsTo(config('rinvex.statistics.models.path'), 'path_id', 'id', 'path'); 168 | } 169 | 170 | /** 171 | * The request always belongs to an agent. 172 | * 173 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 174 | */ 175 | public function agent(): BelongsTo 176 | { 177 | return $this->belongsTo(config('rinvex.statistics.models.agent'), 'agent_id', 'id', 'agent'); 178 | } 179 | 180 | /** 181 | * The request always belongs to an geoip. 182 | * 183 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 184 | */ 185 | public function geoip(): BelongsTo 186 | { 187 | return $this->belongsTo(config('rinvex.statistics.models.geoip'), 'geoip_id', 'id', 'geoip'); 188 | } 189 | 190 | /** 191 | * The request always belongs to a device. 192 | * 193 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 194 | */ 195 | public function device(): BelongsTo 196 | { 197 | return $this->belongsTo(config('rinvex.statistics.models.device'), 'device_id', 'id', 'device'); 198 | } 199 | 200 | /** 201 | * The request always belongs to a platform. 202 | * 203 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 204 | */ 205 | public function platform(): BelongsTo 206 | { 207 | return $this->belongsTo(config('rinvex.statistics.models.platform'), 'platform_id', 'id', 'platform'); 208 | } 209 | 210 | /** 211 | * Get the owning user. 212 | * 213 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 214 | */ 215 | public function user(): MorphTo 216 | { 217 | return $this->morphTo('user', 'user_type', 'user_id', 'id'); 218 | } 219 | 220 | /** 221 | * Get bookings of the given user. 222 | * 223 | * @param \Illuminate\Database\Eloquent\Builder $builder 224 | * @param \Illuminate\Database\Eloquent\Model $user 225 | * 226 | * @return \Illuminate\Database\Eloquent\Builder 227 | */ 228 | public function scopeOfUser(Builder $builder, Model $user): Builder 229 | { 230 | return $builder->where('user_type', $user->getMorphClass())->where('user_id', $user->getKey()); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Models/Route.php: -------------------------------------------------------------------------------- 1 | 'string', 33 | 'action' => 'string', 34 | 'middleware' => 'json', 35 | 'path' => 'string', 36 | 'parameters' => 'json', 37 | ]; 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public $timestamps = false; 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected $observables = [ 48 | 'validating', 49 | 'validated', 50 | ]; 51 | 52 | /** 53 | * The default rules that the model will validate against. 54 | * 55 | * @var array 56 | */ 57 | protected $rules = []; 58 | 59 | /** 60 | * Whether the model should throw a 61 | * ValidationException if it fails validation. 62 | * 63 | * @var bool 64 | */ 65 | protected $throwValidationExceptions = true; 66 | 67 | /** 68 | * Create a new Eloquent model instance. 69 | * 70 | * @param array $attributes 71 | */ 72 | public function __construct(array $attributes = []) 73 | { 74 | $this->setTable(config('rinvex.statistics.tables.routes')); 75 | $this->mergeRules([ 76 | 'name' => 'required|string', 77 | 'action' => 'required|string', 78 | 'middleware' => 'nullable|array', 79 | 'path' => 'required|string', 80 | 'parameters' => 'nullable|array', 81 | ]); 82 | 83 | parent::__construct($attributes); 84 | } 85 | 86 | /** 87 | * The route may have many requests. 88 | * 89 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 90 | */ 91 | public function requests(): HasMany 92 | { 93 | return $this->hasMany(config('rinvex.statistics.models.request'), 'route_id', 'id'); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Providers/StatisticsServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'command.rinvex.statistics.migrate', 34 | PublishCommand::class => 'command.rinvex.statistics.publish', 35 | RollbackCommand::class => 'command.rinvex.statistics.rollback', 36 | ]; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function register() 42 | { 43 | // Merge config 44 | $this->mergeConfigFrom(realpath(__DIR__.'/../../config/config.php'), 'rinvex.statistics'); 45 | 46 | // Bind eloquent models to IoC container 47 | $this->registerModels([ 48 | 'rinvex.statistics.datum' => Datum::class, 49 | 'rinvex.statistics.request' => Request::class, 50 | 'rinvex.statistics.agent' => Agent::class, 51 | 'rinvex.statistics.geoip' => Geoip::class, 52 | 'rinvex.statistics.route' => Route::class, 53 | 'rinvex.statistics.device' => Device::class, 54 | 'rinvex.statistics.platform' => Platform::class, 55 | 'rinvex.statistics.path' => Path::class, 56 | ]); 57 | 58 | // Register console commands 59 | $this->registerCommands($this->commands); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function boot(Router $router) 66 | { 67 | // Publish Resources 68 | $this->publishesConfig('rinvex/laravel-statistics'); 69 | $this->publishesMigrations('rinvex/laravel-statistics'); 70 | ! $this->autoloadMigrations('rinvex/laravel-statistics') || $this->loadMigrationsFrom(__DIR__.'/../../database/migrations'); 71 | 72 | // Push middleware to web group 73 | $router->pushMiddlewareToGroup('web', TrackStatistics::class); 74 | } 75 | } 76 | --------------------------------------------------------------------------------