├── .editorconfig ├── VERSION ├── composer.json ├── pint.json ├── readme.md └── src ├── Exceptions ├── FilterTypeNotSetException.php ├── InvalidFilterTypeException.php └── InvalidRuleDefinitionsException.php ├── Filter.php ├── FilterServiceProvider.php ├── Route ├── AllowFilter.php ├── BlockFilter.php └── RouteFilter.php ├── Stack └── Filter.php ├── Support ├── ParserCreator.php └── helper.php └── config └── browserfilter.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.2.0 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spinen/laravel-browser-filter", 3 | "description": "Filters http requests based on browser type.", 4 | "keywords": [ 5 | "browser hawk", 6 | "laravel", 7 | "middleware", 8 | "spinen" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Jimmy Puckett", 14 | "email": "jimmy.puckett@spinen.com" 15 | }, 16 | { 17 | "name": "Stephen Finney", 18 | "email": "stephen.finney@spinen.com" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=8.1", 23 | "ext-json": "*", 24 | "illuminate/cache": "^9.19|^10|^11", 25 | "illuminate/routing": "^9.19|^10|^11", 26 | "illuminate/support": "^9.42|^10|^11", 27 | "mobiledetect/mobiledetectlib": "~2.8", 28 | "ua-parser/uap-php": "~3.9" 29 | }, 30 | "require-dev": { 31 | "illuminate/http": "^9.19|^10", 32 | "laravel/pint": "^1.2", 33 | "mockery/mockery": "^1.5.1", 34 | "phpunit/phpunit": "^9.6.5", 35 | "psy/psysh": "^0.11", 36 | "symfony/var-dumper": "^6.2" 37 | }, 38 | "autoload": { 39 | "files": [ 40 | "src/Support/helper.php" 41 | ], 42 | "psr-4": { 43 | "Spinen\\BrowserFilter\\": "src/" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Spinen\\BrowserFilter\\": "tests/" 49 | } 50 | }, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "Spinen\\BrowserFilter\\FilterServiceProvider" 55 | ] 56 | } 57 | }, 58 | "config": { 59 | "sort-packages": true 60 | }, 61 | "minimum-stability": "dev", 62 | "prefer-stable": true 63 | } 64 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "no_superfluous_phpdoc_tags": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SPINEN's Laravel Browser Filter 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/spinen/laravel-browser-filter/v/stable)](https://packagist.org/packages/spinen/laravel-browser-filter) 4 | [![Latest Unstable Version](https://poser.pugx.org/spinen/laravel-browser-filter/v/unstable)](https://packagist.org/packages/spinen/laravel-browser-filter) 5 | [![Total Downloads](https://poser.pugx.org/spinen/laravel-browser-filter/downloads)](https://packagist.org/packages/spinen/laravel-browser-filter) 6 | [![License](https://poser.pugx.org/spinen/laravel-browser-filter/license)](https://packagist.org/packages/spinen/laravel-browser-filter) 7 | 8 | This is a Laravel 5 middleware to filter routes based on browser types. 9 | 10 | We specify the browsers that we are going to support at the beginning of a project, so this package makes sure that the visitor is using a supported browser. 11 | 12 | ## Build Status 13 | 14 | | Branch | Status | Coverage | Code Quality | 15 | | ------ | :----: | :------: | :----------: | 16 | | Develop | [![Build Status](https://github.com/spinen/laravel-browser-filter/workflows/CI/badge.svg?branch=develop)](https://github.com/spinen/laravel-browser-filter/workflows/CI/badge.svg?branch=develop) | [![Code Coverage](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/badges/coverage.png?b=develop)](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/?branch=develop) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/?branch=develop) | 17 | | Master | [![Build Status](https://github.com/spinen/laravel-browser-filter/workflows/CI/badge.svg?branch=master)](https://github.com/spinen/laravel-browser-filter/workflows/CI/badge.svg?branch=master) | [![Code Coverage](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/badges/coverage.png?b=develop)](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/?branch=master) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/spinen/laravel-browser-filter/?branch=master) | 18 | 19 | ## Prerequisites 20 | 21 | #### NOTE: If you need to use PHP <7.2 or Laravel <5.2, please stay with version 1.x 22 | 23 | As side from Laravel >= 5.5, there are 2 packages that are required: 24 | 25 | * [mobiledetect](https://github.com/serbanghita/Mobile-Detect) - To get the user agent string. This package is not needed to get to the user agent string, but there are other features that I plan on using in the future so I kept it installed. 26 | * [ua-parser PHP Library](https://github.com/ua-parser/uap-php) - To parse the user agent string 27 | 28 | ## Install 29 | 30 | Install Browser Filter: 31 | 32 | ```bash 33 | $ composer require spinen/laravel-browser-filter 34 | ``` 35 | 36 | The package uses the [auto registration feature](https://laravel.com/docs/5.8/packages#package-discovery) of Laravel 5. 37 | 38 | ```php 39 | 'providers' => [ 40 | // ... 41 | Spinen\BrowserFilter\FilterServiceProvider::class, 42 | ]; 43 | ``` 44 | 45 | ## Register the middleware 46 | 47 | The middleware needs to be registered with the Kernel to allow it to parse the request. 48 | 49 | ### Laravel 11 & newer 50 | 51 | Register the HTTP Stack Middleware for the web group in `bootstrap/app.php`: 52 | 53 | ```php 54 | ->withMiddleware(function (Middleware $middleware) { 55 | // ... 56 | $middleware->web(append: [ 57 | // ... 58 | \Spinen\BrowserFilter\Stack\Filter::class, 59 | ]); 60 | // ... 61 | }) 62 | ``` 63 | 64 | Register the Route Middlewares in `bootstrap/app.php`: 65 | 66 | ```php 67 | ->withMiddleware(function (Middleware $middleware) { 68 | // ... 69 | $middleware->alias([ 70 | // ... 71 | 'browser.allow' => \Spinen\BrowserFilter\Route\AllowFilter::class, 72 | 'browser.block' => \Spinen\BrowserFilter\Route\BlockFilter::class, 73 | ]); 74 | // ... 75 | }) 76 | ``` 77 | 78 | ### Before Laravel 11 79 | 80 | Register the HTTP Stack Middleware for the web group in `app/Http/Kernel.php`: 81 | 82 | ```php 83 | protected $middlewareGroups = [ 84 | 'web' => [ 85 | // .. 86 | \Spinen\BrowserFilter\Stack\Filter::class, 87 | ], 88 | // .. 89 | ``` 90 | 91 | Register the Route Middlewares in `app/Http/Kernel.php`: 92 | 93 | ```php 94 | protected $routeMiddleware = [ 95 | // .. 96 | 'browser.allow' => \Spinen\BrowserFilter\Route\AllowFilter::class, 97 | 'browser.block' => \Spinen\BrowserFilter\Route\BlockFilter::class, 98 | ``` 99 | 100 | ### Page to show if blocked 101 | 102 | Build a page with a named route to redirect blocked browsers to: 103 | 104 | ```php 105 | // This is only a simple example. You would probably want to route to a controller with a view. 106 | Route::get('incompatible_browser', ['as' => 'incompatible_browser', 'uses' => function() { 107 | return "You are using a blocked browser."; 108 | }]); 109 | ``` 110 | 111 | ## Configure middleware options 112 | 113 | Publish the package config file to `config/browserfilter.php`: 114 | 115 | ```bash 116 | $ php artisan vendor:publish --provider="Spinen\BrowserFilter\FilterServiceProvider" 117 | ``` 118 | 119 | This file is fully documented, so please read it to know how to configure the middleware. There are 4 top level items that you can configure... 120 | 121 | 1. type - The type of filtering strategy to apply to the stack filter 122 | 2. rules - The array of devices/browsers/versions to allow or block for *ALL* http requests 123 | 3. route - The name of the route to redirect the user to if they are using a blocked client 124 | 4. timeout - The length of time to cache the client data, where "0" disables the cache 125 | 126 | ## Using the Route middleware 127 | 128 | The route middleware uses the same configuration file as the stack middleware, but ignores the rules. 129 | 130 | The rules are passed in after the ':' behind the route filter that you wish to use... 131 | 132 | ```php 133 | Route::get('tablet_page', [ 134 | 'middleware' => 'browser.allow:Tablet', 135 | 'uses' => function () { 136 | return "Special page that is only accessible to tablets"; 137 | } 138 | ]); 139 | ``` 140 | 141 | or 142 | 143 | ```php 144 | Route::get('ie_is_blocked_page', [ 145 | 'middleware' => 'browser.block:Other/Ie', 146 | 'uses' => function () { 147 | return "Special page that is only accessible to non IE browsers on Desktops"; 148 | } 149 | ]); 150 | ``` 151 | 152 | The format of the filter is `Device/Browser/operatorVersion|operatorVersion2;Device/Browser2/operatorVersion`, so the following rule: 153 | 154 | ```php 155 | $rule = [ 156 | 'Mobile' => '*', 157 | 'Other' => [ 158 | 'Ie' => [ 159 | '<' => '10', 160 | '>' => '13', 161 | ], 162 | ], 163 | 'Tablet' => '*', 164 | ] 165 | ``` 166 | 167 | would be written as: `Mobile;Other/Ie/<10|>13;Tablet`. 168 | -------------------------------------------------------------------------------- /src/Exceptions/FilterTypeNotSetException.php: -------------------------------------------------------------------------------- 1 | client = $parser->parseAgent($detector->getUserAgent()); 57 | } 58 | 59 | /** 60 | * Determines if the client needs to be redirected. 61 | */ 62 | public function determineRedirect(): string|bool 63 | { 64 | return $this->needsRedirecting() ? $this->getRedirectRoute() : false; 65 | } 66 | 67 | /** 68 | * Generate the key to use to cache the determination. 69 | */ 70 | public function generateCacheKey(Request $request): string 71 | { 72 | // NOTE: $request is an unused variable here, but needed in a class that extends this one 73 | return $this->client->device->family.':'.$this->client->ua->family.':'.$this->client->ua->toVersion(); 74 | } 75 | 76 | /** 77 | * Get the browsers being filtered. 78 | */ 79 | public function getBrowsers(): string|array|null 80 | { 81 | return $this->haveRulesForDevice() ? $this->getRules()[$this->client->device->family] : null; 82 | } 83 | 84 | /** 85 | * Get the versions of the browsers being filtered. 86 | */ 87 | public function getBrowserVersions(): string|array|null 88 | { 89 | return $this->haveVersionsForBrowser() ? $this->getRules()[$this->client->device->family][$this->client->ua->family] : null; 90 | } 91 | 92 | /** 93 | * Get the timeout of the cached value. 94 | */ 95 | public function getCacheTimeout(): int 96 | { 97 | return $this->config->get($this->config_path.'timeout'); 98 | } 99 | 100 | /** 101 | * Return the filter type. 102 | * 103 | * @throws FilterTypeNotSetException 104 | */ 105 | public function getFilterType(): string 106 | { 107 | if (is_bool($this->block_filter)) { 108 | return $this->block_filter ? 'block' : 'allow'; 109 | } 110 | 111 | throw new FilterTypeNotSetException(); 112 | } 113 | 114 | /** 115 | * Get the route to the redirect path. 116 | */ 117 | public function getRedirectRoute(): ?string 118 | { 119 | return $this->redirect_route ?: $this->config->get($this->config_path.'route'); 120 | } 121 | 122 | /** 123 | * Return the array of rules. 124 | */ 125 | public function getRules(): array 126 | { 127 | return $this->rules; 128 | } 129 | 130 | /** 131 | * Handle an incoming request. 132 | */ 133 | public function handle(Request $request, Closure $next, string|array|null $filter_string = null, ?string $redirect_route = null) 134 | { 135 | $this->redirect_route = $redirect_route; 136 | 137 | if ($this->onRedirectPath($request)) { 138 | return $next($request); 139 | } 140 | 141 | $cache_key = $this->generateCacheKey($request); 142 | 143 | $redirect = $this->cache->get($cache_key); 144 | 145 | if (is_null($redirect)) { 146 | $this->parseFilterString($filter_string); 147 | 148 | $this->validateRules(); 149 | 150 | $redirect = $this->determineRedirect(); 151 | 152 | $this->cache->put($cache_key, $redirect, $this->getCacheTimeout()); 153 | } 154 | 155 | if ($redirect) { 156 | $request->session() 157 | ->flash('redirected', true); 158 | 159 | return $this->redirector->route($redirect); 160 | } 161 | 162 | return $next($request); 163 | } 164 | 165 | /** 166 | * Check to see if there are defined rules for the device. 167 | */ 168 | public function haveRulesForDevice(): bool 169 | { 170 | return array_key_exists($this->client->device->family, $this->getRules()); 171 | } 172 | 173 | /** 174 | * Check to see if there are defined versions for the browser for the device. 175 | */ 176 | public function haveVersionsForBrowser(): bool 177 | { 178 | return array_key_exists($this->client->device->family, $this->getRules()) && 179 | array_key_exists($this->client->ua->family, $this->getRules()[$this->client->device->family]); 180 | } 181 | 182 | /** 183 | * Checks to see if the browser/client is blocked. 184 | */ 185 | public function isMatched(): bool 186 | { 187 | return $this->isMatchedDevice() || $this->isMatchedBrowser() || $this->isMatchedBrowserVersion(); 188 | } 189 | 190 | /** 191 | * Checks to see if all versions of the browser is blocked. 192 | */ 193 | public function isMatchedBrowser(): bool 194 | { 195 | return '*' === $this->getBrowserVersions(); 196 | } 197 | 198 | /** 199 | * Checks to see if the version of the browser is blocked. 200 | * 201 | * Uses the php version_compare function to decide if there is a match. 202 | * 203 | * @link http://php.net/manual/en/function.version-compare.php 204 | */ 205 | public function isMatchedBrowserVersion(): bool 206 | { 207 | $denied = false; 208 | 209 | // cache it, so that we don't have to keep asking for it 210 | $client_version = $this->client->ua->toVersion(); 211 | 212 | foreach ((array) $this->getBrowserVersions() as $operator => $version) { 213 | $denied |= (bool) version_compare($client_version, $version, $operator); 214 | } 215 | 216 | return (bool) $denied; 217 | } 218 | 219 | /** 220 | * Checks to see if all browsers of the device family is blocked. 221 | */ 222 | public function isMatchedDevice(): bool 223 | { 224 | return '*' === $this->getBrowsers(); 225 | } 226 | 227 | /** 228 | * Decide if the client needs to be redirected. 229 | * 230 | * Here is the logic: 231 | * 232 | * blockedFilter true true false false 233 | * isMatched() true false true false 234 | * redirect no no redirect 235 | * 236 | * so you can see this is a negative xor 237 | */ 238 | public function needsRedirecting(): bool 239 | { 240 | return ! $this->block_filter xor $this->isMatched(); 241 | } 242 | 243 | /** 244 | * Check to see if we are on the redirect page. 245 | * 246 | * If we did not test for this, then we would get into a redirect loop. 247 | */ 248 | public function onRedirectPath(Request $request): bool 249 | { 250 | return $request->session() 251 | ->get('redirected', false); 252 | } 253 | 254 | /** 255 | * Delegate setting the rules from the passed in filter string. 256 | * 257 | * The the filter string will always be null on the stack filter. 258 | */ 259 | abstract public function parseFilterString(string|array|null $filter_string): void; 260 | 261 | /** 262 | * Validate a device browser stanza in the rules. 263 | * 264 | * @throws InvalidRuleDefinitionsException 265 | */ 266 | protected function validateBrowserRules(string $device, string $browser, string|array $versions): void 267 | { 268 | if ('*' === $versions) { 269 | return; 270 | } 271 | 272 | if (! is_array($versions)) { 273 | throw new InvalidRuleDefinitionsException( 274 | sprintf( 275 | 'The value for [%s] must be either an array of browsers or an asterisk (*) for all browsers.', 276 | $browser 277 | ) 278 | ); 279 | } 280 | 281 | foreach ($versions as $operator => $version) { 282 | $this->validateBrowserVersionRules($device, $browser, $operator, $version); 283 | } 284 | } 285 | 286 | /** 287 | * Validate a browser version stanza in the rules. 288 | * 289 | * @throws InvalidRuleDefinitionsException 290 | */ 291 | protected function validateBrowserVersionRules(string $device, string $browser, string $operator, string $version): void 292 | { 293 | if (! in_array( 294 | $operator, 295 | ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], 296 | true 297 | )) { 298 | throw new InvalidRuleDefinitionsException( 299 | sprintf( 300 | 'The comparison operator [%s] for [%s > %s] is invalid.', 301 | $operator, 302 | $device, 303 | $browser 304 | ) 305 | ); 306 | } 307 | } 308 | 309 | /** 310 | * Validate a device stanza in the rules. 311 | * 312 | * @throws InvalidRuleDefinitionsException 313 | */ 314 | protected function validDeviceRule(string $device, string|array $browsers): void 315 | { 316 | if ('*' === $browsers) { 317 | return; 318 | } 319 | 320 | if (! is_array($browsers)) { 321 | throw new InvalidRuleDefinitionsException( 322 | sprintf( 323 | 'The value for [%s] must be either an array of browsers or an asterisk (*) for all browsers.', 324 | $device 325 | ) 326 | ); 327 | } 328 | 329 | foreach ($browsers as $browser => $versions) { 330 | $this->validateBrowserRules($device, $browser, $versions); 331 | } 332 | } 333 | 334 | /** 335 | * Validate the rules. 336 | * 337 | * @throws InvalidRuleDefinitionsException 338 | */ 339 | public function validateRules(): void 340 | { 341 | if (empty($this->getRules())) { 342 | return; 343 | } 344 | 345 | foreach ($this->getRules() as $device => $browsers) { 346 | $this->validDeviceRule($device, $browsers); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/FilterServiceProvider.php: -------------------------------------------------------------------------------- 1 | config_file = realpath(__DIR__.'/config/browserfilter.php'); 22 | } 23 | 24 | /** 25 | * Perform post-registration booting of services. 26 | * 27 | * @return void 28 | */ 29 | public function boot() 30 | { 31 | // Publish configuration file 32 | $this->publishes( 33 | [ 34 | $this->config_file => $this->app['path.config'].DIRECTORY_SEPARATOR.'browserfilter.php', 35 | ] 36 | ); 37 | } 38 | 39 | /** 40 | * Register the service provider. 41 | * 42 | * @return void 43 | */ 44 | public function register() 45 | { 46 | // Use default configuration 47 | $this->mergeConfigFrom($this->config_file, 'browserfilter'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Route/AllowFilter.php: -------------------------------------------------------------------------------- 1 | rules[$device] = '*'; 23 | 24 | return; 25 | } 26 | 27 | // Apply rule to all versions of the browser 28 | if ('*' === $operator_versions) { 29 | $this->rules[$device][$browser] = '*'; 30 | 31 | return; 32 | } 33 | 34 | $this->rules[$device][$browser] = $this->extractVersions($device, $browser, $operator_versions); 35 | } 36 | 37 | /** 38 | * Loop through all of the versions in the string and process them. 39 | */ 40 | private function extractVersions(string $device, string $browser, string $operator_versions): array 41 | { 42 | // Were there existing rules for the browser? 43 | $versions = empty($this->getRules()[$device][$browser]) ? [] : $this->getRules()[$device][$browser]; 44 | 45 | foreach (array_filter(explode('|', $operator_versions)) as $operator_version) { 46 | // Remove everything to the leading numbers 47 | $version = preg_replace('/^[^\\d]*/u', '', $operator_version); 48 | // Default no operator to equals 49 | $operator = str_replace($version, '', $operator_version) ?: '='; 50 | 51 | $versions[$operator] = $version; 52 | } 53 | 54 | return $versions; 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | public function generateCacheKey(Request $request): string 61 | { 62 | return parent::generateCacheKey($request).':'.$this->getFilterType().':'.md5($request->path()); 63 | } 64 | 65 | /** 66 | * Generate the key to use to cache the processed filter string into an array. 67 | */ 68 | private function generateFilterStringCacheKey(string $filter_string): string 69 | { 70 | return 'filter_string:'.md5($filter_string); 71 | } 72 | 73 | /** 74 | * Loop through all of the filters in the string and process them. 75 | */ 76 | public function parseFilterString(string|array|null $filter_string): void 77 | { 78 | if (empty($filter_string)) { 79 | return; 80 | } 81 | 82 | $cache_key = $this->generateFilterStringCacheKey($filter_string); 83 | 84 | $this->rules = $this->cache->get($cache_key, []); 85 | 86 | if ($this->rules !== []) { 87 | return; 88 | } 89 | 90 | array_map([$this, 'extractRule'], array_filter(explode(';', $filter_string))); 91 | 92 | $this->cache->put($cache_key, $this->rules, $this->getCacheTimeout()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Stack/Filter.php: -------------------------------------------------------------------------------- 1 | config->get($this->config_path.'rules', []))). 26 | ':'. 27 | parent::generateCacheKey($request); 28 | } 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | public function parseFilterString($filter_string): void 34 | { 35 | // NOTE: $filter_string is unused, but needed to match signature of the method. 36 | 37 | $this->setFilterType($this->config->get($this->config_path.'type')); 38 | 39 | $this->rules = $this->config->get($this->config_path.'rules', []); 40 | } 41 | 42 | /** 43 | * Set the filter type. 44 | * 45 | * @throws InvalidFilterTypeException 46 | */ 47 | protected function setFilterType(string $type): void 48 | { 49 | if ('allow' === $type) { 50 | $this->block_filter = false; 51 | 52 | return; 53 | } 54 | 55 | if ('block' === $type) { 56 | $this->block_filter = true; 57 | 58 | return; 59 | } 60 | 61 | throw new InvalidFilterTypeException( 62 | sprintf( 63 | 'Invalid filter type [%s] was given. Only allow or block are permitted.', 64 | $type 65 | ) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Support/ParserCreator.php: -------------------------------------------------------------------------------- 1 | parser = Parser::create(); 27 | } 28 | 29 | /** 30 | * Parse the user agent string. 31 | */ 32 | public function parseAgent(string $agent): Client 33 | { 34 | return $this->parser->parse((string) $agent); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Support/helper.php: -------------------------------------------------------------------------------- 1 | 'block', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Blocked devices, browsers and/or versions 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This array defines the items to be filtered out when a request is made 26 | | to all routes. There is a three level structure to the array where it 27 | | goes device -> browser -> version. 28 | | 29 | | The device string can be "Mobile", "Other", or "Tablet" as defined by 30 | | UAParser\Parser. You can define an array of specific browser names that 31 | | you are targeting or use "*" to block all browsers of that device type. 32 | | 33 | | At the final level, you can define an array of comparison operators 34 | | to use for that specific browser or you can use a "*" to block all 35 | | versions of that browser. 36 | | 37 | | We are using php's version_compare function... 38 | | 39 | | @link http://php.net/manual/en/function.version-compare.php 40 | | 41 | | so you can see the operations documented there. 42 | | 43 | | Here is an example... 44 | | 45 | | 'rules' => [ 46 | | 'Mobile' => '*', 47 | | 'Other' => [ 48 | | 'IE' => '*', 49 | | ], 50 | | 'Tablet' => [ 51 | | 'Opera' => [ 52 | | '<' => '6', 53 | | ] 54 | | ], 55 | | ], 56 | | 57 | | In this example, we are allowing/blocking the following... 58 | | 59 | | * All mobile devices 60 | | * All versions of IE that is not on a tablet 61 | | * Any version of Opera less than 6 on a tablet 62 | | 63 | */ 64 | 'rules' => [ 65 | 'Mobile' => [], 66 | 'Other' => [], 67 | 'Tablet' => [], 68 | ], 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Blocked devices, browsers and/or versions 73 | |-------------------------------------------------------------------------- 74 | | 75 | | The name of the route to redirect the user to if the browser is blocked 76 | | 77 | */ 78 | 'route' => 'incompatible_browser', 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Duration to cache the browser as being blocked 83 | |-------------------------------------------------------------------------- 84 | | 85 | | The time in minutes to cache that the client is blocked 86 | | 87 | | A time of "0" will disable the cache 88 | | 89 | */ 90 | 'timeout' => 3600, 91 | 92 | ]; 93 | --------------------------------------------------------------------------------