Full Code of serbanghita/Mobile-Detect for AI

4.8.x 4583ed72248a cached
96 files
1.1 MB
373.2k tokens
182 symbols
1 requests
Download .txt
Showing preview only (1,157K chars total). Download the full file or copy to clipboard to get everything.
Repository: serbanghita/Mobile-Detect
Branch: 4.8.x
Commit: 4583ed72248a
Files: 96
Total size: 1.1 MB

Directory structure:
gitextract_umzpjg5f/

├── .claude/
│   └── settings.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── docs/
│   │   └── index.html
│   └── workflows/
│       ├── 4.8.x-test.yml
│       └── website.yml
├── .gitignore
├── .php-cs-fixer.php
├── .phpcs.xml
├── CHANGELOG.md
├── CLAUDE.md
├── CNAME
├── CONTRIBUTING.md
├── DOCKER-COMPOSE.md
├── KNOWN_LIMITATIONS.md
├── LICENSE
├── MobileDetect.json
├── README-EXAMPLES.md
├── README.md
├── SECURITY.md
├── composer.json
├── docker/
│   ├── Dockerfile.setup
│   └── build.sh
├── docker-compose.yml
├── phpbench.json
├── scripts/
│   ├── dump_magic_methods.php
│   ├── example.php
│   ├── export_to_json.php
│   ├── pre-commit-hook.sh
│   ├── test.php
│   └── test_standalone.php
├── src/
│   ├── Cache/
│   │   ├── Cache.php
│   │   ├── CacheException.php
│   │   └── CacheInvalidArgumentException.php
│   ├── Exception/
│   │   ├── MobileDetectException.php
│   │   └── MobileDetectExceptionCode.php
│   ├── MobileDetect.php
│   └── MobileDetectStandalone.php
├── standalone/
│   ├── autoloader.php
│   └── deps/
│       └── simple-cache/
│           ├── .editorconfig
│           ├── .gitattributes
│           ├── .gitignore
│           ├── LICENSE.md
│           ├── README.md
│           ├── composer.json
│           └── src/
│               ├── CacheException.php
│               ├── CacheInterface.php
│               └── InvalidArgumentException.php
└── tests/
    ├── CacheTest.php
    ├── MobileDetectExceptionTest.php
    ├── MobileDetectGeneralTest.php
    ├── MobileDetectStandaloneTest.php
    ├── MobileDetectVersionTest.php
    ├── MobileDetectWithCacheTest.php
    ├── UserAgentList.inc.php
    ├── UserAgentTest.php
    ├── benchmark/
    │   └── MobileDetectBench.php
    ├── bootstrap.php
    ├── phpunit.xml
    ├── providers/
    │   └── vendors/
    │       ├── AOC.php
    │       ├── Acer.php
    │       ├── Alcatel.php
    │       ├── Allview.php
    │       ├── Amazon.php
    │       ├── Apple.php
    │       ├── Archos.php
    │       ├── Asus.php
    │       ├── Blackberry.php
    │       ├── Dell.php
    │       ├── Google.php
    │       ├── HP.php
    │       ├── HTC.php
    │       ├── Huawei.php
    │       ├── LG.php
    │       ├── Lava.php
    │       ├── Leader.php
    │       ├── Lenovo.php
    │       ├── Mi.php
    │       ├── Microsoft.php
    │       ├── Motorola.php
    │       ├── Mpman.php
    │       ├── Nexus.php
    │       ├── Nokia.php
    │       ├── Onda.php
    │       ├── Others.php
    │       ├── Prestigio.php
    │       ├── Samsung.php
    │       ├── Sony.php
    │       ├── SpecialCases.php
    │       ├── Verizon.php
    │       ├── Vodafone.php
    │       └── ZTE.php
    └── ualist.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .claude/settings.json
================================================
{
  "attribution": {
    "commit": "",
    "pr": ""
  },
  "permissions": {
    "allow": [
      "Bash(git:*)",
      "Bash(gh:*)",
      "Bash(docker:*)",
      "Bash(docker compose:*)",
      "Bash(vendor/bin/phpunit:*)",
      "Bash(vendor/bin/phpstan:*)",
      "Bash(vendor/bin/phpcs:*)",
      "Bash(vendor/bin/phpcbf:*)",
      "Bash(vendor/bin/phpbench:*)",
      "Bash(composer:*)",
      "Bash(php:*)"
    ]
  }
}


================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.php]
indent_style = space
indent_size = 4
max_line_length = 140

================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
.editorconfig           export-ignore
.gitattributes          export-ignore
/docs/                  export-ignore
/scripts/               export-ignore
/tests/                 export-ignore
/.*                     export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
github: "serbanghita"
custom:
 - "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mobiledetectlib%40gmail%2ecom&lc=US&item_name=Mobile%20Detect&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted"


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a bug report for Mobile Detect
title: "[bug]"
labels: 'type: bug'
assignees: serbanghita

---

**Describe the bug**
A clear and concise description of what the bug is.
Include steps to reproduce the behavior.
A clear and concise description of what you expected to happen.

**User-agent/Device/Phone/Tablet (please complete the following information):**
- User-agent [e.g. "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-F926B) AppleWebKit/537.36..."]


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for Mobile Detect
title: "[feature]"
labels: feature
assignees: serbanghita

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.
A clear and concise description of any alternative solutions or features you've considered.
Add any other context or code snippets about the feature request here.


================================================
FILE: .github/docs/index.html
================================================



================================================
FILE: .github/workflows/4.8.x-test.yml
================================================
name: 4.8.x on PHP 8.x

# Run this workflow every time a new commit pushed to your repository
on:
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

  push:
    branches: ['4.8.x']
    paths-ignore:
      - '*.md'
      - '.github/**'
      - 'scripts/**'

  pull_request:
    branches: ['4.8.x']
    paths-ignore:
      - '*.md'
      - '.github/**'
      - 'scripts/**'

jobs:
  run:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        php-version: [8.0, 8.1, 8.2, 8.3, 8.4]
        os: ['ubuntu-latest']
        composer-version: ['latest']
        phpunit-version: ['^9.6.18']
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup PHP ${{ matrix.php-version }} on ${{ matrix.os }}
        uses: shivammathur/setup-php@verbose
        with:
          php-version: ${{ matrix.php-version }}
          extensions: mbstring, intl
          ini-values: post_max_size=256M, max_execution_time=180
          coverage: xdebug
          tools: phpunit:${{ matrix.phpunit-version }}, composer:${{ matrix.composer-version }}

      - name: Install composer
        run: composer install

      - name: Run linting
        run: vendor/bin/phpcs

      - name: Run quality rules
        run: vendor/bin/phpstan analyse --debug --memory-limit=1G --level 3 src tests

      - name: Run tests
        run: XDEBUG_MODE=coverage && phpunit -v -c tests/phpunit.xml --coverage-text --strict-coverage --stop-on-risky
        shell: bash


================================================
FILE: .github/workflows/website.yml
================================================
# Simple workflow for deploying static content to GitHub Pages
name: website

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["4.8.x"]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  # Single deploy job since we're just deploying
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Converts Markdown to HTML
        uses: jaywcjlove/markdown-to-html-cli@main
        with:
          source: README.md
          output: .github/docs/index.html
          title: MobileDetect - the PHP mobile detection class
          description: Mobile Detect is a lightweight PHP class for detecting mobile devices (including tablets). It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
          keywords: php mobile-detect device-detection user-agents mobile-redirects
          corners: false
          dark-mode: false
          img-base64: true
          favicon: data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌐</text></svg>
      - name: Setup Pages
        uses: actions/configure-pages@v5
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          # Upload entire repository
          path: './.github/docs'
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4


================================================
FILE: .gitignore
================================================
vendor/
nbproject/
/*.buildpath
/*.project
/.settings
/error.log
.idea/
*.iml
/coverage
/phpunit.phar
composer.lock
/.mobiledetect.phpcs-cache
/tests/.phpunit.result.cache
/.phpunit.result.cache
/composer.phar
/tests/phpunit.xml.bak
/.coverage
/.phpbench
/index.php
.php-cs-fixer.cache
.DS_Store


================================================
FILE: .php-cs-fixer.php
================================================
<?php

$finder = PhpCsFixer\Finder::create()
    ->exclude('docs')
    ->exclude('.github')
//    ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php')
    ->in(__DIR__)
;

$config = new PhpCsFixer\Config();
return $config->setRules([
    '@PSR12' => true,
    // 'strict_param' => true,
    'array_syntax' => ['syntax' => 'short']
])
    ->setFinder($finder)
;


================================================
FILE: .phpcs.xml
================================================
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer"
         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">

    <file>src</file>
    <file>tests</file>

<!--    <exclude-pattern>samples/Header.php</exclude-pattern>-->
<!--    <exclude-pattern>*/tests/Core/*/*Test\.(inc|css|js)$</exclude-pattern>-->

    <arg name="report-width" value="200"/>
    <arg name="parallel" value="80"/>
    <arg name="cache" value=".mobiledetect.phpcs-cache"/>
    <arg name="colors"/>
    <arg value="np"/>

    <!-- Include the whole PSR12 standard -->
    <rule ref="PSR12">
        <exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
        <exclude name="Generic.Files.LineLength"/>
    </rule>
</ruleset>

================================================
FILE: CHANGELOG.md
================================================
# Change log

# 4.8.10

## Fixed
- [x] `Cache::has()` now properly checks TTL expiration before returning `true` (PSR-16 compliance fix). Previously, `has()` returned `true` for expired items.

## Added
- [x] `Cache::evictExpired()` method to manually clean up expired cache entries. Useful for long-running processes (CLI scripts, workers, daemons) to prevent memory growth.
- [x] Expanded test coverage for `Cache` class: added 17 new tests covering all if/else branches including custom defaults, DateInterval TTL, key validation edge cases, and expiration scenarios.
- [x] `README-EXAMPLES.md` with comprehensive usage examples including long-running processes, framework integration, and debugging.

## Changed
- [x] `Cache::has()` now deletes expired items on check (lazy cleanup, consistent with `get()` behavior).

# 4.8.09

- [x] `sha1` is now the default fn for encoding cache keys. Using `base64` [was causing problems](https://github.com/serbanghita/Mobile-Detect/issues/974#issuecomment-2531597903) in Laravel.

# 4.8.08

- [x] fix for missing psr/cache prod dependency 
- [bug] latest 4.8.07 cause site error Call to a member function get() on false #974
- [x] fix for Docker build not installing dev dependencies

# 4.8.07 (broken in composer, please skip)

- [x] fix cache and generate short cache key (#971)
- [x] Added configuration cacheKeyFn which allows for using a custom cache key creation fn.
- [x] Use Client Hints Sec-CH-UA-Mobile header to detect mobile (#962)
- [x] added Huawei (phone, OS - HarmonyOS, browser) detection (#952)
- [x] Bugfix: Allow Injection of Any PSR Cache Interface (#966)
- [x] PHP 8.4 - implicit nulls are deprecated (#960)


# 4.8.03
- [x] added optional `$config` to MobileDetect constructor.
- [x] added `autoInitOfHttpHeaders` configuration which is by default `true`. This enabled the old behavior from `3.x` and `2.x` that allows automatic detection of HTTP headers and User Agent from $_SERVER.
- [x] refactored internal CloudFront related methods and the way `setHttpHeaders` work. It no longer falls back on `$_SERVER`. The method still calls `setUserAgent` in case `HTTP_USER_AGENT` and friends are present.
- [x] added `maximumUserAgentLength` to the `$config`, by default the limit is `500`.

# 4.8.02
- [x] new user agents
- [x] Samsung Galaxy Tab S6 Lite #919 
- [x] Samsung Galaxy Tab S8 series #912

# 4.8.01

- [x] PHP 8.x only.
- [x] PSR-16 cache support.
- [x] Constructor accepts `CacheFactory` class where you can inject your own PSR-6 Cache interfaces.
- [x] You need to explicitly `setUserAgent("...")` or `setUserAgentHeaders([...])` otherwise an exception is being thrown.
- [x] `scripts/` folder no longer included in the git tag release archive.
- [x] added performance tests
- [x] regexes can be arrays of strings or strings


# 2023

Launched 4.8.xx which contains PHP 8.x support, refactorings and external Cache support.

## 2022

In December 2022 we released the version for PHP7.
Mobile Detect was split into two dev branches: `2.8.x` which will support PHP5, but is deprecated and
`3.74.x` which supports PHP >= 7.3

## 2013

In August 2013 the library has 1800+ stargazers and support for: composer, PHPUnit tests, PSR standards and a new webpage http://mobiledetect.net

# 2012

Throughout 2012 the script has been updated constantly, and we have received tons of feedback and requests.
In July 2012 we moved the repository from Google Code to GitHub in order to quickly accommodate the frequent updates and to involve more people.

## 2011

In December 2011 it received a major update from the first version, an important number of issues were fixed, then 2.0 was launched. 
The new version marks a new mindset and also featuring tablet detection.

## 2009

The first version of the script was developed in 2009, and it was hosted at https://code.google.com/p/php-mobile-detect/, it was a small project with around 30 stars. 
(Original blog post by Victor: http://victorstanciu.ro/detectarea-platformelor-mobile-in-php/)


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Whenever you find a new rule that applies to this project, add it to this file.

## Project Overview

Mobile-Detect is a lightweight PHP library for detecting mobile devices (including tablets) using User-Agent strings and HTTP headers. The main namespace is `Detection\MobileDetect`.

## Git Workflow

When working on 4.8.x-based branches, always rebase changes into the `4.8.x` branch only (not `main` or `master`).
When releasing a new version tag, make sure that the new tag is reflected in the `@version` comment section of `MobileDetect.php` and also 
in the `protected string $VERSION` property of the `MobileDetect` class.
The latest tag should also be reflected in `MobileDetect.json`'s `version` property.

## Code Navigation

Always use LSP tools when working with code references:
- Use `goToDefinition` to find where a class, method, or function is defined
- Use `findReferences` to locate all usages of a symbol
- Use `hover` to get type information and documentation
- Use `documentSymbol` to list all symbols in a file

## Common Commands

### Testing
```bash
# Run all tests with coverage
vendor/bin/phpunit -v -c tests/phpunit.xml --coverage-html .coverage

# Run a single test file
vendor/bin/phpunit -v -c tests/phpunit.xml tests/MobileDetectGeneralTest.php

# Run a specific test method
vendor/bin/phpunit -v -c tests/phpunit.xml --filter testMethodName
```

### Code Quality
```bash
# Linting (PSR-12 standard)
vendor/bin/phpcs

# Auto-fix code style issues
vendor/bin/php-cs-fixer fix

# Static analysis (level 3)
vendor/bin/phpstan analyse --memory-limit=1G --level 3 src tests
```

### Benchmarking
```bash
# Create baseline
vendor/bin/phpbench run tests/benchmark/MobileDetectBench.php --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate --tag=baseline

# Compare against baseline
vendor/bin/phpbench run tests/benchmark/MobileDetectBench.php --ref=baseline --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate
```

## Architecture

### Core Classes (src/)
- **MobileDetect.php** - Main detection class containing:
  - Device/tablet/browser regex patterns as static arrays
  - Magic `isXXXX()` methods for device/browser detection (e.g., `isiPhone()`, `isAndroidOS()`)
  - `isMobile()`, `isTablet()` - Primary detection methods
  - `version()` - Extract version numbers from User-Agent
  - PSR-16 cache integration for regex match results

- **MobileDetectStandalone.php** - Extends MobileDetect for use without Composer (autoloads dependencies from `standalone/`)

- **Cache/Cache.php** - In-memory PSR-16 cache implementation with TTL support

### Test Structure (tests/)
- **MobileDetectGeneralTest.php** - Core functionality tests
- **UserAgentTest.php** - Data-driven tests using User-Agent fixtures
- **providers/vendors/*.php** - Test data files organized by device vendor (Apple.php, Samsung.php, etc.)
  - Each file returns an array of User-Agent strings mapped to expected results (`isMobile`, `isTablet`, `version`, etc.)
- **benchmark/MobileDetectBench.php** - PHPBench performance tests

### Key Patterns
- Detection results are cached using configurable PSR-16 cache (default: in-memory)
- Cache key generation is configurable via `cacheKeyFn` (default: sha1)
- HTTP headers from `$_SERVER` are auto-initialized unless `autoInitOfHttpHeaders` is false
- CloudFront headers are recognized for AWS-based detection


================================================
FILE: CNAME
================================================
mobiledetect.net

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Step-by-step guide to contributing to Mobile Detect library. \
By contributing to Mobile Detect library you agree with the [MIT License](LICENSE).

## Contribute by developing code

### 1. [Fork](https://help.github.com/articles/fork-a-repo/#fork-an-example-repository) the repo

```bash
git clone https://github.com/[yourname]/Mobile-Detect.git
git add remote serbanghita https://github.com/serbanghita/Mobile-Detect.git
git remote -v
...
origin       git@github.com:serbanghita/Mobile-Detect.git
serbanghita  https://github.com/serbanghita/Mobile-Detect.git
```

### 2. Create local branch

Next create your own git working branch from one of the existing branches `4.x`, `3.x` or `2.x`
depending on your PHP version:

```bash
git checkout -b my-new-patch origin/x.x.x
```

### 3. Build

#### Local

```shell
composer install
```

#### With Docker CLI

```shell
DOCKER_BUILDKIT=0 docker build -f ./docker/Dockerfile -t build .
```

#### With `docker-compose.yml` (recommended)

```shell
docker compose build setup
```

### 3. Lint the code

Ensure the code is clean, just run the linters:

#### Local

```bash
./vendor/bin/phpcs
./vendor/bin/phpcbf
```

#### With `docker-compose.yml` (recommended)

```shell
docker compose build runLinting
```

### 4. Run unit and integration tests

If you add new methods or make structural changes to the library then you need to add unit tests
otherwise your PR will not be accepted.
If you add new regexes make sure you commit the User-Agents in [`tests/providers/vendors`](https://github.com/serbanghita/Mobile-Detect/tree/master/tests/providers/vendors).
Now that your changes are done, **run the unit tests**:

#### Locally

```bash
vendor/bin/phpunit -v -c tests/phpunit.xml --coverage-html .coverage
```

#### With `docker-compose.yml` (recommended)

```shell
docker compose run runUnitTests
```

Make sure you check the `.coverage` folder and open the report. \
The coverage should be just like you first started (close to 100%).

### 5. Run performance tests

#### Local

```bash
./vendor/bin/phpbench run tests/Benchmark/MobileDetectBench.php --ref=baseline --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate
```

#### With `docker-compose.yml` (recommended)

```shell
docker compose run runPerfTests
```

Baseline re-creation:

```bash
./vendor/bin/phpbench run tests/Benchmark/ --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate --tag=baseline --dump-file=phpbench-baseline.xml
```


### 6. Commit

If no errors left, then proceed to committing your changes:

```bash
git status
git stage
git commit -m "your commit message here"
git push
```

### 7. Submit PR

Now go to your repo on GitHub and ["Submit the PR"](https://help.github.com/articles/about-pull-requests/).

## Other ways of contributing

### 1. Report issues

1. Specify the User-agent by visiting [http://demo.mobiledetect.net](http://demo.mobiledetect.net).
2. Specify the expected behaviour.

### 2. Add new module, plugin, plugin or port

[Submit new module, plugin, port](../../issues/new?title=New%203rd%20party%20module&body=Name,%20Link%20and%20Description%20of%20the%20module.)
 including the following information:
* Module name
* Description
* Link
* Author

Or you can submit a PR against `README.md`.

### 3. Website updates

1. Our official website is hosted at [http://mobiledetect.net](http://mobiledetect.net).
2. The files are found on the `gh-pages` branch.
3. `git checkout gh-pages`
4. `npm install -g browser-sync`
5. `browser-sync start --s . --f . --port 3000 --reload-debounce 1500 --no-ui`
6. Go to `http://localhost:3000` and make changes.
7. Commit, push and submit the PR against `serbanghita:gh-pages`.



================================================
FILE: DOCKER-COMPOSE.md
================================================
# Docker Compose for Pre-Release Validation

This document describes the Docker Compose setup for running all necessary checks before a release in a controlled PHP environment.

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────────┐
│                              SETUP SERVICE                              │
│  (composer:latest) - Installs dependencies into ./vendor                │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                    service_completed_successfully
                                    │
        ┌───────────────────────────┼───────────────────────────┐
        ▼                           ▼                           ▼
┌───────────────┐         ┌─────────────────┐         ┌─────────────────┐
│ runUnitTests  │         │  runPerfTests   │         │   runLinting    │
│ (php:8.4+xdebug)│       │ (php:8.4-alpine)│         │ (php:8.4-alpine)│
│ phpunit       │         │ phpbench        │         │ phpcs           │
└───────────────┘         └─────────────────┘         └─────────────────┘
        │                           │                           │
        │                           │                           ▼
        │                           │                 ┌─────────────────┐
        │                           │                 │ runQualityCheck │
        │                           │                 │ (php:8.4-alpine)│
        │                           │                 │ phpstan         │
        │                           │                 └─────────────────┘
        │                           │                           │
        └───────────────────────────┴───────────────────────────┘
                                    │
                    all services completed successfully
                                    │
                                    ▼
                        ┌─────────────────────┐
                        │       runAll        │
                        │  Pre-release gate   │
                        └─────────────────────┘
                                    │
                                    ▼
                        ┌─────────────────────┐
                        │  generateJsonModel  │
                        │  export_to_json.php │
                        └─────────────────────┘
```

## Services

| Service | Image | Purpose |
|---------|-------|---------|
| `setup` | composer:latest | Install dependencies |
| `runUnitTests` | alcohol/php:8.4-xdebug | PHPUnit tests with coverage |
| `runPerfTests` | php:8.4-alpine | PHPBench performance tests |
| `runLinting` | php:8.4-alpine | PHPCS code style checks + auto-fix |
| `runQualityCheck` | php:8.4-alpine | PHPStan static analysis |
| `runAll` | php:8.4-alpine | Pre-release validation gate |
| `generateJsonModel` | php:8.4-alpine | Export detection rules to JSON |

## Usage

### Run all pre-release checks

```bash
docker compose -p mobile-detect up --build runAll
```

### Run individual services

```bash
# Unit tests with coverage
docker compose -p mobile-detect up --build runUnitTests

# Performance benchmarks
docker compose -p mobile-detect up --build runPerfTests

# Code style linting
docker compose -p mobile-detect up --build runLinting

# Static analysis
docker compose -p mobile-detect up --build runQualityCheck

# Generate JSON model (runs after all checks pass)
docker compose -p mobile-detect up --build generateJsonModel
```

### Clean up

```bash
docker compose -p mobile-detect down --volumes --remove-orphans
```


================================================
FILE: KNOWN_LIMITATIONS.md
================================================
**Known limitations**

* Mobile Detect script was designed to detect `mobile` devices. Implicitly other devices are considered to be `desktop`.
* User-Agent and HTTP headers sniffing is a non-reliable method of detecting a mobile device.
* If the mobile browser is set on `Desktop mode`, the Mobile Detect script has no indicator (eg. a group of strings) that would allow it to detect that the device is `mobile`.
* Ipad 2019 is being recognized as a desktop because of Safari's default `Request Desktop Website` setting. See details and possible workaround [#820](https://github.com/serbanghita/Mobile-Detect/issues/820)
  * Also see [#886](https://github.com/serbanghita/Mobile-Detect/issues/886#issuecomment-1047187763) 
* Some touchscreen devices (eg. Microsoft Surface) are tough to detect as mobile since they can be used in a laptop mode. See: [#32](https://github.com/serbanghita/Mobile-Detect/issues/32), [#461](https://github.com/serbanghita/Mobile-Detect/issues/461), [#667](https://github.com/serbanghita/Mobile-Detect/issues/667)
* Some mobile devices (eg. IPadOS, Google Pixel Slate). See: [#795](https://github.com/serbanghita/Mobile-Detect/issues/795), [#788](https://github.com/serbanghita/Mobile-Detect/issues/788)
* Detecting the device brand (eg. Apple, Samsung, HTC) is not 100% reliable.
* We don't monitor the quality of the 3rd party tools based on Mobile Detect script. 
We cannot guarantee that they are using the class properly or if they provide the latest version.
* Version `2.x` is made to be PHP 5.3 compatible because of the backward compatibility changes of PHP.
* There are hundreds of devices launched every month, we cannot keep a 100% up-to-date detection rate.
* The script cannot detect the viewport, pixel density or resolution of the screen since it's running server-side.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Şerban Ghiţă, Nick Ilyin and contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MobileDetect.json
================================================
{
    "version": "4.8.10",
    "headerMatch": {
        "HTTP_ACCEPT": {
            "matches": [
                "application\/x-obml2d",
                "application\/vnd.rim.html",
                "text\/vnd.wap.wml",
                "application\/vnd.wap.xhtml+xml"
            ]
        },
        "HTTP_X_WAP_PROFILE": null,
        "HTTP_X_WAP_CLIENTID": null,
        "HTTP_WAP_CONNECTION": null,
        "HTTP_PROFILE": null,
        "HTTP_X_OPERAMINI_PHONE_UA": null,
        "HTTP_X_NOKIA_GATEWAY_ID": null,
        "HTTP_X_ORANGE_ID": null,
        "HTTP_X_VODAFONE_3GPDPCONTEXT": null,
        "HTTP_X_HUAWEI_USERID": null,
        "HTTP_UA_OS": null,
        "HTTP_X_MOBILE_GATEWAY": null,
        "HTTP_X_ATT_DEVICEID": null,
        "HTTP_UA_CPU": {
            "matches": [
                "ARM"
            ]
        },
        "Sec-CH-UA-Mobile": {
            "matches": [
                "?1"
            ]
        }
    },
    "uaHttpHeaders": [
        "HTTP_USER_AGENT",
        "HTTP_X_OPERAMINI_PHONE_UA",
        "HTTP_X_DEVICE_USER_AGENT",
        "HTTP_X_ORIGINAL_USER_AGENT",
        "HTTP_X_SKYFIRE_PHONE",
        "HTTP_X_BOLT_PHONE_UA",
        "HTTP_DEVICE_STOCK_UA",
        "HTTP_X_UCBROWSER_DEVICE_UA"
    ],
    "cloudFrontHttpHeaders": [
        "HTTP_CLOUDFRONT_IS_MOBILE_VIEWER",
        "HTTP_CLOUDFRONT_IS_TABLET_VIEWER",
        "HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER"
    ],
    "uaMatch": {
        "phones": {
            "iPhone": "\\biPhone\\b|\\biPod\\b",
            "BlackBerry": "BlackBerry|\\bBB10\\b|rim[0-9]+|\\b(BBA100|BBB100|BBD100|BBE100|BBF100|STH100)\\b-[0-9]+",
            "Pixel": "; \\bPixel\\b",
            "HTC": [
                "HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)",
                "APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200",
                "ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\\bEVO\\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel"
            ],
            "Nexus": "Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 5X|Nexus 6",
            "Dell": "Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\\b001DL\\b|\\b101DL\\b|\\bGS01\\b",
            "Motorola": [
                "Motorola|DROIDX|DROID BIONIC|\\bDroid\\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955",
                "A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611",
                "MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863",
                "ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317",
                "XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800",
                "XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\\bMoto E\\b|XT1068|XT1092|XT1052"
            ],
            "Samsung": [
                "\\bSamsung\\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310",
                "SM-F946B|SM-A127F",
                "SM-S908E|SM-G955N|SM-S918U1|SM-G998B|SM-G970N|SM-G973U|SM-S901U|SM-A515F|SM-S901E|SM-G980F|SM-S901B",
                "GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510",
                "GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K",
                "GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010",
                "GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100",
                "GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210",
                "GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250",
                "GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420",
                "GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700",
                "GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103",
                "GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800",
                "GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210",
                "GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350",
                "GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660",
                "GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230",
                "GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600",
                "SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930",
                "SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730",
                "SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351",
                "SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430",
                "SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740",
                "SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157",
                "SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657",
                "SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817",
                "SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220",
                "SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225",
                "SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105",
                "SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200",
                "SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717",
                "SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917",
                "SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500",
                "SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777",
                "SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229",
                "SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409",
                "SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609",
                "SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749",
                "SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200",
                "SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497",
                "SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10",
                "SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700",
                "SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700",
                "SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220",
                "SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550",
                "SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920",
                "SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535",
                "SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510",
                "GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582",
                "GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K",
                "SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558",
                "GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F|SM-J330F",
                "SM-G610F|SM-G981B|SM-G892A|SM-A530F|SM-G988N|SM-G781B|SM-A805N|SM-G965F"
            ],
            "LG": [
                "\\bLG\\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200)",
                "LG[- ]?(MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK)",
                "LG[- ]?(E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690)",
                "LG[- ]?(MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740)",
                "LG[- ]?(VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323|M257)|LM-G710"
            ],
            "Sony": [
                "SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i",
                "C5303|C6902|C6903|C6906|C6943|D2533|SOV34|601SO|F8332"
            ],
            "Asus": "Asus.*Galaxy|PadFone.*Mobile|ASUS_Z01QD|ASUS_X00TD",
            "Xiaomi": [
                "^(?!.*\\bx11\\b).*xiaomi.*$|POCOPHONE F1|\\bMI\\b 8|\\bMi\\b 10|Redmi Note 9S|Redmi 5A|Redmi Note 5A Prime|Redmi Note 7 Pro",
                "N2G47H|M2001J2G|M2001J2I|M1805E10A|M2004J11G|M1902F1G|M2002J9G|M2004J19G|M2003J6A1G|M2012K11C|M2007J1SC"
            ],
            "NokiaLumia": "Lumia [0-9]{3,4}",
            "Micromax": "Micromax.*\\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\\b",
            "Palm": "PalmSource|Palm",
            "Vertu": "Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature",
            "Pantech": [
                "PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L",
                "IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S",
                "IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995",
                "IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790"
            ],
            "Fly": "IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250",
            "Wiko": [
                "KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY",
                "BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM"
            ],
            "iMobile": "i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)",
            "SimValley": "\\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\\b",
            "Wolfgang": "AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q",
            "Alcatel": "Alcatel",
            "Nintendo": "Nintendo (3DS|Switch)",
            "Amoi": "Amoi",
            "INQ": "INQ",
            "OnePlus": "ONEPLUS|CPH2663",
            "GenericPhone": "Tapatalk|PDA;|SAGEM|\\bmmp\\b|pocket|\\bpsp\\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\\bwap\\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser",
            "Huawei": "HMSCore|Huawei"
        },
        "tablets": {
            "iPad": "iPad|iPad.*Mobile",
            "NexusTablet": "Android.*Nexus[\\s]+(7|9|10)",
            "GoogleTablet": "Android.*Pixel C",
            "SamsungTablet": [
                "SM-X616B|SM-X610|SM-X516B|SM-X910|SM-X916B|SM-X816B|SM-X810|SM-X710|SM-X716B|SM-X510|SM-P619|SM-T225|SM-T225N|SM-T736B|SM-T505|SM-T733|SM-X205|SM-X210|SM-X216B",
                "SM-X700|SM-X706|SM-X706B|SM-X706U|SM-X706N|SM-X800|SM-X806|SM-X806B|SM-X806U|SM-X806N|SM-X900|SM-X906|SM-X906B|SM-X906U|SM-X906N|SM-P613|SM-X110|SM-X115",
                "SM-T970|SM-T380|SM-T5950|SM-T905|SM-T231|SM-T500|SM-T860|SM-T536|SM-T837A|SM-X200|SM-T220|SM-T870|SM-X906C",
                "SM-T815Y|SM-T585|SM-T285|SM-T825|SM-W708|SM-T835|SM-T830|SM-T837V|SM-T720|SM-T510|SM-T387V|SM-P610|SM-T290|SM-T515|SM-T590|SM-T595|SM-T725|SM-T817P|SM-P585N0|SM-T395|SM-T295|SM-T865|SM-P610N|SM-P615",
                "SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU",
                "SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715",
                "SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237",
                "GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X",
                "GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X",
                "SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210",
                "GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L",
                "SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113",
                "SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SM-X300|SM-T630"
            ],
            "Kindle": "Kindle|Silk.*Accelerated|Android.*\\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\\b|Android.*Silk\/[0-9.]+ like Chrome\/[0-9.]+ (?!Mobile)",
            "SurfaceTablet": "Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)",
            "HPTablet": "HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10",
            "AsusTablet": [
                "ME181C|P01Y|PO1MA|P01Z|\\bP027\\b|\\bP024\\b|\\bP00C\\b",
                "\\bK00C\\b|\\bK00E\\b|\\bK00L\\b|TX201LA|ME176C|ME102A|\\bM80TA\\b|ME372CL|ME560CG|ME372CG|ME302KL| K01A | K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\\bME70C\\b|ME581C|ME581CL|ME8510C",
                "^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\\bK00F\\b"
            ],
            "BlackBerryTablet": "PlayBook|RIM Tablet",
            "HTCtablet": "HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410",
            "MotorolaTablet": "xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617",
            "NookTablet": "Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2",
            "AcerTablet": [
                "Android.*; \\b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\\b",
                "W3-810|\\bA3-A10\\b|\\bA3-A11\\b|\\bA3-A20\\b|\\bA3-A30|A3-A40"
            ],
            "ToshibaTablet": "Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO",
            "LGTablet": "\\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\\b",
            "FujitsuTablet": "Android.*\\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\\b",
            "PrestigioTablet": [
                "PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C",
                "PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD",
                "PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002"
            ],
            "LenovoTablet": [
                "TB-X704L|TB-J606F|TB-X606F|TB-X306X|YT-J706X|TB128FU",
                "YT3-X50M|YT-X705F|YT-X703F|YT-X703L|YT-X705L|YT-X705X|TB2-X30F|TB2-X30L|TB2-X30M|A2107A-F|A2107A-H|TB3-730F|TB3-730M|TB3-730X|TB-7504F|TB-7504X|TB-X704F|TB-X104F|TB3-X70F|TB-X705F|TB-8504F|TB3-X70L|TB3-710F",
                "TB-X103F|TB-X304X|TB-X304F|TB-X304L|TB-X505F|TB-X505L|TB-X505X|TB-X605F|TB-X605L|TB-8703F|TB-8703X|TB-8703N|TB-8704N|TB-8704F|TB-8704X|TB-8704V|TB-7304F|TB-7304I|TB-7304X|Tab2A7-10F|Tab2A7-20F|TB2-X30L|YT3-X50L|YT3-X50F",
                "Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)"
            ],
            "DellTablet": "Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7",
            "XiaomiTablet": "21051182G",
            "YarvikTablet": [
                "Android.*\\b(TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\\b",
                "Android.*\\b(TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211)\\b",
                "Android.*\\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152)\\b"
            ],
            "MedionTablet": "Android.*\\bOYO\\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB",
            "ArnovaTablet": "97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2",
            "IntensoTablet": "INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004",
            "IRUTablet": "M702pro",
            "MegafonTablet": "MegaFon V9|\\bZTE V9\\b|Android.*\\bMT7A\\b",
            "EbodaTablet": "E-Boda (Supreme|Impresspeed|Izzycomm|Essential)",
            "AllViewTablet": "Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)",
            "ArchosTablet": "\\b(101G9|80G9|A101IT)\\b|Qilive 97R|Archos5|\\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\\b",
            "AinolTablet": "NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark",
            "NokiaLumiaTablet": "Lumia 2520",
            "SonyTablet": [
                "EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP641|SGP612|SOT31|SGP771|SGP611|SGP612|SGP712",
                "Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321"
            ],
            "PhilipsTablet": "\\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\\b",
            "CubeTablet": "Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT",
            "CobyTablet": "MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010",
            "MIDTablet": [
                "M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800",
                "MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10"
            ],
            "MSITablet": "MSI \\b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\\b",
            "SMiTTablet": "Android.*(\\bMID\\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)",
            "RockChipTablet": "Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A",
            "FlyTablet": "IQ310|Fly Vision",
            "bqTablet": "Android.*(bq)?.*\\b(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))\\b|Maxwell.*Lite|Maxwell.*Plus",
            "HuaweiTablet": "MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L|BAH-L09|BAH-W09|AGS-L09|CMR-AL19|KOB2-L09|BG2-U01|BG2-W09|BG2-U03|AGS-W09",
            "NecTablet": "\\bN-06D|\\bN-08D",
            "PantechTablet": "Pantech.*P4100",
            "BronchoTablet": "Broncho.*(N701|N708|N802|a710)",
            "VersusTablet": "TOUCHPAD.*[78910]|\\bTOUCHTAB\\b",
            "ZyncTablet": "z1000|Z99 2G|z930|z990|z909|Z919|z900",
            "PositivoTablet": "TB07STA|TB10STA|TB07FTA|TB10FTA",
            "NabiTablet": "Android.*\\bNabi",
            "KoboTablet": "Kobo Touch|\\bK080\\b|\\bVox\\b Build|\\bArc\\b Build",
            "DanewTablet": "DSlide.*\\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\\b",
            "TexetTablet": [
                "TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE",
                "TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD",
                "TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A",
                "NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020"
            ],
            "PlaystationTablet": "Playstation.*(Portable|Vita)",
            "TrekstorTablet": "ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab",
            "PyleAudioTablet": "\\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\\b",
            "AdvanTablet": "Android.* \\b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\\b ",
            "DanyTechTablet": "Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1",
            "GalapadTablet": "Android [0-9.]+; [a-z-]+; \\bG1\\b",
            "MicromaxTablet": "Funbook|Micromax.*\\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\\b",
            "KarbonnTablet": "Android.*\\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\\b",
            "AllFineTablet": "Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide",
            "PROSCANTablet": [
                "\\b(PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\\b",
                "\\b(PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082)\\b",
                "\\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K)\\b"
            ],
            "YONESTablet": "BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026",
            "ChangJiaTablet": [
                "TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503",
                "TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205",
                "TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106"
            ],
            "GUTablet": "TX-A1301|TX-M9002|Q702|kf026",
            "PointOfViewTablet": [
                "TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10",
                "TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945"
            ],
            "OvermaxTablet": "OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027",
            "HCLTablet": "HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync",
            "DPSTablet": "DPS Dream 9|DPS Dual 7",
            "VistureTablet": "V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10",
            "CrestaTablet": "CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989",
            "MediatekTablet": "\\bMT8125|MT8389|MT8135|MT8377\\b",
            "ConcordeTablet": "Concorde([ ]+)?Tab|ConCorde ReadMan",
            "GoCleverTablet": [
                "TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042",
                "TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2",
                "GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76"
            ],
            "ModecomTablet": [
                "FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702",
                "FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003"
            ],
            "VoninoTablet": [
                "\\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS)\\b",
                "\\b(Android.*\\bQ8\\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\\b"
            ],
            "ECSTablet": "V07OT2|TM105A|S10OT1|TR10CS1",
            "StorexTablet": "eZee[_']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab",
            "VodafoneTablet": "SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497|VFD 1400",
            "EssentielBTablet": "Smart[ ']?TAB[ ]+?[0-9]+|Family[ ']?TAB2",
            "RossMoorTablet": "RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711",
            "iMobileTablet": "i-mobile i-note",
            "TolinoTablet": "tolino tab [0-9.]+|tolino shine",
            "AudioSonicTablet": "\\bC-22Q|T7-QC|T-17B|T-17P\\b",
            "AMPETablet": "Android.* A78 ",
            "SkkTablet": "Android.* (SKYPAD|PHOENIX|CYCLOPS)",
            "TecnoTablet": "TECNO P9|TECNO DP8D",
            "JXDTablet": [
                "Android.* \\b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603)\\b",
                "Android.* \\b(S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\\b"
            ],
            "iJoyTablet": [
                "Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7)",
                "Tablet (Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst)",
                "Tablet (Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam)",
                "Tablet (Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)"
            ],
            "FX2Tablet": "FX2 PAD7|FX2 PAD10",
            "XoroTablet": [
                "KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790",
                "PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032",
                "TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151"
            ],
            "ViewsonicTablet": "ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a",
            "VerizonTablet": "QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1",
            "OdysTablet": "LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\\bXELIO\\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10",
            "CaptivaTablet": "CAPTIVA PAD",
            "IconbitTablet": "NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S",
            "TeclastTablet": [
                "T98 4G|\\bP80\\b|\\bX90HD\\b|X98 Air|X98 Air 3G|\\bX89\\b|P80 3G|\\bX80h\\b|P98 Air|\\bX89HD\\b|P98 3G|\\bP90HD\\b|P89 3G|X98 3G",
                "\\bP70h\\b|P79HD 3G|G18d 3G|\\bP79HD\\b|\\bP89s\\b|\\bA88\\b|\\bP10HD\\b|\\bP19HD\\b|G18 3G|\\bP78HD\\b|\\bA78\\b|\\bP75\\b|G17s 3G|G17h 3G",
                "\\bP85t\\b|\\bP90\\b|\\bP11\\b|\\bP98t\\b|\\bP98HD\\b|\\bG18d\\b|\\bP85s\\b|\\bP11HD\\b|\\bP88s\\b|\\bA80HD\\b|\\bA80se\\b|\\bA10h\\b|\\bP89\\b",
                "\\bP78s\\b|\\bG18\\b|\\bP85\\b|\\bA70h\\b|\\bA70\\b|\\bG17\\b|\\bP18\\b|\\bA80s\\b|\\bA11s\\b|\\bP88HD\\b|\\bA80h\\b|\\bP76s\\b|\\bP76h\\b|\\bP98\\b",
                "\\bA10HD\\b|\\bP78\\b|\\bP88\\b|\\bA11\\b|\\bA10t\\b|\\bP76a\\b|\\bP76t\\b|\\bP76e\\b|\\bP85HD\\b|\\bP85a\\b|\\bP86\\b|\\bP75HD\\b|\\bP76v\\b|\\bA12\\b",
                "\\bP75a\\b|\\bA15\\b|\\bP76Ti\\b|\\bP81HD\\b|\\bA10\\b|\\bT760VE\\b|\\bT720HD\\b|\\bP76\\b|\\bP73\\b|\\bP71\\b|\\bP72\\b|\\bT720SE\\b|\\bC520Ti\\b|\\bT760\\b|\\bT720VE\\b|T720-3GE|T720-WiFi"
            ],
            "OndaTablet": [
                "\\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811)\\b[\\s]+",
                "\\b(V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819)\\b[\\s]+",
                "\\b(V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\\b[\\s]+|V10 \\b4G\\b"
            ],
            "JaytechTablet": "TPC-PA762",
            "BlaupunktTablet": "Endeavour 800NG|Endeavour 1010",
            "DigmaTablet": "\\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\\b",
            "EvolioTablet": "ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\\bEvotab\\b|\\bNeura\\b",
            "LavaTablet": "QPAD E704|\\bIvoryS\\b|E-TAB IVORY|\\bE-TAB\\b",
            "AocTablet": "MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712",
            "MpmanTablet": [
                "MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\\bMPG7\\b|MPDCG75|MPDCG71",
                "MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77",
                "MP709|MID701|MID711|MID170|MPDC703|MPQC1010"
            ],
            "CelkonTablet": "CT695|CT888|CT[\\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\\bCT-1\\b",
            "WolderTablet": [
                "miTab \\b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT)\\b",
                "miTab \\b(EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND)\\b",
                "miTab \\b(BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\\b"
            ],
            "MediacomTablet": "M-MPI10C3G|M-SP10EG|M-SP10EGP|M-SP10HXAH|M-SP7HXAH|M-SP10HXBH|M-SP8HXAH|M-SP8MXA",
            "MiTablet": "\\bMI PAD\\b|\\bHM NOTE 1W\\b",
            "NibiruTablet": "Nibiru M1|Nibiru Jupiter One",
            "NexoTablet": "NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI",
            "LeaderTablet": [
                "TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G",
                "TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100"
            ],
            "UbislateTablet": "UbiSlate[\\s]?7C",
            "PocketBookTablet": "Pocketbook",
            "KocasoTablet": "\\b(TB-1207)\\b",
            "HisenseTablet": "\\b(F5281|E2371)\\b",
            "Hudl": "Hudl HT7S3|Hudl 2",
            "TelstraTablet": "T-Hub2",
            "GenericTablet": [
                "Android.*\\b97D\\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\\bA7EB\\b|CatNova8|A1_07|CT704|CT1002",
                "\\bM721\\b|rk30sdk|\\bEVOTAB\\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab",
                "\\bM6pro\\b|CT1020W|arc 10HD|\\bTP750\\b|\\bQTAQZ3\\b|WVT101|TM1088|KT107"
            ]
        },
        "browsers": {
            "Chrome": "\\bCrMo\\b|CriOS.*Mobile|Android.*Chrome\/[.0-9]* Mobile",
            "Dolfin": "\\bDolfin\\b",
            "Opera": "Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR\/[0-9.]+$|Coast\/[0-9.]+",
            "Skyfire": "Skyfire",
            "Edge": "EdgiOS.*Mobile|Mobile Safari\/[.0-9]* Edge",
            "IE": "IEMobile|MSIEMobile",
            "Firefox": "fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS.*Mobile",
            "Bolt": "bolt",
            "TeaShark": "teashark",
            "Blazer": "Blazer",
            "Safari": "Version((?!\\bEdgiOS\\b).)*Mobile.*Safari|Safari.*Mobile|MobileSafari",
            "WeChat": "\\bMicroMessenger\\b",
            "UCBrowser": "UC.*Browser|UCWEB",
            "baiduboxapp": "baiduboxapp",
            "baidubrowser": "baidubrowser",
            "DiigoBrowser": "DiigoBrowser",
            "Mercury": "\\bMercury\\b",
            "ObigoBrowser": "Obigo",
            "NetFront": "NF-Browser",
            "GenericBrowser": "NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger",
            "PaleMoon": "Android.*PaleMoon|Mobile.*PaleMoon",
            "HuaweiBrowser": "HuaweiBrowser"
        },
        "os": {
            "AndroidOS": "Android",
            "BlackBerryOS": "blackberry|\\bBB10\\b|rim tablet os",
            "PalmOS": "PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino",
            "SymbianOS": "Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\\bS60\\b",
            "WindowsMobileOS": "Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Windows Mobile|Windows Phone [0-9.]+|WCE;",
            "WindowsPhoneOS": "Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;",
            "iOS": "\\biPhone.*Mobile|\\biPod|\\biPad|AppleCoreMedia",
            "iPadOS": "CPU OS 13",
            "SailfishOS": "Sailfish",
            "MeeGoOS": "MeeGo",
            "MaemoOS": "Maemo",
            "JavaOS": "J2ME\/|\\bMIDP\\b|\\bCLDC\\b",
            "webOS": "webOS|hpwOS",
            "badaOS": "\\bBada\\b",
            "BREWOS": "BREW",
            "HarmonyOS": "HarmonyOS"
        }
    }
}

================================================
FILE: README-EXAMPLES.md
================================================
# MobileDetect Usage Examples

This document provides code examples for common MobileDetect usage scenarios.

## Basic Usage

### Installation

```bash
composer require mobiledetect/mobiledetectlib
```

### Simple Detection

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

if ($detect->isMobile()) {
    // Any mobile device (phones or tablets)
}

if ($detect->isTablet()) {
    // Tablets only
}

if ($detect->isMobile() && !$detect->isTablet()) {
    // Phones only
}
```

### Detect Specific Devices

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

// Detect specific platforms
if ($detect->isiOS()) {
    // iOS device
}

if ($detect->isAndroidOS()) {
    // Android device
}

// Detect specific devices
if ($detect->isiPhone()) {
    // iPhone
}

if ($detect->isiPad()) {
    // iPad
}

if ($detect->isSamsung()) {
    // Samsung device
}

if ($detect->isSamsungTablet()) {
    // Samsung tablet
}
```

### Detect Browsers

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

if ($detect->isChrome()) {
    // Chrome browser
}

if ($detect->isSafari()) {
    // Safari browser
}

if ($detect->isFirefox()) {
    // Firefox browser
}

if ($detect->isOpera()) {
    // Opera browser
}

if ($detect->isEdge()) {
    // Edge browser
}
```

### Get Version Information

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

// Get version as string
$iOSVersion = $detect->version('iOS');  // e.g., "15_0"

// Get version as float
$iOSVersion = $detect->version('iOS', 'float');  // e.g., 15.0

// Get browser versions
$chromeVersion = $detect->version('Chrome');
$safariVersion = $detect->version('Safari');
```

## Advanced Usage

### Manual User-Agent Setting

```php
use Detection\MobileDetect;

// Disable auto-initialization for better performance
$detect = new MobileDetect(null, ['autoInitOfHttpHeaders' => false]);

// Set User-Agent manually
$detect->setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...');

if ($detect->isMobile()) {
    // Handle mobile
}
```

### Custom HTTP Headers

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

// Set custom headers (useful for proxy/CDN scenarios)
$detect->setHttpHeaders([
    'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...',
    'HTTP_ACCEPT' => 'text/html,application/xhtml+xml...',
]);
```

### Using the `is()` Method

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

// Generic check using rule name
$detect->is('iOS');        // Same as $detect->isiOS()
$detect->is('iPhone');     // Same as $detect->isiPhone()
$detect->is('Chrome');     // Same as $detect->isChrome()
$detect->is('mobile');     // Same as $detect->isMobile()
$detect->is('tablet');     // Same as $detect->isTablet()
```

### Custom Cache Implementation

```php
use Detection\MobileDetect;
use Psr\SimpleCache\CacheInterface;

// Use any PSR-16 compatible cache
$redisCache = new YourRedisCacheAdapter();

$detect = new MobileDetect($redisCache);
```

### Custom Cache Key Function

```php
use Detection\MobileDetect;

// Custom cache key with salt
$detect = new MobileDetect(null, [
    'cacheKeyFn' => fn($key) => sha1($key . 'my-salt'),
]);

// Or use a different hashing algorithm
$detect = new MobileDetect(null, [
    'cacheKeyFn' => fn($key) => md5($key),
]);
```

### Custom Cache TTL

```php
use Detection\MobileDetect;
use DateInterval;

// TTL as integer (seconds)
$detect = new MobileDetect(null, [
    'cacheTtl' => 3600,  // 1 hour
]);

// TTL as DateInterval
$detect = new MobileDetect(null, [
    'cacheTtl' => new DateInterval('PT2H'),  // 2 hours
]);
```

## Long-Running Processes

When using MobileDetect in CLI scripts, workers, or daemons that process many different User-Agents, you should periodically clean up expired cache entries to prevent memory growth.

### Worker Example

```php
use Detection\MobileDetect;
use Detection\Cache\Cache;

$detect = new MobileDetect();
$cache = $detect->getCache();

$iterationCount = 0;

while ($userAgent = getNextUserAgentFromQueue()) {
    $detect->setUserAgent($userAgent);

    $isMobile = $detect->isMobile();
    $isTablet = $detect->isTablet();

    // Process the result...
    processDevice($userAgent, $isMobile, $isTablet);

    $iterationCount++;

    // Periodically clean up expired cache entries
    if ($iterationCount % 1000 === 0 && $cache instanceof Cache) {
        $evicted = $cache->evictExpired();
        echo "Evicted $evicted expired cache entries\n";
    }
}
```

### Batch Processing Example

```php
use Detection\MobileDetect;
use Detection\Cache\Cache;

$detect = new MobileDetect();

// Process a large batch of User-Agents
$userAgents = file('user-agents.txt', FILE_IGNORE_NEW_LINES);

foreach ($userAgents as $index => $ua) {
    $detect->setUserAgent($ua);

    $results[] = [
        'ua' => $ua,
        'mobile' => $detect->isMobile(),
        'tablet' => $detect->isTablet(),
    ];
}

// Clean up after batch processing
$cache = $detect->getCache();
if ($cache instanceof Cache) {
    $cache->evictExpired();
    // Or clear entirely if you're done
    $cache->clear();
}
```

## Framework Integration

### Laravel Middleware Example

```php
namespace App\Http\Middleware;

use Closure;
use Detection\MobileDetect;
use Illuminate\Http\Request;

class DetectMobileDevice
{
    public function handle(Request $request, Closure $next)
    {
        $detect = new MobileDetect();

        $request->attributes->set('is_mobile', $detect->isMobile());
        $request->attributes->set('is_tablet', $detect->isTablet());

        return $next($request);
    }
}
```

### Symfony Service Example

```php
// config/services.yaml
services:
    Detection\MobileDetect:
        public: true
```

```php
// In a controller
use Detection\MobileDetect;

class MyController
{
    public function index(MobileDetect $detect)
    {
        if ($detect->isMobile()) {
            return $this->render('mobile/index.html.twig');
        }

        return $this->render('desktop/index.html.twig');
    }
}
```

## CloudFront Integration

MobileDetect automatically recognizes Amazon CloudFront headers for device detection.

```php
use Detection\MobileDetect;

// When behind CloudFront with device detection enabled,
// these headers are automatically used:
// - HTTP_CLOUDFRONT_IS_MOBILE_VIEWER
// - HTTP_CLOUDFRONT_IS_TABLET_VIEWER
// - HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER

$detect = new MobileDetect();

// Works automatically when CloudFront headers are present
if ($detect->isMobile()) {
    // Mobile device detected via CloudFront
}
```

## Debugging

### Get Matching Information

```php
use Detection\MobileDetect;

$detect = new MobileDetect();
$detect->setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...');

$detect->isMobile();

// Get the regex that matched
$matchingRegex = $detect->getMatchingRegex();

// Get the matches array
$matches = $detect->getMatchesArray();
```

### Access Cache Directly

```php
use Detection\MobileDetect;

$detect = new MobileDetect();

// Get the cache instance
$cache = $detect->getCache();

// Check cached keys (for debugging)
if ($cache instanceof \Detection\Cache\Cache) {
    $keys = $cache->getKeys();
    print_r($keys);
}
```


================================================
FILE: README.md
================================================
![Mobile Detect](http://demo.mobiledetect.net/logo-github.png)

MobileDetect, PHP mobile detection class
========================================

![Workflow status](https://img.shields.io/github/actions/workflow/status/serbanghita/Mobile-Detect/4.8.x-test.yml?style=flat-square)
![Latest tag](https://img.shields.io/github/v/tag/serbanghita/Mobile-Detect?filter=4.*&style=flat-square)
![Monthly Downloads](https://img.shields.io/packagist/dm/mobiledetect/mobiledetectlib?style=flat-square&label=installs)
![Total Downloads](https://img.shields.io/packagist/dt/mobiledetect/mobiledetectlib?style=flat-square&label=installs)
![MIT License](https://img.shields.io/packagist/l/mobiledetect/mobiledetectlib?style=flat-square)

Mobile Detect is a lightweight PHP class for detecting mobile devices (including tablets).
It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.

## Before you install

There are three versions of MobileDetect. 
`4.8.x` is the main version that is ALWAYS going to be updated first.

| Version | Tests                                                                                                                                                                                                 | Namespace | Code                                                             | PHP Version | Status               |
|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|------------------------------------------------------------------|-------------|----------------------|
| 2.8.x   | [![5x](https://img.shields.io/github/actions/workflow/status/serbanghita/Mobile-Detect/2.8.x-test.yml?style=flat-square)](https://github.com/serbanghita/Mobile-Detect/actions/workflows/test.yml)  | `\Mobile_Detect`       | [2.8](https://github.com/serbanghita/Mobile-Detect/tree/2.8.x)   | \>=5.0,<7.0 | Deprecated           |
| 3.74.x  | [![7x](https://img.shields.io/github/actions/workflow/status/serbanghita/Mobile-Detect/3.74.x-test.yml?style=flat-square)](https://github.com/serbanghita/Mobile-Detect/actions/workflows/test.yml) | `Detection\MobileDetect`        | [3.74](https://github.com/serbanghita/Mobile-Detect/tree/3.74.x) | \>=7.4,<8.0 | LTS                  |
| 4.8.x   | [![7x](https://img.shields.io/github/actions/workflow/status/serbanghita/Mobile-Detect/4.8.x-test.yml?style=flat-square)](https://github.com/serbanghita/Mobile-Detect/actions/workflows/test.yml)  | `Detection\MobileDetect`        | [4.8](https://github.com/serbanghita/Mobile-Detect/tree/4.8.x)   | \>=8.0      | Current, **Recommended** |

## 🤝 Supporting

If you are using Mobile Detect open-source package in your production apps, in presentation demos, hobby projects, 
school projects or so, you can sponsor my work by [donating a small amount :+1:](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mobiledetectlib%40gmail%2ecom&lc=US&item_name=Mobile%20Detect&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted).  

I'm currently paying for domains, hosting and spend a lot of my family time to maintain the project and planning the future 
releases. I would highly appreciate any money donations.

Special thanks to:

* the community :+1: for donations, submitting patches and issues
* [Gitbook](https://www.gitbook.com/) team for the open-source license for their technical documentation tool.


## 📃 Documentation

The entire documentation is available on Gitbook: [https://docs.mobiledetect.net](https://docs.mobiledetect.net)

## 👾 Demo

Point your device to:
[https://demo.mobiledetect.net](https://demo.mobiledetect.net)

## 🐛 Testing

``` bash
vendor/bin/phpunit -v -c tests/phpunit.xml --coverage-html .coverage
```

## 🤝 Contributing

Please see the [Contribute guide](https://mobile-detect.gitbook.io/home/contribute) for details.

## 🔒  Security

If you discover any security related issues, please email serbanghita@gmail.com instead of using the issue tracker.

## 🎉 Credits

- [Serban Ghita](https://github.com/serbanghita)
- [All Contributors](https://mobile-detect.gitbook.io/home/credits)


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported          |
|---------| ------------------ |
| 2.8.x   | :white_check_mark: |
| 3.74.x  | :white_check_mark: |
| 4.8.x   | :white_check_mark: |

## Reporting a Vulnerability

Please report all vulnerabilities to Serban Ghita `<serbanghita AT gmail DOT com>`. \
The usual response time is 1 week for lower impact security reports.

Also add an issue with explanation and links in order to keep track of the fix.

Thank you!


================================================
FILE: composer.json
================================================
{
    "name": "mobiledetect/mobiledetectlib",
    "type": "library",
    "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.",
    "keywords": ["mobile", "mobile detect", "mobile detector", "php mobile detect", "detect mobile devices"],
    "homepage": "https://github.com/serbanghita/Mobile-Detect",
    "license": "MIT",
    "authors": [
        {
            "name": "Serban Ghita",
            "email": "serbanghita@gmail.com",
            "homepage": "http://mobiledetect.net",
            "role": "Developer"
        }
    ],
    "require": {
        "php": ">=8.0",
        "psr/simple-cache": "^3"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^v3.75.0",
        "phpunit/phpunit": "^9.6.22",
        "squizlabs/php_codesniffer": "^3.12.1",
        "phpbench/phpbench": "^1.2",
        "phpstan/phpstan": "^2.1.11"
    },
    "autoload": {
        "psr-4": {
            "Detection\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "DetectionTests\\": "tests/"
        }
    },
    "archive": {
      "exclude": ["scripts"]
    }
}


================================================
FILE: docker/Dockerfile.setup
================================================
FROM composer:latest AS build
WORKDIR /app
COPY . .
COPY ./docker/build.sh .
SHELL ["/bin/bash", "-c"]
RUN chmod +x build.sh
CMD ["./build.sh"]


================================================
FILE: docker/build.sh
================================================
echo "Start building ..."
rm -rf vendor/*
rm -f composer.lock composer.phar
set -xe
# Install composer with dev dependencies so we can run tests.
# Compose installs by default the dev dependencies.
composer install


================================================
FILE: docker-compose.yml
================================================
services:
  setup:
    build:
      context: .
      dockerfile: ./docker/Dockerfile.setup
    platform: linux/amd64
    volumes:
      - ./vendor:/app/vendor

  # Example: docker compose -p mobile-detect up --build runUnitTests
  runUnitTests:
    # Need xdebug from this image to run with coverage
    # https://hub.docker.com/r/alcohol/php/tags
    image: alcohol/php:8.4-xdebug
    platform: linux/amd64
    depends_on:
      setup:
        condition: service_completed_successfully
    working_dir: /app
    environment:
      XDEBUG_MODE: coverage
    command: >
      /bin/sh -c "vendor/bin/phpunit -v -c tests/phpunit.xml --coverage-html .coverage --strict-coverage --stop-on-risky"
    volumes:
      - .:/app

  runPerfTests:
    image: php:8.4-alpine
    platform: linux/amd64
    depends_on:
      setup:
        condition: service_completed_successfully
    working_dir: /app
    command: >
      /bin/sh -c "vendor/bin/phpbench run tests/benchmark/MobileDetectBench.php --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate"
    volumes:
      - .:/app

  runLinting:
    image: php:8.4-alpine
    platform: linux/amd64
    depends_on:
      setup:
        condition: service_completed_successfully
    working_dir: /app
    command: >
      /bin/sh -c "vendor/bin/phpcs; vendor/bin/phpcbf"
    volumes:
      - .:/app

  runQualityCheck:
    image: php:8.4-alpine
    platform: linux/amd64
    depends_on:
      setup:
        condition: service_completed_successfully
    working_dir: /app
    command: >
      /bin/sh -c "vendor/bin/phpstan analyse --debug --memory-limit=1G --level 3 src tests"
    volumes:
      - .:/app

  # Pre-release validation gate - runs all checks
  # Usage: docker compose -p mobile-detect up --build runAll
  runAll:
    image: php:8.4-alpine
    platform: linux/amd64
    depends_on:
      runLinting:
        condition: service_completed_successfully
      runQualityCheck:
        condition: service_completed_successfully
      runUnitTests:
        condition: service_completed_successfully
      runPerfTests:
        condition: service_completed_successfully
    command: >
      /bin/sh -c "echo '✅ All pre-release checks passed!'"

  generateJsonModel:
    image: php:8.4-alpine
    platform: linux/amd64
    depends_on:
      runAll:
        condition: service_completed_successfully
    working_dir: /app
    command: >
      /bin/sh -c "php ./scripts/export_to_json.php"
    volumes:
      - .:/app




================================================
FILE: phpbench.json
================================================
{
  "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json",
  "runner.bootstrap": "vendor/autoload.php"
}


================================================
FILE: scripts/dump_magic_methods.php
================================================
<?php

declare(strict_types=1);

use Detection\MobileDetect;

require __DIR__ . '/../vendor/autoload.php';

$detect = new MobileDetect();

/**
 * Dump all methods (+ extended)
 * Use this script to generate comments like "@method bool isiPhone()"
 * php export/dump_magic_methods.php > methods.txt
 */
foreach ($detect->getRules() as $name => $regex) {
    echo "is$name()\n";
}


================================================
FILE: scripts/example.php
================================================
<?php
/**
 * Example using composer's autoloader.
 */

use Detection\MobileDetect;

require_once  __DIR__ . '/../vendor/autoload.php';

$detect = new MobileDetect();
// This is optional. We scan for known $_SERVER variables.
// See: https://github.com/serbanghita/Mobile-Detect/issues/948#issuecomment-1800271108
$detect->setUserAgent('Mozilla/5.0 (iPad; CPU OS 14_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/248.1.504392274 Mobile/15E148 Safari/604.1');

$isMobile = false;
try {
    $isMobile = $detect->isMobile();

} catch (\Detection\Exception\MobileDetectException $e) {
}

var_dump($isMobile);

$isTablet = false;
try {
    $isTablet = $detect->isTablet();
    var_dump($isTablet);
} catch (\Detection\Exception\MobileDetectException $e) {
}

var_dump($isTablet);





================================================
FILE: scripts/export_to_json.php
================================================
<?php
/**
 * Mobile Detect Library
 * - export -
 * =====================
 *
 * Use the resulting JSON export file in other languages
 * other than PHP. Always check for 'version' key because
 * new major versions can modify the structure of the JSON file.
 *
 * The result of running this script is the export.json file.
 *
 * @license     Code and contributions have 'MIT License'
 *              More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt
 *
 */

declare(strict_types=1);

use Detection\MobileDetect;

require __DIR__ . '/../vendor/autoload.php';

$detect = new MobileDetect();

$json = [
    // The current version of Mobile Detect class that
    // is being exported.
    'version' => $detect->getVersion(),

    // All headers that trigger 'isMobile' to be 'true',
    // before reaching the User-Agent match detection.
    'headerMatch' => $detect->getMobileHeaders(),

    // All possible User-Agent headers.
    'uaHttpHeaders' => $detect->getUaHttpHeaders(),

    'cloudFrontHttpHeaders' => $detect->getCloudFrontHttpHeaders(),

    // All the regexes that trigger 'isMobile' or 'isTablet'
    // to be true.
    'uaMatch' => [
        // If match is found, triggers 'isMobile' to be true.
        'phones'   => $detect->getPhoneDevices(),
        // Triggers 'isTablet' to be true.
        'tablets'  => $detect->getTabletDevices(),
        // If match is found, triggers 'isMobile' to be true.
        'browsers' => $detect->getBrowsers(),
        // If match is found, triggers 'isMobile' to be true.
        'os'       => $detect->getOperatingSystems()
    ]
];
$fileName = dirname(__FILE__) . '/../MobileDetect.json';
// Write the JSON file to disk.
// You can import this file in your app.
if (
    file_put_contents($fileName, json_encode($json, JSON_PRETTY_PRINT))
) {
    echo "Done exporting version ". $detect->getVersion() ." to JSON.\nCheck the output at: " . realpath($fileName);
} else {
    echo "Failed to write to disk: " . realpath($fileName) ;
}


================================================
FILE: scripts/pre-commit-hook.sh
================================================
#!/bin/sh

# Linting
vendor/bin/phpcs
vendor/bin/phpcbf

# Unit tests
vendor/bin/phpunit -v -c tests/phpunit.xml --coverage-html .coverage

# Performance tests
vendor/bin/phpbench run tests/Benchmark/MobileDetectBench.php --ref=baseline --retry-threshold=1 --iterations=10 --revs=1000 --report=aggregate


================================================
FILE: scripts/test.php
================================================
<?php

use Detection\Exception\MobileDetectException;
use Detection\MobileDetect;

require __DIR__ . '/../vendor/autoload.php';

$detect = new MobileDetect();
$detect->setUserAgent(' Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; SHV-E160K/VI10.1802 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');
//$detect->setHttpHeaders(array(
//    'HTTP_X_WAP_PROFILE' => '',
//    'HTTP_USER_AGENT'       => 'Mozilla/5.0 (Linux; U; Android 4.1.1; cs-cz; HUAWEI G510-0200 Build/HuaweiG510-0200) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
//));
try {
    var_dump($detect->isMobile());
    var_dump($detect->isMobile());
} catch (MobileDetectException $e) {
    var_dump($e);
}
try {
    var_dump($detect->isTablet());
} catch (MobileDetectException $e) {
    var_dump($e);
}
var_dump($detect->getMatchesArray());
////var_dump($detect->version('IE'));



/*********************************
 *
 * Dump all methods (+ extended)
 *
 ********************************/
//foreach ($detect->getRules() as $name => $regex) {
//    echo "is$name()\n";
//}


================================================
FILE: scripts/test_standalone.php
================================================
<?php
use Detection\Exception\MobileDetectException;
use Detection\MobileDetectStandalone;

require_once '../standalone/autoloader.php';
require_once '../src/MobileDetectStandalone.php';

$detection = new MobileDetectStandalone();
$detection->setUserAgent('iPad');

try {
    var_dump($detection);
    var_dump($detection->isMobile());
    var_dump($detection->isTablet());
} catch (MobileDetectException $e) {
    print_r($e);
}


================================================
FILE: src/Cache/Cache.php
================================================
<?php

declare(strict_types=1);

namespace Detection\Cache;

use Psr\SimpleCache\CacheInterface;
use DateInterval;
use DateTime;

use function is_int;
use function time;

/**
 * In-memory cache implementation of PSR-16
 * @See https://www.php-fig.org/psr/psr-16/
 */
class Cache implements CacheInterface
{
    protected array $cache = [];

    /**
     * @inheritdoc
     * @throws CacheInvalidArgumentException
     */
    public function get(string $key, mixed $default = null): mixed
    {
        $this->checkKey($key);

        if (isset($this->cache[$key])) {
            if ($this->cache[$key]['ttl'] === null || $this->cache[$key]['ttl'] > time()) {
                return $this->cache[$key]['content'];
            }

            $this->deleteSingle($key);
        }

        return $default;
    }

    /**
     * @inheritdoc
     * @throws CacheInvalidArgumentException
     */
    public function set(string $key, mixed $value, int|DateInterval|null $ttl = null): bool
    {
        $this->checkKey($key);

        // From https://www.php-fig.org/psr/psr-16/ "Definitions" -> "Expiration"
        // If a negative or zero TTL is provided, the item MUST be deleted from the cache if it exists, as it is expired already.
        if (is_int($ttl) && $ttl <= 0) {
            $this->deleteSingle($key);
            return false;
        }

        $ttl = $this->getTTL($ttl);

        if ($ttl !== null) {
            $ttl = (time() + $ttl);
        }

        $this->cache[$key] = ['ttl' => $ttl, 'content' => $value];

        return true;
    }

    /** @inheritdoc */
    public function delete(string $key): bool
    {
        $this->checkKey($key);
        $this->deleteSingle($key);

        return true;
    }

    /**
     * Deletes the cache item from memory.
     *
     * @param string $key Cache key
     * @return void
     */
    private function deleteSingle(string $key): void
    {
        unset($this->cache[$key]);
    }

    /** @inheritdoc */
    public function clear(): bool
    {
        $this->cache = [];

        return true;
    }

    /**
     * @inheritdoc
     * @throws CacheInvalidArgumentException
     */
    public function has(string $key): bool
    {
        $this->checkKey($key);

        if (isset($this->cache[$key])) {
            if ($this->cache[$key]['ttl'] === null || $this->cache[$key]['ttl'] > time()) {
                return true;
            }

            $this->deleteSingle($key);
        }

        return false;
    }

    /** @inheritdoc */
    public function getMultiple(iterable $keys, mixed $default = null): iterable
    {
        $data = [];

        foreach ($keys as $key) {
            $data[$key] = $this->get($key, $default);
        }

        return $data;
    }

    /** @inheritdoc */
    public function setMultiple(iterable $values, int|DateInterval|null $ttl = null): bool
    {
        $return = [];

        foreach ($values as $key => $value) {
            $return[] = $this->set($key, $value, $ttl);
        }

        return $this->checkReturn($return);
    }

    /** @inheritdoc */
    public function deleteMultiple(iterable $keys): bool
    {
        foreach ($keys as $key) {
            $this->delete($key);
        }

        return true;
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    protected function checkKey(string $key): string
    {

        if ($key === '' || !preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key)) {
            throw new CacheInvalidArgumentException("Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.");
        }

        return $key;
    }

    /**  */
    protected function getTTL(DateInterval|int|null $ttl): ?int
    {

        if ($ttl instanceof DateInterval) {
            return (new DateTime())->add($ttl)->getTimestamp() - time();
        }

        // We treat 0 as a valid value.
        if (is_int($ttl)) {
            return $ttl;
        }

        return null;
    }

    /**
     * @param bool[]|int[] $booleans
     */
    protected function checkReturn(array $booleans): bool
    {
        foreach ($booleans as $boolean) {
            if (!$boolean) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get all cache keys.
     *
     * @internal Needed for testing purposes.
     * @return array{string}
     */
    public function getKeys(): array
    {
        return array_keys($this->cache);
    }

    /**
     * Evict all expired items from the cache.
     *
     * Useful for long-running processes (CLI scripts, workers, daemons)
     * to periodically clean up expired entries and free memory.
     *
     * @return int Number of items evicted
     */
    public function evictExpired(): int
    {
        $evicted = 0;
        $now = time();

        foreach ($this->cache as $key => $item) {
            if ($item['ttl'] !== null && $item['ttl'] <= $now) {
                unset($this->cache[$key]);
                $evicted++;
            }
        }

        return $evicted;
    }
}


================================================
FILE: src/Cache/CacheException.php
================================================
<?php

declare(strict_types=1);

namespace Detection\Cache;

class CacheException extends \Exception implements \Psr\SimpleCache\CacheException
{
}


================================================
FILE: src/Cache/CacheInvalidArgumentException.php
================================================
<?php

declare(strict_types=1);

namespace Detection\Cache;

use Psr\SimpleCache\InvalidArgumentException;

class CacheInvalidArgumentException extends CacheException implements InvalidArgumentException
{
}


================================================
FILE: src/Exception/MobileDetectException.php
================================================
<?php

declare(strict_types=1);

namespace Detection\Exception;

class MobileDetectException extends \Exception
{
}


================================================
FILE: src/Exception/MobileDetectExceptionCode.php
================================================
<?php

declare(strict_types=1);

namespace Detection\Exception;

class MobileDetectExceptionCode
{
    public const INVALID_USER_AGENT_ERR = 0x1;
    public const IS_MOBILE_ERR = 0x2;
    public const IS_TABLET_ERR = 0x3;
    public const IS_MAGIC_ERR = 0x4;
}


================================================
FILE: src/MobileDetect.php
================================================
<?php

/**
 * Mobile Detect Library
 * Motto: "Every business should have a mobile detection script to detect mobile readers"
 *
 * Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
 * It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
 *
 * Homepage: http://mobiledetect.net
 * GitHub: https://github.com/serbanghita/Mobile-Detect
 * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
 * CONTRIBUTING: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/CONTRIBUTING.md
 * KNOWN LIMITATIONS: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/KNOWN_LIMITATIONS.md
 * EXAMPLES: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
 *
 * @license https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE
 * @author  Serban Ghita <serbanghita@gmail.com> (since 2012)
 * @author  Nick Ilyin <nick.ilyin@gmail.com>
 * @author: Victor Stanciu <vic.stanciu@gmail.com> (original author)
 *
 * @version 4.8.10
 */

declare(strict_types=1);

namespace Detection;

use BadMethodCallException;
use Detection\Cache\Cache;
use Detection\Cache\CacheException;
use Detection\Cache\CacheInvalidArgumentException;
use Detection\Exception\MobileDetectException;
use Detection\Exception\MobileDetectExceptionCode;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException as PsrInvalidArgumentException;

/**
 * Auto-generated isXXXX() magic methods.
 * php export/dump_magic_methods.php
 *
 * @method bool isiPhone()
 * @method bool isBlackBerry()
 * @method bool isPixel()
 * @method bool isHTC()
 * @method bool isNexus()
 * @method bool isDell()
 * @method bool isMotorola()
 * @method bool isSamsung()
 * @method bool isLG()
 * @method bool isSony()
 * @method bool isAsus()
 * @method bool isXiaomi()
 * @method bool isNokiaLumia()
 * @method bool isMicromax()
 * @method bool isPalm()
 * @method bool isVertu()
 * @method bool isPantech()
 * @method bool isFly()
 * @method bool isWiko()
 * @method bool isiMobile()
 * @method bool isSimValley()
 * @method bool isWolfgang()
 * @method bool isAlcatel()
 * @method bool isNintendo()
 * @method bool isAmoi()
 * @method bool isINQ()
 * @method bool isOnePlus()
 * @method bool isGenericPhone()
 * @method bool isHuawei()
 * @method bool isiPad()
 * @method bool isNexusTablet()
 * @method bool isGoogleTablet()
 * @method bool isSamsungTablet()
 * @method bool isKindle()
 * @method bool isSurfaceTablet()
 * @method bool isHPTablet()
 * @method bool isAsusTablet()
 * @method bool isBlackBerryTablet()
 * @method bool isHTCtablet()
 * @method bool isMotorolaTablet()
 * @method bool isNookTablet()
 * @method bool isAcerTablet()
 * @method bool isToshibaTablet()
 * @method bool isLGTablet()
 * @method bool isFujitsuTablet()
 * @method bool isPrestigioTablet()
 * @method bool isLenovoTablet()
 * @method bool isDellTablet()
 * @method bool isYarvikTablet()
 * @method bool isMedionTablet()
 * @method bool isArnovaTablet()
 * @method bool isIntensoTablet()
 * @method bool isIRUTablet()
 * @method bool isMegafonTablet()
 * @method bool isEbodaTablet()
 * @method bool isAllViewTablet()
 * @method bool isArchosTablet()
 * @method bool isAinolTablet()
 * @method bool isNokiaLumiaTablet()
 * @method bool isSonyTablet()
 * @method bool isPhilipsTablet()
 * @method bool isCubeTablet()
 * @method bool isCobyTablet()
 * @method bool isMIDTablet()
 * @method bool isMSITablet()
 * @method bool isSMiTTablet()
 * @method bool isRockChipTablet()
 * @method bool isFlyTablet()
 * @method bool isbqTablet()
 * @method bool isHuaweiTablet()
 * @method bool isNecTablet()
 * @method bool isPantechTablet()
 * @method bool isBronchoTablet()
 * @method bool isVersusTablet()
 * @method bool isZyncTablet()
 * @method bool isPositivoTablet()
 * @method bool isNabiTablet()
 * @method bool isKoboTablet()
 * @method bool isDanewTablet()
 * @method bool isTexetTablet()
 * @method bool isPlaystationTablet()
 * @method bool isTrekstorTablet()
 * @method bool isPyleAudioTablet()
 * @method bool isAdvanTablet()
 * @method bool isDanyTechTablet()
 * @method bool isGalapadTablet()
 * @method bool isMicromaxTablet()
 * @method bool isKarbonnTablet()
 * @method bool isAllFineTablet()
 * @method bool isPROSCANTablet()
 * @method bool isYONESTablet()
 * @method bool isChangJiaTablet()
 * @method bool isGUTablet()
 * @method bool isPointOfViewTablet()
 * @method bool isOvermaxTablet()
 * @method bool isHCLTablet()
 * @method bool isDPSTablet()
 * @method bool isVistureTablet()
 * @method bool isCrestaTablet()
 * @method bool isMediatekTablet()
 * @method bool isConcordeTablet()
 * @method bool isGoCleverTablet()
 * @method bool isModecomTablet()
 * @method bool isVoninoTablet()
 * @method bool isECSTablet()
 * @method bool isStorexTablet()
 * @method bool isVodafoneTablet()
 * @method bool isEssentielBTablet()
 * @method bool isRossMoorTablet()
 * @method bool isiMobileTablet()
 * @method bool isTolinoTablet()
 * @method bool isAudioSonicTablet()
 * @method bool isAMPETablet()
 * @method bool isSkkTablet()
 * @method bool isTecnoTablet()
 * @method bool isJXDTablet()
 * @method bool isiJoyTablet()
 * @method bool isFX2Tablet()
 * @method bool isXoroTablet()
 * @method bool isViewsonicTablet()
 * @method bool isVerizonTablet()
 * @method bool isOdysTablet()
 * @method bool isCaptivaTablet()
 * @method bool isIconbitTablet()
 * @method bool isTeclastTablet()
 * @method bool isOndaTablet()
 * @method bool isJaytechTablet()
 * @method bool isBlaupunktTablet()
 * @method bool isDigmaTablet()
 * @method bool isEvolioTablet()
 * @method bool isLavaTablet()
 * @method bool isAocTablet()
 * @method bool isMpmanTablet()
 * @method bool isCelkonTablet()
 * @method bool isWolderTablet()
 * @method bool isMediacomTablet()
 * @method bool isMiTablet()
 * @method bool isNibiruTablet()
 * @method bool isNexoTablet()
 * @method bool isLeaderTablet()
 * @method bool isUbislateTablet()
 * @method bool isPocketBookTablet()
 * @method bool isKocasoTablet()
 * @method bool isHisenseTablet()
 * @method bool isHudl()
 * @method bool isTelstraTablet()
 * @method bool isGenericTablet()
 * @method bool isAndroidOS()
 * @method bool isBlackBerryOS()
 * @method bool isPalmOS()
 * @method bool isSymbianOS()
 * @method bool isWindowsMobileOS()
 * @method bool isWindowsPhoneOS()
 * @method bool isiOS()
 * @method bool isiPadOS()
 * @method bool isSailfishOS()
 * @method bool isMeeGoOS()
 * @method bool isMaemoOS()
 * @method bool isJavaOS()
 * @method bool iswebOS()
 * @method bool isbadaOS()
 * @method bool isBREWOS()
 * @method bool isHarmonyOS()
 * @method bool isChrome()
 * @method bool isDolfin()
 * @method bool isOpera()
 * @method bool isSkyfire()
 * @method bool isEdge()
 * @method bool isIE()
 * @method bool isFirefox()
 * @method bool isBolt()
 * @method bool isTeaShark()
 * @method bool isBlazer()
 * @method bool isSafari()
 * @method bool isWeChat()
 * @method bool isUCBrowser()
 * @method bool isbaiduboxapp()
 * @method bool isbaidubrowser()
 * @method bool isDiigoBrowser()
 * @method bool isMercury()
 * @method bool isObigoBrowser()
 * @method bool isNetFront()
 * @method bool isGenericBrowser()
 * @method bool isPaleMoon()
 * @method bool isHuaweiBrowser()
 * @method bool isWebKit()
 * @method bool isConsole()
 * @method bool isWatch()
 */
class MobileDetect
{
    /**
     * A cache for resolved matches
     *  Implementation of PSR-16: Common Interface for Caching Libraries
     *  https://www.php-fig.org/psr/psr-16/
     *
     * Replace this with your own implementation.
     */
    protected CacheInterface $cache;

    /**
     * Stores the version number of the current release.
     */
    protected string $VERSION = '4.8.10';

    protected array $config = [
        // Auto-initialization on HTTP headers from $_SERVER['HTTP...']
        // Disable this if you're going for performance and set the
        // User-Agent via $detect->setUserAgent("...").
        // @var boolean
        'autoInitOfHttpHeaders' => true,
        // Maximum HTTP User-Agent value allowed.
        // @var int
        'maximumUserAgentLength' => 500,
        // Function that creates the cache key. e.g. (base64, sha1, custom fn).
        // Note: used to be base64 but we went with sha1 because of fixed length.
        'cacheKeyFn' => 'sha1',
        // Cache TTL
        // @var null|int|\DateInterval
        'cacheTtl' => 86400,
    ];

    /**
     * A frequently used regular expression to extract version #s.
     */
    protected const VERSION_REGEX = '([\w._\+]+)';

    /**
     * A type for the version() method indicating a string return value.
     */
    private const VERSION_TYPE_STRING = 'text';

    /**
     * A type for the version() method indicating a float return value.
     */
    private const VERSION_TYPE_FLOAT = 'float';

    /**
     * The User-Agent HTTP header is stored in here.
     * @var string|null
     */
    protected ?string $userAgent = null;

    /**
     * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
     * @var array
     */
    protected array $httpHeaders = [];

    /**
     * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer.
     * @var array
     */
    protected static array $knownCloudFrontHeaders = [
        'HTTP_CLOUDFRONT_IS_MOBILE_VIEWER',
        'HTTP_CLOUDFRONT_IS_TABLET_VIEWER',
        'HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'
    ];

    protected static string $cloudFrontUA = 'Amazon CloudFront';

    /**
     * The matching regex string. Used only for debugging.
     * @var string
     */
    protected string $matchingRegex = "";

    /**
     * The matches extracted from the regex expression. Used only for debugging.
     * @var array
     */
    protected array $matchesArray = [];

    /**
     * HTTP headers that trigger the 'isMobile' detection to be true.
     * @var array
     */
    protected static array $knownMobilePositiveHeaders = [
        'HTTP_ACCEPT'                  => [
            'matches' => [
                // Opera Mini
                // @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
                'application/x-obml2d',
                // BlackBerry devices.
                'application/vnd.rim.html',
                'text/vnd.wap.wml',
                'application/vnd.wap.xhtml+xml'
            ]],
        'HTTP_X_WAP_PROFILE'           => null,
        'HTTP_X_WAP_CLIENTID'          => null,
        'HTTP_WAP_CONNECTION'          => null,
        'HTTP_PROFILE'                 => null,
        // Reported by Opera on Nokia devices (e.g. C3).
        'HTTP_X_OPERAMINI_PHONE_UA'    => null,
        'HTTP_X_NOKIA_GATEWAY_ID'      => null,
        'HTTP_X_ORANGE_ID'             => null,
        'HTTP_X_VODAFONE_3GPDPCONTEXT' => null,
        'HTTP_X_HUAWEI_USERID'         => null,
        // Reported by Windows Smartphones.
        'HTTP_UA_OS'                   => null,
        // Reported by Verizon, Vodafone proxy system.
        'HTTP_X_MOBILE_GATEWAY'        => null,
        // Seen this on HTC Sensation. SensationXE_Beats_Z715e.
        'HTTP_X_ATT_DEVICEID'          => null,
        // Seen this on a HTC.
        'HTTP_UA_CPU'                  => ['matches' => ['ARM']],
        // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
        // "?1" means that the device wants a "mobile" experience.
        'Sec-CH-UA-Mobile'             => ['matches' => ['?1']],
    ];

    /**
     * List of mobile devices (phones).
     * @var array
     */
    protected static array $phoneDevices = [
        'iPhone'        => '\biPhone\b|\biPod\b', // |\biTunes
        'BlackBerry'    => 'BlackBerry|\bBB10\b|rim[0-9]+|\b(BBA100|BBB100|BBD100|BBE100|BBF100|STH100)\b-[0-9]+',
        'Pixel'         => '; \bPixel\b',
        'HTC'           => [
            'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)',
            'APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200',
            'ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel'
        ],
        'Nexus'         => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 5X|Nexus 6',
        // @todo: Is 'Dell Streak' a tablet or a phone? ;)
        'Dell'          => 'Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b',
        'Motorola'      => [
            'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955',
            'A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611',
            'MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863',
            'ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317',
            'XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800',
            'XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b|XT1068|XT1092|XT1052',
        ],
        'Samsung'       => [
            '\bSamsung\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310',
            'SM-F946B|SM-A127F',
            'SM-S908E|SM-G955N|SM-S918U1|SM-G998B|SM-G970N|SM-G973U|SM-S901U|SM-A515F|SM-S901E|SM-G980F|SM-S901B',
            'GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510',
            'GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K',
            'GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010',
            'GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100',
            'GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210',
            'GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250',
            'GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420',
            'GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700',
            'GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103',
            'GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800',
            'GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210',
            'GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350',
            'GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660',
            'GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230',
            'GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600',
            'SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930',
            'SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730',
            'SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351',
            'SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430',
            'SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740',
            'SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157',
            'SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657',
            'SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817',
            'SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220',
            'SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225',
            'SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105',
            'SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200',
            'SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717',
            'SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917',
            'SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500',
            'SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777',
            'SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229',
            'SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409',
            'SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609',
            'SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749',
            'SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200',
            'SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497',
            'SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10',
            'SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700',
            'SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700',
            'SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220',
            'SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550',
            'SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920',
            'SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535',
            'SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510',
            'GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582',
            'GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K',
            'SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558',
            'GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F|SM-J330F',
            'SM-G610F|SM-G981B|SM-G892A|SM-A530F|SM-G988N|SM-G781B|SM-A805N|SM-G965F',
        ],
        'LG'            => [
            '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200)',
            'LG[- ]?(MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK)',
            'LG[- ]?(E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690)',
            'LG[- ]?(MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740)',
            'LG[- ]?(VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323|M257)|LM-G710',
        ],
        'Sony'          => [
            'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i',
            'C5303|C6902|C6903|C6906|C6943|D2533|SOV34|601SO|F8332',
        ],
        'Asus'          => 'Asus.*Galaxy|PadFone.*Mobile|ASUS_Z01QD|ASUS_X00TD',
        'Xiaomi'        => [
            '^(?!.*\bx11\b).*xiaomi.*$|POCOPHONE F1|\bMI\b 8|\bMi\b 10|Redmi Note 9S|Redmi 5A|Redmi Note 5A Prime|Redmi Note 7 Pro',
            'N2G47H|M2001J2G|M2001J2I|M1805E10A|M2004J11G|M1902F1G|M2002J9G|M2004J19G|M2003J6A1G|M2012K11C|M2007J1SC',
        ],
        'NokiaLumia'    => 'Lumia [0-9]{3,4}',
        // http://www.micromaxinfo.com/mobiles/smartphones
        // Added because the codes might conflict with Acer Tablets.
        'Micromax'  => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b',
        // @todo Complete the regex.
        'Palm'  => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ;
        // Just for fun ;)
        'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature',
        // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH)
        // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android.
        'Pantech'   => [
            'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L',
            'IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S',
            'IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995',
            'IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790',
        ],
        // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones.
        'Fly'   => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250',
        // http://fr.wikomobile.com
        'Wiko'  => [
            'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY',
            'BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM'
        ],
        'iMobile'   => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)',
        // Added simvalley mobile just for fun. They have some interesting devices.
        // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html
        'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b',
         // Wolfgang - a brand that is sold by Aldi supermarkets.
         // http://www.wolfgangmobile.com/
        'Wolfgang'  => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q',
        'Alcatel'   => 'Alcatel',
        'Nintendo'  => 'Nintendo (3DS|Switch)',
        // http://en.wikipedia.org/wiki/Amoi
        'Amoi'  => 'Amoi',
        // http://en.wikipedia.org/wiki/INQ
        'INQ'   => 'INQ',
        'OnePlus'   => 'ONEPLUS|CPH2663',
        // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039
        'GenericPhone'  => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser',
        'Huawei'        => 'HMSCore|Huawei',
    ];

    /**
     * List of tablet devices.
     * @var array
     */
    protected static array $tabletDevices = [
        // @todo: check for mobile friendly emails topic.
        'iPad'              => 'iPad|iPad.*Mobile',
        // Removed |^.*Android.*Nexus(?!(?:Mobile).)*$
        // @see #442
        // @todo Merge NexusTablet into GoogleTablet.
        'NexusTablet'       => 'Android.*Nexus[\s]+(7|9|10)',
        // https://en.wikipedia.org/wiki/Pixel_C
        'GoogleTablet'           => 'Android.*Pixel C',
        'SamsungTablet'     => [
            'SM-X926B|SM-X620|SM-X526B|SM-X520|SM-X626B|SM-X920|SM-X820|SM-X826B|SM-P625|SM-P620|SM-X306B|SM-T730|SM-T976B|SM-T875|SM-T575|SM-T545',
            'SM-X210R|SM-X216R|SM-X356B|SM-T860X|SM-T636B|SM-T509|SM-T503|SM-T720X|SM-T570|SM-T540|SM-T510X|SM-T830X|SM-T820X|SM-T710X|SM-T810X|SM-T365|SM-T550X|SM-T116',
            'SM-X616B|SM-X610|SM-X516B|SM-X910|SM-X916B|SM-X816B|SM-X810|SM-X710|SM-X716B|SM-X510|SM-P619|SM-T225|SM-T225N|SM-T736B|SM-T505|SM-T733|SM-X205|SM-X210|SM-X216B',
            'SM-X700|SM-X706|SM-X706B|SM-X706U|SM-X706N|SM-X800|SM-X806|SM-X806B|SM-X806U|SM-X806N|SM-X900|SM-X906|SM-X906B|SM-X906U|SM-X906N|SM-P613|SM-X110|SM-X115',
            'SM-T970|SM-T380|SM-T5950|SM-T905|SM-T231|SM-T500|SM-T860|SM-T536|SM-T837A|SM-X200|SM-T220|SM-T870|SM-X906C', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone.
            'SM-T815Y|SM-T585|SM-T285|SM-T825|SM-W708|SM-T835|SM-T830|SM-T837V|SM-T720|SM-T510|SM-T387V|SM-P610|SM-T290|SM-T515|SM-T590|SM-T595|SM-T725|SM-T817P|SM-P585N0|SM-T395|SM-T295|SM-T865|SM-P610N|SM-P615',
            'SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU',
            'SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715',
            'SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237',
            'GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X',
            'GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X',
            'SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210',
            'GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L',
            'SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113',
            'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SM-X300|SM-T630',
        ],
        // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
        'Kindle'            => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)',
        // Only the Surface tablets with Windows RT are considered mobile.
        // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
        'SurfaceTablet'     => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)',
        // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT
        'HPTablet'          => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10',
        // Watch out for PadFone, see #132.
        // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/
        'AsusTablet'        => [
            'ME181C|P01Y|PO1MA|P01Z|\bP027\b|\bP024\b|\bP00C\b',
            '\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K01A | K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C',
            '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b',
        ],
        'BlackBerryTablet'  => 'PlayBook|RIM Tablet',
        'HTCtablet'         => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410',
        'MotorolaTablet'    => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617',
        'NookTablet'        => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2',
        // http://www.acer.ro/ac/ro/RO/content/drivers
        // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer)
        // http://us.acer.com/ac/en/US/content/group/tablets
        // http://www.acer.de/ac/de/DE/content/models/tablets/
        // Can conflict with Micromax and Motorola phones codes.
        'AcerTablet'        => [
            'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b',
            'W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30|A3-A40'
        ],
        // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/
        // http://us.toshiba.com/tablets/tablet-finder
        // http://www.toshiba.co.jp/regza/tablet/
        'ToshibaTablet'     => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO',
        // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html
        // http://www.lg.com/us/tablets
        'LGTablet'          => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b',
        'FujitsuTablet'     => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b',
        // Prestigio Tablets http://www.prestigio.com/support
        'PrestigioTablet'   => [
            'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C',
            'PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD',
            'PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002',
        ],
        // http://support.lenovo.com/en_GB/downloads/default.page?#
        'LenovoTablet'      => [
            'TB-X704L|TB-J606F|TB-X606F|TB-X306X|YT-J706X|TB128FU',
            'YT3-X50M|YT-X705F|YT-X703F|YT-X703L|YT-X705L|YT-X705X|TB2-X30F|TB2-X30L|TB2-X30M|A2107A-F|A2107A-H|TB3-730F|TB3-730M|TB3-730X|TB-7504F|TB-7504X|TB-X704F|TB-X104F|TB3-X70F|TB-X705F|TB-8504F|TB3-X70L|TB3-710F',
            'TB-X103F|TB-X304X|TB-X304F|TB-X304L|TB-X505F|TB-X505L|TB-X505X|TB-X605F|TB-X605L|TB-8703F|TB-8703X|TB-8703N|TB-8704N|TB-8704F|TB-8704X|TB-8704V|TB-7304F|TB-7304I|TB-7304X|Tab2A7-10F|Tab2A7-20F|TB2-X30L|YT3-X50L|YT3-X50F',
            'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)',
        ],
        // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets
        'DellTablet'        => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7',
        'XiaomiTablet'      => '21051182G',
        // http://www.yarvik.com/en/matrix/tablets/
        'YarvikTablet'      => [
            'Android.*\b(TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b',
            'Android.*\b(TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211)\b',
            'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152)\b',
        ],
        'MedionTablet'      => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB',
        'ArnovaTablet'      => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2',
        // http://www.intenso.de/kategorie_en.php?kategorie=33
        // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate
        'IntensoTablet'     => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004',
        // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/
        'IRUTablet'         => 'M702pro',
        'MegafonTablet'     => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b',
        // http://www.e-boda.ro/tablete-pc.html
        'EbodaTablet'       => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)',
        // http://www.allview.ro/produse/droseries/lista-tablete-pc/
        'AllViewTablet'           => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)',
        // http://wiki.archosfans.com/index.php?title=Main_Page
        // @note Rewrite the regex format after we add more UAs.
        'ArchosTablet'      => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b',
        // http://www.ainol.com/plugin.php?identifier=ainol&module=product
        'AinolTablet'       => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark',
        'NokiaLumiaTablet'  => 'Lumia 2520',
        // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER
        // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser
        // http://www.sony.jp/support/tablet/
        'SonyTablet'        => [
            'EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP641|SGP612|SOT31|SGP771|SGP611|SGP612|SGP712',
            'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321',
        ],
        // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8
        'PhilipsTablet'     => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b',
        // db + http://www.cube-tablet.com/buy-products.html
        'CubeTablet'        => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT',
        // http://www.cobyusa.com/?p=pcat&pcat_id=3001
        'CobyTablet'        => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010',
        // http://www.match.net.cn/products.asp
        'MIDTablet'         => [
            'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800',
            'MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10',
        ],
        // http://www.msi.com/support
        // @todo Research the Windows Tablets.
        'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b',
        // @todo http://www.kyoceramobile.com/support/drivers/
    //    'KyoceraTablet' => null,
        // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/
    //    'IntextTablet' => null,
        // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets)
        // http://www.imp3.net/14/show.php?itemid=20454
        'SMiTTablet'        => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)',
        // http://www.rock-chips.com/index.php?do=prod&pid=2
        'RockChipTablet'    => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A',
        // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/
        'FlyTablet'         => 'IQ310|Fly Vision',
        // http://www.bqreaders.com/gb/tablets-prices-sale.html
        'bqTablet'          => 'Android.*(bq)?.*\b(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))\b|Maxwell.*Lite|Maxwell.*Plus',
        // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290
        // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets)
        'HuaweiTablet'      => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L|BAH-L09|BAH-W09|AGS-L09|CMR-AL19|KOB2-L09|BG2-U01|BG2-W09|BG2-U03|AGS-W09',
        // Nec or Medias Tab
        'NecTablet'         => '\bN-06D|\bN-08D',
        // Pantech Tablets: http://www.pantechusa.com/phones/
        'PantechTablet'     => 'Pantech.*P4100',
        // Broncho Tablets: http://www.broncho.cn/ (hard to find)
        'BronchoTablet'     => 'Broncho.*(N701|N708|N802|a710)',
        // http://versusuk.com/support.html
        'VersusTablet'      => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b',
        // http://www.zync.in/index.php/our-products/tablet-phablets
        'ZyncTablet'        => 'z1000|Z99 2G|z930|z990|z909|Z919|z900', // Removed "z999" because of https://github.com/serbanghita/Mobile-Detect/issues/717
        // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/
        'PositivoTablet'    => 'TB07STA|TB10STA|TB07FTA|TB10FTA',
        // https://www.nabitablet.com/
        'NabiTablet'        => 'Android.*\bNabi',
        'KoboTablet'        => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build',
        // French Danew Tablets http://www.danew.com/produits-tablette.php
        'DanewTablet'       => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b',
        // Texet Tablets and Readers http://www.texet.ru/tablet/
        'TexetTablet'       => [
            'TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE',
            'TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD',
            'TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A',
            'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020',
        ],
        // Avoid detecting 'PLAYSTATION 3' as mobile.
        'PlaystationTablet' => 'Playstation.*(Portable|Vita)',
        // http://www.trekstor.de/surftabs.html
        'TrekstorTablet'    => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab',
        // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets
        'PyleAudioTablet'   => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b',
        // http://www.advandigital.com/index.php?link=content-product&jns=JP001
        // because of the short codenames we have to include whitespaces to reduce the possible conflicts.
        'AdvanTablet'       => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ',
        // http://www.danytech.com/category/tablet-pc
        'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1',
        // http://www.galapad.net/product.html ; https://github.com/serbanghita/Mobile-Detect/issues/761
        'GalapadTablet'     => 'Android [0-9.]+; [a-z-]+; \bG1\b',
        // http://www.micromaxinfo.com/tablet/funbook
        'MicromaxTablet'    => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b',
        // http://www.karbonnmobiles.com/products_tablet.php
        'KarbonnTablet'     => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b',
        // http://www.myallfine.com/Products.asp
        'AllFineTablet'     => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide',
        // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr=
        'PROSCANTablet'     => [
            '\b(PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b',
            '\b(PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082)\b',
            '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K)\b',
        ],
        // http://www.yonesnav.com/products/products.php
        'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026',
        // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001
        // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html)
        'ChangJiaTablet'    => [
            'TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503',
            'TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205',
            'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106',
        ],
        // http://www.gloryunion.cn/products.asp
        // http://www.allwinnertech.com/en/apply/mobile.html
        // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB)
        // @todo: Softwiner tablets?
        // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions.
        'GUTablet'          => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G
        // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118
        'PointOfViewTablet' => [
            'TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10',
            'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945',
        ],
        // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/
        // @todo: add more tests.
        'OvermaxTablet'     => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027',
        // http://hclmetablet.com/India/index.php
        'HCLTablet'         => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync',
        // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html
        'DPSTablet'         => 'DPS Dream 9|DPS Dual 7',
        // http://www.visture.com/index.asp
        'VistureTablet'     => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10',
        // http://www.mijncresta.nl/tablet
        'CrestaTablet'     => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989',
        // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309
        'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b',
        // Concorde tab
        'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan',
        // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/
        'GoCleverTablet' => [
            'TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042',
            'TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2',
            'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76',
        ],
        // Modecom Tablets - http://www.modecom.eu/tablets/portal/
        'ModecomTablet' => [
            'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702',
            'FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003',
        ],
        // Vonino Tablets
        'VoninoTablet'  => [
            '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS)\b',
            '\b(Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b',
        ],
        // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0
        'ECSTablet'     => 'V07OT2|TM105A|S10OT1|TR10CS1',
        // Storex Tablets - http://storex.fr/espace_client/support.html
        // @note: no need to add all the tablet codes since they are guided by the first regex.
        'StorexTablet'  => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab',
        // Generic Vodafone tablets.
        'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497|VFD 1400',
        // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb
        // Aka: http://www.essentielb.fr/
        'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2',
        // Ross & Moor - http://ross-moor.ru/
        'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711',
        // i-mobile http://product.i-mobilephone.com/Mobile_Device
        'iMobileTablet'        => 'i-mobile i-note',
        // http://www.tolino.de/de/vergleichen/
        'TolinoTablet'  => 'tolino tab [0-9.]+|tolino shine',
        // AudioSonic - a Kmart brand
        // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72&currentPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1
        'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b',
        // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/
        // @todo: add them gradually to avoid conflicts.
        'AMPETablet' => 'Android.* A78 ',
        // Skk Mobile - http://skkmobile.com.ph/product_tablets.php
        'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)',
        // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1
        'TecnoTablet' => 'TECNO P9|TECNO DP8D',
        // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3
        'JXDTablet' => [
            'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603)\b',
            'Android.* \b(S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b',
        ],
        // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/
        'iJoyTablet' => [
            'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7)',
            'Tablet (Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst)',
            'Tablet (Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam)',
            'Tablet (Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)',
        ],
        // http://www.intracon.eu/tablet
        'FX2Tablet' => 'FX2 PAD7|FX2 PAD10',
        // http://www.xoro.de/produkte/
        // @note: Might be the same brand with 'Simply tablets'
        'XoroTablet'        => [
            'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790',
            'PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032',
            'TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151',
        ],
        // http://www1.viewsonic.com/products/computing/tablets/
        'ViewsonicTablet'   => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a',
        // https://www.verizonwireless.com/tablets/verizon/
        'VerizonTablet' => 'QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1',
        // http://www.odys.de/web/internet-tablet_en.html
        'OdysTablet'        => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10',
        // http://www.captiva-power.de/products.html#tablets-en
        'CaptivaTablet'     => 'CAPTIVA PAD',
        // IconBIT - http://www.iconbit.com/products/tablets/
        'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S',
        // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63
        'TeclastTablet' => [
            'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G',
            '\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G',
            '\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b',
            '\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b',
            '\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b',
            '\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi',
        ],
        // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price
        'OndaTablet' => [
            '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811)\b[\s]+',
            '\b(V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819)\b[\s]+',
            '\b(V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+|V10 \b4G\b',
        ],
        'JaytechTablet'     => 'TPC-PA762',
        'BlaupunktTablet'   => 'Endeavour 800NG|Endeavour 1010',
        // http://www.digma.ru/support/download/
        // @todo: Ebooks also (if requested)
        'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b',
        // http://www.evolioshop.com/ro/tablete-pc.html
        // http://www.evolio.ro/support/downloads_static.html?cat=2
        // @todo: Research some more
        'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b',
        // @todo http://www.lavamobiles.com/tablets-data-cards
        'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b',
        // http://www.breezetablet.com/
        'AocTablet' => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712',
        // http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/
        'MpmanTablet' => [
            'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71',
            'MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77',
            'MP709|MID701|MID711|MID170|MPDC703|MPQC1010',
        ],
        // https://www.celkonmobiles.com/?_a=categoryphones&sid=2
        'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b',
        // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab
        'WolderTablet' => [
            'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT)\b',
            'miTab \b(EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND)\b',
            'miTab \b(BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b',
        ],
        'MediacomTablet' => 'M-MPI10C3G|M-SP10EG|M-SP10EGP|M-SP10HXAH|M-SP7HXAH|M-SP10HXBH|M-SP8HXAH|M-SP8MXA',
        // http://www.mi.com/en
        'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b',
        // http://www.nbru.cn/index.html
        'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One',
        // http://navroad.com/products/produkty/tablety/
        // http://navroad.com/products/produkty/tablety/
        'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI',
        // http://leader-online.com/new_site/product-category/tablets/
        // http://www.leader-online.net.au/List/Tablet
        'LeaderTablet' => [
            'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G',
            'TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100',
        ],
        // http://www.datawind.com/ubislate/
        'UbislateTablet' => 'UbiSlate[\s]?7C',
        // http://www.pocketbook-int.com/ru/support
        'PocketBookTablet' => 'Pocketbook',
        // http://www.kocaso.com/product_tablet.html
        'KocasoTablet' => '\b(TB-1207)\b',
        // http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm
        'HisenseTablet' => '\b(F5281|E2371)\b',
        // http://www.tesco.com/direct/hudl/
        'Hudl'              => 'Hudl HT7S3|Hudl 2',
        // http://www.telstra.com.au/home-phone/thub-2/
        'TelstraTablet'     => 'T-Hub2',
        'GenericTablet'     => [
            'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002',
            '\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab',
            '\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b|WVT101|TM1088|KT107',
        ],
    ];

    /**
     * List of mobile Operating Systems.
     * @var array
     */
    protected static array $operatingSystems = [
        'AndroidOS'         => 'Android',
        'BlackBerryOS'      => 'blackberry|\bBB10\b|rim tablet os',
        'PalmOS'            => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino',
        'SymbianOS'         => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b',
        // @reference: http://en.wikipedia.org/wiki/Windows_Mobile
        'WindowsMobileOS'   => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Windows Mobile|Windows Phone [0-9.]+|WCE;',
        // @reference: http://en.wikipedia.org/wiki/Windows_Phone
        // http://wifeng.cn/?r=blog&a=view&id=106
        // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx
        // http://msdn.microsoft.com/library/ms537503.aspx
        // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
        'WindowsPhoneOS'   => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;',
        'iOS'               => '\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia',
        // https://en.wikipedia.org/wiki/IPadOS
        'iPadOS' => 'CPU OS 13',
        // @reference https://en.m.wikipedia.org/wiki/Sailfish_OS
        // https://sailfishos.org/
        'SailfishOS'        => 'Sailfish',
        // http://en.wikipedia.org/wiki/MeeGo
        // @todo: research MeeGo in UAs
        'MeeGoOS'           => 'MeeGo',
        // http://en.wikipedia.org/wiki/Maemo
        // @todo: research Maemo in UAs
        'MaemoOS'           => 'Maemo',
        'JavaOS'            => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135
        'webOS'             => 'webOS|hpwOS',
        'badaOS'            => '\bBada\b',
        'BREWOS'            => 'BREW',
        'HarmonyOS'         => 'HarmonyOS',
    ];

    /**
     * List of mobile User Agents.
     *
     * IMPORTANT: This is a list of mobile browsers only.
     * Since Mobile Detect 2.x.x, this list supports mobile browsers only.
     * Mobile Detect was never designed to detect all browsers.
     * @var array
     */
    protected static array $browsers = [
        //'Vivaldi'         => 'Vivaldi',
        // @reference: https://developers.google.com/chrome/mobile/docs/user-agent
        'Chrome'          => '\bCrMo\b|CriOS.*Mobile|Android.*Chrome/[.0-9]* Mobile',
        'Dolfin'          => '\bDolfin\b',
        'Opera'           => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+$|Coast/[0-9.]+',
        'Skyfire'         => 'Skyfire',
        // Added "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Edge'             => 'EdgiOS.*Mobile|Mobile Safari/[.0-9]* Edge',
        'IE'              => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+
        'Firefox'         => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS.*Mobile',
        'Bolt'            => 'bolt',
        'TeaShark'        => 'teashark',
        'Blazer'          => 'Blazer',
        // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3
        // Excluded "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Safari'          => 'Version((?!\bEdgiOS\b).)*Mobile.*Safari|Safari.*Mobile|MobileSafari',
        // http://en.wikipedia.org/wiki/Midori_(web_browser)
        //'Midori'          => 'midori',
        //'Tizen'           => 'Tizen',
        'WeChat'          => '\bMicroMessenger\b',
        'UCBrowser'       => 'UC.*Browser|UCWEB',
        'baiduboxapp'     => 'baiduboxapp',
        'baidubrowser'    => 'baidubrowser',
        // https://github.com/serbanghita/Mobile-Detect/issues/7
        'DiigoBrowser'    => 'DiigoBrowser',
        // http://www.puffinbrowser.com/index.php
        // https://github.com/serbanghita/Mobile-Detect/issues/752
        // 'Puffin'            => 'Puffin',
        // http://mercury-browser.com/index.html
        'Mercury'          => '\bMercury\b',
        // http://en.wikipedia.org/wiki/Obigo_Browser
        'ObigoBrowser' => 'Obigo',
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront' => 'NF-Browser',
        // @reference: http://en.wikipedia.org/wiki/Minimo
        // http://en.wikipedia.org/wiki/Vision_Mobile_Browser
        'GenericBrowser'  => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger',
        // @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser)
        'PaleMoon'        => 'Android.*PaleMoon|Mobile.*PaleMoon',
        'HuaweiBrowser'   => 'HuaweiBrowser',
    ];

    /**
     * All possible HTTP headers that represent the
     * User-Agent string.
     * @var array
     */
    protected static array $knownUserAgentHttpHeaders = [
        // The default User-Agent string.
        'HTTP_USER_AGENT',
        // Header can occur on devices using Opera Mini.
        'HTTP_X_OPERAMINI_PHONE_UA',
        // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
        'HTTP_X_DEVICE_USER_AGENT',
        'HTTP_X_ORIGINAL_USER_AGENT',
        'HTTP_X_SKYFIRE_PHONE',
        'HTTP_X_BOLT_PHONE_UA',
        'HTTP_DEVICE_STOCK_UA',
        'HTTP_X_UCBROWSER_DEVICE_UA'
    ];

    /**
     * The individual segments that could exist in a User-Agent string. VER refers to the regular
     * expression defined in the constant self::VERSION_REGEX.
     * @var array
     */
    protected static array $properties = [

        // Build
        'Mobile'        => 'Mobile/[VER]',
        'Build'         => 'Build/[VER]',
        'Version'       => 'Version/[VER]',
        'VendorID'      => 'VendorID/[VER]',

        // Devices
        'iPad'          => 'iPad.*CPU[a-z ]+[VER]',
        'iPhone'        => 'iPhone.*CPU[a-z ]+[VER]',
        'iPod'          => 'iPod.*CPU[a-z ]+[VER]',
        //'BlackBerry'    => array('BlackBerry[VER]', 'BlackBerry [VER];'),
        'Kindle'        => 'Kindle/[VER]',

        // Browser
        'Chrome'        => ['Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'],
        'Coast'         => ['Coast/[VER]'],
        'Dolfin'        => 'Dolfin/[VER]',
        // @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
        'Firefox'       => ['Firefox/[VER]', 'FxiOS/[VER]'],
        'Fennec'        => 'Fennec/[VER]',
        // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
        // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx
        'Edge' => 'Edge/[VER]',
        'IE'      => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'],
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront'      => 'NetFront/[VER]',
        'NokiaBrowser'  => 'NokiaBrowser/[VER]',
        'Opera'         => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]'],
        'Opera Mini'    => 'Opera Mini/[VER]',
        'Opera Mobi'    => 'Version/[VER]',
        'UCBrowser'    => ['UCWEB[VER]', 'UC.*Browser/[VER]'],
        'MQQBrowser'    => 'MQQBrowser/[VER]',
        'MicroMessenger' => 'MicroMessenger/[VER]',
        'baiduboxapp'   => 'baiduboxapp/[VER]',
        'baidubrowser'  => 'baidubrowser/[VER]',
        'SamsungBrowser' => 'SamsungBrowser/[VER]',
        'Iron'          => 'Iron/[VER]',
        // @note: Safari 7534.48.3 is actually Version 5.1.
        // @note: On BlackBerry the Version is overwriten by the OS.
        'Safari'        => ['Version/[VER]', 'Safari/[VER]'],
        'Skyfire'       => 'Skyfire/[VER]',
        'Tizen'         => 'Tizen/[VER]',
        'Webkit'        => 'webkit[ /][VER]',
        'PaleMoon'         => 'PaleMoon/[VER]',
        'SailfishBrowser'  => 'SailfishBrowser/[VER]',

        // Engine
        'Gecko'         => 'Gecko/[VER]',
        'Trident'       => 'Trident/[VER]',
        'Presto'        => 'Presto/[VER]',
        'Goanna'           => 'Goanna/[VER]',

        // OS
        'iOS'              => ' \bi?OS\b [VER][ ;]{1}',
        'Android'          => 'Android [VER]',
        'Sailfish'         => 'Sailfish [VER]',
        'BlackBerry'       => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
        'BREW'             => 'BREW [VER]',
        'Java'             => 'Java/[VER]',
        // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx
        // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases
        'Windows Phone OS' => ['Windows Phone OS [VER]', 'Windows Phone [VER]'],
        'Windows Phone'    => 'Windows Phone [VER]',
        'Windows CE'       => 'Windows CE/[VER]',
        // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd
        'Windows NT'       => 'Windows NT [VER]',
        'Symbian'          => ['SymbianOS/[VER]', 'Symbian/[VER]'],
        'webOS'            => ['webOS/[VER]', 'hpwOS/[VER];'],
    ];

    /**
     * Construct an instance of this class.
     */
    public function __construct(
        ?CacheInterface $cache = null,
        array $config = [],
    ) {
        // If no custom cache provided then use our own.
        $this->cache = $cache ?? new Cache();
        // Override config from user.
        $this->config = array_merge($this->config, $config);

        // Beware that if you use "autoInitOfHttpHeaders: false" and you forget to setUserAgent
        // to something other than a string, an MobileDetectException exception will be thrown.
        if ($this->config['autoInitOfHttpHeaders']) {
            $this->autoInitKnownHttpHeaders();
        }
    }

    /**
     * Get the current script version.
     * Used in demo.php file.
     *
     * @return string The version number in semantic version format.
     */
    public function getVersion(): string
    {
        return $this->VERSION;
    }

    /**
     * On startup Mobile Detect library will auto-initiate from the existing
     * HTTP headers extracted from $_SERVER.
     *
     * @return void
     */
    public function autoInitKnownHttpHeaders(): void
    {
        // Go through known HTTP headers that we care about.
        // See "4.1.18. Protocol-Specific Meta-Variables" of http://www.faqs.org/rfcs/rfc3875.html
        $knownHttpHeaders = array_merge(
            array_values(self::$knownUserAgentHttpHeaders),
            array_keys(self::$knownMobilePositiveHeaders),
            array_values(self::$knownCloudFrontHeaders)
        );

        // Did not iterate through global $_SERVER to find ['HTTP...'] header values
        // because it's very slow and on some servers it can have more than 50 worthless keys.
        //        $httpHeaders = array_filter($_SERVER, function ($key) {
        //            return str_starts_with($key, 'HTTP_');
        //        }, ARRAY_FILTER_USE_KEY);
        $httpHeaders = [];
        foreach ($knownHttpHeaders as $headerName) {
            if (isset($_SERVER[$headerName])) {
                $httpHeaders[$headerName] = $_SERVER[$headerName];
            }
        }
        $this->setHttpHeaders($httpHeaders);

        // Set the User-Agent even if it's an empty string so that "autoInitOfHttpHeaders" doesn't throw an exception.
        // https://github.com/serbanghita/Mobile-Detect/issues/946#issuecomment-1885675939
        if (!$this->hasUserAgent()) {
            $this->setUserAgent("");
        }
    }

    /**
     * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
     *
     * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
     *                           the headers. The default null is left for backwards compatibility.
     */
    public function setHttpHeaders(array $httpHeaders = []): void
    {
        $this->httpHeaders = $httpHeaders;

        // Don't process any further if no actual HTTP headers were set.
        if (count($httpHeaders) === 0) {
            return;
        }

        // Setting new HTTP headers automatically resets the User-Agent.
        // Set current User-Agent from known User-Agent-like HTTP header(s).
        $userAgent = "";
        foreach ($this->getUaHttpHeaders() as $altHeader) {
            if (!empty($this->httpHeaders[$altHeader])) {
                $userAgent .= $this->httpHeaders[$altHeader] . " ";
            }
        }

        if (!empty($userAgent)) {
            $this->setUserAgent($userAgent);
        }

        // Override User-Agent string if 'Amazon Cloudfront' specific HTTP headers are present.
        if (
            $this->hasHttpHeader(self::$knownCloudFrontHeaders[0]) ||
            $this->hasHttpHeader(self::$knownCloudFrontHeaders[1])
        ) {
            $this->setUserAgent(self::$cloudFrontUA);
        }
    }

    /**
     * Retrieves the HTTP headers.
     *
     * @return array
     */
    public function getHttpHeaders(): array
    {
        return $this->httpHeaders;
    }

    public function hasHttpHeaders(): bool
    {
        return count($this->httpHeaders) > 0;
    }

    protected function hasHttpHeader(string $name): bool
    {
        return !empty($this->httpHeaders[$name]);
    }

    /**
     * Retrieves a particular header. If it doesn't exist, no exception/error is caused.
     * Simply null is returned.
     *
     * @param string $header The name of the header to retrieve. Can be HTTP compliant such as
     *                       "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
     *                       all-caps, HTTP_ prefixed, underscore separated awesomeness.
     *
     * @return string|null The value of the header.
     */
    public function getHttpHeader(string $header): ?string
    {
        // are we using PHP-flavored headers?
        if (!str_contains($header, '_')) {
            $header = str_replace('-', '_', $header);
            $header = strtoupper($header);
        }

        // test the alternate, too
        $altHeader = 'HTTP_' . $header;

        //Test both the regular and the HTTP_ prefix
        if (isset($this->httpHeaders[$header])) {
            return $this->httpHeaders[$header];
        } elseif (isset($this->httpHeaders[$altHeader])) {
            return $this->httpHeaders[$altHeader];
        }

        return null;
    }

    public function getMobileHeaders(): array
    {
        return static::$knownMobilePositiveHeaders;
    }

    /**
     * Get all possible HTTP headers that
     * can contain the User-Agent string.
     *
     * @return array List of HTTP headers.
     */
    public function getUaHttpHeaders(): array
    {
        return static::$knownUserAgentHttpHeaders;
    }

    /**
     * Retrieves the HTTP CloudFront headers
     * that trigger a mobile detection.
     *
     * @return array
     */
    public function getCloudFrontHttpHeaders(): array
    {
        return static::$knownCloudFrontHeaders;
    }

    /**
     * Prepare the User-Agent string for matching phase.
     *
     * @param string $userAgent The User-Agent string.
     * @return string
     */
    private function prepareUserAgent(string $userAgent): string
    {
        $userAgent = trim($userAgent);
        return substr($userAgent, 0, $this->config['maximumUserAgentLength']);
    }

    /**
     * Set the User-Agent to be used.
     *
     * @param string $userAgent The User-Agent string.
     * @return string
     */
    public function setUserAgent(string $userAgent): string
    {
        $preparedUserAgent = $this->prepareUserAgent($userAgent);
        return $this->userAgent = $preparedUserAgent;
    }

    /**
     * Retrieve the User-Agent.
     *
     * @return string|null The user agent if it's set.
     */
    public function getUserAgent(): ?string
    {
        return $this->userAgent;
    }

    public function hasUserAgent(): bool
    {
        return is_string($this->userAgent);
    }

    public function isUserAgentEmpty(): bool
    {
        return $this->hasUserAgent() && $this->userAgent === '';
    }

    public function getMatchingRegex(): ?string
    {
        return $this->matchingRegex;
    }

    public function getMatchesArray(): ?array
    {
        return $this->matchesArray;
    }

    /**
     * Retrieve the list of known phone devices.
     *
     * @return array List of phone devices.
     */
    public static function getPhoneDevices(): array
    {
        return static::$phoneDevices;
    }

    /**
     * Retrieve the list of known tablet devices.
     *
     * @return array List of tablet devices.
     */
    public static function getTabletDevices(): array
    {
        return static::$tabletDevices;
    }

    /**
     * Retrieve the list of known browsers. Specifically, the user agents.
     *
     * @return array List of browsers / user agents.
     */
    public static function getBrowsers(): array
    {
        return static::$browsers;
    }

    /**
     * Method gets the mobile detection rules.
     * This method is used for the magic methods $detect->is*().
     * Retrieve the current set of rules.
     *
     * @return array
     */
    public function getRules(): array
    {
        static $rules;

        if (!$rules) {
            $rules = array_merge(
                static::$browsers,
                static::$operatingSystems,
                static::$phoneDevices,
                static::$tabletDevices
            );
        }

        return $rules;
    }

    /**
     * Retrieve the list of mobile operating systems.
     *
     * @return array The list of mobile operating systems.
     */
    public static function getOperatingSystems(): array
    {
        return static::$operatingSystems;
    }

    /**
     * Check the HTTP headers for signs of mobile.
     * This is the fastest mobile check possible; it's used
     * inside isMobile() method.
     *
     * @return bool
     */
    public function checkHttpHeadersForMobile(): bool
    {
        foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) {
            if (isset($this->httpHeaders[$mobileHeader])) {
                if (isset($matchType['matches']) && is_array($matchType['matches'])) {
                    foreach ($matchType['matches'] as $_match) {
                        if (str_contains($this->httpHeaders[$mobileHeader], $_match)) {
                            return true;
                        }
                    }

                    return false;
                } else {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Magic overloading method.
     *
     * @param string $name
     * @param array $arguments
     * @return bool
     * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
     * @throws \Exception
     */
    public function __call(string $name, array $arguments)
    {
        // make sure the name starts with 'is', otherwise
        if (!str_starts_with($name, 'is')) {
            throw new BadMethodCallException("No such method exists: $name");
        }

        $ruleName = substr($name, 2);

        return $this->is($ruleName);
    }

    /**
     * Check if the device is mobile.
     * Returns true if any type of mobile device detected, including special ones
     * @return bool
     * @throws MobileDetectException
     */
    public function isMobile(): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No valid user-agent has been set.', MobileDetectExceptionCode::INVALID_USER_AGENT_ERR);
        }

        if ($this->isUserAgentEmpty()) {
            return false;
        }

        // Cache check.
        try {
            $cacheKey = $this->createCacheKey("mobile");
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem !== null) {
                return $cacheItem;
            }

            // Special case: Amazon CloudFront mobile viewer
            if (
                $this->getUserAgent() === self::$cloudFrontUA &&
                $this->getHttpHeader('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER') === 'true'
            ) {
                $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                return true;
            }

            if ($this->hasHttpHeaders() && $this->checkHttpHeadersForMobile()) {
                $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                return true;
            } else {
                $result = $this->matchUserAgentWithFirstFoundMatchingRule();
                $this->cache->set($cacheKey, $result, $this->config['cacheTtl']);
                return $result;
            }
        } catch (CacheInvalidArgumentException | CacheException | PsrInvalidArgumentException $e) {
            throw new MobileDetectException("Cache problem in isMobile(): {$e->getMessage()}", MobileDetectExceptionCode::IS_MOBILE_ERR, $e);
        }
    }

    /**
     * Check if the device is a tablet.
     * Return true if any type of tablet device is detected.
     * @return bool
     * @throws MobileDetectException
     */
    public function isTablet(): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No user-agent has been set.', MobileDetectExceptionCode::INVALID_USER_AGENT_ERR);
        }

        if ($this->isUserAgentEmpty()) {
            return false;
        }

        // Cache check.
        try {
            $cacheKey = $this->createCacheKey("tablet");
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem !== null) {
                return $cacheItem;
            }

            // Special case: Amazon CloudFront mobile viewer
            if (
                $this->getUserAgent() === self::$cloudFrontUA &&
                $this->getHttpHeader('HTTP_CLOUDFRONT_IS_TABLET_VIEWER') === 'true'
            ) {
                $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                return true;
            }

            foreach (static::$tabletDevices as $_regex) {
                $regexString = $_regex;
                // "regex" is array of "strings"
                if (is_array($_regex)) {
                    $regexString = implode("|", $_regex);
                }
                if ($this->match($regexString, $this->getUserAgent())) {
                    $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                    return true;
                }

                //                if (is_array($_regex)) {
                //                    foreach ($_regex as $regexString) {
                //                        $result = $this->match($regexString, $this->getUserAgent());
                //                        if ($result) {
                //                            $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                //                            return true;
                //                        }
                //                    }
                //                } else {
                //                    // assume the regex is a "string"
                //                    if ($this->match($_regex, $this->getUserAgent())) {
                //                        $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                //                        return true;
                //                    }
                //                }
            }

            $this->cache->set($cacheKey, false, $this->config['cacheTtl']);
            return false;
        } catch (CacheInvalidArgumentException | CacheException | PsrInvalidArgumentException $e) {
            throw new MobileDetectException("Cache problem in isTablet(): {$e->getMessage()}", MobileDetectExceptionCode::IS_TABLET_ERR, $e);
        }
    }

    /**
     * Checks if a rule (e.g. isIphone, isIOS, etc.) matches its regex against the User-Agent.
     *
     * @param string $ruleName
     * @return bool
     * @throws MobileDetectException
     */
    public function is(string $ruleName): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No user-agent has been set.', MobileDetectExceptionCode::INVALID_USER_AGENT_ERR);
        }

        if ($this->isUserAgentEmpty()) {
            return false;
        }

        // Cache check.
        try {
            $cacheKey = $this->createCacheKey($ruleName);
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem !== null) {
                return $cacheItem;
            }

            $result = $this->matchUserAgentWithRule($ruleName);

            // Cache save.
            $this->cache->set($cacheKey, $result, $this->config['cacheTtl']);
            return $result;
        } catch (CacheInvalidArgumentException | CacheException | PsrInvalidArgumentException $e) {
            throw new MobileDetectException("Cache problem in is(): {$e->getMessage()}", MobileDetectExceptionCode::IS_MAGIC_ERR, $e);
        }
    }

    /**
     * Some detection rules are relative (not standard),
     * because of the diversity of devices, vendors and
     * their conventions in representing the User-Agent or
     * the HTTP headers.
     *
     * This method will be used to check custom regexes against
     * the User-Agent string.
     *
     * @param string $regex
     * @param string $userAgent
     * @return bool
     *
     * @todo: search in the HTTP headers too.
     */
    public function match(string $regex, string $userAgent): bool
    {
        $match = (bool) preg_match(sprintf('#%s#is', $regex), $userAgent, $matches);
        // If positive match is found, store the results for debug.
        if ($match) {
            $this->matchingRegex = $regex;
            $this->matchesArray = $matches;
        }

        return $match;
    }

    /**
     * Find a detection rule that matches the current User-agent.
     * @return bool
     */
    protected function matchUserAgentWithFirstFoundMatchingRule(): bool
    {
        // Begin general search.
        foreach ($this->getRules() as $_regex) {
            if (empty($_regex)) {
                continue;
            }

            // regex is an array of "strings"
            if (is_array($_regex)) {
                foreach ($_regex as $regexString) {
                    if ($this->match($regexString, $this->getUserAgent())) {
                        return true;
                    }
                }
            } else {
                // assume regex is "string"
                if ($this->match($_regex, $this->getUserAgent())) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Search for a certain key in the rules array.
     * If the key is found then try to match the corresponding
     * regex against the User-Agent.
     *
     * @param string $ruleName
     * @return bool
     */
    protected function matchUserAgentWithRule(string $ruleName): bool
    {
        $result = false;
        // Make the keys lowercase, so we can match: isIphone(), isiPhone(), isiphone(), etc.
        $ruleName = strtolower($ruleName);
        // change the keys to lower case
        $_rules = array_change_key_case($this->getRules());

        if (false === empty($_rules[$ruleName])) {
            $regexString = $_rules[$ruleName];
            if (is_array($_rules[$ruleName])) {
                $regexString = implode("|", $_rules[$ruleName]);
            }
            $result = $this->match($regexString, $this->getUserAgent());
            //            if (is_array($_rules[$ruleName])) {
            //             foreach($_rules[$ruleName] as $ruleRegex) {
            //                 $result = $this->match($ruleRegex, $this->getUserAgent());
            //                 if ($result) {
            //                     return true;
            //                 }
            //             }
            //            } else {
            //                $result = $this->match($_rules[$ruleName], $this->getUserAgent());
            //            }
        }

        return $result;
    }

    /**
     * Prepare the version number.
     * @todo Remove the error suppression from str_replace() call.
     *
     * @param string $ver The string version, like "2.6.21.2152";
     * @return float
     */
    public function prepareVersionNo(string $ver): float
    {
        $ver = str_replace(['_', ' ', '/'], '.', $ver);
        $arrVer = explode('.', $ver, 2);

        if (isset($arrVer[1])) {
            $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
        }

        return (float) implode('.', $arrVer);
    }

    /**
     * Check the version of the given property in the User-Agent.
     * Will return a float number. (e.g. 2_0 will return 2.0, 4.3.1 will return 4.31)
     *
     * @param string $propertyName The name of the property. See self::getProperties() array
     *                             keys for all possible properties.
     * @param string $type         Either self::VERSION_TYPE_STRING to get a string value or
     *                             self::VERSION_TYPE_FLOAT indicating a float value. This parameter
     *                             is optional and defaults to self::VERSION_TYPE_STRING. Passing an
     *                             invalid parameter will default to the type as well.
     *
     * @return string|float|false The version of the property we are trying to extract.
     */
    public function version(string $propertyName, string $type = self::VERSION_TYPE_STRING): float|bool|string
    {
        if (empty($propertyName) || !$this->hasUserAgent()) {
            return false;
        }

        // set the $type to the default if we don't recognize the type
        if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) {
            $type = self::VERSION_TYPE_STRING;
        }

        $properties = self::getProperties();

        // Check if the property exists in the properties array.
        if (true === isset($properties[$propertyName])) {
            // Prepare the pattern to be matched.
            // Make sure we always deal with an array (string is converted).
            $properties[$propertyName] = (array) $properties[$propertyName];

            foreach ($properties[$propertyName] as $propertyMatchString) {
                $propertyPattern = str_replace('[VER]', self::VERSION_REGEX, $propertyMatchString);

                // Identify and extract the version.
                preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);

                if (false === empty($match[1])) {
                    return ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);
                }
            }
        }

        return false;
    }

    public function getCache(): CacheInterface
    {
        return $this->cache;
    }

    /**
     * Creates the cache key string based on the defined fn.
     * Function can be customized in the constructor. See `$config['cacheKeyFn']`.
     *
     * @throws CacheException
     */
    protected function createCacheKey(string $key): string
    {
        $userAgentKey = $this->hasUserAgent() ? $this->userAgent : '';
        $httpHeadersKey = $this->hasHttpHeaders() ? static::flattenHeaders($this->httpHeaders) : '';

        $cacheKey = "$key:$userAgentKey:$httpHeadersKey";

        $cacheKeyFn = $this->config['cacheKeyFn'];

        if (!is_callable($cacheKeyFn)) {
            throw new CacheException('cacheKeyFn is not a function.');
        }

        return call_user_func($cacheKeyFn, $cacheKey);
    }

    public static function flattenHeaders(array $httpHeaders): string
    {
        $key = '';
        foreach ($httpHeaders as $name => $value) {
            $key .= "$name: $value" . PHP_EOL;
        }
        return trim($key);
    }

    /**
     * Get the properties array.
     *
     * @return array
     */
    public static function getProperties(): array
    {
        return static::$properties;
    }
}


================================================
FILE: src/MobileDetectStandalone.php
================================================
<?php

namespace Detection;

require_once dirname(__FILE__) . '/../standalone/autoloader.php';

class MobileDetectStandalone extends MobileDetect
{
}


================================================
FILE: standalone/autoloader.php
================================================
<?php
$dir = dirname(__FILE__);

spl_autoload_register(function ($class) use ($dir) {
    $classMap = [
        // "mobiledetect/mobiledetectlib"
        "Detection\Cache\Cache" => $dir . "/../src/Cache/Cache.php",
        "Detection\Cache\CacheException" => $dir . "/../src/Cache/CacheException.php",
        "Detection\Cache\CacheInvalidArgumentException" => $dir . "/../src/Cache/CacheInvalidArgumentException.php",
        "Detection\Exception\MobileDetectException" => $dir . "/../src/Exception/MobileDetectException.php",
        "Detection\Exception\MobileDetectExceptionCode" => $dir . "/../src/Exception/MobileDetectExceptionCode.php",
        "Detection\MobileDetect" => $dir . "/../src/MobileDetect.php",

        // "psr/simple-cache"
        "Psr\SimpleCache\CacheException" => $dir . "/deps/simple-cache/src/CacheException.php",
        "Psr\SimpleCache\CacheInterface" => $dir . "/deps/simple-cache/src/CacheInterface.php",
        "Psr\SimpleCache\InvalidArgumentException" => $dir . "/deps/simple-cache/src/InvalidArgumentException.php",
    ];

    $fileFound = $classMap[$class] ?? false;

    if ($fileFound) {
        require $fileFound;
        return true;
    }

    return false;
});


================================================
FILE: standalone/deps/simple-cache/.editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: standalone/deps/simple-cache/.gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.gitattributes     export-ignore
/.gitignore         export-ignore
/.travis.yml        export-ignore
/phpunit.xml.dist   export-ignore
/.scrutinizer.yml   export-ignore
/.editorconfig      export-ignore
/tests              export-ignore


================================================
FILE: standalone/deps/simple-cache/.gitignore
================================================
build
composer.lock
docs
vendor


================================================
FILE: standalone/deps/simple-cache/LICENSE.md
================================================
# The MIT License (MIT)

Copyright (c) 2016 PHP Framework Interoperability Group

> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.


================================================
FILE: standalone/deps/simple-cache/README.md
================================================
PHP FIG Simple Cache PSR
========================

This repository holds all interfaces related to PSR-16.

Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details.

You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package.


================================================
FILE: standalone/deps/simple-cache/composer.json
================================================
{
    "name": "psr/simple-cache",
    "description": "Common interfaces for simple caching",
    "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"],
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "https://www.php-fig.org/"
        }
    ],
    "require": {
        "php": ">=8.0.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\SimpleCache\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.0.x-dev"
        }
    }
}


================================================
FILE: standalone/deps/simple-cache/src/CacheException.php
================================================
<?php

namespace Psr\SimpleCache;

/**
 * Interface used for all types of exceptions thrown by the implementing library.
 */
interface CacheException extends \Throwable
{
}


================================================
FILE: standalone/deps/simple-cache/src/CacheInterface.php
================================================
<?php

namespace Psr\SimpleCache;

interface CacheInterface
{
    /**
     * Fetches a value from the cache.
     *
     * @param string $key     The unique key of this item in the cache.
     * @param mixed  $default Default value to return if the key does not exist.
     *
     * @return mixed The value of the item from the cache, or $default in case of cache miss.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function get(string $key, mixed $default = null): mixed;

    /**
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
     *
     * @param string                 $key   The key of the item to store.
     * @param mixed                  $value The value of the item to store, must be serializable.
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
     *                                      the driver supports TTL then the library may set a default value
     *                                      for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool;

    /**
     * Delete an item from the cache by its unique key.
     *
     * @param string $key The unique cache key of the item to delete.
     *
     * @return bool True if the item was successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function delete(string $key): bool;

    /**
     * Wipes clean the entire cache's keys.
     *
     * @return bool True on success and false on failure.
     */
    public function clear(): bool;

    /**
     * Obtains multiple cache items by their unique keys.
     *
     * @param iterable<string> $keys    A list of keys that can be obtained in a single operation.
     * @param mixed            $default Default value to return for keys that do not exist.
     *
     * @return iterable<string, mixed> A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function getMultiple(iterable $keys, mixed $default = null): iterable;

    /**
     * Persists a set of key => value pairs in the cache, with an optional TTL.
     *
     * @param iterable               $values A list of key => value pairs for a multiple-set operation.
     * @param null|int|\DateInterval $ttl    Optional. The TTL value of this item. If no value is sent and
     *                                       the driver supports TTL then the library may set a default value
     *                                       for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $values is neither an array nor a Traversable,
     *   or if any of the $values are not a legal value.
     */
    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool;

    /**
     * Deletes multiple cache items in a single operation.
     *
     * @param iterable<string> $keys A list of string-based keys to be deleted.
     *
     * @return bool True if the items were successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function deleteMultiple(iterable $keys): bool;

    /**
     * Determines whether an item is present in the cache.
     *
     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
     * and not to be used within your live applications operations for get/set, as this method
     * is subject to a race condition where your has() will return true and immediately after,
     * another script can remove it making the state of your app out of date.
     *
     * @param string $key The cache item key.
     *
     * @return bool
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function has(string $key): bool;
}


================================================
FILE: standalone/deps/simple-cache/src/InvalidArgumentException.php
================================================
<?php

namespace Psr\SimpleCache;

/**
 * Exception interface for invalid cache arguments.
 *
 * When an invalid argument is passed it must throw an exception which implements
 * this interface
 */
interface InvalidArgumentException extends CacheException
{
}


================================================
FILE: tests/CacheTest.php
================================================
<?php

namespace DetectionTests;

use DateInterval;
use Detection\Cache\Cache;
use Detection\Cache\CacheInvalidArgumentException;
use PHPUnit\Framework\TestCase;

final class CacheTest extends TestCase
{
    protected Cache $cache;
    protected function setUp(): void
    {
        $this->cache = new Cache();
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testGetInvalidCacheKeyThrowsException()
    {
        $this->expectException(CacheInvalidArgumentException::class);
        $this->cache->get('');
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetInvalidCacheKeyThrowsException()
    {
        $this->expectException(CacheInvalidArgumentException::class);
        $this->cache->set('', 'a', 100);
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testGetNonExistentReturnsNull()
    {
        $this->assertNull($this->cache->get('random'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testGetNonExistentReturnsCustomDefault(): void
    {
        $this->assertEquals('customDefault', $this->cache->get('random', 'customDefault'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testGetExpiredItemReturnsDefault(): void
    {
        $this->cache->set('expiring', 'value', 1);
        sleep(2);
        $this->assertNull($this->cache->get('expiring'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testGetExpiredItemReturnsCustomDefault(): void
    {
        $this->cache->set('expiring', 'value', 1);
        sleep(2);
        $this->assertEquals('fallback', $this->cache->get('expiring', 'fallback'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetGetBooleanValues()
    {
        $this->cache->set('isMobile', true, 100);
        $this->assertTrue($this->cache->get('isMobile'));

        $this->cache->set('isTablet', false, 100);
        $this->assertFalse($this->cache->get('isTablet'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetGetZeroTTL()
    {
        $this->cache->set('isMobile', true, 0);
        $this->assertNull($this->cache->get('isMobile'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetGetNegativeTTL()
    {
        $this->cache->set('isMobile', true, -999);
        $this->assertNull($this->cache->get('isMobile'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetZeroTTLWithInvalidKeyThrowsException()
    {
        $this->expectException(CacheInvalidArgumentException::class);
        $this->cache->set('', true, 0);
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetNegativeTTLWithInvalidKeyThrowsException()
    {
        $this->expectException(CacheInvalidArgumentException::class);
        $this->cache->set('', true, -999);
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetValidTtlAsAnIntegerReturnsTheSetValue()
    {
        $this->cache->set('isMobile', 'someValue', 1000);
        $this->assertEquals('someValue', $this->cache->get('isMobile'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetNullTtlReturnsTheSetValue()
    {
        $this->cache->set('isMobile', 'abc');
        $this->assertEquals('abc', $this->cache->get('isMobile'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testSetWithDateIntervalTtl(): void
    {
        $this->cache->set('withInterval', 'intervalValue', new DateInterval('PT1H'));
        $this->assertEquals('intervalValue', $this->cache->get('withInterval'));
    }

    /**
     * @throws CacheInvalidArgumentException
     */
    public function testDeletionOfValidRecord()
    {
        $this->cache->set('isMobile', 'a b c', 100);
        $this->assertEquals('a b c', $this->cache->get('isMobile'))
Download .txt
gitextract_umzpjg5f/

├── .claude/
│   └── settings.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── docs/
│   │   └── index.html
│   └── workflows/
│       ├── 4.8.x-test.yml
│       └── website.yml
├── .gitignore
├── .php-cs-fixer.php
├── .phpcs.xml
├── CHANGELOG.md
├── CLAUDE.md
├── CNAME
├── CONTRIBUTING.md
├── DOCKER-COMPOSE.md
├── KNOWN_LIMITATIONS.md
├── LICENSE
├── MobileDetect.json
├── README-EXAMPLES.md
├── README.md
├── SECURITY.md
├── composer.json
├── docker/
│   ├── Dockerfile.setup
│   └── build.sh
├── docker-compose.yml
├── phpbench.json
├── scripts/
│   ├── dump_magic_methods.php
│   ├── example.php
│   ├── export_to_json.php
│   ├── pre-commit-hook.sh
│   ├── test.php
│   └── test_standalone.php
├── src/
│   ├── Cache/
│   │   ├── Cache.php
│   │   ├── CacheException.php
│   │   └── CacheInvalidArgumentException.php
│   ├── Exception/
│   │   ├── MobileDetectException.php
│   │   └── MobileDetectExceptionCode.php
│   ├── MobileDetect.php
│   └── MobileDetectStandalone.php
├── standalone/
│   ├── autoloader.php
│   └── deps/
│       └── simple-cache/
│           ├── .editorconfig
│           ├── .gitattributes
│           ├── .gitignore
│           ├── LICENSE.md
│           ├── README.md
│           ├── composer.json
│           └── src/
│               ├── CacheException.php
│               ├── CacheInterface.php
│               └── InvalidArgumentException.php
└── tests/
    ├── CacheTest.php
    ├── MobileDetectExceptionTest.php
    ├── MobileDetectGeneralTest.php
    ├── MobileDetectStandaloneTest.php
    ├── MobileDetectVersionTest.php
    ├── MobileDetectWithCacheTest.php
    ├── UserAgentList.inc.php
    ├── UserAgentTest.php
    ├── benchmark/
    │   └── MobileDetectBench.php
    ├── bootstrap.php
    ├── phpunit.xml
    ├── providers/
    │   └── vendors/
    │       ├── AOC.php
    │       ├── Acer.php
    │       ├── Alcatel.php
    │       ├── Allview.php
    │       ├── Amazon.php
    │       ├── Apple.php
    │       ├── Archos.php
    │       ├── Asus.php
    │       ├── Blackberry.php
    │       ├── Dell.php
    │       ├── Google.php
    │       ├── HP.php
    │       ├── HTC.php
    │       ├── Huawei.php
    │       ├── LG.php
    │       ├── Lava.php
    │       ├── Leader.php
    │       ├── Lenovo.php
    │       ├── Mi.php
    │       ├── Microsoft.php
    │       ├── Motorola.php
    │       ├── Mpman.php
    │       ├── Nexus.php
    │       ├── Nokia.php
    │       ├── Onda.php
    │       ├── Others.php
    │       ├── Prestigio.php
    │       ├── Samsung.php
    │       ├── Sony.php
    │       ├── SpecialCases.php
    │       ├── Verizon.php
    │       ├── Vodafone.php
    │       └── ZTE.php
    └── ualist.json
Download .txt
SYMBOL INDEX (182 symbols across 18 files)

FILE: src/Cache/Cache.php
  class Cache (line 18) | class Cache implements CacheInterface
    method get (line 26) | public function get(string $key, mixed $default = null): mixed
    method set (line 45) | public function set(string $key, mixed $value, int|DateInterval|null $...
    method delete (line 68) | public function delete(string $key): bool
    method deleteSingle (line 82) | private function deleteSingle(string $key): void
    method clear (line 88) | public function clear(): bool
    method has (line 99) | public function has(string $key): bool
    method getMultiple (line 115) | public function getMultiple(iterable $keys, mixed $default = null): it...
    method setMultiple (line 127) | public function setMultiple(iterable $values, int|DateInterval|null $t...
    method deleteMultiple (line 139) | public function deleteMultiple(iterable $keys): bool
    method checkKey (line 151) | protected function checkKey(string $key): string
    method getTTL (line 162) | protected function getTTL(DateInterval|int|null $ttl): ?int
    method checkReturn (line 180) | protected function checkReturn(array $booleans): bool
    method getKeys (line 197) | public function getKeys(): array
    method evictExpired (line 210) | public function evictExpired(): int

FILE: src/Cache/CacheException.php
  class CacheException (line 7) | class CacheException extends \Exception implements \Psr\SimpleCache\Cach...

FILE: src/Cache/CacheInvalidArgumentException.php
  class CacheInvalidArgumentException (line 9) | class CacheInvalidArgumentException extends CacheException implements In...

FILE: src/Exception/MobileDetectException.php
  class MobileDetectException (line 7) | class MobileDetectException extends \Exception

FILE: src/Exception/MobileDetectExceptionCode.php
  class MobileDetectExceptionCode (line 7) | class MobileDetectExceptionCode

FILE: src/MobileDetect.php
  class MobileDetect (line 231) | class MobileDetect
    method __construct (line 1050) | public function __construct(
    method getVersion (line 1072) | public function getVersion(): string
    method autoInitKnownHttpHeaders (line 1083) | public function autoInitKnownHttpHeaders(): void
    method setHttpHeaders (line 1119) | public function setHttpHeaders(array $httpHeaders = []): void
    method getHttpHeaders (line 1155) | public function getHttpHeaders(): array
    method hasHttpHeaders (line 1160) | public function hasHttpHeaders(): bool
    method hasHttpHeader (line 1165) | protected function hasHttpHeader(string $name): bool
    method getHttpHeader (line 1180) | public function getHttpHeader(string $header): ?string
    method getMobileHeaders (line 1201) | public function getMobileHeaders(): array
    method getUaHttpHeaders (line 1212) | public function getUaHttpHeaders(): array
    method getCloudFrontHttpHeaders (line 1223) | public function getCloudFrontHttpHeaders(): array
    method prepareUserAgent (line 1234) | private function prepareUserAgent(string $userAgent): string
    method setUserAgent (line 1246) | public function setUserAgent(string $userAgent): string
    method getUserAgent (line 1257) | public function getUserAgent(): ?string
    method hasUserAgent (line 1262) | public function hasUserAgent(): bool
    method isUserAgentEmpty (line 1267) | public function isUserAgentEmpty(): bool
    method getMatchingRegex (line 1272) | public function getMatchingRegex(): ?string
    method getMatchesArray (line 1277) | public function getMatchesArray(): ?array
    method getPhoneDevices (line 1287) | public static function getPhoneDevices(): array
    method getTabletDevices (line 1297) | public static function getTabletDevices(): array
    method getBrowsers (line 1307) | public static function getBrowsers(): array
    method getRules (line 1319) | public function getRules(): array
    method getOperatingSystems (line 1340) | public static function getOperatingSystems(): array
    method checkHttpHeadersForMobile (line 1352) | public function checkHttpHeadersForMobile(): bool
    method __call (line 1382) | public function __call(string $name, array $arguments)
    method isMobile (line 1400) | public function isMobile(): bool
    method isTablet (line 1446) | public function isTablet(): bool
    method is (line 1515) | public function is(string $ruleName): bool
    method match (line 1558) | public function match(string $regex, string $userAgent): bool
    method matchUserAgentWithFirstFoundMatchingRule (line 1574) | protected function matchUserAgentWithFirstFoundMatchingRule(): bool
    method matchUserAgentWithRule (line 1608) | protected function matchUserAgentWithRule(string $ruleName): bool
    method prepareVersionNo (line 1644) | public function prepareVersionNo(string $ver): float
    method version (line 1669) | public function version(string $propertyName, string $type = self::VER...
    method getCache (line 1703) | public function getCache(): CacheInterface
    method createCacheKey (line 1714) | protected function createCacheKey(string $key): string
    method flattenHeaders (line 1730) | public static function flattenHeaders(array $httpHeaders): string
    method getProperties (line 1744) | public static function getProperties(): array

FILE: src/MobileDetectStandalone.php
  class MobileDetectStandalone (line 7) | class MobileDetectStandalone extends MobileDetect

FILE: standalone/deps/simple-cache/src/CacheException.php
  type CacheException (line 8) | interface CacheException extends \Throwable

FILE: standalone/deps/simple-cache/src/CacheInterface.php
  type CacheInterface (line 5) | interface CacheInterface
    method get (line 18) | public function get(string $key, mixed $default = null): mixed;
    method set (line 34) | public function set(string $key, mixed $value, null|int|\DateInterval ...
    method delete (line 46) | public function delete(string $key): bool;
    method clear (line 53) | public function clear(): bool;
    method getMultiple (line 67) | public function getMultiple(iterable $keys, mixed $default = null): it...
    method setMultiple (line 83) | public function setMultiple(iterable $values, null|int|\DateInterval $...
    method deleteMultiple (line 96) | public function deleteMultiple(iterable $keys): bool;
    method has (line 113) | public function has(string $key): bool;

FILE: standalone/deps/simple-cache/src/InvalidArgumentException.php
  type InvalidArgumentException (line 11) | interface InvalidArgumentException extends CacheException

FILE: tests/CacheTest.php
  class CacheTest (line 10) | final class CacheTest extends TestCase
    method setUp (line 13) | protected function setUp(): void
    method testGetInvalidCacheKeyThrowsException (line 21) | public function testGetInvalidCacheKeyThrowsException()
    method testSetInvalidCacheKeyThrowsException (line 30) | public function testSetInvalidCacheKeyThrowsException()
    method testGetNonExistentReturnsNull (line 39) | public function testGetNonExistentReturnsNull()
    method testGetNonExistentReturnsCustomDefault (line 47) | public function testGetNonExistentReturnsCustomDefault(): void
    method testGetExpiredItemReturnsDefault (line 55) | public function testGetExpiredItemReturnsDefault(): void
    method testGetExpiredItemReturnsCustomDefault (line 65) | public function testGetExpiredItemReturnsCustomDefault(): void
    method testSetGetBooleanValues (line 75) | public function testSetGetBooleanValues()
    method testSetGetZeroTTL (line 87) | public function testSetGetZeroTTL()
    method testSetGetNegativeTTL (line 96) | public function testSetGetNegativeTTL()
    method testSetZeroTTLWithInvalidKeyThrowsException (line 105) | public function testSetZeroTTLWithInvalidKeyThrowsException()
    method testSetNegativeTTLWithInvalidKeyThrowsException (line 114) | public function testSetNegativeTTLWithInvalidKeyThrowsException()
    method testSetValidTtlAsAnIntegerReturnsTheSetValue (line 123) | public function testSetValidTtlAsAnIntegerReturnsTheSetValue()
    method testSetNullTtlReturnsTheSetValue (line 132) | public function testSetNullTtlReturnsTheSetValue()
    method testSetWithDateIntervalTtl (line 141) | public function testSetWithDateIntervalTtl(): void
    method testDeletionOfValidRecord (line 150) | public function testDeletionOfValidRecord()
    method testDeleteInvalidKeyThrowsException (line 161) | public function testDeleteInvalidKeyThrowsException(): void
    method testDeleteNonExistentKeyReturnsTrue (line 170) | public function testDeleteNonExistentKeyReturnsTrue(): void
    method testClear (line 178) | public function testClear()
    method testGetMultiple (line 190) | public function testGetMultiple(): void
    method testSetMultiple (line 208) | public function testSetMultiple(): void
    method testDeleteMultiple (line 220) | public function testDeleteMultiple(): void
    method testHasReturnsTrueForValidCacheRecord (line 236) | public function testHasReturnsTrueForValidCacheRecord(): void
    method testHasReturnsTrueForValidNonNullTtl (line 245) | public function testHasReturnsTrueForValidNonNullTtl(): void
    method testHasReturnsFalseForExpiredCacheRecord (line 254) | public function testHasReturnsFalseForExpiredCacheRecord(): void
    method testHasReturnsFalseForNonExistentCacheRecord (line 264) | public function testHasReturnsFalseForNonExistentCacheRecord(): void
    method testHasThrowsExceptionForInvalidKey (line 272) | public function testHasThrowsExceptionForInvalidKey(): void
    method testGetMultipleWithCustomDefault (line 281) | public function testGetMultipleWithCustomDefault(): void
    method testSetMultipleReturnsFalseWhenOneFails (line 297) | public function testSetMultipleReturnsFalseWhenOneFails(): void
    method testCheckKeyWithInvalidCharactersThrowsException (line 307) | public function testCheckKeyWithInvalidCharactersThrowsException(): void
    method testCheckKeyWithSpecialCharsThrowsException (line 316) | public function testCheckKeyWithSpecialCharsThrowsException(): void
    method testCheckKeyExceeding64CharsThrowsException (line 325) | public function testCheckKeyExceeding64CharsThrowsException(): void
    method testCheckKeyWith64CharsIsValid (line 335) | public function testCheckKeyWith64CharsIsValid(): void
    method testEvictExpiredRemovesExpiredItems (line 345) | public function testEvictExpiredRemovesExpiredItems(): void
    method testEvictExpiredKeepsNullTtlItems (line 365) | public function testEvictExpiredKeepsNullTtlItems(): void
    method testEvictExpiredReturnsZeroWhenNothingToEvict (line 382) | public function testEvictExpiredReturnsZeroWhenNothingToEvict(): void
    method testEvictExpiredOnEmptyCache (line 393) | public function testEvictExpiredOnEmptyCache(): void

FILE: tests/MobileDetectExceptionTest.php
  class MobileDetectExceptionTest (line 19) | final class MobileDetectExceptionTest extends TestCase
    method testIsMobileThrowsExceptionWithCodeWhenNoUserAgent (line 24) | public function testIsMobileThrowsExceptionWithCodeWhenNoUserAgent(): ...
    method testIsTabletThrowsExceptionWithCodeWhenNoUserAgent (line 46) | public function testIsTabletThrowsExceptionWithCodeWhenNoUserAgent(): ...
    method testIsThrowsExceptionWithCodeWhenNoUserAgent (line 66) | public function testIsThrowsExceptionWithCodeWhenNoUserAgent(): void
    method testIsMobileCacheExceptionHasProperCodeAndChain (line 85) | public function testIsMobileCacheExceptionHasProperCodeAndChain(): void
    method testIsTabletCacheExceptionHasProperCodeAndChain (line 122) | public function testIsTabletCacheExceptionHasProperCodeAndChain(): void
    method testIsMagicCacheExceptionHasProperCodeAndChain (line 151) | public function testIsMagicCacheExceptionHasProperCodeAndChain(): void
    method testFullExceptionChainForDebugging (line 183) | public function testFullExceptionChainForDebugging(): void
    method testExceptionCodesAreUniqueForCategorization (line 220) | public function testExceptionCodesAreUniqueForCategorization(): void
    method testRecommendedExceptionHandlingPattern (line 248) | public function testRecommendedExceptionHandlingPattern(): void
    method createFailingCache (line 281) | private function createFailingCache(\Throwable $exceptionToThrow): Cac...
    method buildExceptionChain (line 296) | private function buildExceptionChain(\Throwable $exception): array
    method getRootCause (line 312) | private function getRootCause(\Throwable $exception): \Throwable
    method getErrorTypeFromCode (line 321) | private function getErrorTypeFromCode(int $code): string

FILE: tests/MobileDetectGeneralTest.php
  class MobileDetectGeneralTest (line 13) | final class MobileDetectGeneralTest extends TestCase
    method testClassExists (line 15) | public function testClassExists()
    method testBadMethodCall (line 20) | public function testBadMethodCall()
    method testNoUserAgentSetAndAutoInitOfHttpHeadersIsFalse (line 31) | public function testNoUserAgentSetAndAutoInitOfHttpHeadersIsFalse()
    method testNoUserAgentSet (line 43) | public function testNoUserAgentSet()
    method testEmptyStringAsAUserAgent (line 52) | public function testEmptyStringAsAUserAgent()
    method testAutoInitPicksUpKnownHttpHeaders (line 62) | public function testAutoInitPicksUpKnownHttpHeaders()
    method testValidHeadersThatDoNotContainHttpUserAgentHeaderButNoUserAgentIsManuallySet (line 75) | public function testValidHeadersThatDoNotContainHttpUserAgentHeaderBut...
    method testValidHeadersThatDoNotContainHttpUserAgentHeaderButNoUserAgentIsManuallySetAndAutoInitOfHttpHeadersIsFalse (line 85) | public function testValidHeadersThatDoNotContainHttpUserAgentHeaderBut...
    method testValidHeadersThatContainHttpUserAgentHeaderButNoUserAgentIsManuallySet (line 101) | public function testValidHeadersThatContainHttpUserAgentHeaderButNoUse...
    method testScriptVersion (line 114) | public function testScriptVersion()
    method testBasicMethods (line 126) | public function testBasicMethods()
    method headersProvider (line 168) | public function headersProvider(): array
    method testHeaders (line 210) | public function testHeaders(array $headers)
    method testInvalidHeader (line 233) | public function testInvalidHeader($headers)
    method testEmptyHeaders (line 240) | public function testEmptyHeaders()
    method userAgentProvider (line 247) | public function userAgentProvider(): array
    method testGetUserAgent (line 269) | public function testGetUserAgent($headers, $expectedUserAgent)
    method testSetHttpHeaders (line 280) | public function testSetHttpHeaders()
    method testSetCfHeaders (line 296) | public function testSetCfHeaders()
    method testSetUserAgent (line 333) | public function testSetUserAgent()
    method testSetLongUserAgent (line 340) | public function testSetLongUserAgent()
    method quickHeadersData (line 349) | public function quickHeadersData(): array
    method testQuickHeaders (line 413) | public function testQuickHeaders($headers)
    method quickNonMobileHeadersData (line 421) | public function quickNonMobileHeadersData(): array
    method testNonMobileQuickHeaders (line 451) | public function testNonMobileQuickHeaders($headers)
    method testRules (line 458) | public function testRules()
    method testRulesExtended (line 471) | public function testRulesExtended()

FILE: tests/MobileDetectStandaloneTest.php
  class MobileDetectStandaloneTest (line 13) | final class MobileDetectStandaloneTest extends TestCase
    method testClassExists (line 15) | public function testClassExists()
    method testClassWithDefaultCache (line 23) | public function testClassWithDefaultCache(): void
    method testClassWithCustomCacheKeyFnInvalidKey (line 35) | public function testClassWithCustomCacheKeyFnInvalidKey(): void

FILE: tests/MobileDetectVersionTest.php
  class MobileDetectVersionTest (line 8) | final class MobileDetectVersionTest extends TestCase
    method versionDataProvider (line 10) | public function versionDataProvider(): array
    method testVersionExtraction (line 55) | public function testVersionExtraction($userAgent, $property, $stringVe...
    method crazyVersionNumbers (line 71) | public function crazyVersionNumbers(): array
    method testPrepareVersionNo (line 89) | public function testPrepareVersionNo($raw, $expected)

FILE: tests/MobileDetectWithCacheTest.php
  class MobileDetectWithCacheTest (line 11) | final class MobileDetectWithCacheTest extends TestCase
    method testFlattenHeaders (line 18) | public function testFlattenHeaders()
    method testDefaultCacheClassCreatesACacheRecord (line 38) | public function testDefaultCacheClassCreatesACacheRecord()
    method testDefaultCacheClassCreatesMultipleCacheRecordsForAllCalls (line 51) | public function testDefaultCacheClassCreatesMultipleCacheRecordsForAll...
    method testCustomCacheWithInvalidFnThrowsException (line 86) | public function testCustomCacheWithInvalidFnThrowsException()
    method testCustomCacheForConsecutiveCalls (line 100) | public function testCustomCacheForConsecutiveCalls()
    method testGetCacheKeyIsUsedInConsecutiveCallsIfFoundIn (line 117) | public function testGetCacheKeyIsUsedInConsecutiveCallsIfFoundIn()

FILE: tests/UserAgentTest.php
  class UserAgentTest (line 13) | final class UserAgentTest extends TestCase
    method setUp (line 19) | public function setUp(): void
    method generateJson (line 24) | public static function generateJson()
    method setUpBeforeClass (line 128) | public static function setUpBeforeClass(): void
    method userAgentData (line 151) | public function userAgentData(): array
    method testUserAgents (line 165) | public function testUserAgents($userAgent, $isMobile, $isTablet, $vers...
    method testIsSamsung (line 204) | public function testIsSamsung()

FILE: tests/benchmark/MobileDetectBench.php
  class MobileDetectBench (line 14) | final class MobileDetectBench
    method benchIsMobileAgainstBestMatch (line 25) | public function benchIsMobileAgainstBestMatch(): void
    method benchIsMobileAgainstWorstMatch (line 42) | public function benchIsMobileAgainstWorstMatch(): void
    method benchIsTabletAgainstBestMatch (line 58) | public function benchIsTabletAgainstBestMatch(): void
    method benchIsTabletAgainstWorstMatch (line 73) | public function benchIsTabletAgainstWorstMatch(): void
    method benchIsIOS (line 85) | public function benchIsIOS(): void
    method benchIsIpad (line 97) | public function benchIsIpad(): void
    method benchIsSamsung (line 109) | public function benchIsSamsung(): void
    method benchIsSamsungTablet (line 121) | public function benchIsSamsungTablet(): void
    method benchIsMobileCacheKeyFnSha1AgainstBestMatch (line 131) | public function benchIsMobileCacheKeyFnSha1AgainstBestMatch(): void
Condensed preview — 96 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,200K chars).
[
  {
    "path": ".claude/settings.json",
    "chars": 424,
    "preview": "{\n  \"attribution\": {\n    \"commit\": \"\",\n    \"pr\": \"\"\n  },\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(git:*)\",\n      \"B"
  },
  {
    "path": ".editorconfig",
    "chars": 127,
    "preview": "root = true\n[*]\nend_of_line = lf\ninsert_final_newline = true\n[*.php]\nindent_style = space\nindent_size = 4\nmax_line_lengt"
  },
  {
    "path": ".gitattributes",
    "chars": 247,
    "preview": "* text=auto eol=lf\n.editorconfig           export-ignore\n.gitattributes          export-ignore\n/docs/                  e"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 231,
    "preview": "github: \"serbanghita\"\ncustom:\n - \"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mobiledetectlib%40gmail%"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 483,
    "preview": "---\nname: Bug report\nabout: Create a bug report for Mobile Detect\ntitle: \"[bug]\"\nlabels: 'type: bug'\nassignees: serbangh"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 552,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for Mobile Detect\ntitle: \"[feature]\"\nlabels: feature\nassignees: serbang"
  },
  {
    "path": ".github/docs/index.html",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": ".github/workflows/4.8.x-test.yml",
    "chars": 1506,
    "preview": "name: 4.8.x on PHP 8.x\n\n# Run this workflow every time a new commit pushed to your repository\non:\n  # Allows you to run "
  },
  {
    "path": ".github/workflows/website.yml",
    "chars": 2017,
    "preview": "# Simple workflow for deploying static content to GitHub Pages\nname: website\n\non:\n  # Runs on pushes targeting the defau"
  },
  {
    "path": ".gitignore",
    "chars": 296,
    "preview": "vendor/\nnbproject/\n/*.buildpath\n/*.project\n/.settings\n/error.log\n.idea/\n*.iml\n/coverage\n/phpunit.phar\ncomposer.lock\n/.mo"
  },
  {
    "path": ".php-cs-fixer.php",
    "chars": 384,
    "preview": "<?php\n\n$finder = PhpCsFixer\\Finder::create()\n    ->exclude('docs')\n    ->exclude('.github')\n//    ->notPath('src/Symfony"
  },
  {
    "path": ".phpcs.xml",
    "chars": 784,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" name=\"PHP_CodeSniffer\"\n         xsi"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 4008,
    "preview": "# Change log\n\n# 4.8.10\n\n## Fixed\n- [x] `Cache::has()` now properly checks TTL expiration before returning `true` (PSR-16"
  },
  {
    "path": "CLAUDE.md",
    "chars": 3497,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\nWhen"
  },
  {
    "path": "CNAME",
    "chars": 16,
    "preview": "mobiledetect.net"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3696,
    "preview": "# Contributing\nStep-by-step guide to contributing to Mobile Detect library. \\\nBy contributing to Mobile Detect library y"
  },
  {
    "path": "DOCKER-COMPOSE.md",
    "chars": 3615,
    "preview": "# Docker Compose for Pre-Release Validation\n\nThis document describes the Docker Compose setup for running all necessary "
  },
  {
    "path": "KNOWN_LIMITATIONS.md",
    "chars": 1815,
    "preview": "**Known limitations**\n\n* Mobile Detect script was designed to detect `mobile` devices. Implicitly other devices are cons"
  },
  {
    "path": "LICENSE",
    "chars": 1099,
    "preview": "MIT License\n\nCopyright (c) 2021 Şerban Ghiţă, Nick Ilyin and contributors.\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "MobileDetect.json",
    "chars": 38195,
    "preview": "{\n    \"version\": \"4.8.10\",\n    \"headerMatch\": {\n        \"HTTP_ACCEPT\": {\n            \"matches\": [\n                \"appli"
  },
  {
    "path": "README-EXAMPLES.md",
    "chars": 7265,
    "preview": "# MobileDetect Usage Examples\n\nThis document provides code examples for common MobileDetect usage scenarios.\n\n## Basic U"
  },
  {
    "path": "README.md",
    "chars": 4254,
    "preview": "![Mobile Detect](http://demo.mobiledetect.net/logo-github.png)\n\nMobileDetect, PHP mobile detection class\n==============="
  },
  {
    "path": "SECURITY.md",
    "chars": 486,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n|---------| ------------------ |\n| 2.8.x   | "
  },
  {
    "path": "composer.json",
    "chars": 1226,
    "preview": "{\n    \"name\": \"mobiledetect/mobiledetectlib\",\n    \"type\": \"library\",\n    \"description\": \"Mobile_Detect is a lightweight "
  },
  {
    "path": "docker/Dockerfile.setup",
    "chars": 144,
    "preview": "FROM composer:latest AS build\nWORKDIR /app\nCOPY . .\nCOPY ./docker/build.sh .\nSHELL [\"/bin/bash\", \"-c\"]\nRUN chmod +x buil"
  },
  {
    "path": "docker/build.sh",
    "chars": 215,
    "preview": "echo \"Start building ...\"\nrm -rf vendor/*\nrm -f composer.lock composer.phar\nset -xe\n# Install composer with dev dependen"
  },
  {
    "path": "docker-compose.yml",
    "chars": 2472,
    "preview": "services:\n  setup:\n    build:\n      context: .\n      dockerfile: ./docker/Dockerfile.setup\n    platform: linux/amd64\n   "
  },
  {
    "path": "phpbench.json",
    "chars": 111,
    "preview": "{\n  \"$schema\":\"./vendor/phpbench/phpbench/phpbench.schema.json\",\n  \"runner.bootstrap\": \"vendor/autoload.php\"\n}\n"
  },
  {
    "path": "scripts/dump_magic_methods.php",
    "chars": 379,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Detection\\MobileDetect;\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$detect = ne"
  },
  {
    "path": "scripts/example.php",
    "chars": 792,
    "preview": "<?php\n/**\n * Example using composer's autoloader.\n */\n\nuse Detection\\MobileDetect;\n\nrequire_once  __DIR__ . '/../vendor/"
  },
  {
    "path": "scripts/export_to_json.php",
    "chars": 2016,
    "preview": "<?php\n/**\n * Mobile Detect Library\n * - export -\n * =====================\n *\n * Use the resulting JSON export file in ot"
  },
  {
    "path": "scripts/pre-commit-hook.sh",
    "chars": 304,
    "preview": "#!/bin/sh\n\n# Linting\nvendor/bin/phpcs\nvendor/bin/phpcbf\n\n# Unit tests\nvendor/bin/phpunit -v -c tests/phpunit.xml --cover"
  },
  {
    "path": "scripts/test.php",
    "chars": 1105,
    "preview": "<?php\n\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetect;\n\nrequire __DIR__ . '/../vendor/autoloa"
  },
  {
    "path": "scripts/test_standalone.php",
    "chars": 430,
    "preview": "<?php\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetectStandalone;\n\nrequire_once '../standalone/"
  },
  {
    "path": "src/Cache/Cache.php",
    "chars": 5055,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Detection\\Cache;\n\nuse Psr\\SimpleCache\\CacheInterface;\nuse DateInterval;\nuse D"
  },
  {
    "path": "src/Cache/CacheException.php",
    "chars": 148,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Detection\\Cache;\n\nclass CacheException extends \\Exception implements \\Psr\\Sim"
  },
  {
    "path": "src/Cache/CacheInvalidArgumentException.php",
    "chars": 207,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Detection\\Cache;\n\nuse Psr\\SimpleCache\\InvalidArgumentException;\n\nclass CacheI"
  },
  {
    "path": "src/Exception/MobileDetectException.php",
    "chars": 116,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Detection\\Exception;\n\nclass MobileDetectException extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/Exception/MobileDetectExceptionCode.php",
    "chars": 261,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Detection\\Exception;\n\nclass MobileDetectExceptionCode\n{\n    public const INVA"
  },
  {
    "path": "src/MobileDetect.php",
    "chars": 89484,
    "preview": "<?php\n\n/**\n * Mobile Detect Library\n * Motto: \"Every business should have a mobile detection script to detect mobile rea"
  },
  {
    "path": "src/MobileDetectStandalone.php",
    "chars": 150,
    "preview": "<?php\n\nnamespace Detection;\n\nrequire_once dirname(__FILE__) . '/../standalone/autoloader.php';\n\nclass MobileDetectStanda"
  },
  {
    "path": "standalone/autoloader.php",
    "chars": 1209,
    "preview": "<?php\n$dir = dirname(__FILE__);\n\nspl_autoload_register(function ($class) use ($dir) {\n    $classMap = [\n        // \"mobi"
  },
  {
    "path": "standalone/deps/simple-cache/.editorconfig",
    "chars": 271,
    "preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.o"
  },
  {
    "path": "standalone/deps/simple-cache/.gitattributes",
    "chars": 395,
    "preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
  },
  {
    "path": "standalone/deps/simple-cache/.gitignore",
    "chars": 32,
    "preview": "build\ncomposer.lock\ndocs\nvendor\n"
  },
  {
    "path": "standalone/deps/simple-cache/LICENSE.md",
    "chars": 1137,
    "preview": "# The MIT License (MIT)\n\nCopyright (c) 2016 PHP Framework Interoperability Group\n\n> Permission is hereby granted, free o"
  },
  {
    "path": "standalone/deps/simple-cache/README.md",
    "chars": 563,
    "preview": "PHP FIG Simple Cache PSR\n========================\n\nThis repository holds all interfaces related to PSR-16.\n\nNote that th"
  },
  {
    "path": "standalone/deps/simple-cache/composer.json",
    "chars": 553,
    "preview": "{\n    \"name\": \"psr/simple-cache\",\n    \"description\": \"Common interfaces for simple caching\",\n    \"keywords\": [\"psr\", \"ps"
  },
  {
    "path": "standalone/deps/simple-cache/src/CacheException.php",
    "chars": 173,
    "preview": "<?php\n\nnamespace Psr\\SimpleCache;\n\n/**\n * Interface used for all types of exceptions thrown by the implementing library."
  },
  {
    "path": "standalone/deps/simple-cache/src/CacheInterface.php",
    "chars": 4822,
    "preview": "<?php\n\nnamespace Psr\\SimpleCache;\n\ninterface CacheInterface\n{\n    /**\n     * Fetches a value from the cache.\n     *\n    "
  },
  {
    "path": "standalone/deps/simple-cache/src/InvalidArgumentException.php",
    "chars": 260,
    "preview": "<?php\n\nnamespace Psr\\SimpleCache;\n\n/**\n * Exception interface for invalid cache arguments.\n *\n * When an invalid argumen"
  },
  {
    "path": "tests/CacheTest.php",
    "chars": 10922,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse DateInterval;\nuse Detection\\Cache\\Cache;\nuse Detection\\Cache\\CacheInvalidArgumentE"
  },
  {
    "path": "tests/MobileDetectExceptionTest.php",
    "chars": 12918,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\Cache\\CacheException;\nuse Detection\\Exception\\MobileDetectException;\nuse"
  },
  {
    "path": "tests/MobileDetectGeneralTest.php",
    "chars": 16834,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetect;\nuse PHPUnit"
  },
  {
    "path": "tests/MobileDetectStandaloneTest.php",
    "chars": 1206,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetectStandalone;\nu"
  },
  {
    "path": "tests/MobileDetectVersionTest.php",
    "chars": 3229,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\MobileDetect;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class MobileDetectV"
  },
  {
    "path": "tests/MobileDetectWithCacheTest.php",
    "chars": 4292,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\Cache\\Cache;\nuse Detection\\Exception\\MobileDetectException;\nuse Detectio"
  },
  {
    "path": "tests/UserAgentList.inc.php",
    "chars": 689,
    "preview": "<?php\n\n/**\n * @license     MIT License https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt\n * @link    "
  },
  {
    "path": "tests/UserAgentTest.php",
    "chars": 6657,
    "preview": "<?php\n\nnamespace DetectionTests;\n\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetect;\nuse PHPUnit"
  },
  {
    "path": "tests/benchmark/MobileDetectBench.php",
    "chars": 4621,
    "preview": "<?php\n\nnamespace DetectionTests\\Benchmark;\n\nuse Detection\\Exception\\MobileDetectException;\nuse Detection\\MobileDetect;\n\n"
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 254,
    "preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nrequire_once dirname(__FILE__) . '/../src/MobileDetect.php';\nrequir"
  },
  {
    "path": "tests/phpunit.xml",
    "chars": 839,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xs"
  },
  {
    "path": "tests/providers/vendors/AOC.php",
    "chars": 415,
    "preview": "<?php\n\nreturn [\n    'AOC' => [\n        'Mozilla/5.0 (Linux; Android 4.0.4; MW0922 Build/IMM76D) AppleWebKit/537.36 (KHTM"
  },
  {
    "path": "tests/providers/vendors/Acer.php",
    "chars": 3331,
    "preview": "<?php\n\nreturn [\n    'Acer' => [\n        'Mozilla/5.0 (Linux; U; Android 3.2.1; en-us; A100 Build/HTK55D) AppleWebKit/534"
  },
  {
    "path": "tests/providers/vendors/Alcatel.php",
    "chars": 10566,
    "preview": "<?php\n\nreturn [\n    'Alcatel' => [\n        'Mozilla/5.0 (Linux; U; Android 2.3.7; en-in; MB525 Build/GWK74; CyanogenMod-"
  },
  {
    "path": "tests/providers/vendors/Allview.php",
    "chars": 962,
    "preview": " <?php\n return [\n    'Allview' => [\n\n        'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; ALLVIEW P5 Build/IML74K) Appl"
  },
  {
    "path": "tests/providers/vendors/Amazon.php",
    "chars": 4113,
    "preview": "<?php\n\nreturn [\n    'Amazon' => [\n        'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K) AppleWebKit/5"
  },
  {
    "path": "tests/providers/vendors/Apple.php",
    "chars": 7735,
    "preview": "<?php\n\nreturn [\n    'Apple' => [\n        'iTunes/9.1.1'                                                                 "
  },
  {
    "path": "tests/providers/vendors/Archos.php",
    "chars": 5208,
    "preview": "<?php\n\nreturn [\n   'Archos' => [\n\n        'Mozilla/5.0 (Linux; Android 6.0; Archos 97c Platinum Build/MRA58K) AppleWebKi"
  },
  {
    "path": "tests/providers/vendors/Asus.php",
    "chars": 5906,
    "preview": "<?php\n\nreturn [\n    'ASUS' => [\n        'Mozilla/5.0 (Linux; U; Android 3.2.1; en-us; Transformer TF101 Build/HTK75) App"
  },
  {
    "path": "tests/providers/vendors/Blackberry.php",
    "chars": 8925,
    "preview": "<?php\n\nreturn [\n    'BlackBerry' => [\n        'Mozilla/5.0 (BlackBerry; U; BlackBerry 9300; en) AppleWebKit/534.8+ (KHTM"
  },
  {
    "path": "tests/providers/vendors/Dell.php",
    "chars": 1979,
    "preview": "<?php\n\nreturn [\n    'Dell' => [\n            'Mozilla/5.0 (Linux; U; Android 1.6; en-gb; Dell Streak Build/Donut AppleWeb"
  },
  {
    "path": "tests/providers/vendors/Google.php",
    "chars": 4775,
    "preview": "<?php\n\nreturn [\n    'Google' => [\n        'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit"
  },
  {
    "path": "tests/providers/vendors/HP.php",
    "chars": 1694,
    "preview": "<?php\n\nreturn [\n    'HP' => [\n        'Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-GB) AppleWebKit/534.6 (KHTML, l"
  },
  {
    "path": "tests/providers/vendors/HTC.php",
    "chars": 74616,
    "preview": "<?php\n\nreturn [\n    'HTC' => [\n            'Mozilla/5.0 (X11; Linux x86_64; Z520m; en-ca) AppleWebKit/534.24 (KHTML, lik"
  },
  {
    "path": "tests/providers/vendors/Huawei.php",
    "chars": 7152,
    "preview": "<?php\n\nreturn [\n    'Huawei' => [\n        'Mozilla/5.0 (Linux; U; Android 2.1-update1; bg-bg; Ideos S7 Build/ERE27) Appl"
  },
  {
    "path": "tests/providers/vendors/LG.php",
    "chars": 6623,
    "preview": "<?php\n\nreturn [\n    'LG' => [\n        'Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; LG-VS410PP Build/GRK39F) AppleWebKit"
  },
  {
    "path": "tests/providers/vendors/Lava.php",
    "chars": 3469,
    "preview": "<?php\n\nreturn [\n    'Lava' => [\n        'Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Iris 349 Build/MocorDroid2.3.5) Ap"
  },
  {
    "path": "tests/providers/vendors/Leader.php",
    "chars": 221,
    "preview": "<?php\n\nreturn [\n    [\n        'Mozilla/5.0 (Linux; U; Android 4.2.2; en-au; TBLT10Q-32GB Build/JDQ39) AppleWebKit/534.30"
  },
  {
    "path": "tests/providers/vendors/Lenovo.php",
    "chars": 14760,
    "preview": "<?php\n\nreturn [\n    'Lenovo' => [\n\n        'Mozilla/5.0 (Linux; U; Android 4.0.4; es-es; IdeaTab_A1107 Build/MR1) AppleW"
  },
  {
    "path": "tests/providers/vendors/Mi.php",
    "chars": 4705,
    "preview": "<?php\n\nreturn [\n    'Mi' => [\n        'Mozilla/5.0 (Linux; U; Android 4.2; xx-xx; HM NOTE 1W Build/JDQ39) AppleWebKit/53"
  },
  {
    "path": "tests/providers/vendors/Microsoft.php",
    "chars": 12704,
    "preview": "<?php\n\nreturn [\n    'Microsoft' => [\n        // @See https://github.com/serbanghita/Mobile-Detect/issues/564\n        'Mo"
  },
  {
    "path": "tests/providers/vendors/Motorola.php",
    "chars": 10081,
    "preview": "<?php\n\nreturn [\n    'Motorola' => [\n        'MOT-W510/08.11.05R MIB/BER2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 EGE/1"
  },
  {
    "path": "tests/providers/vendors/Mpman.php",
    "chars": 609,
    "preview": "<?php\n\nreturn [\n    'Mpman' => [\n        'Mozilla/5.0 (Linux; U; Android 4.2.2; en-us; MPDC703 Build/JDQ39) AppleWebKit/"
  },
  {
    "path": "tests/providers/vendors/Nexus.php",
    "chars": 3287,
    "preview": "<?php\n\nreturn [\n    'Nexus' => [\n        // Tablets\n        'Mozilla/5.0 (Linux; Android 6.0; Nexus 9 Build/MRA58N) Appl"
  },
  {
    "path": "tests/providers/vendors/Nokia.php",
    "chars": 21626,
    "preview": "<?php\n\nreturn [\n    'Nokia' => [\n        'Nokia200/2.0 (12.04) Profile/MIDP-2.1 Configuration/CLDC-1.1 UCWEB/2.0 (Java; "
  },
  {
    "path": "tests/providers/vendors/Onda.php",
    "chars": 1304,
    "preview": "<?php\n\nreturn [\n    'Onda' => [\n        'Mozilla/5.0 (Linux; Android 4.2.2; V975i Build/JDQ39) AppleWebKit/537.36 (KHTML"
  },
  {
    "path": "tests/providers/vendors/Others.php",
    "chars": 52477,
    "preview": "<?php\n\nreturn [\n\n    'AdvanDigital' => [\n        'Mozilla/5.0 (Linux; U; Android 4.2.2; en-us; E1C Build/JDQ39) AppleWeb"
  },
  {
    "path": "tests/providers/vendors/Prestigio.php",
    "chars": 1524,
    "preview": "<?php\n\nreturn [\n    'Prestigio' => [\n        'Mozilla/5.0 (Linux; U; Android 4.2.2; en-gb; PMP5297C_QUAD Build/JDQ39) Ap"
  },
  {
    "path": "tests/providers/vendors/Samsung.php",
    "chars": 68967,
    "preview": "<?php\n\nreturn [\n    'Samsung' => [\n        'MQQBrowser/4.0/Mozilla/5.0 (Linux; U; Android 3.2; zh-cn; GT-P6800 Build/HTJ"
  },
  {
    "path": "tests/providers/vendors/Sony.php",
    "chars": 19418,
    "preview": "<?php\n\nreturn [\n    'Sony' => [\n        'SonyEricssonK800i/R1AA Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC"
  },
  {
    "path": "tests/providers/vendors/SpecialCases.php",
    "chars": 27422,
    "preview": "<?php\n\nreturn [\n    'Avant' => [\n        'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; Avant Browser; rv:11.0) like "
  },
  {
    "path": "tests/providers/vendors/Verizon.php",
    "chars": 1254,
    "preview": "<?php\n\nreturn [\n    'Verizon' => [\n        'Mozilla/5.0 (Linux; Android 5.1.1; QTAQZ3 Build/LMY47V) AppleWebKit/537.36 ("
  },
  {
    "path": "tests/providers/vendors/Vodafone.php",
    "chars": 1142,
    "preview": "<?php\n\nreturn [\n    'Vodafone' => [\n        'Mozilla/5.0 (Linux; U; Android 3.2; hu-hu; SmartTab10-MSM8260-V02d-Dec02201"
  },
  {
    "path": "tests/providers/vendors/ZTE.php",
    "chars": 1272,
    "preview": "<?php\n\nreturn [\n    'ZTE' => [\n        'Mozilla/5.0 (Linux; Android 4.0.4; V8200plus Build/IMM76I) AppleWebKit/537.36 (K"
  },
  {
    "path": "tests/ualist.json",
    "chars": 499123,
    "preview": "{\n    \"hash\": \"fed28bf03dc9f17c68fc4c5dec785c2bbf86f320\",\n    \"user_agents\": [\n        {\n            \"vendor\": \"Lava\",\n "
  }
]

About this extraction

This page contains the full source code of the serbanghita/Mobile-Detect GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 96 files (1.1 MB), approximately 373.2k tokens, and a symbol index with 182 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!