view statistics

This commit is contained in:
merdan 2021-03-15 11:23:49 +05:00
parent 4f099ca263
commit 2619410bb4
112 changed files with 30395 additions and 51 deletions

View File

@ -0,0 +1,47 @@
<?php
namespace Composer\Installers;
class WinterInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'modules/{$name}/',
'plugin' => 'plugins/{$vendor}/{$name}/',
'theme' => 'themes/{$name}/'
);
/**
* Format package name.
*
* For package type winter-plugin, cut off a trailing '-plugin' if present.
*
* For package type winter-theme, cut off a trailing '-theme' if present.
*
*/
public function inflectPackageVars($vars)
{
if ($vars['type'] === 'winter-plugin') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'winter-theme') {
return $this->inflectThemeVars($vars);
}
return $vars;
}
protected function inflectPluginVars($vars)
{
$vars['name'] = preg_replace('/^oc-|-plugin$/', '', $vars['name']);
$vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']);
return $vars;
}
protected function inflectThemeVars($vars)
{
$vars['name'] = preg_replace('/^oc-|-theme$/', '', $vars['name']);
return $vars;
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace Composer\Installers\Test;
use Composer\Installers\WinterInstaller;
use Composer\Package\Package;
use Composer\Composer;
use PHPUnit\Framework\TestCase as BaseTestCase;
class WinterInstallerTest extends BaseTestCase
{
/**
* @var WinterInstaller
*/
private $installer;
public function setUp()
{
$this->installer = new WinterInstaller(
new Package('NyanCat', '4.2', '4.2'),
new Composer()
);
}
/**
* @dataProvider packageNameInflectionProvider
*/
public function testInflectPackageVars($type, $vendor, $name, $expectedVendor, $expectedName)
{
$this->assertEquals(
$this->installer->inflectPackageVars(array(
'vendor' => $vendor,
'name' => $name,
'type' => $type
)),
array('vendor' => $expectedVendor, 'name' => $expectedName, 'type' => $type)
);
}
public function packageNameInflectionProvider()
{
return array(
array(
'winter-plugin',
'acme',
'subpagelist',
'acme',
'subpagelist',
),
array(
'winter-plugin',
'acme',
'subpagelist-plugin',
'acme',
'subpagelist',
),
array(
'winter-plugin',
'acme',
'semanticwinter',
'acme',
'semanticwinter',
),
// tests vendor name containing a hyphen
array(
'winter-plugin',
'foo-bar-co',
'blog',
'foobarco',
'blog'
),
// tests that exactly one '-theme' is cut off
array(
'winter-theme',
'acme',
'some-theme-theme',
'acme',
'some-theme',
),
// tests that names without '-theme' suffix stay valid
array(
'winter-theme',
'acme',
'someothertheme',
'acme',
'someothertheme',
),
);
}
}

View File

@ -0,0 +1,52 @@
<?php namespace PolloZen\MostVisited;
use Backend;
use System\Classes\PluginBase;
use RainLab\Blog\Models\Post as PostModel;
/**
* MostVisited Plugin Information File
*/
class Plugin extends PluginBase
{
public $require = ['RainLab.Blog'];
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => 'Most Visited Posts',
'description' => 'Register visit to RainLab Blog publication and retrieve the most visited publications',
'author' => 'PolloZen',
'icon' => 'icon-list-ul'
];
}
/**
* Boot method, called right before the request route.
*
* @return array
*/
public function boot(){
PostModel::extend(function($model){
$model->hasMany['visits'] = ['PolloZen\MostVisited\Models\Visits'];
});
}
/**
* Registers any front-end components implemented in this plugin.
*
* @return array
*/
public function registerComponents(){
return [
'PolloZen\MostVisited\Components\RegisterVisit' => 'registerVisit',
'PolloZen\MostVisited\Components\TopVisited' => 'topPosts',
];
}
}

View File

@ -0,0 +1,58 @@
#Most Visited Post for [RainLab Blog](https://octobercms.com/plugin/rainlab-blog)
Plugin to register visits to [RainLab Blog](https://octobercms.com/plugin/rainlab-blog) Blog Publications and create a list of the most visited posts in a period of time
##Installing the watcher
**IMPORTANT**
In order to register the visit to a Publication `RegisterVisit` component must be added to Post Page
##Create a most visited posts list
Add the `TopVisitedComponent`
This component has parameters
**Most Visited From** - The time period to get the most visited publications
- Today
- Current Week
- Last Week
- All the time
**Category filter**
You can select a category filter, this way you can get the Top 10 from a particular category. If no category is selected, the component will retrieve the top 10 from all your publications
**Top**
How many publications must be retrieved
###Examples
Using these three parameters you can construct different lists. Eg.
- **Last week**, top **10** from **local news**
- **Today** top **5** from **all the site**
- Top **10** from **all the site** in **all the time**
###Displaying the results
The `TopVisitedComponent` inject the **mostVisitedPosts** object
Use as you already use the RainLab blog post
```
{% for post in mostVisitedPosts %}
<div class="post">
<div class="postImage"><img alt="" src="{{post.featured_images[0].path}}"></div>
<div class="post-content">
<h3>{{post.title}}</h3>
<a href="{{post.url}}">Continue reading</a>
</div>
</div>
{% endfor %}
```
##Support and bugs reporting
You can write in the forum or visit me in [Github](https://github.com/sanPuerquitoProgramador/most-visited-posts)

View File

@ -0,0 +1,32 @@
<?php namespace PolloZen\MostVisited\Components;
use Cms\Classes\ComponentBase;
use Carbon\Carbon;
use PolloZen\MostVisited\Models\Visits;
class RegisterVisit extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Register Visit',
'description' => 'Attach this component to your blog post page/partial in order to register the user visit'
];
}
public function defineProperties()
{
return [];
}
public function onRun(){
if($this->page[ 'post' ]){
if($this->page[ 'post' ]->id){
$idPost = $this->page[ 'post' ]->id;
$today = Carbon::today();
$visit = new Visits;
$visit = $visit->firstOrCreate(['post_id'=>$idPost, 'date'=>$today]);
$visit->whereId($visit->id)->increment('visits');
}
}
}
}

View File

@ -0,0 +1,196 @@
<?php namespace PolloZen\MostVisited\Components;
use Cms\Classes\Page;
use Cms\Classes\ComponentBase;
use Carbon\Carbon;
use PolloZen\MostVisited\Models\Visits;
use RainLab\Blog\Models\Post;
use RainLab\Blog\Models\Category;
class TopVisited extends ComponentBase
{
/**
* @var Illuminate\Database\Eloquent\Collection | array
*/
public $mostVisitedPosts;
/**
* Reference to the page name for linking to posts.
* @var string
*/
public $postPage;
/**
* Category filter
*/
public $category;
public function componentDetails()
{
return [
'name' => 'Top Visited Component',
'description' => 'Retrieve the top visited RainLab Blog Posts'
];
}
/**
* Definition of propertys
* @return [array]
*/
public function defineProperties()
{
return [
'period' =>[
'title' => 'Most visited from:',
'description' => '',
'default' => 2,
'type' => 'dropdown',
'options' => [
'1' => 'Today',
'2' => 'Current week',
'3' => 'Yesterday',
'4' => 'Last week',
'5' => 'All time'
],
'showExternalParam' => false
],
'category' =>[
'title' => 'Category Filter',
'description' => 'Filter result by category. All categories by default',
'type' => 'dropdown',
'placeholder' => 'Select a category',
'showExternalParam' => false,
'default' => 0
],
'postPerPage' => [
'title' => 'Top',
'description' => 'How many results must be fetched',
'default' => 5,
'type' => 'string'
],
'postPage' => [
'title' => 'Post page',
'description' => 'Page to show linked posts',
'type' => 'dropdown',
'default' => 'blog/post',
'group' => 'Links',
],
'slug' => [
'title' => 'rainlab.blog::lang.settings.post_slug',
'description' => 'rainlab.blog::lang.settings.post_slug_description',
'default' => '{{ :slug }}',
'type' => 'string',
'group' => 'Links'
]
];
}
/**
* [getPostPageOptions]
* @return [array][Blog]
*/
public function getPostPageOptions()
{
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
}
/**
* [getCategoryOptions]
* @return [array list] [Blog Categories]
*/
public function getCategoryOptions(){
$categories = [0=>'No filter'] + Category::orderBy('name')->lists('name','id');
return $categories;
}
public function onRun(){
$this->prepareVars();
$this->mostVisitedPosts = $this->page['mostVisitedPosts'] = $this->getMostVisitedPosts();
}
/**
* prepare Vars function
* @return [object]
*/
protected function prepareVars() {
/*Get the category filter*/
$this->category = ($this->property('category')!=0) ? $this->property('category') : null;
/* Get post page */
$this->postPage = $this->property('postPage') ? $this->property('postPage') : '404';
/* Top */
$this->postPerPage = is_int($this->property('postPerPage')) ? $this->property('postPerPage') : 5;
}
/**
* getTop Function [Obtiene los Post ID del rango y categorua seleccionados]
* @return [type] [description]
*/
protected function getTop(){
switch($this->property('period')){
case '1':
$dateRange = Carbon::today();
break;
case '2':
$fromDate = Carbon::now()->startOfWeek()->format('Y-m-d');
$toDate = Carbon::now()->endOfWeek()->format('Y-m-d');
break;
case '3':
$dateRange = Carbon::yesterday();
break;
case '4':
$fromDate = Carbon::now()->subDays(7)->startOfWeek()->format('Y/m/d');
$toDate = Carbon::now()->subDays(7)->endOfWeek()->format('Y/m/d');
break;
default:
// Si no hay fecha se toman todos
break;
}
$v = Visits::select('pollozen_mostvisited_visits.post_id');
if(isset($dateRange)){
$v->where('date',$dateRange);
} elseif (isset($fromDate)) {
$v->whereBetween('date', array($fromDate, $toDate));
}
$v ->selectRaw('sum(visits) as visits, count(pollozen_mostvisited_visits.post_id) as touchs')
->groupBy('post_id')
->orderBy('visits','desc');
if($this->category !== null){
$v->join('rainlab_blog_posts_categories',function($join){
$join ->on('pollozen_mostvisited_visits.post_id','=','rainlab_blog_posts_categories.post_id')
->where('rainlab_blog_posts_categories.category_id','=',$this->category);
});
}
$v->limit($this->property('postPerPage'));
$topIds = $v -> lists('post_id');
return $topIds;
}
protected function getMostVisitedPosts(){
/* Obtenemos los ID de los más visitados en el rango solicitado */
$topIds = $this->getTop();
if(count($topIds)!=0){
$placeholders = implode(',', array_fill(0, count($topIds), '?')) ;
/* Empezamos con el objeto de los posts en general que estén publicados*/
$p = Post::isPublished();
$p->whereIn('id', $topIds);
$p->orderByRaw("FIELD(id,{$placeholders})",$topIds);
$mostVisitedPosts = $p->get();
/* Agregamos el helper de la URL*/
$mostVisitedPosts->each(function($post) {
$post->setUrl($this->postPage,$this->controller);
});
/* Mandamos los resultados */
return $mostVisitedPosts;
} else {
return [];
}
}
}

View File

@ -0,0 +1 @@
<p>Nothing to see here. This component is added to the post page in order to register the visit</p>

View File

@ -0,0 +1,18 @@
{% set mostVisitedPosts = __SELF__.mostVisitedPosts %}
<div class="popular-post">
<h3>Top Publications</h3>
<div>
<ul>
{% for post in mostVisitedPosts %}
<li>
<div>
<div class="thumb"><img alt="" src="{{post.featured_images[0].path}}"></div>
<div class="post-content">
<h3><a href="{{post.url}}">{{post.title}}</a></h3>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>

View File

@ -0,0 +1,37 @@
<?php namespace PolloZen\MostVisited\Models;
use Model;
// use RainLab\Blog\Models\Category as BlogCategory;
use RainLab\Blog\Models\Post;
use Config;
/**
* Visits Model
*/
class Visits extends Model
{
/**
* @var string The database table used by the model.
*/
public $table = 'pollozen_mostvisited_visits';
/**
* @var array Guarded fields
*/
protected $guarded = ['*'];
/**
* @var array Fillable fields
*/
protected $fillable = ['post_id','date'];
/**
* @var array Relations
*/
public $belongsTo = [
'post' => ['Rainlab\Blog\Models\Post']
];
}

View File

@ -0,0 +1,8 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: ID
searchable: true

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true

View File

@ -0,0 +1,27 @@
<?php namespace PolloZen\MostVisited\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
class CreateVisitsTable extends Migration
{
public function up()
{
Schema::create('pollozen_mostvisited_visits', function(Blueprint $table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('post_id')->unsigned();
$table->date('date');
$table->smallInteger('visits')->unsigned()->default(0);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('pollozen_mostvisited_visits');
}
}

View File

@ -0,0 +1,5 @@
1.0.1:
- First version of MostVisited. Create the Visitis table
- create_visits_table.php
1.0.2:
- Improve a new query to get the top visited post. Same results in 4x faster

View File

@ -0,0 +1,19 @@
# MIT license
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.

View File

@ -0,0 +1,228 @@
<?php namespace Vdomah\BlogViews;
use Db;
use Cookie;
use Event;
use Session;
use System\Classes\PluginBase;
use Rainlab\Blog\Components\Post as PostComponent;
use Rainlab\Blog\Models\Post as PostModel;
use Cms\Classes\Controller;
use DeviceDetector\Parser\Bot as BotParser;
use Vdomah\BlogViews\Components\Popular;
use Vdomah\BlogViews\Components\Views;
use Vdomah\BlogViews\Models\Settings;
/**
* BlogViews Plugin Information File
*/
class Plugin extends PluginBase
{
const POST_VIEWED = 'vdomah-blog-post-viewed';
const EVENT_BEFORE_TRACK = 'blogviews.before.track';
const PARAM_TRACK_PREVENT = 'trackPrevent';
const PARAM_TRACK_BOT = 'trackBot';
/**
* @var array Require the RainLab.Blog plugin
*/
public $require = ['RainLab.Blog'];
/**
* @var string Table to store post views count
*/
public $table_views = 'vdomah_blogviews_views';
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => 'vdomah.blogviews::lang.plugin.name',
'description' => 'vdomah.blogviews::lang.plugin.description',
'author' => 'Art Gek',
'icon' => 'icon-signal',
'homepage' => 'https://github.com/vdomah/blogviews'
];
}
/**
* Registers any front-end components implemented in this plugin.
*
* @return array
*/
public function registerComponents()
{
return [
Views::class => 'views',
Popular::class => 'popularPosts'
];
}
public function boot()
{
PostComponent::extend(function($component) {
if ($this->app->runningInBackend() || !Controller::getController()) {
return;
}
$result = Event::fire(self::EVENT_BEFORE_TRACK, [$this, $component]);
$preventTrack = isset($result[0][self::PARAM_TRACK_PREVENT]) && $result[0][self::PARAM_TRACK_PREVENT];
if ($preventTrack)
return;
$trackBot = isset($result[0][self::PARAM_TRACK_BOT]) && $result[0][self::PARAM_TRACK_BOT];
if ($this->isBot() && !$trackBot) {
// do not do anything if a bot is detected
return;
}
$post = $this->getPost($component);
if (!is_null($post)) {
$this->track($post);
}
return true;
});
PostModel::extend(function($model) {
$model->addDynamicMethod('getViewsAttribute', function() use ($model) {
$obj = Db::table('vdomah_blogviews_views')
->where('post_id', $model->getKey());
$out = 0;
if ($obj->count() > 0) {
$out = $obj->first()->views;
}
return $out;
});
});
}
private function setViews($post, $views = null)
{
$obj = Db::table($this->table_views)
->where('post_id', $post->getKey());
if ($obj->count() > 0) {
$row = $obj->first();
if (!$views) {
$views = ++$row->views;
}
$obj->update(['views' => $views]);
} else {
if (!$views) {
$views = 1;
}
Db::table($this->table_views)->insert([
'post_id' => $post->getKey(),
'views' => $views
]);
}
}
private function isBot()
{
$is_bot = false;
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$botParser = new BotParser();
$botParser->setUserAgent($_SERVER['HTTP_USER_AGENT']);
$botParser->discardDetails();
$is_bot = $botParser->parse();
}
return $is_bot;
}
/*
* Getting slug value using logic from Cms\Classes\Controller setComponentPropertiesFromParams
*
* @return PostModel
*/
private function getPost($component)
{
$slugValue = $component->property('slug');
$routerParameters = Controller::getController()->getRouter()->getParameters();
$slugValueFromUrl = null;
if (preg_match('/^\{\{([^\}]+)\}\}$/', $slugValue, $matches)) {
$paramName = trim($matches[1]);
if (substr($paramName, 0, 1) == ':') {
$routeParamName = substr($paramName, 1);
$slugValueFromUrl = array_key_exists($routeParamName, $routerParameters)
? $routerParameters[$routeParamName]
: null;
}
}
if (!$slugValueFromUrl)
return null;
$post = PostModel::whereSlug($slugValueFromUrl)->first();
return $post;
}
private function track($post)
{
if (Settings::get('double_tracking_prevent_method', 'cookie') == 'session') {
$this->setViewsSession($post);
} else {
$this->setViewsCookie($post);
}
}
private function setViewsSession($post)
{
if (!Session::has(self::POST_VIEWED)) {
Session::put(self::POST_VIEWED, []);
}
if (!in_array($post->getKey(), Session::get(self::POST_VIEWED))) {
$this->setViews($post);
Session::push(self::POST_VIEWED, $post->getKey());
}
}
private function setViewsCookie($post)
{
$cookName = self::POST_VIEWED . '-' . $post->getKey();
if (Cookie::get( $cookName, 0 ) == 0) {
$this->setViews($post);
Cookie::queue( $cookName, '1', 525000 );
}
}
public function registerSettings()
{
return [
'config' => [
'label' => 'vdomah.blogviews::lang.plugin.name',
'description' => 'vdomah.blogviews::lang.plugin.description_settings',
'category' => 'rainlab.blog::lang.blog.menu_label',
'icon' => 'icon-signal',
'class' => Settings::class,
'order' => 501,
'permissions' => [
'blogviews-menu-settings',
],
],
];
}
}

View File

@ -0,0 +1,15 @@
# Blog Views Extension
This plugin is an extension to the [RainLab.Blog](https://github.com/rainlab/blog-plugin) plugin. This extension add views tracking to blog posts and allows to add views counter to a post and display popular posts widget.
Adds dynamic views property to Post model: {{ post.views }}
### Components
#### Popular Posts
The `popularPosts` component can be used to display top viewed posts.
- **postsLimit** - The max number of posts to show.
- **postPage** - Reference to the page name for linking to posts.
- **noPostsMessage** - Message to display when there are no posts.
#### Views (counter)
The `Views` component can be used to display post views number. In most cases use just {{ post.views }} without this component.

View File

@ -0,0 +1,141 @@
<?php namespace Vdomah\BlogViews\Components;
use Cms\Classes\ComponentBase;
use Rainlab\Blog\Models\Post as BlogPost;
use Rainlab\Blog\Models\Category as BlogCategory;
use Cms\Classes\Page;
class Popular extends ComponentBase
{
/**
* @var Rainlab\Blog\Models\Post The post model used for display.
*/
public $posts;
/**
* Message to display when there are no posts.
* @var string
*/
public $noPostsMessage;
/**
* Reference to the page name for linking to posts.
* @var string
*/
public $postPage;
/**
* The max number of posts to show.
* @var string
*/
public $postsLimit;
public function componentDetails()
{
return [
'name' => 'vdomah.blogviews::lang.component.popular_name',
'description' => 'vdomah.blogviews::lang.component.popular_description'
];
}
public function defineProperties()
{
return [
'category' => [
'title' => 'vdomah.blogviews::lang.properties.category',
'type' => 'dropdown',
'default' => '{{ :category }}',
],
'postsLimit' => [
'title' => 'vdomah.blogviews::lang.properties.posts_limit',
'type' => 'string',
'validationPattern' => '^[0-9]+$',
'validationMessage' => 'vdomah.blogviews::lang.properties.posts_limit_validation',
'default' => '3',
],
'noPostsMessage' => [
'title' => 'vdomah.blogviews::lang.properties.posts_no_posts',
'description' => 'vdomah.blogviews::lang.properties.posts_no_posts_description',
'type' => 'string',
'default' => 'No posts found',
'showExternalParam' => false
],
'postPage' => [
'title' => 'vdomah.blogviews::lang.properties.posts_post',
'description' => 'vdomah.blogviews::lang.properties.posts_post_description',
'type' => 'dropdown',
'default' => 'blog/post',
'group' => 'Links',
],
];
}
public function getCategoryOptions()
{
return array_merge(
[
null => e(trans('vdomah.blogviews::lang.properties.all_option')),
0 => e(trans('vdomah.blogviews::lang.properties.no_option'))
],
BlogCategory::lists('name', 'slug')
);
}
public function onRun()
{
$this->prepareVars();
$this->posts = $this->page['posts'] = $this->listPosts();
}
protected function listPosts()
{
/*
* List all the posts
*/
$query = BlogPost::isPublished()
->leftJoin('vdomah_blogviews_views as pv', 'pv.post_id', '=', 'rainlab_blog_posts.id')
;
$category_slug = $this->property('category');
if ((is_string($category_slug) && strlen($category_slug) == 0) || $category_slug === false)
$category_slug = null;
if ($category_slug !== null) {
if ($category_slug == 0)
$query = $query->has('categories', '=', 0);
elseif ($category_slug > 0)
$query->whereHas('categories', function($q) use ($category_slug) {
$q->where('slug', $category_slug);
});
}
$query = $query->orderBy('views', 'DESC')
->limit($this->postsLimit)
;
$posts = $query->get();
$posts->each(function($post) {
$post->setUrl($this->postPage, $this->controller);
});
return $posts;
}
protected function prepareVars()
{
$this->postsLimit = $this->page['postsLimit'] = $this->property('postsLimit');
$this->noPostsMessage = $this->page['noPostsMessage'] = $this->property('noPostsMessage');
/*
* Page links
*/
$this->postPage = $this->page['postPage'] = $this->property('postPage');
}
public function getPostPageOptions()
{
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
}
}

View File

@ -0,0 +1,63 @@
<?php namespace Vdomah\BlogViews\Components;
use Cms\Classes\ComponentBase;
use Rainlab\Blog\Models\Post as BlogPost;
use Db;
class Views extends ComponentBase
{
/**
* @var Rainlab\Blog\Models\Post The post model used for display.
*/
public $post;
public function componentDetails()
{
return [
'name' => 'vdomah.blogviews::lang.component.views_name',
'description' => 'vdomah.blogviews::lang.component.views_description'
];
}
public function defineProperties()
{
return [
'slug' => [
'title' => 'rainlab.blog::lang.properties.post_slug',
'description' => 'rainlab.blog::lang.properties.post_slug_description',
'default' => '{{ :slug }}',
'type' => 'string'
]
];
}
public function onRun()
{
$this->views = $this->page['views'] = $this->getViews();
}
protected function loadPost()
{
$slug = $this->property('slug');
$post = BlogPost::isPublished()->where('slug', $slug)->first();
return $post;
}
protected function getViews()
{
$out = 0;
$post = $this->loadPost();
if(!is_null($post)) {
$obj = Db::table('vdomah_blogviews_views')
->where('post_id', $post->getKey());
if ($obj->count() > 0) {
$out = $obj->first()->views;
}
}
return $out;
}
}

View File

@ -0,0 +1,12 @@
{% set posts = __SELF__.posts %}
<ul>
{% for post in posts %}
<li>
<div>
<a href="{{ post.url }}">{{ post.title }}</a>
<div class="meta"><span class="date">{{ post.published_at|date('M d, Y') }}</span> </div>
</div>
</li>
{% endfor %}
</ul>

View File

@ -0,0 +1,3 @@
{% set post = __SELF__.post %}
Views: {{ views }}

View File

@ -0,0 +1,35 @@
<?php
return [
'plugin' => [
'name' => 'Blog Views',
'description' => 'The plugin enables blog posts views tracking and displaying popular articles.',
'description_settings' => 'Blog Views tracking settings',
],
'properties' => [
'category' => 'Category',
'all_option' => '- All categories -',
'no_option' => '- With no category -',
'posts_limit' => 'Limit',
'posts_limit_validation' => 'Invalid format of the limit value',
'posts_no_posts' => 'No posts message',
'posts_no_posts_description' => 'Message to display in the blog post popular list in case if there are no posts. This property is used by the default component partial.',
'posts_post' => 'Post page',
'posts_post_description' => 'Name of the blog post page file for the "Learn more" links. This property is used by the default component partial.'
],
'post' => [
'tab_views' => 'Views'
],
'component' => [
'popular_name' => 'Popular Posts',
'popular_description' => 'Most viewed posts list',
'views_name' => 'Post Views',
'views_description' => 'Show post views'
],
'settings' => [
'double_tracking_prevent_method' => 'Double tracking prevent method',
'double_tracking_prevent_method_comment' => 'What method to use to store user\'s id. For preventing tracking his visit multiple times',
'cookie' => 'Cookie',
'session' => 'Session',
],
];

View File

@ -0,0 +1,12 @@
<?php namespace Vdomah\BlogViews\Models;
use October\Rain\Database\Model;
use System\Classes\PluginManager;
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'vdomah_blogviews_settings';
public $settingsFields = 'fields.yaml';
}

View File

@ -0,0 +1,10 @@
fields:
double_tracking_prevent_method:
span: left
label: vdomah.blogviews::lang.settings.double_tracking_prevent_method
type: dropdown
default: cookie
comment: vdomah.blogviews::lang.settings.double_tracking_prevent_method_comment
options:
cookie: vdomah.blogviews::lang.settings.cookie
session: vdomah.blogviews::lang.settings.session

View File

@ -0,0 +1,23 @@
<?php namespace Vdomah\User\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class CreatePostViewsTable extends Migration
{
public function up()
{
Schema::create('vdomah_blogviews_views', function($table)
{
$table->engine = 'InnoDB';
$table->integer('views');
$table->integer('post_id')->unsigned()->nullable()->index();
$table->primary(array('post_id'));
});
}
public function down()
{
Schema::dropIfExists('vdomah_blogviews_views');
}
}

View File

@ -0,0 +1,31 @@
1.0.0:
- First version of BlogViews
- create_post_views_table.php
1.0.1:
- Limit and link params fixed
1.0.2:
- Added 'views' dynamic property to Post model
1.0.3:
- Improve the translation. Add MIT license. Pull request by gergo85
1.0.4:
- Default value to 0 in getViewsAttribute (thanks to hambern)
1.0.5:
- Add support for whatever slug url name parameter like {{ :slug_some_custom_url_parameter }}
1.0.6:
- Ability to choose blog category for Popular posts
1.0.7:
- Dynamic category slug url parameter. Only published posts in Popular component
1.0.8:
- "Fixed error using blogviews with sitemap: check if Controller instantiated before extend"
1.0.9:
- "Get url parameters directly from Controller instance without component. Using Cookie instead of Session"
1.0.10:
- "Fix bug when post is not found."
1.0.11:
- Don't count web crawler bots visits.
1.0.12:
- "blogviews.before.track added with trackPrevent and trackBot options."
1.0.13:
- "Both Cookie and Session methods available to prevent doubletracking"
1.0.14:
- "Fix: bot detection if no HTTP_USER_AGENT is present in request"

View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit71a0c07406b6d528b5494257df58789c::getLoader();

View File

@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.

View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,10 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'DeviceDetector\\' => array($vendorDir . '/piwik/device-detector'),
);

View File

@ -0,0 +1,70 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit71a0c07406b6d528b5494257df58789c
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit71a0c07406b6d528b5494257df58789c', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit71a0c07406b6d528b5494257df58789c', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit71a0c07406b6d528b5494257df58789c::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit71a0c07406b6d528b5494257df58789c::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire71a0c07406b6d528b5494257df58789c($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire71a0c07406b6d528b5494257df58789c($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

View File

@ -0,0 +1,35 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit71a0c07406b6d528b5494257df58789c
{
public static $files = array (
'04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php',
);
public static $prefixLengthsPsr4 = array (
'D' =>
array (
'DeviceDetector\\' => 15,
),
);
public static $prefixDirsPsr4 = array (
'DeviceDetector\\' =>
array (
0 => __DIR__ . '/..' . '/piwik/device-detector',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit71a0c07406b6d528b5494257df58789c::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit71a0c07406b6d528b5494257df58789c::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View File

@ -0,0 +1,111 @@
[
{
"name": "mustangostang/spyc",
"version": "0.6.3",
"version_normalized": "0.6.3.0",
"source": {
"type": "git",
"url": "git@github.com:mustangostang/spyc.git",
"reference": "4627c838b16550b666d15aeae1e5289dd5b77da0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0",
"reference": "4627c838b16550b666d15aeae1e5289dd5b77da0",
"shasum": ""
},
"require": {
"php": ">=5.3.1"
},
"require-dev": {
"phpunit/phpunit": "4.3.*@dev"
},
"time": "2019-09-10T13:16:29+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.5.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"Spyc.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "mustangostang",
"email": "vlad.andersen@gmail.com"
}
],
"description": "A simple YAML loader/dumper class for PHP",
"homepage": "https://github.com/mustangostang/spyc/",
"keywords": [
"spyc",
"yaml",
"yml"
]
},
{
"name": "piwik/device-detector",
"version": "3.12.4",
"version_normalized": "3.12.4.0",
"source": {
"type": "git",
"url": "https://github.com/matomo-org/device-detector.git",
"reference": "6a92e45a55eb507f53581e9add7dc82835ad6424"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/6a92e45a55eb507f53581e9add7dc82835ad6424",
"reference": "6a92e45a55eb507f53581e9add7dc82835ad6424",
"shasum": ""
},
"require": {
"mustangostang/spyc": "*",
"php": ">=5.5"
},
"require-dev": {
"fabpot/php-cs-fixer": "~1.7",
"matthiasmullie/scrapbook": "@stable",
"phpunit/phpunit": "^4.8.36",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0"
},
"suggest": {
"doctrine/cache": "Can directly be used for caching purpose",
"ext-yaml": "Necessary for using the Pecl YAML parser"
},
"time": "2020-03-31T08:53:12+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"DeviceDetector\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The Matomo Team",
"email": "hello@matomo.org",
"homepage": "https://matomo.org/team/"
}
],
"description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, media players, mobile apps, feed readers, libraries, etc), operating systems, devices, brands and models.",
"homepage": "https://matomo.org",
"keywords": [
"devicedetection",
"parser",
"useragent"
]
}
]

View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011 Vladimir Andersen
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.

View File

@ -0,0 +1,30 @@
**Spyc** is a YAML loader/dumper written in pure PHP. Given a YAML document, Spyc will return an array that
you can use however you see fit. Given an array, Spyc will return a string which contains a YAML document
built from your data.
**YAML** is an amazingly human friendly and strikingly versatile data serialization language which can be used
for log files, config files, custom protocols, the works. For more information, see http://www.yaml.org.
Spyc supports YAML 1.0 specification.
## Using Spyc
Using Spyc is trivial:
```php
<?php
require_once "spyc.php";
$Data = Spyc::YAMLLoad('spyc.yaml');
```
or (if you prefer functional syntax)
```php
<?php
require_once "spyc.php";
$Data = spyc_load_file('spyc.yaml');
```
## Donations, anyone?
If you find Spyc useful, I'm accepting Bitcoin donations (who doesn't these days?) at 193bEkLP7zMrNLZm9UdUet4puGD5mQiLai

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "mustangostang/spyc",
"description": "A simple YAML loader/dumper class for PHP",
"type": "library",
"keywords": [
"spyc",
"yaml",
"yml"
],
"homepage": "https://github.com/mustangostang/spyc/",
"authors" : [{
"name": "mustangostang",
"email": "vlad.andersen@gmail.com"
}],
"license": "MIT",
"require": {
"php": ">=5.3.1"
},
"autoload": {
"files": [ "Spyc.php" ]
},
"require-dev": {
"phpunit/phpunit": "4.3.*@dev"
},
"extra": {
"branch-alias": {
"dev-master": "0.5.x-dev"
}
}
}

View File

@ -0,0 +1,25 @@
<?php
#
# S P Y C
# a simple php yaml class
#
# Feel free to dump an array to YAML, and then to load that YAML back into an
# array. This is a good way to test the limitations of the parser and maybe
# learn some basic YAML.
#
include('../Spyc.php');
$array[] = 'Sequence item';
$array['The Key'] = 'Mapped value';
$array[] = array('A sequence','of a sequence');
$array[] = array('first' => 'A sequence','second' => 'of mapped values');
$array['Mapped'] = array('A sequence','which is mapped');
$array['A Note'] = 'What if your text is too long?';
$array['Another Note'] = 'If that is the case, the dumper will probably fold your text by using a block. Kinda like this.';
$array['The trick?'] = 'The trick is that we overrode the default indent, 2, to 4 and the default wordwrap, 40, to 60.';
$array['Old Dog'] = "And if you want\n to preserve line breaks, \ngo ahead!";
$array['key:withcolon'] = "Should support this to";
$yaml = Spyc::YAMLDump($array,4,60);

View File

@ -0,0 +1,21 @@
<?php
#
# S P Y C
# a simple php yaml class
#
# license: [MIT License, http://www.opensource.org/licenses/mit-license.php]
#
include('../Spyc.php');
$array = Spyc::YAMLLoad('../spyc.yaml');
echo '<pre><a href="spyc.yaml">spyc.yaml</a> loaded into PHP:<br/>';
print_r($array);
echo '</pre>';
echo '<pre>YAML Data dumped back:<br/>';
echo Spyc::YAMLDump($array);
echo '</pre>';

View File

@ -0,0 +1,17 @@
<?php
php5to4 ("../spyc.php", 'spyc-latest.php4');
function php5to4 ($src, $dest) {
$code = file_get_contents ($src);
$code = preg_replace ('#(public|private|protected)\s+\$#i', 'var \$', $code);
$code = preg_replace ('#(public|private|protected)\s+static\s+\$#i', 'var \$', $code);
$code = preg_replace ('#(public|private|protected)\s+function#i', 'function', $code);
$code = preg_replace ('#(public|private|protected)\s+static\s+function#i', 'function', $code);
$code = preg_replace ('#throw new Exception\\(([^)]*)\\)#i', 'trigger_error($1,E_USER_ERROR)', $code);
$code = str_replace ('self::', '$this->', $code);
$f = fopen ($dest, 'w');
fwrite($f, $code);
fclose ($f);
print "Written to $dest.\n";
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
<?php
#
# S P Y C
# a simple php yaml class
# v0.3
#
# author: [chris wanstrath, chris@ozmm.org]
# websites: [http://www.yaml.org, http://spyc.sourceforge.net/]
# license: [MIT License, http://www.opensource.org/licenses/mit-license.php]
# copyright: (c) 2005-2006 Chris Wanstrath
#
# We're gonna load a file into memory and see if we get what we expect.
# If not, we're gonna complain.
#
# Pretty lo-fi. Let's see if we can't get some unit testing going in the next,
# I dunno, 20 months? Alright. Go team.
#
error_reporting(E_ALL);
include('spyc.php4');
$yaml = Spyc::YAMLLoad('../spyc.yaml');
// print_r ($yaml);
# Added in .2
if ($yaml[1040] != "Ooo, a numeric key!")
die('Key: 1040 failed');
# Test mappings / types
if ($yaml['String'] != "Anyone's name, really.")
die('Key: String failed');
if ($yaml['Int'] !== 13)
die('Key: Int failed');
if ($yaml['True'] !== true)
die('Key: True failed');
if ($yaml['False'] !== false)
die('Key: False failed');
if ($yaml['Zero'] !== 0)
die('Key: Zero failed');
if (isset($yaml['Null']))
die('Key: Null failed');
if ($yaml['Float'] !== 5.34)
die('Key: Float failed');
# Test sequences
if ($yaml[0] != "PHP Class")
die('Sequence 0 failed');
if ($yaml[1] != "Basic YAML Loader")
die('Sequence 1 failed');
if ($yaml[2] != "Very Basic YAML Dumper")
die('Sequence 2 failed');
# A sequence of a sequence
if ($yaml[3] != array("YAML is so easy to learn.",
"Your config files will never be the same."))
die('Sequence 3 failed');
# Sequence of mappings
if ($yaml[4] != array("cpu" => "1.5ghz", "ram" => "1 gig",
"os" => "os x 10.4.1"))
die('Sequence 4 failed');
# Mapped sequence
if ($yaml['domains'] != array("yaml.org", "php.net"))
die("Key: 'domains' failed");
# A sequence like this.
if ($yaml[5] != array("program" => "Adium", "platform" => "OS X",
"type" => "Chat Client"))
die('Sequence 5 failed');
# A folded block as a mapped value
if ($yaml['no time'] != "There isn't any time for your tricks!\nDo you understand?")
die("Key: 'no time' failed");
# A literal block as a mapped value
if ($yaml['some time'] != "There is nothing but time\nfor your tricks.")
die("Key: 'some time' failed");
# Crazy combinations
if ($yaml['databases'] != array( array("name" => "spartan", "notes" =>
array( "Needs to be backed up",
"Needs to be normalized" ),
"type" => "mysql" )))
die("Key: 'databases' failed");
# You can be a bit tricky
if ($yaml["if: you'd"] != "like")
die("Key: 'if: you\'d' failed");
# Inline sequences
if ($yaml[6] != array("One", "Two", "Three", "Four"))
die("Sequence 6 failed");
# Nested Inline Sequences
if ($yaml[7] != array("One", array("Two", "And", "Three"), "Four", "Five"))
die("Sequence 7 failed");
# Nested Nested Inline Sequences
if ($yaml[8] != array( "This", array("Is", "Getting", array("Ridiculous", "Guys")),
"Seriously", array("Show", "Mercy")))
die("Sequence 8 failed");
# Inline mappings
if ($yaml[9] != array("name" => "chris", "age" => "young", "brand" => "lucky strike"))
die("Sequence 9 failed");
# Nested inline mappings
if ($yaml[10] != array("name" => "mark", "age" => "older than chris",
"brand" => array("marlboro", "lucky strike")))
die("Sequence 10 failed");
# References -- they're shaky, but functional
if ($yaml['dynamic languages'] != array('Perl', 'Python', 'PHP', 'Ruby'))
die("Key: 'dynamic languages' failed");
if ($yaml['compiled languages'] != array('C/C++', 'Java'))
die("Key: 'compiled languages' failed");
if ($yaml['all languages'] != array(
array('Perl', 'Python', 'PHP', 'Ruby'),
array('C/C++', 'Java')
))
die("Key: 'all languages' failed");
# Added in .2.2: Escaped quotes
if ($yaml[11] != "you know, this shouldn't work. but it does.")
die("Sequence 11 failed.");
if ($yaml[12] != "that's my value.")
die("Sequence 12 failed.");
if ($yaml[13] != "again, that's my value.")
die("Sequence 13 failed.");
if ($yaml[14] != "here's to \"quotes\", boss.")
die("Sequence 14 failed.");
if ($yaml[15] != array( 'name' => "Foo, Bar's", 'age' => 20))
die("Sequence 15 failed.");
if ($yaml[16] != array( 0 => "a", 1 => array (0 => 1, 1 => 2), 2 => "b"))
die("Sequence 16 failed.");
if ($yaml['endloop'] != "Does this line in the end indeed make Spyc go to an infinite loop?")
die("[endloop] failed.");
print "spyc.yaml parsed correctly\n";
?>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="all">
<directory suffix="Test.php">tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,219 @@
#
# S P Y C
# a simple php yaml class
#
# authors: [vlad andersen (vlad.andersen@gmail.com), chris wanstrath (chris@ozmm.org)]
# websites: [http://www.yaml.org, http://spyc.sourceforge.net/]
# license: [MIT License, http://www.opensource.org/licenses/mit-license.php]
# copyright: (c) 2005-2006 Chris Wanstrath, 2006-2014 Vlad Andersen
#
# spyc.yaml - A file containing the YAML that Spyc understands.
---
# Mappings - with proper types
String: Anyone's name, really.
Int: 13
BadHex: f0xf3
Hex: 0xf3
True: true
False: false
Zero: 0
Null: NULL
NotNull: 'null'
NotTrue: 'y'
NotBoolTrue: 'true'
NotInt: '5'
Float: 5.34
Negative: -90
SmallFloat: 0.7
NewLine: \n
QuotedNewLine: "\n"
# A sequence
- PHP Class
- Basic YAML Loader
- Very Basic YAML Dumper
# A sequence of a sequence
-
- YAML is so easy to learn.
- Your config files will never be the same.
# Sequence of mappings
-
cpu: 1.5ghz
ram: 1 gig
os : os x 10.4.1
# Mapped sequence
domains:
- yaml.org
- php.net
# A sequence like this.
- program: Adium
platform: OS X
type: Chat Client
# A folded block as a mapped value
no time: >
There isn't any time
for your tricks!
Do you understand?
# A literal block as a mapped value
some time: |
There is nothing but time
for your tricks.
# Crazy combinations
databases:
- name: spartan
notes:
- Needs to be backed up
- Needs to be normalized
type: mysql
# You can be a bit tricky
"if: you'd": like
# Inline sequences
- [One, Two, Three, Four]
# Nested Inline Sequences
- [One, [Two, And, Three], Four, Five]
# Nested Nested Inline Sequences
- [This, [Is, Getting, [Ridiculous, Guys]], Seriously, [Show, Mercy]]
# Inline mappings
- {name: chris, age: young, brand: lucky strike}
# Nested inline mappings
- {name: mark, age: older than chris, brand: [marlboro, lucky strike]}
# References -- they're shaky, but functional
dynamic languages: &DLANGS
- Perl
- Python
- PHP
- Ruby
compiled languages: &CLANGS
- C/C++
- Java
all languages:
- *DLANGS
- *CLANGS
# Added in .2.2: Escaped quotes
- you know, this shouldn't work. but it does.
- 'that''s my value.'
- 'again, that\'s my value.'
- "here's to \"quotes\", boss."
# added in .2.3
- {name: "Foo, Bar's", age: 20}
# Added in .2.4: bug [ 1418193 ] Quote Values in Nested Arrays
- [a, ['1', "2"], b]
# Add in .5.2: Quoted new line values.
- "First line\nSecond line\nThird line"
# Added in .2.4: malformed YAML
all
javascripts: [dom1.js, dom.js]
# Added in .2
1040: Ooo, a numeric key! # And working comments? Wow! Colons in comments: a menace (0.3).
hash_1: Hash #and a comment
hash_2: "Hash #and a comment"
"hash#3": "Hash (#) can appear in key too"
float_test: 1.0
float_test_with_quotes: '1.0'
float_inverse_test: 001
a_really_large_number: 115792089237316195423570985008687907853269984665640564039457584007913129639936 # 2^256
int array: [ 1, 2, 3 ]
array on several lines:
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]
morelesskey: "<value>"
array_of_zero: [0]
sophisticated_array_of_zero: {rx: {tx: [0]} }
switches:
- { row: 0, col: 0, func: {tx: [0, 1]} }
empty_sequence: [ ]
empty_hash: { }
special_characters: "[{]]{{]]"
asterisks: "*"
empty_key:
:
key: value
trailing_colon: "foo:"
multiline_items:
- type: SomeItem
values: [blah, blah, blah,
blah]
ints: [2, 54, 12,
2143]
many_lines: |
A quick
fox
jumped
over
a lazy
dog
werte:
1: nummer 1
0: Stunde 0
noindent_records:
- record1: value1
- record2: value2
"a:1": [1000]
"a:2":
- 2000
a:3: [3000]
complex_unquoted_key:
a:b:''test': value
array with commas:
["0","1"]
invoice: ["Something", "", '', "Something else"]
quotes: ['Something', "Nothing", 'Anything', "Thing"]
# [Endloop]
endloop: |
Does this line in the end indeed make Spyc go to an infinite loop?

View File

@ -0,0 +1,194 @@
<?php
class DumpTest extends PHPUnit_Framework_TestCase {
private $files_to_test = array();
public function setUp() {
$this->files_to_test = array (__DIR__.'/../spyc.yaml', 'failing1.yaml', 'indent_1.yaml', 'quotes.yaml');
}
public function testShortSyntax() {
$dump = spyc_dump(array ('item1', 'item2', 'item3'));
$awaiting = "- item1\n- item2\n- item3\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDump() {
foreach ($this->files_to_test as $file) {
$yaml = spyc_load(file_get_contents($file));
$dump = Spyc::YAMLDump ($yaml);
$yaml_after_dump = Spyc::YAMLLoad ($dump);
$this->assertEquals ($yaml, $yaml_after_dump);
}
}
public function testDumpWithQuotes() {
$Spyc = new Spyc();
$Spyc->setting_dump_force_quotes = true;
foreach ($this->files_to_test as $file) {
$yaml = $Spyc->load(file_get_contents($file));
$dump = $Spyc->dump ($yaml);
$yaml_after_dump = Spyc::YAMLLoad ($dump);
$this->assertEquals ($yaml, $yaml_after_dump);
}
}
public function testDumpArrays() {
$dump = Spyc::YAMLDump(array ('item1', 'item2', 'item3'));
$awaiting = "---\n- item1\n- item2\n- item3\n";
$this->assertEquals ($awaiting, $dump);
}
public function testNull() {
$dump = Spyc::YAMLDump(array('a' => 1, 'b' => null, 'c' => 3));
$awaiting = "---\na: 1\nb: null\nc: 3\n";
$this->assertEquals ($awaiting, $dump);
}
public function testNext() {
$array = array("aaa", "bbb", "ccc");
#set arrays internal pointer to next element
next($array);
$dump = Spyc::YAMLDump($array);
$awaiting = "---\n- aaa\n- bbb\n- ccc\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpingMixedArrays() {
$array = array();
$array[] = 'Sequence item';
$array['The Key'] = 'Mapped value';
$array[] = array('A sequence','of a sequence');
$array[] = array('first' => 'A sequence','second' => 'of mapped values');
$array['Mapped'] = array('A sequence','which is mapped');
$array['A Note'] = 'What if your text is too long?';
$array['Another Note'] = 'If that is the case, the dumper will probably fold your text by using a block. Kinda like this.';
$array['The trick?'] = 'The trick is that we overrode the default indent, 2, to 4 and the default wordwrap, 40, to 60.';
$array['Old Dog'] = "And if you want\n to preserve line breaks, \ngo ahead!";
$array['key:withcolon'] = "Should support this to";
$yaml = Spyc::YAMLDump($array,4,60);
}
public function testMixed() {
$dump = Spyc::YAMLDump(array(0 => 1, 'b' => 2, 1 => 3));
$awaiting = "---\n0: 1\nb: 2\n1: 3\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpNumerics() {
$dump = Spyc::YAMLDump(array ('404', '405', '500'));
$awaiting = "---\n- \"404\"\n- \"405\"\n- \"500\"\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpAsterisks() {
$dump = Spyc::YAMLDump(array ('*'));
$awaiting = "---\n- '*'\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpAmpersands() {
$dump = Spyc::YAMLDump(array ('some' => '&foo'));
$awaiting = "---\nsome: '&foo'\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpExclamations() {
$dump = Spyc::YAMLDump(array ('some' => '!foo'));
$awaiting = "---\nsome: '!foo'\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpExclamations2() {
$dump = Spyc::YAMLDump(array ('some' => 'foo!'));
$awaiting = "---\nsome: foo!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpApostrophes() {
$dump = Spyc::YAMLDump(array ('some' => "'Biz' pimpt bedrijventerreinen"));
$awaiting = "---\nsome: \"'Biz' pimpt bedrijventerreinen\"\n";
$this->assertEquals ($awaiting, $dump);
}
public function testDumpNumericHashes() {
$dump = Spyc::YAMLDump(array ("titel"=> array("0" => "", 1 => "Dr.", 5 => "Prof.", 6 => "Prof. Dr.")));
$awaiting = "---\ntitel:\n 0: \"\"\n 1: Dr.\n 5: Prof.\n 6: Prof. Dr.\n";
$this->assertEquals ($awaiting, $dump);
}
public function testEmpty() {
$dump = Spyc::YAMLDump(array("foo" => array()));
$awaiting = "---\nfoo: [ ]\n";
$this->assertEquals ($awaiting, $dump);
}
public function testHashesInKeys() {
$dump = Spyc::YAMLDump(array ('#color' => '#ffffff'));
$awaiting = "---\n\"#color\": '#ffffff'\n";
$this->assertEquals ($awaiting, $dump);
}
public function testParagraph() {
$dump = Spyc::YAMLDump(array ('key' => "|\n value"));
$awaiting = "---\nkey: |\n value\n";
$this->assertEquals ($awaiting, $dump);
}
public function testParagraphTwo() {
$dump = Spyc::YAMLDump(array ('key' => 'Congrats, pimpt bedrijventerreinen pimpt bedrijventerreinen pimpt bedrijventerreinen!'));
$awaiting = "---\nkey: >\n Congrats, pimpt bedrijventerreinen pimpt\n bedrijventerreinen pimpt\n bedrijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testString() {
$dump = Spyc::YAMLDump(array ('key' => array('key_one' => 'Congrats, pimpt bedrijventerreinen!')));
$awaiting = "---\nkey:\n key_one: Congrats, pimpt bedrijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testStringLong() {
$dump = Spyc::YAMLDump(array ('key' => array('key_one' => 'Congrats, pimpt bedrijventerreinen pimpt bedrijventerreinen pimpt bedrijventerreinen!')));
$awaiting = "---\nkey:\n key_one: >\n Congrats, pimpt bedrijventerreinen pimpt\n bedrijventerreinen pimpt\n bedrijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testStringDoubleQuote() {
$dump = Spyc::YAMLDump(array ('key' => array('key_one' => array('key_two' => '"Système d\'e-réservation"'))));
$awaiting = "---\nkey:\n key_one:\n key_two: |\n Système d'e-réservation\n";
$this->assertEquals ($awaiting, $dump);
}
public function testLongStringDoubleQuote() {
$dump = Spyc::YAMLDump(array ('key' => array('key_one' => array('key_two' => '"Système d\'e-réservation bedrijventerreinen pimpt" bedrijventerreinen!'))));
$awaiting = "---\nkey:\n key_one:\n key_two: |\n \"Système d'e-réservation bedrijventerreinen pimpt\" bedrijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testStringStartingWithSpace() {
$dump = Spyc::YAMLDump(array ('key' => array('key_one' => " Congrats, pimpt bedrijventerreinen \n pimpt bedrijventerreinen pimpt bedrijventerreinen!")));
$awaiting = "---\nkey:\n key_one: |\n Congrats, pimpt bedrijventerreinen\n pimpt bedrijventerreinen pimpt bedrijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
public function testPerCentOne() {
$dump = Spyc::YAMLDump(array ('key' => "%name%, pimpts bedrijventerreinen!"));
$awaiting = "---\nkey: '%name%, pimpts bedrijventerreinen!'\n";
$this->assertEquals ($awaiting, $dump);
}
public function testPerCentAndSimpleQuote() {
$dump = Spyc::YAMLDump(array ('key' => "%name%, pimpt's bedrijventerreinen!"));
$awaiting = "---\nkey: \"%name%, pimpt's bedrijventerreinen!\"\n";
$this->assertEquals ($awaiting, $dump);
}
public function testPerCentAndDoubleQuote() {
$dump = Spyc::YAMLDump(array ('key' => '%name%, pimpt\'s "bed"rijventerreinen!'));
$awaiting = "---\nkey: |\n %name%, pimpt's \"bed\"rijventerreinen!\n";
$this->assertEquals ($awaiting, $dump);
}
}

View File

@ -0,0 +1,68 @@
<?php
class IndentTest extends PHPUnit_Framework_TestCase {
protected $Y;
protected function setUp() {
$this->Y = Spyc::YAMLLoad(__DIR__."/indent_1.yaml");
}
public function testIndent_1() {
$this->assertEquals (array ('child_1' => 2, 'child_2' => 0, 'child_3' => 1), $this->Y['root']);
}
public function testIndent_2() {
$this->assertEquals (array ('child_1' => 1, 'child_2' => 2), $this->Y['root2']);
}
public function testIndent_3() {
$this->assertEquals (array (array ('resolutions' => array (1024 => 768, 1920 => 1200), 'producer' => 'Nec')), $this->Y['display']);
}
public function testIndent_4() {
$this->assertEquals (array (
array ('resolutions' => array (1024 => 768)),
array ('resolutions' => array (1920 => 1200)),
), $this->Y['displays']);
}
public function testIndent_5() {
$this->assertEquals (array (array (
'row' => 0,
'col' => 0,
'headsets_affected' => array (
array (
'ports' => array (0),
'side' => 'left',
)
),
'switch_function' => array (
'ics_ptt' => true
)
)), $this->Y['nested_hashes_and_seqs']);
}
public function testIndent_6() {
$this->assertEquals (array (
'h' => array (
array ('a' => 'b', 'a1' => 'b1'),
array ('c' => 'd')
)
), $this->Y['easier_nest']);
}
public function testIndent_space() {
$this->assertEquals ("By four\n spaces", $this->Y['one_space']);
}
public function testListAndComment() {
$this->assertEquals (array ('one', 'two', 'three'), $this->Y['list_and_comment']);
}
public function testAnchorAndAlias() {
$this->assertEquals (array ('database' => 'rails_dev', 'adapter' => 'mysql', 'host' => 'localhost'), $this->Y['development']);
$this->assertEquals (array (1 => 'abc'), $this->Y['zzz']);
}
}

View File

@ -0,0 +1,17 @@
<?php
class LoadTest extends PHPUnit_Framework_TestCase {
public function testQuotes() {
$test_values = array(
"adjacent '''' \"\"\"\" quotes.",
"adjacent '''' quotes.",
"adjacent \"\"\"\" quotes.",
);
foreach($test_values as $value) {
$yaml = array($value);
$dump = Spyc::YAMLDump ($yaml);
$yaml_loaded = Spyc::YAMLLoad ($dump);
$this->assertEquals ($yaml, $yaml_loaded);
}
}
}

View File

@ -0,0 +1,413 @@
<?php
class ParseTest extends PHPUnit_Framework_TestCase {
protected $yaml;
protected function setUp() {
$this->yaml = spyc_load_file(__DIR__.'/../spyc.yaml');
}
public function testMergeHashKeys() {
$Expected = array (
array ('step' => array('instrument' => 'Lasik 2000', 'pulseEnergy' => 5.4, 'pulseDuration' => 12, 'repetition' => 1000, 'spotSize' => '1mm')),
array ('step' => array('instrument' => 'Lasik 2000', 'pulseEnergy' => 5.4, 'pulseDuration' => 12, 'repetition' => 1000, 'spotSize' => '2mm')),
);
$Actual = spyc_load_file (__DIR__.'/indent_1.yaml');
$this->assertEquals ($Expected, $Actual['steps']);
}
public function testDeathMasks() {
$Expected = array ('sad' => 2, 'magnificent' => 4);
$Actual = spyc_load_file (__DIR__.'/indent_1.yaml');
$this->assertEquals ($Expected, $Actual['death masks are']);
}
public function testDevDb() {
$Expected = array ('adapter' => 'mysql', 'host' => 'localhost', 'database' => 'rails_dev');
$Actual = spyc_load_file (__DIR__.'/indent_1.yaml');
$this->assertEquals ($Expected, $Actual['development']);
}
public function testNumericKey() {
$this->assertEquals ("Ooo, a numeric key!", $this->yaml[1040]);
}
public function testMappingsString() {
$this->assertEquals ("Anyone's name, really.", $this->yaml['String']);
}
public function testMappingsInt() {
$this->assertSame (13, $this->yaml['Int']);
}
public function testMappingsHex() {
$this->assertSame (243, $this->yaml['Hex']);
$this->assertSame ('f0xf3', $this->yaml['BadHex']);
}
public function testMappingsBooleanTrue() {
$this->assertSame (true, $this->yaml['True']);
}
public function testMappingsBooleanFalse() {
$this->assertSame (false, $this->yaml['False']);
}
public function testMappingsZero() {
$this->assertSame (0, $this->yaml['Zero']);
}
public function testMappingsNull() {
$this->assertSame (null, $this->yaml['Null']);
}
public function testMappingsNotNull() {
$this->assertSame ('null', $this->yaml['NotNull']);
}
public function testMappingsFloat() {
$this->assertSame (5.34, $this->yaml['Float']);
}
public function testMappingsNegative() {
$this->assertSame (-90, $this->yaml['Negative']);
}
public function testMappingsSmallFloat() {
$this->assertSame (0.7, $this->yaml['SmallFloat']);
}
public function testNewline() {
$this->assertSame ('\n', $this->yaml['NewLine']);
}
public function testQuotedNewline() {
$this->assertSame ("\n", $this->yaml['QuotedNewLine']);
}
public function testSeq0() {
$this->assertEquals ("PHP Class", $this->yaml[0]);
}
public function testSeq1() {
$this->assertEquals ("Basic YAML Loader", $this->yaml[1]);
}
public function testSeq2() {
$this->assertEquals ("Very Basic YAML Dumper", $this->yaml[2]);
}
public function testSeq3() {
$this->assertEquals (array("YAML is so easy to learn.",
"Your config files will never be the same."), $this->yaml[3]);
}
public function testSeqMap() {
$this->assertEquals (array("cpu" => "1.5ghz", "ram" => "1 gig",
"os" => "os x 10.4.1"), $this->yaml[4]);
}
public function testMappedSequence() {
$this->assertEquals (array("yaml.org", "php.net"), $this->yaml['domains']);
}
public function testAnotherSequence() {
$this->assertEquals (array("program" => "Adium", "platform" => "OS X",
"type" => "Chat Client"), $this->yaml[5]);
}
public function testFoldedBlock() {
$this->assertEquals ("There isn't any time for your tricks!\nDo you understand?", $this->yaml['no time']);
}
public function testLiteralAsMapped() {
$this->assertEquals ("There is nothing but time\nfor your tricks.", $this->yaml['some time']);
}
public function testCrazy() {
$this->assertEquals (array( array("name" => "spartan", "notes" =>
array( "Needs to be backed up",
"Needs to be normalized" ),
"type" => "mysql" )), $this->yaml['databases']);
}
public function testColons() {
$this->assertEquals ("like", $this->yaml["if: you'd"]);
}
public function testInline() {
$this->assertEquals (array("One", "Two", "Three", "Four"), $this->yaml[6]);
}
public function testNestedInline() {
$this->assertEquals (array("One", array("Two", "And", "Three"), "Four", "Five"), $this->yaml[7]);
}
public function testNestedNestedInline() {
$this->assertEquals (array( "This", array("Is", "Getting", array("Ridiculous", "Guys")),
"Seriously", array("Show", "Mercy")), $this->yaml[8]);
}
public function testInlineMappings() {
$this->assertEquals (array("name" => "chris", "age" => "young", "brand" => "lucky strike"), $this->yaml[9]);
}
public function testNestedInlineMappings() {
$this->assertEquals (array("name" => "mark", "age" => "older than chris",
"brand" => array("marlboro", "lucky strike")), $this->yaml[10]);
}
public function testReferences() {
$this->assertEquals (array('Perl', 'Python', 'PHP', 'Ruby'), $this->yaml['dynamic languages']);
}
public function testReferences2() {
$this->assertEquals (array('C/C++', 'Java'), $this->yaml['compiled languages']);
}
public function testReferences3() {
$this->assertEquals (array(
array('Perl', 'Python', 'PHP', 'Ruby'),
array('C/C++', 'Java')
), $this->yaml['all languages']);
}
public function testEscapedQuotes() {
$this->assertEquals ("you know, this shouldn't work. but it does.", $this->yaml[11]);
}
public function testEscapedQuotes_2() {
$this->assertEquals ( "that's my value.", $this->yaml[12]);
}
public function testEscapedQuotes_3() {
$this->assertEquals ("again, that's my value.", $this->yaml[13]);
}
public function testQuotes() {
$this->assertEquals ("here's to \"quotes\", boss.", $this->yaml[14]);
}
public function testQuoteSequence() {
$this->assertEquals ( array( 'name' => "Foo, Bar's", 'age' => 20), $this->yaml[15]);
}
public function testShortSequence() {
$this->assertEquals (array( 0 => "a", 1 => array (0 => 1, 1 => 2), 2 => "b"), $this->yaml[16]);
}
public function testQuotedNewlines() {
$this->assertEquals ("First line\nSecond line\nThird line", $this->yaml[17]);
}
public function testHash_1() {
$this->assertEquals ("Hash", $this->yaml['hash_1']);
}
public function testHash_2() {
$this->assertEquals ('Hash #and a comment', $this->yaml['hash_2']);
}
public function testHash_3() {
$this->assertEquals ('Hash (#) can appear in key too', $this->yaml['hash#3']);
}
public function testEndloop() {
$this->assertEquals ("Does this line in the end indeed make Spyc go to an infinite loop?", $this->yaml['endloop']);
}
public function testReallyLargeNumber() {
$this->assertEquals ('115792089237316195423570985008687907853269984665640564039457584007913129639936', $this->yaml['a_really_large_number']);
}
public function testFloatWithZeros() {
$this->assertSame ('1.0', $this->yaml['float_test']);
}
public function testFloatWithQuotes() {
$this->assertSame ('1.0', $this->yaml['float_test_with_quotes']);
}
public function testFloatInverse() {
$this->assertEquals ('001', $this->yaml['float_inverse_test']);
}
public function testIntArray() {
$this->assertEquals (array (1, 2, 3), $this->yaml['int array']);
}
public function testArrayOnSeveralLines() {
$this->assertEquals (array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), $this->yaml['array on several lines']);
}
public function testArrayWithCommas() {
$this->assertEquals(array (0, 1), $this->yaml['array with commas']);
}
public function testmoreLessKey() {
$this->assertEquals ('<value>', $this->yaml['morelesskey']);
}
public function testArrayOfZero() {
$this->assertSame (array(0), $this->yaml['array_of_zero']);
}
public function testSophisticatedArrayOfZero() {
$this->assertSame (array('rx' => array ('tx' => array (0))), $this->yaml['sophisticated_array_of_zero']);
}
public function testSwitches() {
$this->assertEquals (array (array ('row' => 0, 'col' => 0, 'func' => array ('tx' => array(0, 1)))), $this->yaml['switches']);
}
public function testEmptySequence() {
$this->assertSame (array(), $this->yaml['empty_sequence']);
}
public function testEmptyHash() {
$this->assertSame (array(), $this->yaml['empty_hash']);
}
public function testEmptykey() {
$this->assertSame (array('' => array ('key' => 'value')), $this->yaml['empty_key']);
}
public function testMultilines() {
$this->assertSame (array(array('type' => 'SomeItem', 'values' => array ('blah', 'blah', 'blah', 'blah'), 'ints' => array(2, 54, 12, 2143))), $this->yaml['multiline_items']);
}
public function testManyNewlines() {
$this->assertSame ('A quick
fox
jumped
over
a lazy
dog', $this->yaml['many_lines']);
}
public function testWerte() {
$this->assertSame (array ('1' => 'nummer 1', '0' => 'Stunde 0'), $this->yaml['werte']);
}
/* public function testNoIndent() {
$this->assertSame (array(
array ('record1'=>'value1'),
array ('record2'=>'value2')
)
, $this->yaml['noindent_records']);
} */
public function testColonsInKeys() {
$this->assertSame (array (1000), $this->yaml['a:1']);
}
public function testColonsInKeys2() {
$this->assertSame (array (2000), $this->yaml['a:2']);
}
public function testUnquotedColonsInKeys() {
$this->assertSame (array (3000), $this->yaml['a:3']);
}
public function testComplicatedKeyWithColon() {
$this->assertSame(array("a:b:''test'" => 'value'), $this->yaml['complex_unquoted_key']);
}
public function testKeysInMappedValueException() {
$this->setExpectedException('Exception');
Spyc::YAMLLoad('x: y: z:');
}
public function testKeysInValueException() {
$this->setExpectedException('Exception');
Spyc::YAMLLoad('x: y: z');
}
public function testSpecialCharacters() {
$this->assertSame ('[{]]{{]]', $this->yaml['special_characters']);
}
public function testAngleQuotes() {
$Quotes = Spyc::YAMLLoad(__DIR__.'/quotes.yaml');
$this->assertEquals (array ('html_tags' => array ('<br>', '<p>'), 'html_content' => array ('<p>hello world</p>', 'hello<br>world'), 'text_content' => array ('hello world')),
$Quotes);
}
public function testFailingColons() {
$Failing = Spyc::YAMLLoad(__DIR__.'/failing1.yaml');
$this->assertSame (array ('MyObject' => array ('Prop1' => array ('key1:val1'))),
$Failing);
}
public function testQuotesWithComments() {
$Expected = 'bar';
$Actual = spyc_load_file (__DIR__.'/comments.yaml');
$this->assertEquals ($Expected, $Actual['foo']);
}
public function testArrayWithComments() {
$Expected = array ('x', 'y', 'z');
$Actual = spyc_load_file (__DIR__.'/comments.yaml');
$this->assertEquals ($Expected, $Actual['arr']);
}
public function testAfterArrayWithKittens() {
$Expected = 'kittens';
$Actual = spyc_load_file (__DIR__.'/comments.yaml');
$this->assertEquals ($Expected, $Actual['bar']);
}
// Plain characters http://www.yaml.org/spec/1.2/spec.html#id2789510
public function testKai() {
$Expected = array('-example' => 'value');
$Actual = spyc_load_file (__DIR__.'/indent_1.yaml');
$this->assertEquals ($Expected, $Actual['kai']);
}
public function testKaiList() {
$Expected = array ('-item', '-item', '-item');
$Actual = spyc_load_file (__DIR__.'/indent_1.yaml');
$this->assertEquals ($Expected, $Actual['kai_list_of_items']);
}
public function testDifferentQuoteTypes() {
$expected = array ('Something', "", "", "Something else");
$this->assertSame ($expected, $this->yaml['invoice']);
}
public function testDifferentQuoteTypes2() {
$expected = array ('Something', "Nothing", "Anything", "Thing");
$this->assertSame ($expected, $this->yaml['quotes']);
}
// Separation spaces http://www.yaml.org/spec/1.2/spec.html#id2778394
public function testMultipleArrays() {
$expected = array(array(array('x')));
$this->assertSame($expected, Spyc::YAMLLoad("- - - x"));
}
public function testElementWithEmptyHash()
{
$element = "hash: {}\narray: []";
$yaml = Spyc::YAMLLoadString($element);
$this->assertEquals($yaml['hash'], []);
$this->assertEquals($yaml['array'], []);
$yaml = Spyc::YAMLLoadString($element, [
'setting_empty_hash_as_object' => true
]);
$this->assertInstanceOf(stdClass::class, $yaml['hash']);
$this->assertEquals($yaml['array'], []);
}
}

View File

@ -0,0 +1,76 @@
<?php
function roundTrip($a) { return Spyc::YAMLLoad(Spyc::YAMLDump(array('x' => $a))); }
class RoundTripTest extends PHPUnit_Framework_TestCase {
protected function setUp() {
}
public function testNull() {
$this->assertEquals (array ('x' => null), roundTrip (null));
}
public function testY() {
$this->assertEquals (array ('x' => 'y'), roundTrip ('y'));
}
public function testExclam() {
$this->assertEquals (array ('x' => '!yeah'), roundTrip ('!yeah'));
}
public function test5() {
$this->assertEquals (array ('x' => '5'), roundTrip ('5'));
}
public function testSpaces() {
$this->assertEquals (array ('x' => 'x '), roundTrip ('x '));
}
public function testApostrophes() {
$this->assertEquals (array ('x' => "'biz'"), roundTrip ("'biz'"));
}
public function testNewLines() {
$this->assertEquals (array ('x' => "\n"), roundTrip ("\n"));
}
public function testHashes() {
$this->assertEquals (array ('x' => array ("#color" => '#fff')), roundTrip (array ("#color" => '#fff')));
}
public function testPreserveString() {
$result1 = roundTrip ('0');
$result2 = roundTrip ('true');
$this->assertTrue (is_string ($result1['x']));
$this->assertTrue (is_string ($result2['x']));
}
public function testPreserveBool() {
$result = roundTrip (true);
$this->assertTrue (is_bool ($result['x']));
}
public function testPreserveInteger() {
$result = roundTrip (0);
$this->assertTrue (is_int ($result['x']));
}
public function testWordWrap() {
$this->assertEquals (array ('x' => "aaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), roundTrip ("aaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
}
public function testABCD() {
$this->assertEquals (array ('a', 'b', 'c', 'd'), Spyc::YAMLLoad(Spyc::YAMLDump(array('a', 'b', 'c', 'd'))));
}
public function testABCD2() {
$a = array('a', 'b', 'c', 'd'); // Create a simple list
$b = Spyc::YAMLDump($a); // Dump the list as YAML
$c = Spyc::YAMLLoad($b); // Load the dumped YAML
$d = Spyc::YAMLDump($c); // Re-dump the data
$this->assertSame($b, $d);
}
}

View File

@ -0,0 +1,3 @@
foo: 'bar' #Comment
arr: ['x', 'y', 'z'] # Comment here
bar: kittens

View File

@ -0,0 +1,2 @@
MyObject:
Prop1: {key1:val1}

View File

@ -0,0 +1,70 @@
root:
child_1: 2
child_2: 0
child_3: 1
root2:
child_1: 1
# A comment
child_2: 2
displays:
- resolutions:
1024: 768
- resolutions:
1920: 1200
display:
- resolutions:
1024: 768
1920: 1200
producer: "Nec"
nested_hashes_and_seqs:
- { row: 0, col: 0, headsets_affected: [{ports: [0], side: left}], switch_function: {ics_ptt: true} }
easier_nest: { h: [{a: b, a1: b1}, {c: d}] }
one_space: |
By four
spaces
steps:
- step: &id001
instrument: Lasik 2000
pulseEnergy: 5.4
pulseDuration: 12
repetition: 1000
spotSize: 1mm
- step:
<<: *id001
spotSize: 2mm
death masks are:
sad: 2
<<: {magnificent: 4}
login: &login
adapter: mysql
host: localhost
development:
database: rails_dev
<<: *login
"key": "value:"
colon_only: ":"
list_and_comment: [one, two, three] # comment
kai:
-example: value
kai_list_of_items:
- -item
- '-item'
-item
&foo bar:
1: "abc"
zzz: *foo

View File

@ -0,0 +1,8 @@
html_tags:
- <br>
- <p>
html_content:
- <p>hello world</p>
- hello<br>world
text_content:
- hello world

View File

@ -0,0 +1,22 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Cache;
interface Cache
{
public function fetch($id);
public function contains($id);
public function save($id, $data, $lifeTime = 0);
public function delete($id);
public function flushAll();
}

View File

@ -0,0 +1,63 @@
<?php
namespace DeviceDetector\Cache;
use Psr\SimpleCache\CacheInterface;
class PSR16Bridge implements Cache
{
/**
* @var CacheInterface
*/
private $cache;
/**
* PSR16Bridge constructor.
* @param CacheInterface $cache
*/
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* @inheritDoc
*/
public function fetch($id)
{
return $this->cache->get($id, false);
}
/**
* @inheritDoc
*/
public function contains($id)
{
return $this->cache->has($id);
}
/**
* @inheritDoc
*/
public function save($id, $data, $lifeTime = 0)
{
return $this->cache->set($id, $data, func_num_args() < 3 ? null : $lifeTime);
}
/**
* @inheritDoc
*/
public function delete($id)
{
return $this->cache->delete($id);
}
/**
* @inheritDoc
*/
public function flushAll()
{
return $this->cache->clear();
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace DeviceDetector\Cache;
use Psr\Cache\CacheItemPoolInterface;
class PSR6Bridge implements Cache
{
/**
* @var CacheItemPoolInterface
*/
private $pool;
/**
* PSR6Bridge constructor.
* @param CacheItemPoolInterface $pool
*/
public function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
}
/**
* @inheritDoc
*/
public function fetch($id)
{
$item = $this->pool->getItem($id);
return $item->isHit() ? $item->get() : false;
}
/**
* @inheritDoc
*/
public function contains($id)
{
return $this->pool->hasItem($id);
}
/**
* @inheritDoc
*/
public function save($id, $data, $lifeTime = 0)
{
$item = $this->pool->getItem($id);
$item->set($data);
if (func_num_args() > 2) {
$item->expiresAfter($lifeTime);
}
return $this->pool->save($item);
}
/**
* @inheritDoc
*/
public function delete($id)
{
return $this->pool->deleteItem($id);
}
/**
* @inheritDoc
*/
public function flushAll()
{
return $this->pool->clear();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Cache;
/**
* Class StaticCache
*
* Simple Cache that caches in a static property
* (Speeds up multiple detections in one request)
*
* @package DeviceDetector\Cache
*/
class StaticCache implements Cache
{
/**
* Holds the static cache data
* @var array
*/
protected static $staticCache = array();
public function fetch($id)
{
return $this->contains($id) ? self::$staticCache[$id] : false;
}
public function contains($id)
{
return isset(self::$staticCache[$id]) || array_key_exists($id, self::$staticCache);
}
public function save($id, $data, $lifeTime = 0)
{
self::$staticCache[$id] = $data;
return true;
}
public function delete($id)
{
unset(self::$staticCache[$id]);
return true;
}
public function flushAll()
{
self::$staticCache = array();
return true;
}
}

View File

@ -0,0 +1,895 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector;
use DeviceDetector\Cache\StaticCache;
use DeviceDetector\Cache\Cache;
use DeviceDetector\Parser\Bot;
use DeviceDetector\Parser\BotParserAbstract;
use DeviceDetector\Parser\Client\Browser;
use DeviceDetector\Parser\OperatingSystem;
use DeviceDetector\Parser\Client\ClientParserAbstract;
use DeviceDetector\Parser\Device\DeviceParserAbstract;
use DeviceDetector\Parser\VendorFragment;
use DeviceDetector\Yaml\Parser AS YamlParser;
use DeviceDetector\Yaml\Spyc;
/**
* Class DeviceDetector
*
* Magic Device Type Methods
* @method boolean isSmartphone()
* @method boolean isFeaturePhone()
* @method boolean isTablet()
* @method boolean isPhablet()
* @method boolean isConsole()
* @method boolean isPortableMediaPlayer()
* @method boolean isCarBrowser()
* @method boolean isTV()
* @method boolean isSmartDisplay()
* @method boolean isCamera()
*
* Magic Client Type Methods
* @method boolean isBrowser()
* @method boolean isFeedReader()
* @method boolean isMobileApp()
* @method boolean isPIM()
* @method boolean isLibrary()
* @method boolean isMediaPlayer()
*
* @package DeviceDetector
*/
class DeviceDetector
{
/**
* Current version number of DeviceDetector
*/
const VERSION = '3.12.4';
/**
* Holds all registered client types
* @var array
*/
public static $clientTypes = array();
/**
* Operating system families that are known as desktop only
*
* @var array
*/
protected static $desktopOsArray = array('AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS');
/**
* Constant used as value for unknown browser / os
*/
const UNKNOWN = "UNK";
/**
* Holds the useragent that should be parsed
* @var string
*/
protected $userAgent;
/**
* Holds the operating system data after parsing the UA
* @var array
*/
protected $os = null;
/**
* Holds the client data after parsing the UA
* @var array
*/
protected $client = null;
/**
* Holds the device type after parsing the UA
* @var int
*/
protected $device = null;
/**
* Holds the device brand data after parsing the UA
* @var string
*/
protected $brand = '';
/**
* Holds the device model data after parsing the UA
* @var string
*/
protected $model = '';
/**
* Holds bot information if parsing the UA results in a bot
* (All other information attributes will stay empty in that case)
*
* If $discardBotInformation is set to true, this property will be set to
* true if parsed UA is identified as bot, additional information will be not available
*
* If $skipBotDetection is set to true, bot detection will not be performed and isBot will
* always be false
*
* @var array|boolean
*/
protected $bot = null;
/**
* @var bool
*/
protected $discardBotInformation = false;
/**
* @var bool
*/
protected $skipBotDetection = false;
/**
* Holds the cache class used for caching the parsed yml-Files
* @var \DeviceDetector\Cache\Cache|\Doctrine\Common\Cache\CacheProvider
*/
protected $cache = null;
/**
* Holds the parser class used for parsing yml-Files
* @var \DeviceDetector\Yaml\Parser
*/
protected $yamlParser = null;
/**
* @var ClientParserAbstract[]
*/
protected $clientParsers = array();
/**
* @var DeviceParserAbstract[]
*/
protected $deviceParsers = array();
/**
* @var BotParserAbstract[]
*/
public $botParsers = array();
/**
* @var bool
*/
private $parsed = false;
/**
* Constructor
*
* @param string $userAgent UA to parse
*/
public function __construct($userAgent = '')
{
if ($userAgent != '') {
$this->setUserAgent($userAgent);
}
$this->addClientParser('FeedReader');
$this->addClientParser('MobileApp');
$this->addClientParser('MediaPlayer');
$this->addClientParser('PIM');
$this->addClientParser('Browser');
$this->addClientParser('Library');
$this->addDeviceParser('HbbTv');
$this->addDeviceParser('Console');
$this->addDeviceParser('CarBrowser');
$this->addDeviceParser('Camera');
$this->addDeviceParser('PortableMediaPlayer');
$this->addDeviceParser('Mobile');
$this->addBotParser(new Bot());
}
public function __call($methodName, $arguments)
{
foreach (DeviceParserAbstract::getAvailableDeviceTypes() as $deviceName => $deviceType) {
if (strtolower($methodName) == 'is' . strtolower(str_replace(' ', '', $deviceName))) {
return $this->getDevice() == $deviceType;
}
}
foreach (self::$clientTypes as $client) {
if (strtolower($methodName) == 'is' . strtolower(str_replace(' ', '', $client))) {
return $this->getClient('type') == $client;
}
}
throw new \BadMethodCallException("Method $methodName not found");
}
/**
* Sets the useragent to be parsed
*
* @param string $userAgent
*/
public function setUserAgent($userAgent)
{
if ($this->userAgent != $userAgent) {
$this->reset();
}
$this->userAgent = $userAgent;
}
protected function reset()
{
$this->bot = null;
$this->client = null;
$this->device = null;
$this->os = null;
$this->brand = '';
$this->model = '';
$this->parsed = false;
}
/**
* @param ClientParserAbstract|string $parser
* @throws \Exception
*/
public function addClientParser($parser)
{
if (is_string($parser) && class_exists('DeviceDetector\\Parser\\Client\\' . $parser)) {
$className = 'DeviceDetector\\Parser\\Client\\' . $parser;
$parser = new $className();
}
if ($parser instanceof ClientParserAbstract) {
$this->clientParsers[] = $parser;
self::$clientTypes[] = $parser->getName();
return;
}
throw new \Exception('client parser not found');
}
public function getClientParsers()
{
return $this->clientParsers;
}
/**
* @param DeviceParserAbstract|string $parser
* @throws \Exception
*/
public function addDeviceParser($parser)
{
if (is_string($parser) && class_exists('DeviceDetector\\Parser\\Device\\' . $parser)) {
$className = 'DeviceDetector\\Parser\\Device\\' . $parser;
$parser = new $className();
}
if ($parser instanceof DeviceParserAbstract) {
$this->deviceParsers[] = $parser;
return;
}
throw new \Exception('device parser not found');
}
public function getDeviceParsers()
{
return $this->deviceParsers;
}
/**
* @param BotParserAbstract $parser
*/
public function addBotParser(BotParserAbstract $parser)
{
$this->botParsers[] = $parser;
}
public function getBotParsers()
{
return $this->botParsers;
}
/**
* Sets whether to discard additional bot information
* If information is discarded it's only possible check whether UA was detected as bot or not.
* (Discarding information speeds up the detection a bit)
*
* @param bool $discard
*/
public function discardBotInformation($discard = true)
{
$this->discardBotInformation = $discard;
}
/**
* Sets whether to skip bot detection.
* It is needed if we want bots to be processed as a simple clients. So we can detect if it is mobile client,
* or desktop, or enything else. By default all this information is not retrieved for the bots.
*
* @param bool $skip
*/
public function skipBotDetection($skip = true)
{
$this->skipBotDetection = $skip;
}
/**
* Returns if the parsed UA was identified as a Bot
*
* @see bots.yml for a list of detected bots
*
* @return bool
*/
public function isBot()
{
return !empty($this->bot);
}
/**
* Returns if the parsed UA was identified as a touch enabled device
*
* Note: That only applies to windows 8 tablets
*
* @return bool
*/
public function isTouchEnabled()
{
$regex = 'Touch';
return !!$this->matchUserAgent($regex);
}
/**
* Returns if the parsed UA contains the 'Android; Tablet;' fragment
*
* @return bool
*/
protected function hasAndroidTableFragment()
{
$regex = 'Android( [\.0-9]+)?; Tablet;';
return !!$this->matchUserAgent($regex);
}
/**
* Returns if the parsed UA contains the 'Android; Mobile;' fragment
*
* @return bool
*/
protected function hasAndroidMobileFragment()
{
$regex = 'Android( [\.0-9]+)?; Mobile;';
return !!$this->matchUserAgent($regex);
}
protected function usesMobileBrowser()
{
return $this->getClient('type') == 'browser' && Browser::isMobileOnlyBrowser($this->getClient('short_name'));
}
public function isMobile()
{
// Mobile device types
if (!empty($this->device) && in_array($this->device, array(
DeviceParserAbstract::DEVICE_TYPE_FEATURE_PHONE,
DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE,
DeviceParserAbstract::DEVICE_TYPE_TABLET,
DeviceParserAbstract::DEVICE_TYPE_PHABLET,
DeviceParserAbstract::DEVICE_TYPE_CAMERA,
DeviceParserAbstract::DEVICE_TYPE_PORTABLE_MEDIA_PAYER,
))
) {
return true;
}
// non mobile device types
if (!empty($this->device) && in_array($this->device, array(
DeviceParserAbstract::DEVICE_TYPE_TV,
DeviceParserAbstract::DEVICE_TYPE_SMART_DISPLAY,
DeviceParserAbstract::DEVICE_TYPE_CONSOLE
))
) {
return false;
}
// Check for browsers available for mobile devices only
if ($this->usesMobileBrowser()) {
return true;
}
$osShort = $this->getOs('short_name');
if (empty($osShort) || self::UNKNOWN == $osShort) {
return false;
}
return !$this->isBot() && !$this->isDesktop();
}
/**
* Returns if the parsed UA was identified as desktop device
* Desktop devices are all devices with an unknown type that are running a desktop os
*
* @see self::$desktopOsArray
*
* @return bool
*/
public function isDesktop()
{
$osShort = $this->getOs('short_name');
if (empty($osShort) || self::UNKNOWN == $osShort) {
return false;
}
// Check for browsers available for mobile devices only
if ($this->usesMobileBrowser()) {
return false;
}
$decodedFamily = OperatingSystem::getOsFamily($osShort);
return in_array($decodedFamily, self::$desktopOsArray);
}
/**
* Returns the operating system data extracted from the parsed UA
*
* If $attr is given only that property will be returned
*
* @param string $attr property to return(optional)
*
* @return array|string
*/
public function getOs($attr = '')
{
if ($attr == '') {
return $this->os;
}
if (!isset($this->os[$attr])) {
return self::UNKNOWN;
}
return $this->os[$attr];
}
/**
* Returns the client data extracted from the parsed UA
*
* If $attr is given only that property will be returned
*
* @param string $attr property to return(optional)
*
* @return array|string
*/
public function getClient($attr = '')
{
if ($attr == '') {
return $this->client;
}
if (!isset($this->client[$attr])) {
return self::UNKNOWN;
}
return $this->client[$attr];
}
/**
* Returns the device type extracted from the parsed UA
*
* @see DeviceParserAbstract::$deviceTypes for available device types
*
* @return int|null
*/
public function getDevice()
{
return $this->device;
}
/**
* Returns the device type extracted from the parsed UA
*
* @see DeviceParserAbstract::$deviceTypes for available device types
*
* @return string
*/
public function getDeviceName()
{
if ($this->getDevice() !== null) {
return DeviceParserAbstract::getDeviceName($this->getDevice());
}
return '';
}
/**
* Returns the device brand extracted from the parsed UA
*
* @see self::$deviceBrand for available device brands
*
* @return string
*/
public function getBrand()
{
return $this->brand;
}
/**
* Returns the full device brand name extracted from the parsed UA
*
* @see self::$deviceBrand for available device brands
*
* @return string
*/
public function getBrandName()
{
return DeviceParserAbstract::getFullName($this->getBrand());
}
/**
* Returns the device model extracted from the parsed UA
*
* @return string
*/
public function getModel()
{
return $this->model;
}
/**
* Returns the user agent that is set to be parsed
*
* @return string
*/
public function getUserAgent()
{
return $this->userAgent;
}
/**
* Returns the bot extracted from the parsed UA
*
* @return array
*/
public function getBot()
{
return $this->bot;
}
/**
* Returns true, if userAgent was already parsed with parse()
*
* @return bool
*/
public function isParsed()
{
return $this->parsed;
}
/**
* Triggers the parsing of the current user agent
*/
public function parse()
{
if ($this->isParsed()) {
return;
}
$this->parsed = true;
// skip parsing for empty useragents or those not containing any letter
if (empty($this->userAgent) || !preg_match('/([a-z])/i', $this->userAgent)) {
return;
}
$this->parseBot();
if ($this->isBot()) {
return;
}
$this->parseOs();
/**
* Parse Clients
* Clients might be browsers, Feed Readers, Mobile Apps, Media Players or
* any other application accessing with an parseable UA
*/
$this->parseClient();
$this->parseDevice();
}
/**
* Parses the UA for bot information using the Bot parser
*/
protected function parseBot()
{
if ($this->skipBotDetection) {
$this->bot = false;
return false;
}
$parsers = $this->getBotParsers();
foreach ($parsers as $parser) {
$parser->setUserAgent($this->getUserAgent());
$parser->setYamlParser($this->getYamlParser());
$parser->setCache($this->getCache());
if ($this->discardBotInformation) {
$parser->discardDetails();
}
$bot = $parser->parse();
if (!empty($bot)) {
$this->bot = $bot;
break;
}
}
}
protected function parseClient()
{
$parsers = $this->getClientParsers();
foreach ($parsers as $parser) {
$parser->setYamlParser($this->getYamlParser());
$parser->setCache($this->getCache());
$parser->setUserAgent($this->getUserAgent());
$client = $parser->parse();
if (!empty($client)) {
$this->client = $client;
break;
}
}
}
protected function parseDevice()
{
$parsers = $this->getDeviceParsers();
foreach ($parsers as $parser) {
$parser->setYamlParser($this->getYamlParser());
$parser->setCache($this->getCache());
$parser->setUserAgent($this->getUserAgent());
if ($parser->parse()) {
$this->device = $parser->getDeviceType();
$this->model = $parser->getModel();
$this->brand = $parser->getBrand();
break;
}
}
/**
* If no brand has been assigned try to match by known vendor fragments
*/
if (empty($this->brand)) {
$vendorParser = new VendorFragment($this->getUserAgent());
$vendorParser->setYamlParser($this->getYamlParser());
$vendorParser->setCache($this->getCache());
$this->brand = $vendorParser->parse();
}
$osShortName = $this->getOs('short_name');
$osFamily = OperatingSystem::getOsFamily($osShortName);
$osVersion = $this->getOs('version');
$clientName = $this->getClient('name');
/**
* Assume all devices running iOS / Mac OS are from Apple
*/
if (empty($this->brand) && in_array($osShortName, array('ATV', 'IOS', 'MAC'))) {
$this->brand = 'AP';
}
/**
* Chrome on Android passes the device type based on the keyword 'Mobile'
* If it is present the device should be a smartphone, otherwise it's a tablet
* See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent
*/
if (is_null($this->device) && $osFamily == 'Android' && Browser::getBrowserFamily($this->getClient('short_name')) == 'Chrome') {
if ($this->matchUserAgent('Chrome/[\.0-9]* Mobile')) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE;
} else if ($this->matchUserAgent('Chrome/[\.0-9]* (?!Mobile)')) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET;
}
}
/**
* Some user agents simply contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices as tablets
*/
if (is_null($this->device) && ($this->hasAndroidTableFragment() || $this->matchUserAgent('Opera Tablet'))) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET;
}
/**
* Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices as smartphones
*/
if (is_null($this->device) && $this->hasAndroidMobileFragment()) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE;
}
/**
* Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published
* too late, there were a bunch of tablets running with 2.x
* With 4.0 the two trees were merged and it is for smartphones and tablets
*
* So were are expecting that all devices running Android < 2 are smartphones
* Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown
*/
if (is_null($this->device) && $osShortName == 'AND' && $osVersion != '') {
if (version_compare($osVersion, '2.0') == -1) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE;
} elseif (version_compare($osVersion, '3.0') >= 0 and version_compare($osVersion, '4.0') == -1) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET;
}
}
/**
* All detected feature phones running android are more likely a smartphone
*/
if ($this->device == DeviceParserAbstract::DEVICE_TYPE_FEATURE_PHONE && $osFamily == 'Android') {
$this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE;
}
/**
* According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
* Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the
* UA string, the computer has touch capability, and is running Windows 8 (or later).
* This UA string will be transmitted on a touch-enabled system running Windows 8 (RT)
*
* As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that
* all Windows 8 touch devices are tablets.
*/
if (is_null($this->device) && ($osShortName == 'WRT' || ($osShortName == 'WIN' && version_compare($osVersion, '8') >= 0)) && $this->isTouchEnabled()) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET;
}
/**
* All devices running Opera TV Store are assumed to be a tv
*/
if ($this->matchUserAgent('Opera TV Store')) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TV;
}
/**
* Devices running Kylo or Espital TV Browsers are assumed to be a TV
*/
if (is_null($this->device) && in_array($clientName, array('Kylo', 'Espial TV Browser'))) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_TV;
}
// set device type to desktop for all devices running a desktop os that were not detected as an other device type
if (is_null($this->device) && $this->isDesktop()) {
$this->device = DeviceParserAbstract::DEVICE_TYPE_DESKTOP;
}
}
protected function parseOs()
{
$osParser = new OperatingSystem();
$osParser->setUserAgent($this->getUserAgent());
$osParser->setYamlParser($this->getYamlParser());
$osParser->setCache($this->getCache());
$this->os = $osParser->parse();
}
protected function matchUserAgent($regex)
{
$regex = '/(?:^|[^A-Z_-])(?:' . str_replace('/', '\/', $regex) . ')/i';
if (preg_match($regex, $this->userAgent, $matches)) {
return $matches;
}
return false;
}
/**
* Parses a useragent and returns the detected data
*
* ATTENTION: Use that method only for testing or very small applications
* To get fast results from DeviceDetector you need to make your own implementation,
* that should use one of the caching mechanisms. See README.md for more information.
*
* @internal
* @deprecated
*
* @param string $ua UserAgent to parse
*
* @return array
*/
public static function getInfoFromUserAgent($ua)
{
$deviceDetector = new DeviceDetector($ua);
$deviceDetector->parse();
if ($deviceDetector->isBot()) {
return array(
'user_agent' => $deviceDetector->getUserAgent(),
'bot' => $deviceDetector->getBot()
);
}
$osFamily = OperatingSystem::getOsFamily($deviceDetector->getOs('short_name'));
$browserFamily = \DeviceDetector\Parser\Client\Browser::getBrowserFamily($deviceDetector->getClient('short_name'));
$processed = array(
'user_agent' => $deviceDetector->getUserAgent(),
'os' => $deviceDetector->getOs(),
'client' => $deviceDetector->getClient(),
'device' => array(
'type' => $deviceDetector->getDeviceName(),
'brand' => $deviceDetector->getBrand(),
'model' => $deviceDetector->getModel(),
),
'os_family' => $osFamily !== false ? $osFamily : 'Unknown',
'browser_family' => $browserFamily !== false ? $browserFamily : 'Unknown',
);
return $processed;
}
/**
* Sets the Cache class
*
* @param Cache|\Doctrine\Common\Cache\CacheProvider $cache
* @throws \Exception
*/
public function setCache($cache)
{
if ($cache instanceof Cache ||
(class_exists('\Doctrine\Common\Cache\CacheProvider') && $cache instanceof \Doctrine\Common\Cache\CacheProvider)
) {
$this->cache = $cache;
return;
}
throw new \Exception('Cache not supported');
}
/**
* Returns Cache object
*
* @return \Doctrine\Common\Cache\CacheProvider
*/
public function getCache()
{
if (!empty($this->cache)) {
return $this->cache;
}
return new StaticCache();
}
/**
* Sets the Yaml Parser class
*
* @param YamlParser
* @throws \Exception
*/
public function setYamlParser($yamlParser)
{
if ($yamlParser instanceof YamlParser) {
$this->yamlParser = $yamlParser;
return;
}
throw new \Exception('Yaml Parser not supported');
}
/**
* Returns Yaml Parser object
*
* @return YamlParser
*/
public function getYamlParser()
{
if (!empty($this->yamlParser)) {
return $this->yamlParser;
}
return new Spyc();
}
}

View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,70 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser;
/**
* Class Bot
*
* Parses a user agent for bot information
*
* Detected bots are defined in regexes/bots.yml
*
* @package DeviceDetector\Parser
*/
class Bot extends BotParserAbstract
{
protected $fixtureFile = 'regexes/bots.yml';
protected $parserName = 'bot';
protected $discardDetails = false;
/**
* Enables information discarding
*/
public function discardDetails()
{
$this->discardDetails = true;
}
/**
* Parses the current UA and checks whether it contains bot information
*
* @see bots.yml for list of detected bots
*
* Step 1: Build a big regex containing all regexes and match UA against it
* -> If no matches found: return
* -> Otherwise:
* Step 2: Walk through the list of regexes in bots.yml and try to match every one
* -> Return the matched data
*
* If $discardDetails is set to TRUE, the Step 2 will be skipped
* $bot will be set to TRUE instead
*
* NOTE: Doing the big match before matching every single regex speeds up the detection
*/
public function parse()
{
$result = null;
if ($this->preMatchOverall()) {
if ($this->discardDetails) {
$result = true;
} else {
foreach ($this->getRegexes() as $regex) {
$matches = $this->matchUserAgent($regex['regex']);
if ($matches) {
unset($regex['regex']);
$result = $regex;
break;
}
}
}
}
return $result;
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser;
/**
* Class BotParserAbstract
*
* Abstract class for all bot parsers
*
* @package DeviceDetector\Parser
*/
abstract class BotParserAbstract extends ParserAbstract
{
/**
* Enables information discarding
*/
abstract public function discardDetails();
}

View File

@ -0,0 +1,413 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
use DeviceDetector\Parser\Client\Browser\Engine;
/**
* Class Browser
*
* Client parser for browser detection
*
* @package DeviceDetector\Parser\Client
*/
class Browser extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/browsers.yml';
protected $parserName = 'browser';
/**
* Known browsers mapped to their internal short codes
*
* @var array
*/
protected static $availableBrowsers = array(
'2B' => '2345 Browser',
'36' => '360 Phone Browser',
'3B' => '360 Browser',
'AA' => 'Avant Browser',
'AB' => 'ABrowse',
'AF' => 'ANT Fresco',
'AG' => 'ANTGalio',
'AL' => 'Aloha Browser',
'AH' => 'Aloha Browser Lite',
'AM' => 'Amaya',
'AO' => 'Amigo',
'AN' => 'Android Browser',
'AD' => 'AOL Shield',
'AR' => 'Arora',
'AV' => 'Amiga Voyager',
'AW' => 'Amiga Aweb',
'AT' => 'Atomic Web Browser',
'AS' => 'Avast Secure Browser',
'VG' => 'AVG Secure Browser',
'BA' => 'Beaker Browser',
'BM' => 'Beamrise',
'BB' => 'BlackBerry Browser',
'BD' => 'Baidu Browser',
'BS' => 'Baidu Spark',
'BI' => 'Basilisk',
'BE' => 'Beonex',
'BH' => 'BlackHawk',
'BJ' => 'Bunjalloo',
'BL' => 'B-Line',
'BR' => 'Brave',
'BK' => 'BriskBard',
'BX' => 'BrowseX',
'CA' => 'Camino',
'CL' => 'CCleaner',
'CC' => 'Coc Coc',
'CD' => 'Comodo Dragon',
'C1' => 'Coast',
'CX' => 'Charon',
'CE' => 'CM Browser',
'CF' => 'Chrome Frame',
'HC' => 'Headless Chrome',
'CH' => 'Chrome',
'CI' => 'Chrome Mobile iOS',
'CK' => 'Conkeror',
'CM' => 'Chrome Mobile',
'CN' => 'CoolNovo',
'CO' => 'CometBird',
'CB' => 'COS Browser',
'CP' => 'ChromePlus',
'CR' => 'Chromium',
'CY' => 'Cyberfox',
'CS' => 'Cheshire',
'CT' => 'Crusta',
'CU' => 'Cunaguaro',
'CV' => 'Chrome Webview',
'DB' => 'dbrowser',
'DE' => 'Deepnet Explorer',
'DT' => 'Delta Browser',
'DF' => 'Dolphin',
'DO' => 'Dorado',
'DL' => 'Dooble',
'DI' => 'Dillo',
'DD' => 'DuckDuckGo Privacy Browser',
'EC' => 'Ecosia',
'EI' => 'Epic',
'EL' => 'Elinks',
'EB' => 'Element Browser',
'EZ' => 'eZ Browser',
'EU' => 'EUI Browser',
'EP' => 'GNOME Web',
'ES' => 'Espial TV Browser',
'FA' => 'Falkon',
'FX' => 'Faux Browser',
'F1' => 'Firefox Mobile iOS',
'FB' => 'Firebird',
'FD' => 'Fluid',
'FE' => 'Fennec',
'FF' => 'Firefox',
'FK' => 'Firefox Focus',
'FY' => 'Firefox Reality',
'FR' => 'Firefox Rocket',
'FL' => 'Flock',
'FM' => 'Firefox Mobile',
'FW' => 'Fireweb',
'FN' => 'Fireweb Navigator',
'FU' => 'FreeU',
'GA' => 'Galeon',
'GE' => 'Google Earth',
'HA' => 'Hawk Turbo Browser',
'HO' => 'hola! Browser',
'HJ' => 'HotJava',
'HU' => 'Huawei Browser',
'IB' => 'IBrowse',
'IC' => 'iCab',
'I2' => 'iCab Mobile',
'I1' => 'Iridium',
'I3' => 'Iron Mobile',
'I4' => 'IceCat',
'ID' => 'IceDragon',
'IV' => 'Isivioo',
'IW' => 'Iceweasel',
'IE' => 'Internet Explorer',
'IM' => 'IE Mobile',
'IR' => 'Iron',
'JS' => 'Jasmine',
'JI' => 'Jig Browser',
'JO' => 'Jio Browser',
'KB' => 'K.Browser',
'KI' => 'Kindle Browser',
'KM' => 'K-meleon',
'KO' => 'Konqueror',
'KP' => 'Kapiko',
'KN' => 'Kinza',
'KW' => 'Kiwi',
'KY' => 'Kylo',
'KZ' => 'Kazehakase',
'LB' => 'Cheetah Browser',
'LF' => 'LieBaoFast',
'LG' => 'LG Browser',
'LI' => 'Links',
'LO' => 'Lovense Browser',
'LU' => 'LuaKit',
'LS' => 'Lunascape',
'LX' => 'Lynx',
'M1' => 'mCent',
'MB' => 'MicroB',
'MC' => 'NCSA Mosaic',
'MZ' => 'Meizu Browser',
'ME' => 'Mercury',
'MF' => 'Mobile Safari',
'MI' => 'Midori',
'MO' => 'Mobicip',
'MU' => 'MIUI Browser',
'MS' => 'Mobile Silk',
'MN' => 'Minimo',
'MT' => 'Mint Browser',
'MX' => 'Maxthon',
'NB' => 'Nokia Browser',
'NO' => 'Nokia OSS Browser',
'NV' => 'Nokia Ovi Browser',
'NX' => 'Nox Browser',
'NE' => 'NetSurf',
'NF' => 'NetFront',
'NL' => 'NetFront Life',
'NP' => 'NetPositive',
'NS' => 'Netscape',
'NT' => 'NTENT Browser',
'OC' => 'Oculus Browser',
'O1' => 'Opera Mini iOS',
'OB' => 'Obigo',
'OD' => 'Odyssey Web Browser',
'OF' => 'Off By One',
'OE' => 'ONE Browser',
'OX' => 'Opera GX',
'OG' => 'Opera Neon',
'OH' => 'Opera Devices',
'OI' => 'Opera Mini',
'OM' => 'Opera Mobile',
'OP' => 'Opera',
'ON' => 'Opera Next',
'OO' => 'Opera Touch',
'OS' => 'Ordissimo',
'OR' => 'Oregano',
'OY' => 'Origyn Web Browser',
'OV' => 'Openwave Mobile Browser',
'OW' => 'OmniWeb',
'OT' => 'Otter Browser',
'PL' => 'Palm Blazer',
'PM' => 'Pale Moon',
'PP' => 'Oppo Browser',
'PR' => 'Palm Pre',
'PU' => 'Puffin',
'PW' => 'Palm WebPro',
'PA' => 'Palmscape',
'PX' => 'Phoenix',
'PO' => 'Polaris',
'PT' => 'Polarity',
'PS' => 'Microsoft Edge',
'Q1' => 'QQ Browser Mini',
'QQ' => 'QQ Browser',
'QT' => 'Qutebrowser',
'QZ' => 'QupZilla',
'QM' => 'Qwant Mobile',
'QW' => 'QtWebEngine',
'RE' => 'Realme Browser',
'RK' => 'Rekonq',
'RM' => 'RockMelt',
'SB' => 'Samsung Browser',
'SA' => 'Sailfish Browser',
'SC' => 'SEMC-Browser',
'SE' => 'Sogou Explorer',
'SF' => 'Safari',
'SW' => 'SalamWeb',
'SH' => 'Shiira',
'S1' => 'SimpleBrowser',
'SK' => 'Skyfire',
'SS' => 'Seraphic Sraf',
'SL' => 'Sleipnir',
'SN' => 'Snowshoe',
'SO' => 'Sogou Mobile Browser',
'S2' => 'Splash',
'SI' => 'Sputnik Browser',
'SR' => 'Sunrise',
'SP' => 'SuperBird',
'SU' => 'Super Fast Browser',
'S0' => 'START Internet Browser',
'ST' => 'Streamy',
'SX' => 'Swiftfox',
'SZ' => 'Seznam Browser',
'TO' => 't-online.de Browser',
'TA' => 'Tao Browser',
'TF' => 'TenFourFox',
'TB' => 'Tenta Browser',
'TZ' => 'Tizen Browser',
'TS' => 'TweakStyle',
'TV' => 'TV Bro',
'UB' => 'UBrowser',
'UC' => 'UC Browser',
'UM' => 'UC Browser Mini',
'UT' => 'UC Browser Turbo',
'UZ' => 'Uzbl',
'VI' => 'Vivaldi',
'VV' => 'vivo Browser',
'VB' => 'Vision Mobile Browser',
'WI' => 'Wear Internet Browser',
'WP' => 'Web Explorer',
'WE' => 'WebPositive',
'WF' => 'Waterfox',
'WH' => 'Whale Browser',
'WO' => 'wOSBrowser',
'WT' => 'WeTab Browser',
'YA' => 'Yandex Browser',
'YL' => 'Yandex Browser Lite',
'XI' => 'Xiino'
// detected browsers in older versions
// 'IA' => 'Iceape', => pim
// 'SM' => 'SeaMonkey', => pim
);
/**
* Browser families mapped to the short codes of the associated browsers
*
* @var array
*/
protected static $browserFamilies = array(
'Android Browser' => array('AN', 'MU'),
'BlackBerry Browser' => array('BB'),
'Baidu' => array('BD', 'BS'),
'Amiga' => array('AV', 'AW'),
'Chrome' => array('CH', 'BA', 'BR', 'CC', 'CD', 'CM', 'CI', 'CF', 'CN', 'CR', 'CP', 'DD', 'IR', 'RM', 'AO', 'TS', 'VI', 'PT', 'AS', 'TB', 'AD', 'SB', 'WP', 'I3', 'CV', 'WH', 'SZ', 'QW', 'LF', 'KW', '2B', 'CE', 'EC', 'MT', 'MS', 'HA', 'OC', 'MZ', 'BM', 'KN', 'SW', 'M1', 'FA', 'TA', 'AH', 'CL', 'SU', 'EU', 'UB', 'LO', 'VG', 'TV'),
'Firefox' => array('FF', 'FE', 'FM', 'SX', 'FB', 'PX', 'MB', 'EI', 'WF', 'CU', 'TF', 'QM', 'FR', 'I4', 'GZ', 'MO', 'F1', 'BI', 'MN', 'BH', 'TO', 'OS', 'FY'),
'Internet Explorer' => array('IE', 'IM', 'PS'),
'Konqueror' => array('KO'),
'NetFront' => array('NF'),
'NetSurf' => array('NE'),
'Nokia Browser' => array('NB', 'NO', 'NV', 'DO'),
'Opera' => array('OP', 'OM', 'OI', 'ON', 'OO', 'OG', 'OH', 'O1', 'OX'),
'Safari' => array('SF', 'MF', 'SO'),
'Sailfish Browser' => array('SA')
);
/**
* Browsers that are available for mobile devices only
*
* @var array
*/
protected static $mobileOnlyBrowsers = array(
'36', 'OC', 'PU', 'SK', 'MF', 'OI', 'OM', 'DD', 'DB', 'ST', 'BL', 'IV', 'FM', 'C1', 'AL', 'SA', 'SB', 'FR', 'WP', 'HA', 'NX', 'HU', 'VV', 'RE', 'CB', 'MZ', 'UM', 'FK', 'FX', 'WI', 'MN', 'M1', 'AH', 'SU', 'EU', 'EZ', 'UT', 'DT', 'S0'
);
/**
* Returns list of all available browsers
* @return array
*/
public static function getAvailableBrowsers()
{
return self::$availableBrowsers;
}
/**
* Returns list of all available browser families
* @return array
*/
public static function getAvailableBrowserFamilies()
{
return self::$browserFamilies;
}
/**
* @param string $browserLabel
* @return bool|string If false, "Unknown"
*/
public static function getBrowserFamily($browserLabel)
{
foreach (self::$browserFamilies as $browserFamily => $browserLabels) {
if (in_array($browserLabel, $browserLabels)) {
return $browserFamily;
}
}
return false;
}
/**
* Returns if the given browser is mobile only
*
* @param string $browser Label or name of browser
* @return bool
*/
public static function isMobileOnlyBrowser($browser)
{
return in_array($browser, self::$mobileOnlyBrowsers) || (in_array($browser, self::$availableBrowsers) && in_array(array_search($browser, self::$availableBrowsers), self::$mobileOnlyBrowsers));
}
public function parse()
{
foreach ($this->getRegexes() as $regex) {
$matches = $this->matchUserAgent($regex['regex']);
if ($matches) {
break;
}
}
if (empty($matches)) {
return null;
}
$name = $this->buildByMatch($regex['name'], $matches);
foreach (self::getAvailableBrowsers() as $browserShort => $browserName) {
if (strtolower($name) == strtolower($browserName)) {
$version = (string) $this->buildVersion($regex['version'], $matches);
$engine = $this->buildEngine(isset($regex['engine']) ? $regex['engine'] : array(), $version);
$engineVersion = $this->buildEngineVersion($engine);
return array(
'type' => 'browser',
'name' => $browserName,
'short_name' => $browserShort,
'version' => $version,
'engine' => $engine,
'engine_version' => $engineVersion,
);
}
}
// This Exception should never be thrown. If so a defined browser name is missing in $availableBrowsers
throw new \Exception(sprintf('Detected browser name "%s" was not found in $availableBrowsers. Tried to parse user agent: %s', $name, $this->userAgent)); // @codeCoverageIgnore
}
protected function buildEngine($engineData, $browserVersion)
{
$engine = '';
// if an engine is set as default
if (isset($engineData['default'])) {
$engine = $engineData['default'];
}
// check if engine is set for browser version
if (array_key_exists('versions', $engineData) && is_array($engineData['versions'])) {
foreach ($engineData['versions'] as $version => $versionEngine) {
if (version_compare($browserVersion, $version) >= 0) {
$engine = $versionEngine;
}
}
}
// try to detect the engine using the regexes
if (empty($engine)) {
$engineParser = new Engine();
$engineParser->setYamlParser($this->getYamlParser());
$engineParser->setCache($this->getCache());
$engineParser->setUserAgent($this->userAgent);
$engine = $engineParser->parse();
}
return $engine;
}
protected function buildEngineVersion($engine)
{
$engineVersionParser = new Engine\Version($this->userAgent, $engine);
return $engineVersionParser->parse();
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client\Browser;
use DeviceDetector\Parser\Client\ClientParserAbstract;
/**
* Class Engine
*
* Client parser for browser engine detection
*
* @package DeviceDetector\Parser\Client\Browser
*/
class Engine extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/browser_engine.yml';
protected $parserName = 'browserengine';
/**
* Known browser engines mapped to their internal short codes
*
* @var array
*/
protected static $availableEngines = array(
'WebKit',
'Blink',
'Trident',
'Text-based',
'Dillo',
'iCab',
'Elektra',
'Presto',
'Gecko',
'KHTML',
'NetFront',
'Edge',
'NetSurf',
'Servo'
);
/**
* Returns list of all available browser engines
* @return array
*/
public static function getAvailableEngines()
{
return self::$availableEngines;
}
public function parse()
{
foreach ($this->getRegexes() as $regex) {
$matches = $this->matchUserAgent($regex['regex']);
if ($matches) {
break;
}
}
if (!$matches) {
return '';
}
$name = $this->buildByMatch($regex['name'], $matches);
foreach (self::getAvailableEngines() as $engineName) {
if (strtolower($name) == strtolower($engineName)) {
return $engineName;
}
}
// This Exception should never be thrown. If so a defined browser name is missing in $availableEngines
throw new \Exception('Detected browser engine was not found in $availableEngines. Tried to parse user agent: '.$this->userAgent); // @codeCoverageIgnore
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client\Browser\Engine;
use DeviceDetector\Parser\Client\ClientParserAbstract;
/**
* Class Version
*
* Client parser for browser engine version detection
*
* @package DeviceDetector\Parser\Client\Browser\Engine
*/
class Version extends ClientParserAbstract
{
/**
* @var string
*/
private $engine;
/**
* Version constructor.
*
* @param string $ua
* @param string $engine
*/
public function __construct($ua, $engine)
{
parent::__construct($ua);
$this->engine = $engine;
}
public function parse()
{
if (empty($this->engine)) {
return '';
}
preg_match("~$this->engine\s*/?\s*((?(?=\d+\.\d)\d+[.\d]*|\d{1,7}(?=(?:\D|$))))~i", $this->userAgent, $matches);
if (!$matches) {
return '';
}
return array_pop($matches);
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
use DeviceDetector\Parser\ParserAbstract;
abstract class ClientParserAbstract extends ParserAbstract
{
protected $fixtureFile = '';
protected $parserName = '';
/**
* Parses the current UA and checks whether it contains any client information
*
* @see $fixtureFile for file with list of detected clients
*
* Step 1: Build a big regex containing all regexes and match UA against it
* -> If no matches found: return
* -> Otherwise:
* Step 2: Walk through the list of regexes in feed_readers.yml and try to match every one
* -> Return the matched feed reader
*
* NOTE: Doing the big match before matching every single regex speeds up the detection
*/
public function parse()
{
$result = null;
if ($this->preMatchOverall()) {
foreach ($this->getRegexes() as $regex) {
$matches = $this->matchUserAgent($regex['regex']);
if ($matches) {
$result = array(
'type' => $this->parserName,
'name' => $this->buildByMatch($regex['name'], $matches),
'version' => $this->buildVersion($regex['version'], $matches)
);
break;
}
}
}
return $result;
}
/**
* Returns all names defined in the regexes
*
* Attention: This method might not return all names of detected clients
*
* @return array
*/
public static function getAvailableClients()
{
$instance = new static();
$regexes = $instance->getRegexes();
$names = array();
foreach ($regexes as $regex) {
if ($regex['name'] != '$1') {
$names[] = $regex['name'];
}
}
natcasesort($names);
return array_unique($names);
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
/**
* Class FeedReader
*
* Client parser for feed reader detection
*
* @package DeviceDetector\Parser\Client
*/
class FeedReader extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/feed_readers.yml';
protected $parserName = 'feed reader';
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
/**
* Class Library
*
* Client parser for tool & software detection
*
* @package DeviceDetector\Parser\Client
*/
class Library extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/libraries.yml';
protected $parserName = 'library';
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
/**
* Class MediaPlayer
*
* Client parser for mediaplayer detection
*
* @package DeviceDetector\Parser\Client
*/
class MediaPlayer extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/mediaplayers.yml';
protected $parserName = 'mediaplayer';
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
/**
* Class MobileApp
*
* Client parser for mobile app detection
*
* @package DeviceDetector\Parser\Client
*/
class MobileApp extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/mobile_apps.yml';
protected $parserName = 'mobile app';
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Client;
/**
* Class PIM
*
* Client parser for pim (personal information manager) detection
*
* @package DeviceDetector\Parser\Client
*/
class PIM extends ClientParserAbstract
{
protected $fixtureFile = 'regexes/client/pim.yml';
protected $parserName = 'pim';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class Camera
*
* Device parser for camera detection
*
* @package DeviceDetector\Parser\Device
*/
class Camera extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/cameras.yml';
protected $parserName = 'camera';
public function parse()
{
if (!$this->preMatchOverall()) {
return false;
}
return parent::parse();
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class CarBrowser
*
* Device parser for car browser detection
*
* @package DeviceDetector\Parser\Device
*/
class CarBrowser extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/car_browsers.yml';
protected $parserName = 'car browser';
public function parse()
{
if (!$this->preMatchOverall()) {
return false;
}
return parent::parse();
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class Console
*
* Device parser for console detection
*
* @package DeviceDetector\Parser\Device
*/
class Console extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/consoles.yml';
protected $parserName = 'console';
public function parse()
{
if (!$this->preMatchOverall()) {
return false;
}
return parent::parse();
}
}

View File

@ -0,0 +1,832 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
use DeviceDetector\Parser\ParserAbstract;
/**
* Class DeviceParserAbstract
*
* Abstract class for all device parsers
*
* @package DeviceDetector\Parser\Device
*/
abstract class DeviceParserAbstract extends ParserAbstract
{
protected $deviceType = null;
protected $model = null;
protected $brand = null;
const DEVICE_TYPE_DESKTOP = 0;
const DEVICE_TYPE_SMARTPHONE = 1;
const DEVICE_TYPE_TABLET = 2;
const DEVICE_TYPE_FEATURE_PHONE = 3;
const DEVICE_TYPE_CONSOLE = 4;
const DEVICE_TYPE_TV = 5; // including set top boxes, blu-ray players,...
const DEVICE_TYPE_CAR_BROWSER = 6;
const DEVICE_TYPE_SMART_DISPLAY = 7;
const DEVICE_TYPE_CAMERA = 8;
const DEVICE_TYPE_PORTABLE_MEDIA_PAYER = 9;
const DEVICE_TYPE_PHABLET = 10;
const DEVICE_TYPE_SMART_SPEAKER = 11;
/**
* Detectable device types
*
* @var array
*/
protected static $deviceTypes = array(
'desktop' => self::DEVICE_TYPE_DESKTOP,
'smartphone' => self::DEVICE_TYPE_SMARTPHONE,
'tablet' => self::DEVICE_TYPE_TABLET,
'feature phone' => self::DEVICE_TYPE_FEATURE_PHONE,
'console' => self::DEVICE_TYPE_CONSOLE,
'tv' => self::DEVICE_TYPE_TV,
'car browser' => self::DEVICE_TYPE_CAR_BROWSER,
'smart display' => self::DEVICE_TYPE_SMART_DISPLAY,
'camera' => self::DEVICE_TYPE_CAMERA,
'portable media player' => self::DEVICE_TYPE_PORTABLE_MEDIA_PAYER,
'phablet' => self::DEVICE_TYPE_PHABLET,
'smart speaker' => self::DEVICE_TYPE_SMART_SPEAKER,
);
/**
* Known device brands
*
* Note: Before using a new brand in on of the regex files, it needs to be added here
*
* @var array
*/
public static $deviceBrands = array(
'3Q' => '3Q',
'4G' => '4Good',
'AE' => 'Ace',
'AA' => 'AllCall',
'AC' => 'Acer',
'A9' => 'Advan',
'AD' => 'Advance',
'A3' => 'AGM',
'AZ' => 'Ainol',
'AI' => 'Airness',
'0A' => 'AIS',
'AW' => 'Aiwa',
'AK' => 'Akai',
'1A' => 'Alba',
'AL' => 'Alcatel',
'4A' => 'Aligator',
'3A' => 'AllDocube',
'A2' => 'Allview',
'A7' => 'Allwinner',
'A1' => 'Altech UEC',
'A5' => 'altron',
'AN' => 'Arnova',
'2A' => 'Atom',
'KN' => 'Amazon',
'AG' => 'AMGOO',
'AO' => 'Amoi',
'AP' => 'Apple',
'AR' => 'Archos',
'AS' => 'ARRIS',
'AB' => 'Arian Space',
'AT' => 'Airties',
'A6' => 'Ark',
'A4' => 'Ask',
'A8' => 'Assistant',
'A0' => 'ANS',
'AU' => 'Asus',
'AH' => 'AVH',
'AV' => 'Avvio',
'AX' => 'Audiovox',
'AY' => 'Axxion',
'AM' => 'Azumi Mobile',
'BB' => 'BBK',
'BE' => 'Becker',
'B5' => 'Beeline',
'BI' => 'Bird',
'BT' => 'Bitel',
'BG' => 'BGH',
'BL' => 'Beetel',
'BP' => 'Blaupunkt',
'B3' => 'Bluboo',
'BF' => 'Black Fox',
'B6' => 'BDF',
'BM' => 'Bmobile',
'BN' => 'Barnes & Noble',
'BO' => 'BangOlufsen',
'BQ' => 'BenQ',
'BS' => 'BenQ-Siemens',
'BU' => 'Blu',
'BD' => 'Bluegood',
'B2' => 'Blackview',
'B4' => 'bogo',
'BW' => 'Boway',
'BZ' => 'Bezkam',
'BX' => 'bq',
'BV' => 'Bravis',
'BR' => 'Brondi',
'B1' => 'Bush',
'CB' => 'CUBOT',
'CF' => 'Carrefour',
'CP' => 'Captiva',
'CS' => 'Casio',
'R4' => 'Casper',
'CA' => 'Cat',
'C9' => 'CAGI',
'CE' => 'Celkon',
'CC' => 'ConCorde',
'C2' => 'Changhong',
'2C' => 'Ghong',
'CH' => 'Cherry Mobile',
'1C' => 'Chuwi',
'L8' => 'Clarmin',
'CK' => 'Cricket',
'C1' => 'Crosscall',
'CL' => 'Compal',
'CN' => 'CnM',
'CM' => 'Crius Mea',
'C3' => 'China Mobile',
'CR' => 'CreNova',
'CT' => 'Capitel',
'CQ' => 'Compaq',
'CO' => 'Coolpad',
'C5' => 'Condor',
'CW' => 'Cowon',
'CU' => 'Cube',
'CY' => 'Coby Kyros',
'C6' => 'Comio',
'C7' => 'ComTrade Tesla',
'C8' => 'Concord',
'CX' => 'Crescent',
'C4' => 'Cyrus',
'CV' => 'CVTE',
'D5' => 'Daewoo',
'DA' => 'Danew',
'DT' => 'Datang',
'D1' => 'Datsun',
'DE' => 'Denver',
'DW' => 'DeWalt',
'DX' => 'DEXP',
'DS' => 'Desay',
'DB' => 'Dbtel',
'DC' => 'DoCoMo',
'DG' => 'Dialog',
'DI' => 'Dicam',
'D4' => 'Digi',
'D3' => 'Digicel',
'DD' => 'Digiland',
'D2' => 'Digma',
'D6' => 'Divisat',
'DL' => 'Dell',
'DN' => 'DNS',
'DM' => 'DMM',
'DO' => 'Doogee',
'DV' => 'Doov',
'DP' => 'Dopod',
'DR' => 'Doro',
'DU' => 'Dune HD',
'EB' => 'E-Boda',
'EA' => 'EBEST',
'EC' => 'Ericsson',
'E7' => 'Ergo',
'ED' => 'Energizer',
'E4' => 'Echo Mobiles',
'ES' => 'ECS',
'E6' => 'EE',
'EI' => 'Ezio',
'EM' => 'Eks Mobility',
'EL' => 'Elephone',
'EG' => 'Elenberg',
'EP' => 'Easypix',
'EK' => 'EKO',
'E1' => 'Energy Sistem',
'ER' => 'Ericy',
'EE' => 'Essential',
'EN' => 'Eton',
'E2' => 'Essentielb',
'1E' => 'Etuline',
'ET' => 'eTouch',
'EV' => 'Evertek',
'E3' => 'Evolio',
'EO' => 'Evolveo',
'EX' => 'Explay',
'E0' => 'EvroMedia',
'E5' => 'Extrem',
'EZ' => 'Ezze',
'E8' => 'E-tel',
'E9' => 'Evercoss',
'EU' => 'Eurostar',
'FA' => 'Fairphone',
'FM' => 'Famoco',
'FE' => 'Fengxiang',
'FI' => 'FiGO',
'FL' => 'Fly',
'F1' => 'FinePower',
'FT' => 'Freetel',
'FR' => 'Forstar',
'FO' => 'Foxconn',
'F2' => 'FORME',
'FN' => 'FNB',
'FU' => 'Fujitsu',
'FD' => 'Fondi',
'GT' => 'G-TiDE',
'GM' => 'Garmin-Asus',
'GA' => 'Gateway',
'GD' => 'Gemini',
'GN' => 'General Mobile',
'GE' => 'Geotel',
'GH' => 'Ghia',
'GI' => 'Gionee',
'GG' => 'Gigabyte',
'GS' => 'Gigaset',
'GZ' => 'Ginzzu',
'G4' => 'Globex',
'GC' => 'GOCLEVER',
'GL' => 'Goly',
'GO' => 'Google',
'G1' => 'GoMobile',
'GR' => 'Gradiente',
'GP' => 'Grape',
'GU' => 'Grundig',
'HF' => 'Hafury',
'HA' => 'Haier',
'HS' => 'Hasee',
'HE' => 'HannSpree',
'HI' => 'Hisense',
'HL' => 'Hi-Level',
'H2' => 'Highscreen',
'H1' => 'Hoffmann',
'HM' => 'Homtom',
'HO' => 'Hosin',
'HZ' => 'Hoozo',
'HP' => 'HP',
'HT' => 'HTC',
'HU' => 'Huawei',
'HX' => 'Humax',
'HY' => 'Hyrican',
'HN' => 'Hyundai',
'IA' => 'Ikea',
'IB' => 'iBall',
'IJ' => 'i-Joy',
'IY' => 'iBerry',
'IH' => 'iHunt',
'IK' => 'iKoMo',
'IE' => 'iView',
'IM' => 'i-mate',
'I1' => 'iOcean',
'I2' => 'IconBIT',
'IL' => 'IMO Mobile',
'I7' => 'iLA',
'IW' => 'iNew',
'IP' => 'iPro',
'IF' => 'Infinix',
'I0' => 'InFocus',
'I5' => 'InnJoo',
'IN' => 'Innostream',
'IS' => 'Insignia',
'I4' => 'Inoi',
'IR' => 'iRola',
'IU' => 'iRulu',
'I6' => 'Irbis',
'II' => 'Inkti',
'IX' => 'Intex',
'IO' => 'i-mobile',
'IQ' => 'INQ',
'IT' => 'Intek',
'IV' => 'Inverto',
'I3' => 'Impression',
'IZ' => 'iTel',
'I9' => 'iZotron',
'JA' => 'JAY-Tech',
'JI' => 'Jiayu',
'JO' => 'Jolla',
'J5' => 'Just5',
'KL' => 'Kalley',
'K4' => 'Kaan',
'K7' => 'Kaiomy',
'K6' => 'Kanji',
'KA' => 'Karbonn',
'K5' => 'KATV1',
'KD' => 'KDDI',
'K1' => 'Kiano',
'KV' => 'Kivi',
'KI' => 'Kingsun',
'KC' => 'Kocaso',
'KG' => 'Kogan',
'KO' => 'Konka',
'KM' => 'Komu',
'KB' => 'Koobee',
'KT' => 'K-Touch',
'KH' => 'KT-Tech',
'KK' => 'Kodak',
'KP' => 'KOPO',
'KW' => 'Konrow',
'KR' => 'Koridy',
'K2' => 'KRONO',
'KS' => 'Kempler & Strauss',
'K3' => 'Keneksi',
'KU' => 'Kumai',
'KY' => 'Kyocera',
'KZ' => 'Kazam',
'KE' => 'Krüger&Matz',
'LQ' => 'LAIQ',
'L2' => 'Landvo',
'L6' => 'Land Rover',
'LV' => 'Lava',
'LA' => 'Lanix',
'LK' => 'Lark',
'LC' => 'LCT',
'L5' => 'Leagoo',
'LD' => 'Ledstar',
'L1' => 'LeEco',
'L4' => 'Lemhoov',
'LE' => 'Lenovo',
'LN' => 'Lenco',
'LT' => 'Leotec',
'L7' => 'Lephone',
'LP' => 'Le Pan',
'LG' => 'LG',
'LI' => 'Lingwin',
'LO' => 'Loewe',
'LM' => 'Logicom',
'L3' => 'Lexand',
'LX' => 'Lexibook',
'LY' => 'LYF',
'LU' => 'Lumus',
'L9' => 'Luna',
'MN' => 'M4tel',
'MJ' => 'Majestic',
'MA' => 'Manta Multimedia',
'5M' => 'Mann',
'2M' => 'Masstel',
'MW' => 'Maxwest',
'7M' => 'Maxcom',
'M0' => 'Maze',
'MB' => 'Mobistel',
'0M' => 'Mecool',
'M3' => 'Mecer',
'MD' => 'Medion',
'M2' => 'MEEG',
'M1' => 'Meizu',
'3M' => 'Meitu',
'ME' => 'Metz',
'MX' => 'MEU',
'MI' => 'MicroMax',
'M5' => 'MIXC',
'MH' => 'Mobiola',
'4M' => 'Mobicel',
'M6' => 'Mobiistar',
'MC' => 'Mediacom',
'MK' => 'MediaTek',
'MO' => 'Mio',
'M7' => 'Miray',
'MM' => 'Mpman',
'M4' => 'Modecom',
'MF' => 'Mofut',
'MR' => 'Motorola',
'MV' => 'Movic',
'MS' => 'Microsoft',
'M9' => 'MTC',
'MP' => 'MegaFon',
'MZ' => 'MSI',
'MU' => 'Memup',
'MT' => 'Mitsubishi',
'ML' => 'MLLED',
'MQ' => 'M.T.T.',
'N4' => 'MTN',
'MY' => 'MyPhone',
'1M' => 'MYFON',
'MG' => 'MyWigo',
'M8' => 'Myria',
'6M' => 'Mystery',
'N3' => 'Navon',
'N7' => 'National',
'N5' => 'NOA',
'NE' => 'NEC',
'NF' => 'Neffos',
'NA' => 'Netgear',
'NU' => 'NeuImage',
'NG' => 'NGM',
'NZ' => 'NG Optics',
'N6' => 'Nobby',
'NO' => 'Nous',
'NI' => 'Nintendo',
'N1' => 'Noain',
'N2' => 'Nextbit',
'NK' => 'Nokia',
'NV' => 'Nvidia',
'NB' => 'Noblex',
'NM' => 'Nomi',
'N0' => 'Nuvo',
'NL' => 'NUU Mobile',
'NY' => 'NYX Mobile',
'NN' => 'Nikon',
'NW' => 'Newgen',
'NS' => 'NewsMy',
'NX' => 'Nexian',
'NT' => 'NextBook',
'O3' => 'O+',
'OB' => 'Obi',
'O1' => 'Odys',
'OD' => 'Onda',
'ON' => 'OnePlus',
'OP' => 'OPPO',
'OR' => 'Orange',
'OS' => 'Ordissimo',
'OT' => 'O2',
'OK' => 'Ouki',
'OE' => 'Oukitel',
'OU' => 'OUYA',
'OO' => 'Opsson',
'OV' => 'Overmax',
'OY' => 'Oysters',
'OW' => 'öwn',
'PN' => 'Panacom',
'PA' => 'Panasonic',
'PB' => 'PCBOX',
'PC' => 'PCD',
'PD' => 'PCD Argentina',
'PE' => 'PEAQ',
'PG' => 'Pentagram',
'PH' => 'Philips',
'PI' => 'Pioneer',
'PX' => 'Pixus',
'PL' => 'Polaroid',
'P5' => 'Polytron',
'P9' => 'Primepad',
'P6' => 'Proline',
'PM' => 'Palm',
'PO' => 'phoneOne',
'PT' => 'Pantech',
'PY' => 'Ployer',
'P4' => 'Plum',
'PV' => 'Point of View',
'PP' => 'PolyPad',
'P2' => 'Pomp',
'P3' => 'PPTV',
'PS' => 'Positivo',
'PR' => 'Prestigio',
'P1' => 'ProScan',
'PU' => 'PULID',
'QI' => 'Qilive',
'QT' => 'Qtek',
'QH' => 'Q-Touch',
'QM' => 'QMobile',
'QA' => 'Quantum',
'QU' => 'Quechua',
'QO' => 'Qumo',
'RA' => 'Ramos',
'RC' => 'RCA Tablets',
'RB' => 'Readboy',
'RI' => 'Rikomagic',
'RN' => 'Rinno',
'RV' => 'Riviera',
'RM' => 'RIM',
'RK' => 'Roku',
'RO' => 'Rover',
'R6' => 'RoverPad',
'RR' => 'Roadrover',
'R1' => 'Rokit',
'R3' => 'Rombica',
'RT' => 'RT Project',
'RX' => 'Ritmix',
'R7' => 'Ritzviva',
'R5' => 'Ross&Moor',
'R2' => 'R-TV',
'RG' => 'RugGear',
'RU' => 'Runbo',
'SQ' => 'Santin',
'SA' => 'Samsung',
'S0' => 'Sanei',
'SD' => 'Sega',
'SL' => 'Selfix',
'SE' => 'Sony Ericsson',
'S1' => 'Sencor',
'SF' => 'Softbank',
'SX' => 'SFR',
'SG' => 'Sagem',
'SH' => 'Sharp',
'7S' => 'Shift Phones',
'3S' => 'Shuttle',
'SI' => 'Siemens',
'SJ' => 'Silent Circle',
'1S' => 'Sigma',
'SN' => 'Sendo',
'S6' => 'Senseit',
'EW' => 'Senwa',
'SW' => 'Sky',
'SK' => 'Skyworth',
'SC' => 'Smartfren',
'SO' => 'Sony',
'OI' => 'Sonim',
'SP' => 'Spice',
'6S' => 'Spectrum',
'5S' => 'Sunvell',
'SU' => 'SuperSonic',
'S5' => 'Supra',
'SV' => 'Selevision',
'SY' => 'Sanyo',
'SM' => 'Symphony',
'4S' => 'Syrox',
'SR' => 'Smart',
'S7' => 'Smartisan',
'S4' => 'Star',
'SB' => 'STF Mobile',
'S8' => 'STK',
'S9' => 'Savio',
'2S' => 'Starway',
'ST' => 'Storex',
'S2' => 'Stonex',
'S3' => 'SunVan',
'SZ' => 'Sumvision',
'SS' => 'SWISSMOBILITY',
'10' => 'Simbans',
'X1' => 'Safaricom',
'TA' => 'Tesla',
'T5' => 'TB Touch',
'TC' => 'TCL',
'T7' => 'Teclast',
'TE' => 'Telit',
'T4' => 'ThL',
'TH' => 'TiPhone',
'TB' => 'Tecno Mobile',
'TP' => 'TechPad',
'TD' => 'Tesco',
'TI' => 'TIANYU',
'TG' => 'Telego',
'TL' => 'Telefunken',
'T2' => 'Telenor',
'TM' => 'T-Mobile',
'TN' => 'Thomson',
'TQ' => 'Timovi',
'TY' => 'Tooky',
'T1' => 'Tolino',
'T9' => 'Top House',
'TO' => 'Toplux',
'T8' => 'Touchmate',
'TS' => 'Toshiba',
'TT' => 'TechnoTrend',
'T6' => 'TrekStor',
'T3' => 'Trevi',
'TU' => 'Tunisie Telecom',
'TR' => 'Turbo-X',
'1T' => 'Turbo',
'11' => 'True',
'TV' => 'TVC',
'TX' => 'TechniSat',
'TZ' => 'teXet',
'UC' => 'U.S. Cellular',
'UH' => 'Uhappy',
'UG' => 'Ugoos',
'UL' => 'Ulefone',
'UO' => 'Unnecto',
'UN' => 'Unowhy',
'US' => 'Uniscope',
'UX' => 'Unimax',
'UM' => 'UMIDIGI',
'UU' => 'Unonu',
'UK' => 'UTOK',
'UA' => 'Umax',
'UT' => 'UTStarcom',
'UZ' => 'Unihertz',
'VA' => 'Vastking',
'VD' => 'Videocon',
'VE' => 'Vertu',
'VN' => 'Venso',
'V5' => 'Vivax',
'VI' => 'Vitelcom',
'V7' => 'Vinga',
'VK' => 'VK Mobile',
'VS' => 'ViewSonic',
'V9' => 'Vsun',
'V8' => 'Vesta',
'VT' => 'Vestel',
'VR' => 'Vernee',
'V4' => 'Verizon',
'VL' => 'Verykool',
'V6' => 'VGO TEL',
'VV' => 'Vivo',
'VX' => 'Vertex',
'V3' => 'Vinsoc',
'V2' => 'Vonino',
'VG' => 'Vorago',
'V1' => 'Voto',
'VO' => 'Voxtel',
'VF' => 'Vodafone',
'VZ' => 'Vizio',
'VW' => 'Videoweb',
'VU' => 'Vulcan',
'WA' => 'Walton',
'WF' => 'Wileyfox',
'WN' => 'Wink',
'WM' => 'Weimei',
'WE' => 'WellcoM',
'WY' => 'Wexler',
'WI' => 'Wiko',
'WP' => 'Wieppo',
'WL' => 'Wolder',
'WG' => 'Wolfgang',
'WO' => 'Wonu',
'W1' => 'Woo',
'WX' => 'Woxter',
'XV' => 'X-View',
'XI' => 'Xiaomi',
'XL' => 'Xiaolajiao',
'XN' => 'Xion',
'XO' => 'Xolo',
'XR' => 'Xoro',
'YA' => 'Yarvik',
'YD' => 'Yandex',
'Y2' => 'Yes',
'YE' => 'Yezz',
'Y1' => 'Yu',
'YU' => 'Yuandao',
'YS' => 'Yusun',
'YO' => 'Yota',
'YT' => 'Ytone',
'YX' => 'Yxtel',
'ZE' => 'Zeemi',
'ZK' => 'Zenek',
'ZO' => 'Zonda',
'ZP' => 'Zopo',
'ZT' => 'ZTE',
'ZU' => 'Zuum',
'ZN' => 'Zen',
'ZY' => 'Zync',
'ZQ' => 'ZYQ',
'XT' => 'X-TIGI',
'XB' => 'NEXBOX',
// legacy brands, might be removed in future versions
'WB' => 'Web TV',
'XX' => 'Unknown'
);
public function getDeviceType()
{
return $this->deviceType;
}
/**
* Returns available device types
*
* @see $deviceTypes
* @return array
*/
public static function getAvailableDeviceTypes()
{
return self::$deviceTypes;
}
/**
* Returns names of all available device types
*
* @return array
*/
public static function getAvailableDeviceTypeNames()
{
return array_keys(self::$deviceTypes);
}
/**
* Returns the name of the given device type
*
* @param int $deviceType one of the DEVICE_TYPE_* constants
*
* @return mixed
*/
public static function getDeviceName($deviceType)
{
return array_search($deviceType, self::$deviceTypes);
}
/**
* Returns the detected device model
*
* @return string
*/
public function getModel()
{
return $this->model;
}
/**
* Returns the detected device brand
*
* @return string
*/
public function getBrand()
{
return $this->brand;
}
/**
* Returns the full brand name for the given short name
*
* @param string $brandId short brand name
* @return string
*/
public static function getFullName($brandId)
{
if (array_key_exists($brandId, self::$deviceBrands)) {
return self::$deviceBrands[$brandId];
}
return '';
}
/**
* Sets the useragent to be parsed
*
* @param string $userAgent
*/
public function setUserAgent($userAgent)
{
$this->reset();
parent::setUserAgent($userAgent);
}
public function parse()
{
$regexes = $this->getRegexes();
foreach ($regexes as $brand => $regex) {
$matches = $this->matchUserAgent($regex['regex']);
if ($matches) {
break;
}
}
if (empty($matches)) {
return false;
}
if ($brand != 'Unknown') {
$brandId = array_search($brand, self::$deviceBrands);
if ($brandId === false) {
// This Exception should never be thrown. If so a defined brand name is missing in $deviceBrands
throw new \Exception("The brand with name '$brand' should be listed in the deviceBrands array. Tried to parse user agent: ".$this->userAgent); // @codeCoverageIgnore
}
$this->brand = $brandId;
}
if (isset($regex['device']) && in_array($regex['device'], self::$deviceTypes)) {
$this->deviceType = self::$deviceTypes[$regex['device']];
}
$this->model = '';
if (isset($regex['model'])) {
$this->model = $this->buildModel($regex['model'], $matches);
}
if (isset($regex['models'])) {
foreach ($regex['models'] as $modelRegex) {
$modelMatches = $this->matchUserAgent($modelRegex['regex']);
if ($modelMatches) {
break;
}
}
if (empty($modelMatches)) {
return true;
}
$this->model = trim($this->buildModel($modelRegex['model'], $modelMatches));
if (isset($modelRegex['brand']) && $brandId = array_search($modelRegex['brand'], self::$deviceBrands)) {
$this->brand = $brandId;
}
if (isset($modelRegex['device']) && in_array($modelRegex['device'], self::$deviceTypes)) {
$this->deviceType = self::$deviceTypes[$modelRegex['device']];
}
}
return true;
}
protected function buildModel($model, $matches)
{
$model = $this->buildByMatch($model, $matches);
$model = str_replace('_', ' ', $model);
$model = preg_replace('/ TD$/i', '', $model);
if ($model === 'Build') {
return null;
}
return $model;
}
protected function reset()
{
$this->deviceType = null;
$this->model = null;
$this->brand = null;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class HbbTv
*
* Device parser for hbbtv detection
*
* @package DeviceDetector\Parser\Device
*/
class HbbTv extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/televisions.yml';
protected $parserName = 'tv';
/**
* Parses the current UA and checks whether it contains HbbTv information
*
* @see televisions.yml for list of detected televisions
*/
public function parse()
{
// only parse user agents containing hbbtv fragment
if (!$this->isHbbTv()) {
return false;
}
parent::parse();
// always set device type to tv, even if no model/brand could be found
$this->deviceType = self::DEVICE_TYPE_TV;
return true;
}
/**
* Returns if the parsed UA was identified as a HbbTV device
*
* @return bool
*/
public function isHbbTv()
{
$regex = 'HbbTV/([1-9]{1}(?:\.[0-9]{1}){1,2})';
$match = $this->matchUserAgent($regex);
return $match ? $match[1] : false;
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class Mobile
*
* Device parser for mobile detection
*
* @package DeviceDetector\Parser\Device
*/
class Mobile extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/mobiles.yml';
protected $parserName = 'mobile';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser\Device;
/**
* Class PortableMediaPlayer
*
* Device parser for portable media player detection
*
* @package DeviceDetector\Parser\Device
*/
class PortableMediaPlayer extends DeviceParserAbstract
{
protected $fixtureFile = 'regexes/device/portable_media_player.yml';
protected $parserName = 'portablemediaplayer';
public function parse()
{
if (!$this->preMatchOverall()) {
return false;
}
return parent::parse();
}
}

View File

@ -0,0 +1,250 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser;
/**
* Class OperatingSystem
*
* Parses the useragent for operating system information
*
* Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml
* This class also defined some operating system families and methods to get the family for a specific os
*
* @package DeviceDetector\Parser
*/
class OperatingSystem extends ParserAbstract
{
protected $fixtureFile = 'regexes/oss.yml';
protected $parserName = 'os';
/**
* Known operating systems mapped to their internal short codes
*
* @var array
*/
protected static $operatingSystems = array(
'AIX' => 'AIX',
'AND' => 'Android',
'AMG' => 'AmigaOS',
'ATV' => 'Apple TV',
'ARL' => 'Arch Linux',
'BTR' => 'BackTrack',
'SBA' => 'Bada',
'BEO' => 'BeOS',
'BLB' => 'BlackBerry OS',
'QNX' => 'BlackBerry Tablet OS',
'BMP' => 'Brew',
'CES' => 'CentOS',
'COS' => 'Chrome OS',
'CYN' => 'CyanogenMod',
'DEB' => 'Debian',
'DFB' => 'DragonFly',
'FED' => 'Fedora',
'FOS' => 'Firefox OS',
'FIR' => 'Fire OS',
'BSD' => 'FreeBSD',
'GNT' => 'Gentoo',
'GTV' => 'Google TV',
'HPX' => 'HP-UX',
'HAI' => 'Haiku OS',
'IRI' => 'IRIX',
'INF' => 'Inferno',
'KOS' => 'KaiOS',
'KNO' => 'Knoppix',
'KBT' => 'Kubuntu',
'LIN' => 'GNU/Linux',
'LBT' => 'Lubuntu',
'VLN' => 'VectorLinux',
'MAC' => 'Mac',
'MAE' => 'Maemo',
'MDR' => 'Mandriva',
'SMG' => 'MeeGo',
'MCD' => 'MocorDroid',
'MIN' => 'Mint',
'MLD' => 'MildWild',
'MOR' => 'MorphOS',
'NBS' => 'NetBSD',
'MTK' => 'MTK / Nucleus',
'WII' => 'Nintendo',
'NDS' => 'Nintendo Mobile',
'OS2' => 'OS/2',
'T64' => 'OSF1',
'OBS' => 'OpenBSD',
'ORD' => 'Ordissimo',
'PSP' => 'PlayStation Portable',
'PS3' => 'PlayStation',
'RHT' => 'Red Hat',
'ROS' => 'RISC OS',
'REM' => 'Remix OS',
'RZD' => 'RazoDroiD',
'SAB' => 'Sabayon',
'SSE' => 'SUSE',
'SAF' => 'Sailfish OS',
'SLW' => 'Slackware',
'SOS' => 'Solaris',
'SYL' => 'Syllable',
'SYM' => 'Symbian',
'SYS' => 'Symbian OS',
'S40' => 'Symbian OS Series 40',
'S60' => 'Symbian OS Series 60',
'SY3' => 'Symbian^3',
'TDX' => 'ThreadX',
'TIZ' => 'Tizen',
'UBT' => 'Ubuntu',
'WTV' => 'WebTV',
'WIN' => 'Windows',
'WCE' => 'Windows CE',
'WIO' => 'Windows IoT',
'WMO' => 'Windows Mobile',
'WPH' => 'Windows Phone',
'WRT' => 'Windows RT',
'XBX' => 'Xbox',
'XBT' => 'Xubuntu',
'YNS' => 'YunOs',
'IOS' => 'iOS',
'POS' => 'palmOS',
'WOS' => 'webOS'
);
/**
* Operating system families mapped to the short codes of the associated operating systems
*
* @var array
*/
protected static $osFamilies = array(
'Android' => array('AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS'),
'AmigaOS' => array('AMG', 'MOR'),
'Apple TV' => array('ATV'),
'BlackBerry' => array('BLB', 'QNX'),
'Brew' => array('BMP'),
'BeOS' => array('BEO', 'HAI'),
'Chrome OS' => array('COS'),
'Firefox OS' => array('FOS', 'KOS'),
'Gaming Console' => array('WII', 'PS3'),
'Google TV' => array('GTV'),
'IBM' => array('OS2'),
'iOS' => array('IOS'),
'RISC OS' => array('ROS'),
'GNU/Linux' => array('LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF', 'ORD'),
'Mac' => array('MAC'),
'Mobile Gaming Console' => array('PSP', 'NDS', 'XBX'),
'Real-time OS' => array('MTK', 'TDX'),
'Other Mobile' => array('WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE'),
'Symbian' => array('SYM', 'SYS', 'SY3', 'S60', 'S40'),
'Unix' => array('SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'),
'WebTV' => array('WTV'),
'Windows' => array('WIN'),
'Windows Mobile' => array('WPH', 'WMO', 'WCE', 'WRT', 'WIO')
);
/**
* Returns all available operating systems
*
* @return array
*/
public static function getAvailableOperatingSystems()
{
return self::$operatingSystems;
}
/**
* Returns all available operating system families
*
* @return array
*/
public static function getAvailableOperatingSystemFamilies()
{
return self::$osFamilies;
}
public function parse()
{
$return = array();
foreach ($this->getRegexes() as $osRegex) {
$matches = $this->matchUserAgent($osRegex['regex']);
if ($matches) {
break;
}
}
if (!$matches) {
return $return;
}
$name = $this->buildByMatch($osRegex['name'], $matches);
$short = 'UNK';
foreach (self::$operatingSystems as $osShort => $osName) {
if (strtolower($name) == strtolower($osName)) {
$name = $osName;
$short = $osShort;
}
}
$return = array(
'name' => $name,
'short_name' => $short,
'version' => $this->buildVersion($osRegex['version'], $matches),
'platform' => $this->parsePlatform()
);
if (in_array($return['name'], self::$operatingSystems)) {
$return['short_name'] = array_search($return['name'], self::$operatingSystems);
}
return $return;
}
protected function parsePlatform()
{
if ($this->matchUserAgent('arm')) {
return 'ARM';
} elseif ($this->matchUserAgent('WOW64|x64|win64|amd64|x86_64')) {
return 'x64';
} elseif ($this->matchUserAgent('i[0-9]86|i86pc')) {
return 'x86';
}
return '';
}
/**
* Returns the operating system family for the given operating system
*
* @param $osLabel
* @return bool|string If false, "Unknown"
*/
public static function getOsFamily($osLabel)
{
foreach (self::$osFamilies as $family => $labels) {
if (in_array($osLabel, $labels)) {
return $family;
}
}
return false;
}
/**
* Returns the full name for the given short name
*
* @param $os
* @param bool $ver
*
* @return bool|string
*/
public static function getNameFromId($os, $ver = false)
{
if (array_key_exists($os, self::$operatingSystems)) {
$osFullName = self::$operatingSystems[$os];
return trim($osFullName . " " . $ver);
}
return false;
}
}

View File

@ -0,0 +1,329 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser;
use DeviceDetector\Cache\StaticCache;
use DeviceDetector\DeviceDetector;
use DeviceDetector\Cache\Cache;
use DeviceDetector\Yaml\Parser AS YamlParser;
use DeviceDetector\Yaml\Spyc;
/**
* Class ParserAbstract
*
* @package DeviceDetector\Parser
*/
abstract class ParserAbstract
{
/**
* Holds the path to the yml file containing regexes
* @var string
*/
protected $fixtureFile;
/**
* Holds the internal name of the parser
* Used for caching
* @var string
*/
protected $parserName;
/**
* Holds the user agent the should be parsed
* @var string
*/
protected $userAgent;
/**
* Holds an array with method that should be available global
* @var array
*/
protected $globalMethods;
/**
* Holds an array with regexes to parse, if already loaded
* @var array
*/
protected $regexList;
/**
* Indicates how deep versioning will be detected
* if $maxMinorParts is 0 only the major version will be returned
* @var int
*/
protected static $maxMinorParts = 1;
/**
* Versioning constant used to set max versioning to major version only
* Version examples are: 3, 5, 6, 200, 123, ...
*/
const VERSION_TRUNCATION_MAJOR = 0;
/**
* Versioning constant used to set max versioning to minor version
* Version examples are: 3.4, 5.6, 6.234, 0.200, 1.23, ...
*/
const VERSION_TRUNCATION_MINOR = 1;
/**
* Versioning constant used to set max versioning to path level
* Version examples are: 3.4.0, 5.6.344, 6.234.2, 0.200.3, 1.2.3, ...
*/
const VERSION_TRUNCATION_PATCH = 2;
/**
* Versioning constant used to set versioning to build number
* Version examples are: 3.4.0.12, 5.6.334.0, 6.234.2.3, 0.200.3.1, 1.2.3.0, ...
*/
const VERSION_TRUNCATION_BUILD = 3;
/**
* Versioning constant used to set versioning to unlimited (no truncation)
*/
const VERSION_TRUNCATION_NONE = null;
/**
* @var Cache|\Doctrine\Common\Cache\CacheProvider
*/
protected $cache;
/**
* @var YamlParser
*/
protected $yamlParser;
abstract public function parse();
public function __construct($ua='')
{
$this->setUserAgent($ua);
}
/**
* Set how DeviceDetector should return versions
* @param int|null $type Any of the VERSION_TRUNCATION_* constants
*/
public static function setVersionTruncation($type)
{
if (in_array($type, array(self::VERSION_TRUNCATION_BUILD,
self::VERSION_TRUNCATION_NONE,
self::VERSION_TRUNCATION_MAJOR,
self::VERSION_TRUNCATION_MINOR,
self::VERSION_TRUNCATION_PATCH))) {
self::$maxMinorParts = $type;
}
}
/**
* Sets the user agent to parse
*
* @param string $ua user agent
*/
public function setUserAgent($ua)
{
$this->userAgent = $ua;
}
/**
* Returns the internal name of the parser
*
* @return string
*/
public function getName()
{
return $this->parserName;
}
/**
* Returns the result of the parsed yml file defined in $fixtureFile
*
* @return array
*/
protected function getRegexes()
{
if (empty($this->regexList)) {
$cacheKey = 'DeviceDetector-'.DeviceDetector::VERSION.'regexes-'.$this->getName();
$cacheKey = preg_replace('/([^a-z0-9_-]+)/i', '', $cacheKey);
$this->regexList = $this->getCache()->fetch($cacheKey);
if (empty($this->regexList)) {
$this->regexList = $this->getYamlParser()->parseFile(
$this->getRegexesDirectory().DIRECTORY_SEPARATOR.$this->fixtureFile
);
$this->getCache()->save($cacheKey, $this->regexList);
}
}
return $this->regexList;
}
/**
* @return string
*/
protected function getRegexesDirectory()
{
return dirname(__DIR__);
}
/**
* Matches the useragent against the given regex
*
* @param string $regex
* @return array|bool
*/
protected function matchUserAgent($regex)
{
// only match if useragent begins with given regex or there is no letter before it
$regex = '/(?:^|[^A-Z0-9\-_]|[^A-Z0-9\-]_|sprd-)(?:' . str_replace('/', '\/', $regex) . ')/i';
if (preg_match($regex, $this->userAgent, $matches)) {
return $matches;
}
return false;
}
/**
* @param string $item
* @param array $matches
* @return string type
*/
protected function buildByMatch($item, $matches)
{
for ($nb=1;$nb<=3;$nb++) {
if (strpos($item, '$' . $nb) === false) {
continue;
}
$replace = isset($matches[$nb]) ? $matches[$nb] : '';
$item = trim(str_replace('$' . $nb, $replace, $item));
}
return $item;
}
/**
* Builds the version with the given $versionString and $matches
*
* Example:
* $versionString = 'v$2'
* $matches = array('version_1_0_1', '1_0_1')
* return value would be v1.0.1
*
* @param $versionString
* @param $matches
* @return mixed|string
*/
protected function buildVersion($versionString, $matches)
{
$versionString = $this->buildByMatch($versionString, $matches);
$versionString = str_replace('_', '.', $versionString);
if (null !== self::$maxMinorParts && substr_count($versionString, '.') > self::$maxMinorParts) {
$versionParts = explode('.', $versionString);
$versionParts = array_slice($versionParts, 0, 1+self::$maxMinorParts);
$versionString = implode('.', $versionParts);
}
return trim($versionString, ' .');
}
/**
* Tests the useragent against a combination of all regexes
*
* All regexes returned by getRegexes() will be reversed and concated with '|'
* Afterwards the big regex will be tested against the user agent
*
* Method can be used to speed up detections by making a big check before doing checks for every single regex
*
* @return bool
*/
protected function preMatchOverall()
{
$regexes = $this->getRegexes();
static $overAllMatch;
$cacheKey = $this->parserName.DeviceDetector::VERSION.'-all';
$cacheKey = preg_replace('/([^a-z0-9_-]+)/i', '', $cacheKey);
if (empty($overAllMatch)) {
$overAllMatch = $this->getCache()->fetch($cacheKey);
}
if (empty($overAllMatch)) {
// reverse all regexes, so we have the generic one first, which already matches most patterns
$overAllMatch = array_reduce(array_reverse($regexes), function ($val1, $val2) {
if (!empty($val1)) {
return $val1.'|'.$val2['regex'];
} else {
return $val2['regex'];
}
});
$this->getCache()->save($cacheKey, $overAllMatch);
}
return $this->matchUserAgent($overAllMatch);
}
/**
* Sets the Cache class
*
* @param Cache|\Doctrine\Common\Cache\CacheProvider $cache
* @throws \Exception
*/
public function setCache($cache)
{
if ($cache instanceof Cache ||
(class_exists('\Doctrine\Common\Cache\CacheProvider') && $cache instanceof \Doctrine\Common\Cache\CacheProvider)) {
$this->cache = $cache;
return;
}
throw new \Exception('Cache not supported');
}
/**
* Returns Cache object
*
* @return Cache|\Doctrine\Common\Cache\CacheProvider
*/
public function getCache()
{
if (!empty($this->cache)) {
return $this->cache;
}
return new StaticCache();
}
/**
* Sets the YamlParser class
*
* @param YamlParser
* @throws \Exception
*/
public function setYamlParser($yamlParser)
{
if ($yamlParser instanceof YamlParser) {
$this->yamlParser = $yamlParser;
return;
}
throw new \Exception('Yaml Parser not supported');
}
/**
* Returns YamlParser object
*
* @return YamlParser
*/
public function getYamlParser()
{
if (!empty($this->yamlParser)) {
return $this->yamlParser;
}
return new Spyc();
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Parser;
use DeviceDetector\Parser\Device\DeviceParserAbstract;
/**
* Class VendorFragments
*
* Device parser for vendor fragment detection
*
* @package DeviceDetector\Parser\Device
*/
class VendorFragment extends ParserAbstract
{
protected $fixtureFile = 'regexes/vendorfragments.yml';
protected $parserName = 'vendorfragments';
protected $matchedRegex = null;
public function parse()
{
foreach ($this->getRegexes() as $brand => $regexes) {
foreach ($regexes as $regex) {
if ($this->matchUserAgent($regex.'[^a-z0-9]+')) {
$this->matchedRegex = $regex;
return array_search($brand, DeviceParserAbstract::$deviceBrands);
}
}
}
return '';
}
public function getMatchedRegex()
{
return $this->matchedRegex;
}
}

View File

@ -0,0 +1,241 @@
DeviceDetector
==============
[![Latest Stable Version](https://poser.pugx.org/piwik/device-detector/v/stable)](https://packagist.org/packages/piwik/device-detector)
[![Latest Unstable Version](https://poser.pugx.org/piwik/device-detector/v/unstable)](https://packagist.org/packages/piwik/device-detector)
[![Total Downloads](https://poser.pugx.org/piwik/device-detector/downloads)](https://packagist.org/packages/piwik/device-detector)
[![License](https://poser.pugx.org/piwik/device-detector/license)](https://packagist.org/packages/piwik/device-detector)
## Code Status
[![Build Status](https://travis-ci.org/matomo-org/device-detector.svg?branch=master)](https://travis-ci.org/matomo-org/device-detector)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/matomo-org/device-detector.svg)](http://isitmaintained.com/project/matomo-org/device-detector "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/matomo-org/device-detector.svg)](http://isitmaintained.com/project/matomo-org/device-detector "Percentage of issues still open")
## Description
The Universal Device Detection library that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, feed readers, media players, PIMs, ...), operating systems, brands and models.
## Usage
Using DeviceDetector with composer is quite easy. Just add piwik/device-detector to your projects requirements. And use some code like this one:
```php
require_once 'vendor/autoload.php';
use DeviceDetector\DeviceDetector;
use DeviceDetector\Parser\Device\DeviceParserAbstract;
// OPTIONAL: Set version truncation to none, so full versions will be returned
// By default only minor versions will be returned (e.g. X.Y)
// for other options see VERSION_TRUNCATION_* constants in DeviceParserAbstract class
DeviceParserAbstract::setVersionTruncation(DeviceParserAbstract::VERSION_TRUNCATION_NONE);
$userAgent = $_SERVER['HTTP_USER_AGENT']; // change this to the useragent you want to parse
$dd = new DeviceDetector($userAgent);
// OPTIONAL: Set caching method
// By default static cache is used, which works best within one php process (memory array caching)
// To cache across requests use caching in files or memcache
// $dd->setCache(new Doctrine\Common\Cache\PhpFileCache('./tmp/'));
// OPTIONAL: Set custom yaml parser
// By default Spyc will be used for parsing yaml files. You can also use another yaml parser.
// You may need to implement the Yaml Parser facade if you want to use another parser than Spyc or [Symfony](https://github.com/symfony/yaml)
// $dd->setYamlParser(new DeviceDetector\Yaml\Symfony());
// OPTIONAL: If called, getBot() will only return true if a bot was detected (speeds up detection a bit)
// $dd->discardBotInformation();
// OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
// $dd->skipBotDetection();
$dd->parse();
if ($dd->isBot()) {
// handle bots,spiders,crawlers,...
$botInfo = $dd->getBot();
} else {
$clientInfo = $dd->getClient(); // holds information about browser, feed reader, media player, ...
$osInfo = $dd->getOs();
$device = $dd->getDeviceName();
$brand = $dd->getBrandName();
$model = $dd->getModel();
}
```
Instead of using the full power of DeviceDetector it might in some cases be better to use only specific parsers.
If you aim to check if a given useragent is a bot and don't require any of the other information, you can directly use the bot parser.
```php
require_once 'vendor/autoload.php';
use DeviceDetector\Parser\Bot AS BotParser;
$botParser = new BotParser();
$botParser->setUserAgent($userAgent);
// OPTIONAL: discard bot information. parse() will then return true instead of information
$botParser->discardDetails();
$result = $botParser->parse();
if (!is_null($result)) {
// do not do anything if a bot is detected
return;
}
// handle non-bot requests
```
## Using without composer
Alternatively to using composer you can also use the included `autoload.php`.
This script will register an autoloader to dynamically load all classes in `DeviceDetector` namespace.
Device Detector requires a YAML parser. By default `Spyc` parser is used.
As this library is not included you need to include it manually or use another YAML parser.
```php
<?php
include_once 'path/to/spyc/Spyc.php';
include_once 'path/to/device-detector/autoload.php';
use DeviceDetector\DeviceDetector;
$deviceDetector = new DeviceDetector();
// ...
```
### Caching
By default, DeviceDetector uses a built-in array cache. To get better performance, you can use your own caching solution:
* You can create a class that implement `DeviceDetector\Cache\Cache`
* You can directly use a Doctrine Cache object (useful if your project already uses Doctrine)
* Or if your project uses a [PSR-6](http://www.php-fig.org/psr/psr-6/) or [PSR-16](http://www.php-fig.org/psr/psr-16/) compliant caching system (like [symfony/cache](https://github.com/symfony/cache) or [matthiasmullie/scrapbook](https://github.com/matthiasmullie/scrapbook)), you can inject them the following way:
```php
// Example with PSR-6 and Symfony
$cache = new Symfony\Component\Cache\Adapter\ApcuAdapter();
$dd->setCache(
new DeviceDetector\Cache\PSR6Bridge($cache)
);
// Example with PSR-16 and ScrapBook
$cache = new \MatthiasMullie\Scrapbook\Psr16\SimpleCache(
new \MatthiasMullie\Scrapbook\Adapters\Apc()
);
$dd->setCache(
new DeviceDetector\Cache\PSR16Bridge($cache)
);
// Example with Doctrine
$dd->setCache(
new Doctrine\Common\Cache\ApcuCache()
);
```
## Contributing
### Hacking the library
This is a free/libre library under license LGPL v3 or later.
Your pull requests and/or feedback is very welcome!
### Listing all user agents from your logs
Sometimes it may be useful to generate the list of most used user agents on your website,
extracting this list from your access logs using the following command:
```
zcat ~/path/to/access/logs* | awk -F'"' '{print $6}' | sort | uniq -c | sort -rn | head -n20000 > /home/matomo/top-user-agents.txt
```
### Contributors
Created by the [Matomo team](http://matomo.org/team/), Stefan Giehl, Matthieu Aubry, Michał Gaździk,
Tomasz Majczak, Grzegorz Kaszuba, Piotr Banaszczyk and contributors.
Together we can build the best Device Detection library.
We are looking forward to your contributions and pull requests!
## Tests
See also: [QA at Matomo](http://matomo.org/qa/)
### Running tests
```
cd /path/to/device-detector
curl -sS https://getcomposer.org/installer | php
php composer.phar install
./vendor/bin/phpunit
```
## Device Detector for other languages
There are already a few ports of this tool to other languages:
- **.NET** https://github.com/AgileFlexAgency/MatomoDeviceDetector.NET
- **.NET** https://github.com/totpero/DeviceDetector.NET
- **Ruby** https://github.com/podigee/device_detector
- **JavaScript/TypeScript/NodeJS** https://github.com/etienne-martin/device-detector-js
- **Python 3** https://github.com/thinkwelltwd/device_detector
- **Crystal** https://github.com/creadone/device_detector
- **Elixir** https://github.com/elixir-inspector/ua_inspector
- **Java** https://github.com/mngsk/device-detector
## What Device Detector is able to detect
The lists below are auto generated and updated from time to time. Some of them might not be complete.
*Last update: 2020/03/31*
### List of detected operating systems:
AIX, Android, AmigaOS, Apple TV, Arch Linux, BackTrack, Bada, BeOS, BlackBerry OS, BlackBerry Tablet OS, Brew, CentOS, Chrome OS, CyanogenMod, Debian, DragonFly, Fedora, Firefox OS, Fire OS, FreeBSD, Gentoo, Google TV, HP-UX, Haiku OS, IRIX, Inferno, KaiOS, Knoppix, Kubuntu, GNU/Linux, Lubuntu, VectorLinux, Mac, Maemo, Mandriva, MeeGo, MocorDroid, Mint, MildWild, MorphOS, NetBSD, MTK / Nucleus, Nintendo, Nintendo Mobile, OS/2, OSF1, OpenBSD, Ordissimo, PlayStation Portable, PlayStation, Red Hat, RISC OS, Remix OS, RazoDroiD, Sabayon, SUSE, Sailfish OS, Slackware, Solaris, Syllable, Symbian, Symbian OS, Symbian OS Series 40, Symbian OS Series 60, Symbian^3, ThreadX, Tizen, Ubuntu, WebTV, Windows, Windows CE, Windows IoT, Windows Mobile, Windows Phone, Windows RT, Xbox, Xubuntu, YunOs, iOS, palmOS, webOS
### List of detected browsers:
2345 Browser, 360 Phone Browser, 360 Browser, Avant Browser, ABrowse, ANT Fresco, ANTGalio, Aloha Browser, Aloha Browser Lite, Amaya, Amigo, Android Browser, AOL Shield, Arora, Amiga Voyager, Amiga Aweb, Atomic Web Browser, Avast Secure Browser, AVG Secure Browser, Beaker Browser, Beamrise, BlackBerry Browser, Baidu Browser, Baidu Spark, Basilisk, Beonex, BlackHawk, Bunjalloo, B-Line, Brave, BriskBard, BrowseX, Camino, CCleaner, Coc Coc, Comodo Dragon, Coast, Charon, CM Browser, Chrome Frame, Headless Chrome, Chrome, Chrome Mobile iOS, Conkeror, Chrome Mobile, CoolNovo, CometBird, COS Browser, ChromePlus, Chromium, Cyberfox, Cheshire, Crusta, Cunaguaro, Chrome Webview, dbrowser, Deepnet Explorer, Delta Browser, Dolphin, Dorado, Dooble, Dillo, DuckDuckGo Privacy Browser, Ecosia, Epic, Elinks, Element Browser, eZ Browser, EUI Browser, GNOME Web, Espial TV Browser, Falkon, Faux Browser, Firefox Mobile iOS, Firebird, Fluid, Fennec, Firefox, Firefox Focus, Firefox Reality, Firefox Rocket, Flock, Firefox Mobile, Fireweb, Fireweb Navigator, FreeU, Galeon, Google Earth, Hawk Turbo Browser, hola! Browser, HotJava, Huawei Browser, IBrowse, iCab, iCab Mobile, Iridium, Iron Mobile, IceCat, IceDragon, Isivioo, Iceweasel, Internet Explorer, IE Mobile, Iron, Jasmine, Jig Browser, Jio Browser, K.Browser, Kindle Browser, K-meleon, Konqueror, Kapiko, Kinza, Kiwi, Kylo, Kazehakase, Cheetah Browser, LieBaoFast, LG Browser, Links, Lovense Browser, LuaKit, Lunascape, Lynx, mCent, MicroB, NCSA Mosaic, Meizu Browser, Mercury, Mobile Safari, Midori, Mobicip, MIUI Browser, Mobile Silk, Minimo, Mint Browser, Maxthon, Nokia Browser, Nokia OSS Browser, Nokia Ovi Browser, Nox Browser, NetSurf, NetFront, NetFront Life, NetPositive, Netscape, NTENT Browser, Oculus Browser, Opera Mini iOS, Obigo, Odyssey Web Browser, Off By One, ONE Browser, Opera GX, Opera Neon, Opera Devices, Opera Mini, Opera Mobile, Opera, Opera Next, Opera Touch, Ordissimo, Oregano, Origyn Web Browser, Openwave Mobile Browser, OmniWeb, Otter Browser, Palm Blazer, Pale Moon, Oppo Browser, Palm Pre, Puffin, Palm WebPro, Palmscape, Phoenix, Polaris, Polarity, Microsoft Edge, QQ Browser Mini, QQ Browser, Qutebrowser, QupZilla, Qwant Mobile, QtWebEngine, Realme Browser, Rekonq, RockMelt, Samsung Browser, Sailfish Browser, SEMC-Browser, Sogou Explorer, Safari, SalamWeb, Shiira, SimpleBrowser, Skyfire, Seraphic Sraf, Sleipnir, Snowshoe, Sogou Mobile Browser, Splash, Sputnik Browser, Sunrise, SuperBird, Super Fast Browser, START Internet Browser, Streamy, Swiftfox, Seznam Browser, t-online.de Browser, Tao Browser, TenFourFox, Tenta Browser, Tizen Browser, TweakStyle, TV Bro, UBrowser, UC Browser, UC Browser Mini, UC Browser Turbo, Uzbl, Vivaldi, vivo Browser, Vision Mobile Browser, Wear Internet Browser, Web Explorer, WebPositive, Waterfox, Whale Browser, wOSBrowser, WeTab Browser, Yandex Browser, Yandex Browser Lite, Xiino
### List of detected browser engines:
WebKit, Blink, Trident, Text-based, Dillo, iCab, Elektra, Presto, Gecko, KHTML, NetFront, Edge, NetSurf, Servo
### List of detected libraries:
aiohttp, curl, Faraday, Go-http-client, Google HTTP Java Client, Guzzle (PHP HTTP Client), HTTPie, HTTP_Request2, Java, libdnf, Mechanize, Node Fetch, OkHttp, Perl, Perl REST::Client, Python Requests, Python urllib, REST Client for Ruby, RestSharp, ScalaJ HTTP, urlgrabber (yum), Wget, WWW-Mechanize
### List of detected media players:
Audacious, Banshee, Boxee, Clementine, Deezer, FlyCast, Foobar2000, Google Podcasts, iTunes, Kodi, MediaMonkey, Miro, mpv, Music Player Daemon, NexPlayer, Nightingale, QuickTime, Songbird, Stagefright, SubStream, VLC, Winamp, Windows Media Player, XBMC
### List of detected mobile apps:
AndroidDownloadManager, AntennaPod, Apple News, Baidu Box App, BeyondPod, BingWebApp, bPod, CastBox, Castro, Castro 2, CrosswalkApp, DoggCatcher, douban App, Facebook, Facebook Messenger, FeedR, Flipboard App, Google Go, Google Play Newsstand, Google Plus, Google Search App, iCatcher, Instacast, Instagram App, Line, NewsArticle App, Overcast, Pinterest, Player FM, Pocket Casts, Podcast & Radio Addict, Podcast Republic, Podcasts, Podcat, Podcatcher Deluxe, Podkicker, RSSRadio, Sina Weibo, SogouSearch App, tieba, WeChat, WhatsApp, Yahoo! Japan, Yelp Mobile, YouTube and *mobile apps using [AFNetworking](https://github.com/AFNetworking/AFNetworking)*
### List of detected PIMs (personal information manager):
Airmail, Barca, DAVdroid, Lotus Notes, MailBar, Microsoft Outlook, Outlook Express, Postbox, SeaMonkey, The Bat!, Thunderbird
### List of detected feed readers:
Akregator, Apple PubSub, BashPodder, Breaker, Downcast, FeedDemon, Feeddler RSS Reader, gPodder, JetBrains Omea Reader, Liferea, NetNewsWire, Newsbeuter, NewsBlur, NewsBlur Mobile App, PritTorrent, Pulp, QuiteRSS, ReadKit, Reeder, RSS Bandit, RSS Junkie, RSSOwl, Stringer
### List of brands with detected devices:
3Q, 4Good, Ace, Acer, Advan, Advance, AGM, Ainol, Airness, Airties, AIS, Aiwa, Akai, Alba, Alcatel, Aligator, AllCall, AllDocube, Allview, Allwinner, Altech UEC, altron, Amazon, AMGOO, Amoi, ANS, Apple, Archos, Arian Space, Ark, Arnova, ARRIS, Ask, Assistant, Asus, Atom, Audiovox, AVH, Avvio, Axxion, Azumi Mobile, BangOlufsen, Barnes & Noble, BBK, BDF, Becker, Beeline, Beetel, BenQ, BenQ-Siemens, Bezkam, BGH, Bird, Bitel, Black Fox, Blackview, Blaupunkt, Blu, Bluboo, Bluegood, Bmobile, bogo, Boway, bq, Bravis, Brondi, Bush, CAGI, Capitel, Captiva, Carrefour, Casio, Casper, Cat, Celkon, Changhong, Cherry Mobile, China Mobile, Chuwi, Clarmin, CnM, Coby Kyros, Comio, Compal, Compaq, ComTrade Tesla, Concord, ConCorde, Condor, Coolpad, Cowon, CreNova, Crescent, Cricket, Crius Mea, Crosscall, Cube, CUBOT, CVTE, Cyrus, Daewoo, Danew, Datang, Datsun, Dbtel, Dell, Denver, Desay, DeWalt, DEXP, Dialog, Dicam, Digi, Digicel, Digiland, Digma, Divisat, DMM, DNS, DoCoMo, Doogee, Doov, Dopod, Doro, Dune HD, E-Boda, E-tel, Easypix, EBEST, Echo Mobiles, ECS, EE, EKO, Eks Mobility, Elenberg, Elephone, Energizer, Energy Sistem, Ergo, Ericsson, Ericy, Essential, Essentielb, Eton, eTouch, Etuline, Eurostar, Evercoss, Evertek, Evolio, Evolveo, EvroMedia, Explay, Extrem, Ezio, Ezze, Fairphone, Famoco, Fengxiang, FiGO, FinePower, Fly, FNB, Fondi, FORME, Forstar, Foxconn, Freetel, Fujitsu, G-TiDE, Garmin-Asus, Gateway, Gemini, General Mobile, Geotel, Ghia, Ghong, Gigabyte, Gigaset, Ginzzu, Gionee, Globex, GOCLEVER, Goly, GoMobile, Google, Gradiente, Grape, Grundig, Hafury, Haier, HannSpree, Hasee, Hi-Level, Highscreen, Hisense, Hoffmann, Homtom, Hoozo, Hosin, HP, HTC, Huawei, Humax, Hyrican, Hyundai, i-Joy, i-mate, i-mobile, iBall, iBerry, IconBIT, iHunt, Ikea, iKoMo, iLA, IMO Mobile, Impression, iNew, Infinix, InFocus, Inkti, InnJoo, Innostream, Inoi, INQ, Insignia, Intek, Intex, Inverto, iOcean, iPro, Irbis, iRola, iRulu, iTel, iView, iZotron, JAY-Tech, Jiayu, Jolla, Just5, K-Touch, Kaan, Kaiomy, Kalley, Kanji, Karbonn, KATV1, Kazam, KDDI, Kempler & Strauss, Keneksi, Kiano, Kingsun, Kivi, Kocaso, Kodak, Kogan, Komu, Konka, Konrow, Koobee, KOPO, Koridy, KRONO, Krüger&Matz, KT-Tech, Kumai, Kyocera, LAIQ, Land Rover, Landvo, Lanix, Lark, Lava, LCT, Leagoo, Ledstar, LeEco, Lemhoov, Lenco, Lenovo, Leotec, Le Pan, Lephone, Lexand, Lexibook, LG, Lingwin, Loewe, Logicom, Lumus, Luna, LYF, M.T.T., M4tel, Majestic, Mann, Manta Multimedia, Masstel, Maxcom, Maxwest, Maze, Mecer, Mecool, Mediacom, MediaTek, Medion, MEEG, MegaFon, Meitu, Meizu, Memup, Metz, MEU, MicroMax, Microsoft, Mio, Miray, Mitsubishi, MIXC, MLLED, Mobicel, Mobiistar, Mobiola, Mobistel, Modecom, Mofut, Motorola, Movic, Mpman, MSI, MTC, MTN, MYFON, MyPhone, Myria, Mystery, MyWigo, National, Navon, NEC, Neffos, Netgear, NeuImage, Newgen, NewsMy, NEXBOX, Nexian, Nextbit, NextBook, NGM, NG Optics, Nikon, Nintendo, NOA, Noain, Nobby, Noblex, Nokia, Nomi, Nous, NUU Mobile, Nuvo, Nvidia, NYX Mobile, O+, O2, Obi, Odys, Onda, OnePlus, OPPO, Opsson, Orange, Ordissimo, Ouki, Oukitel, OUYA, Overmax, Oysters, Palm, Panacom, Panasonic, Pantech, PCBOX, PCD, PCD Argentina, PEAQ, Pentagram, Philips, phoneOne, Pioneer, Pixus, Ployer, Plum, Point of View, Polaroid, PolyPad, Polytron, Pomp, Positivo, PPTV, Prestigio, Primepad, Proline, ProScan, PULID, Q-Touch, Qilive, QMobile, Qtek, Quantum, Quechua, Qumo, R-TV, Ramos, RCA Tablets, Readboy, Rikomagic, RIM, Rinno, Ritmix, Ritzviva, Riviera, Roadrover, Rokit, Roku, Rombica, Ross&Moor, Rover, RoverPad, RT Project, RugGear, Runbo, Safaricom, Sagem, Samsung, Sanei, Santin, Sanyo, Savio, Sega, Selevision, Selfix, Sencor, Sendo, Senseit, Senwa, SFR, Sharp, Shift Phones, Shuttle, Siemens, Sigma, Silent Circle, Simbans, Sky, Skyworth, Smart, Smartfren, Smartisan, Softbank, Sonim, Sony, Sony Ericsson, Spectrum, Spice, Star, Starway, STF Mobile, STK, Stonex, Storex, Sumvision, SunVan, Sunvell, SuperSonic, Supra, SWISSMOBILITY, Symphony, Syrox, T-Mobile, TB Touch, TCL, TechniSat, TechnoTrend, TechPad, Teclast, Tecno Mobile, Telefunken, Telego, Telenor, Telit, Tesco, Tesla, teXet, ThL, Thomson, TIANYU, Timovi, TiPhone, Tolino, Tooky, Top House, Toplux, Toshiba, Touchmate, TrekStor, Trevi, True, Tunisie Telecom, Turbo, Turbo-X, TVC, U.S. Cellular, Ugoos, Uhappy, Ulefone, Umax, UMIDIGI, Unihertz, Unimax, Uniscope, Unknown, Unnecto, Unonu, Unowhy, UTOK, UTStarcom, Vastking, Venso, Verizon, Vernee, Vertex, Vertu, Verykool, Vesta, Vestel, VGO TEL, Videocon, Videoweb, ViewSonic, Vinga, Vinsoc, Vitelcom, Vivax, Vivo, Vizio, VK Mobile, Vodafone, Vonino, Vorago, Voto, Voxtel, Vsun, Vulcan, Walton, Web TV, Weimei, WellcoM, Wexler, Wieppo, Wiko, Wileyfox, Wink, Wolder, Wolfgang, Wonu, Woo, Woxter, X-TIGI, X-View, Xiaolajiao, Xiaomi, Xion, Xolo, Xoro, Yandex, Yarvik, Yes, Yezz, Yota, Ytone, Yu, Yuandao, Yusun, Yxtel, Zeemi, Zen, Zenek, Zonda, Zopo, ZTE, Zuum, Zync, ZYQ, öwn
### List of detected bots:
360Spider, Aboundexbot, Acoon, AddThis.com, ADMantX, aHrefs Bot, Alexa Crawler, Alexa Site Audit, Amazon Route53 Health Check, Amorank Spider, Analytics SEO Crawler, ApacheBench, Applebot, Arachni, archive.org bot, Ask Jeeves, Awario, Awario, Backlink-Check.de, BacklinkCrawler, Baidu Spider, BazQux Reader, BingBot, BitlyBot, Blekkobot, BLEXBot Crawler, Bloglovin, Blogtrottr, BoardReader, BoardReader Blog Indexer, Bountii Bot, BrandVerity, Browsershots, BUbiNG, Buck, Butterfly Robot, Bytespider, CareerBot, Castro 2, Catchpoint, CATExplorador, ccBot crawler, Charlotte, Cliqzbot, CloudFlare Always Online, CloudFlare AMP Fetcher, Collectd, CommaFeed, CSS Certificate Spider, Cốc Cốc Bot, Datadog Agent, Datanyze, Dataprovider, Daum, Dazoobot, Discobot, Domain Re-Animator Bot, DotBot, DuckDuckGo Bot, Easou Spider, eCairn-Grabber, EMail Exractor, EmailWolf, Embedly, evc-batch, ExaBot, ExactSeek Crawler, Ezooms, eZ Publish Link Validator, Facebook External Hit, Feedbin, FeedBurner, Feedly, Feedspot, Feed Wrangler, Fever, Findxbot, Flipboard, FreshRSS, Generic Bot, Generic Bot, Genieo Web filter, Gigablast, Gigabot, Gluten Free Crawler, Gmail Image Proxy, Goo, Googlebot, Google Cloud Scheduler, Google Favicon, Google PageSpeed Insights, Google Partner Monitoring, Google Search Console, Google Stackdriver Monitoring, Google Structured Data Testing Tool, Grapeshot, Heritrix, Heureka Feed, HTTPMon, HubPages, HubSpot, ICC-Crawler, ichiro, IDG/IT, IIS Site Analysis, Inktomi Slurp, inoreader, IP-Guide Crawler, IPS Agent, Kaspersky, Kouio, Larbin web crawler, LCC, Let's Encrypt Validation, Lighthouse, Linkdex Bot, LinkedIn Bot, LTX71, Lycos, Magpie-Crawler, MagpieRSS, Mail.Ru Bot, masscan, Mastodon Bot, Meanpath Bot, MetaInspector, MetaJobBot, Mixrank Bot, MJ12 Bot, Mnogosearch, MojeekBot, Monitor.Us, Munin, Nagios check_http, NalezenCzBot, nbertaupete95, Netcraft Survey Bot, netEstate, NetLyzer FastProbe, NetResearchServer, Netvibes, NewsBlur, NewsGator, NLCrawler, Nmap, Nutch-based Bot, Nuzzel, oBot, Octopus, Omgili bot, Openindex Spider, OpenLinkProfiler, OpenWebSpider, Orange Bot, Outbrain, PagePeeker, PaperLiBot, Phantomas, PHP Server Monitor, Picsearch bot, Pingdom Bot, Pinterest, PocketParser, Pompos, PritTorrent, QuerySeekerSpider, Quora Link Preview, Qwantify, Rainmeter, RamblerMail Image Proxy, Reddit Bot, Riddler, Rogerbot, ROI Hunter, RSSRadio Bot, SafeDNSBot, Scooter, ScoutJet, Scrapy, Screaming Frog SEO Spider, ScreenerBot, Semrush Bot, Sensika Bot, Sentry Bot, SEOENGBot, SEOkicks-Robot, Seoscanners.net, Server Density, Seznam Bot, Seznam Email Proxy, Seznam Zbozi.cz, ShopAlike, Shopify Partner, ShopWiki, SilverReader, SimplePie, SISTRIX Crawler, SISTRIX Optimizer, Site24x7 Website Monitoring, Siteimprove, SiteSucker, Sixy.ch, Skype URI Preview, Slackbot, SMTBot, Snapchat Proxy, Sogou Spider, Soso Spider, Sparkler, Speedy, Spinn3r, Spotify, Sputnik Bot, sqlmap, SSL Labs, Startpagina Linkchecker, StatusCake, Superfeedr Bot, Survey Bot, Tarmot Gezgin, TelegramBot, The Knowledge AI, theoldreader, TinEye Crawler, Tiny Tiny RSS, TLSProbe, TraceMyFile, Trendiction Bot, TurnitinBot, TweetedTimes Bot, Tweetmeme Bot, Twingly Recon, Twitterbot, UkrNet Mail Proxy, UniversalFeedParser, Uptimebot, Uptime Robot, URLAppendBot, Vagabondo, Visual Site Mapper Crawler, VK Share Button, W3C CSS Validator, W3C I18N Checker, W3C Link Checker, W3C Markup Validation Service, W3C MobileOK Checker, W3C Unified Validator, Wappalyzer, WebbCrawler, Weborama, WebPageTest, WebSitePulse, WebThumbnail, WeSEE:Search, WikiDo, Willow Internet Crawler, WooRank, WordPress, Wotbox, YaCy, Yahoo! Cache System, Yahoo! Japan BRW, Yahoo! Link Preview, Yahoo! Slurp, Yahoo Gemini, Yandex Bot, Yeti/Naverbot, Yottaa Site Monitor, Youdao Bot, Yourls, Yunyun Bot, Zao, Ze List, zgrab, Zookabot, ZumBot

View File

@ -0,0 +1,14 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Yaml;
interface Parser
{
public function parseFile($file);
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Yaml;
use Exception;
/**
* Class Pecl
*
* Parses a YAML file with LibYAML library
*
* @package DeviceDetector\Yaml
* @see http://php.net/manual/en/function.yaml-parse-file.php
*/
class Pecl implements Parser
{
/**
* @param string $file The path to the YAML file to be parsed
*
* @return mixed The YAML converted to a PHP value or FALSE on failure
* @throws Exception If the YAML extension is not installed
*/
public function parseFile($file)
{
if(function_exists('yaml_parse_file') === false)
{
throw new Exception('Pecl YAML extension is not installed');
}
return yaml_parse_file($file);
}
}

View File

@ -0,0 +1,19 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Yaml;
use \Spyc AS SpycParser;
class Spyc implements Parser
{
public function parseFile($file)
{
return SpycParser::YAMLLoad($file);
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* Device Detector - The Universal Device Detection library for parsing User Agents
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
*/
namespace DeviceDetector\Yaml;
use Symfony\Component\Yaml\Parser AS SymfonyParser;
class Symfony implements Parser
{
public function parseFile($file)
{
$parser = new SymfonyParser();
return $parser->parse(file_get_contents($file));
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* PSR-4 autoloader implementation for the DeviceDetector namespace.
* First we define the 'dd_autoload' function, and then we register
* it with 'spl_autoload_register' so that PHP knows to use it.
*/
/**
* Automatically include the file that defines <code>class</code>.
*
* @param string $class
* the name of the class to load
*
* @return void
*/
function dd_autoload($class)
{
if (strpos($class, 'DeviceDetector\\') !== false) {
$namespace_map = array('DeviceDetector\\' => __DIR__ . '/');
foreach ($namespace_map as $prefix => $dir)
{
/* First swap out the namespace prefix with a directory... */
$path = str_replace($prefix, $dir, $class);
/* replace the namespace separator with a directory separator... */
$path = str_replace('\\', '/', $path);
/* and finally, add the PHP file extension to the result. */
$path = $path . '.php';
/* $path should now contain the path to a PHP file defining $class */
@include $path;
}
}
}
spl_autoload_register('dd_autoload');

View File

@ -0,0 +1,42 @@
{
"name": "piwik/device-detector",
"type": "library",
"description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, media players, mobile apps, feed readers, libraries, etc), operating systems, devices, brands and models.",
"keywords": ["useragent","parser","devicedetection"],
"homepage": "https://matomo.org",
"license": "LGPL-3.0-or-later",
"authors": [
{
"name": "The Matomo Team",
"email": "hello@matomo.org",
"homepage": "https://matomo.org/team/"
}
],
"support": {
"forum": "http://forum.matomo.org/",
"issues": "https://github.com/matomo-org/device-detector/issues",
"wiki": "https://dev.matomo.org/",
"source": "https://github.com/matomo-org/piwik"
},
"autoload": {
"psr-4": { "DeviceDetector\\": "" }
},
"require": {
"php": ">=5.5",
"mustangostang/spyc": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36",
"fabpot/php-cs-fixer": "~1.7",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0",
"matthiasmullie/scrapbook": "@stable"
},
"suggest": {
"doctrine/cache": "Can directly be used for caching purpose",
"ext-yaml": "Necessary for using the Pecl YAML parser"
},
"archive": {
"exclude": ["/autoload.php"]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
- regex: 'NetFront'
name: 'NetFront'
- regex: 'Edge'
name: 'Edge'
- regex: 'Trident'
name: 'Trident'
- regex: 'Blink'
name: 'Blink'
- regex: '(?:Apple)?WebKit'
name: 'WebKit'
- regex: 'Presto'
name: 'Presto'
- regex: '(?<!like )Gecko'
name: 'Gecko'
- regex: 'KHTML'
name: 'KHTML'
- regex: 'NetSurf'
name: 'NetSurf'
- regex: 'Servo'
name: 'Servo'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,144 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
- regex: 'Akregator(?:/(\d+[\.\d]+))?'
name: 'Akregator'
version: '$1'
url: 'http://userbase.kde.org/Akregator'
type: 'Feed Reader'
- regex: 'Apple-PubSub(?:/(\d+[\.\d]+))?'
name: 'Apple PubSub'
version: '$1'
url: 'https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/pubsub.1.html'
type: 'Feed Reader'
- regex: 'BashPodder'
name: 'BashPodder'
version: ''
url: 'http://lincgeek.org/bashpodder/'
type: 'Feed Reader'
- regex: 'Breaker/v([\d\.]+)'
name: 'Breaker'
version: '$1'
url: 'https://www.breaker.audio/'
type: 'Feed Reader App'
- regex: 'Downcast/([\d\.]+)'
name: 'Downcast'
version: '$1'
url: 'http://downcastapp.com/'
type: 'Feed Reader App'
- regex: 'FeedDemon(?:/(\d+[\.\d]+))?'
name: 'FeedDemon'
version: '$1'
url: 'http://www.feeddemon.com/'
type: 'Feed Reader'
- regex: 'Feeddler(?:RSS|PRO)(?:[/ ](\d+[\.\d]+))?'
name: 'Feeddler RSS Reader'
version: '$1'
url: 'http://www.chebinliu.com/projects/iphone/feeddler-rss-reader/'
type: 'Feed Reader App'
- regex: 'QuiteRSS(?:[/ ](\d+[\.\d]+))?'
name: 'QuiteRSS'
version: '$1'
url: https://quiterss.org
type: 'Feed Reader App'
- regex: 'gPodder/([\d\.]+)'
name: 'gPodder'
version: '$1'
url: 'http://gpodder.org/'
type: 'Feed Reader App'
- regex: 'JetBrains Omea Reader(?:[/ ](\d+[\.\d]+))?'
name: 'JetBrains Omea Reader'
version: '$1'
url: 'http://www.jetbrains.com/omea/reader/'
type: 'Feed Reader'
- regex: 'Liferea(?:[/ ](\d+[\.\d]+))?'
name: 'Liferea'
version: '$1'
url: 'http://liferea.sf.net/'
type: 'Feed Reader'
- regex: 'NetNewsWire(?:[/ ](\d+[\.\d]+))?'
name: 'NetNewsWire'
version: '$1'
url: 'http://netnewswireapp.com/'
type: 'Feed Reader'
- regex: 'NewsBlur (?:iPhone|iPad) App(?: v(\d+[\.\d]+))?'
name: 'NewsBlur Mobile App'
version: '$1'
url: 'http://www.newsblur.com'
type: 'Feed Reader App'
- regex: 'NewsBlur(?:/(\d+[\.\d]+))'
name: 'NewsBlur'
version: '$1'
url: 'http://www.newsblur.com'
type: 'Feed Reader'
- regex: 'newsbeuter(?:[/ ](\d+[\.\d]+))?'
name: 'Newsbeuter'
version: '$1'
url: 'http://www.newsbeuter.org/'
type: 'Feed Reader'
- regex: 'PritTorrent/([\d\.]+)'
name: 'PritTorrent'
version: '$1'
url: 'http://bitlove.org'
type: 'Feed Reader'
- regex: 'Pulp[/ ](\d+[\.\d]+)'
name: 'Pulp'
version: '$1'
url: 'http://www.acrylicapps.com/pulp/'
type: 'Feed Reader App'
- regex: 'ReadKit(?:[/ ](\d+[\.\d]+))?'
name: 'ReadKit'
version: '$1'
url: 'http://readkitapp.com/'
type: 'Feed Reader App'
- regex: 'Reeder(?:[/ ](\d+[\.\d]+))?'
name: 'Reeder'
version: '$1'
url: 'http://reederapp.com/'
type: 'Feed Reader App'
- regex: 'RSSBandit(?:[/ ](\d+[\.\d]+))?'
name: 'RSS Bandit'
version: '$1'
url: 'http://www.rssbandit.org)'
type: 'Feed Reader'
- regex: 'RSS Junkie(?:[/ ](\d+[\.\d]+))?'
name: 'RSS Junkie'
version: '$1'
url: 'https://play.google.com/store/apps/details?id=com.bitpowder.rssjunkie'
type: 'Feed Reader App'
- regex: 'RSSOwl(?:[/ ](\d+[\.\d]+))?'
name: 'RSSOwl'
version: '$1'
url: 'http://www.rssowl.org/'
type: 'Feed Reader'
- regex: 'Stringer'
name: 'Stringer'
version: ''
url: 'https://github.com/swanson/stringer'
type: 'Feed Reader'

View File

@ -0,0 +1,108 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
- regex: 'Wget(?:/(\d+[\.\d]+))?'
name: 'Wget'
version: '$1'
- regex: 'Guzzle(?:/(\d+[\.\d]+))?'
name: 'Guzzle (PHP HTTP Client)'
version: '$1'
- regex: '(?:lib)?curl(?:/(\d+[\.\d]+))?'
name: 'curl'
version: '$1'
- regex: 'python-requests(?:/(\d+[\.\d]+))?'
name: 'Python Requests'
version: '$1'
- regex: 'Python-urllib(?:/?(\d+[\.\d]+))?'
name: 'Python urllib'
version: '$1'
- regex: 'Java(?:/?(\d+[\.\d]+))?'
name: 'Java'
version: '$1'
- regex: '(?:perlclient|libwww-perl)(?:/?(\d+[\.\d]+))?'
name: 'Perl'
version: '$1'
- regex: 'okhttp/([\d\.]+)'
name: 'OkHttp'
version: '$1'
- regex: 'HTTP_Request2(?:/(\d+[\.\d]+))?'
name: 'HTTP_Request2'
version: '$1'
- regex: 'HTTP_Request2(?:/(\d+[\.\d]+))?'
name: 'HTTP_Request2'
version: '$1'
url: 'http://pear.php.net/package/http_request2'
- regex: 'Mechanize(?:/(\d+[\.\d]+))?'
name: 'Mechanize'
version: '$1'
url: 'http://github.com/sparklemotion/mechanize/'
- regex: 'aiohttp(?:/(\d+[\.\d]+))?'
name: 'aiohttp'
version: '$1'
- regex: 'Google-HTTP-Java-Client(?:/(\d+[\.\d\w-]+))?'
name: 'Google HTTP Java Client'
version: '$1'
- regex: 'WWW-Mechanize(?:/(\d+[\.\d]+))?'
name: 'WWW-Mechanize'
version: '$1'
- regex: 'Faraday(?: v(\d+[\.\d]+))?'
name: 'Faraday'
version: '$1'
- regex: '(?:Go-http-client|Go )/?(?:(\d+[\.\d]+))?(?: package http)?'
name: 'Go-http-client'
version: '$1'
- regex: 'urlgrabber(?:/(\d+[\.\d]+))?'
name: 'urlgrabber (yum)'
version: '$1'
- regex: 'libdnf(?:/(\d+[\.\d]+))?'
name: 'libdnf'
version: '$1'
- regex: 'HTTPie(?:/(\d+[\.\d]+))?'
name: 'HTTPie'
version: '$1'
- regex: 'rest-client/(\d+[\.\d]+).*ruby'
name: 'REST Client for Ruby'
version: '$1'
- regex: 'RestSharp/(\d+[\.\d]+)'
name: 'RestSharp'
version: '$1'
url: 'http://restsharp.org/'
- regex: 'scalaj-http/(\d+[\.\d]+)'
name: 'ScalaJ HTTP'
version: '$1'
url: 'https://github.com/scalaj/scalaj-http'
- regex: 'REST::Client/(\d+)'
name: 'Perl REST::Client'
version: '$1'
url: 'https://metacpan.org/pod/REST::Client'
- regex: 'node-fetch/(\d+[\.\d]+)'
name: 'Node Fetch'
version: $1
url: 'https://github.com/node-fetch/node-fetch'

View File

@ -0,0 +1,102 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
- regex: 'Audacious(?:[ /]([\d\.]+))?'
name: 'Audacious'
version: '$1'
- regex: 'Banshee(?:[ /]([\d\.]+))?'
name: 'Banshee'
version: '$1'
- regex: 'Boxee(?:[ /]([\d\.]+))?'
name: 'Boxee'
version: '$1'
- regex: 'Clementine(?:[ /]([\d\.]+))?'
name: 'Clementine'
version: '$1'
- regex: 'Deezer(?:/([\d\.]+))?'
name: 'Deezer'
version: '$1'
- regex: 'iTunes(?:-iPhone|-iPad)?(?:/([\d\.]+))?'
name: 'iTunes'
version: '$1'
- regex: 'FlyCast(?:/([\d\.]+))?'
name: 'FlyCast'
version: '$1'
- regex: 'foobar2000(?:/([\d\.]+))?'
name: 'Foobar2000'
version: '$1'
- regex: 'MediaMonkey(?:[ /](\d+[\.\d]+))?'
name: 'MediaMonkey'
version: '$1'
- regex: 'Miro(?:/(\d+[\.\d]+))?'
name: 'Miro'
version: '$1'
- regex: 'NexPlayer(?:/(\d+[\.\d]+))?'
name: 'NexPlayer'
version: '$1'
- regex: 'Nightingale(?:/([\d\.]+))?'
name: 'Nightingale'
version: '$1'
- regex: 'QuickTime(?:(?:(?:.+qtver=)|(?:(?: E-)?[\./]))([\d\.]+))?'
name: 'QuickTime'
version: '$1'
- regex: 'Songbird(?:/([\d\.]+))?'
name: 'Songbird'
version: '$1'
- regex: 'SubStream(?:/([\d\.]+))?'
name: 'SubStream'
version: '$1'
- regex: '(?:Lib)?VLC(?:/([\d\.]+))?'
name: 'VLC'
version: '$1'
- regex: 'Winamp(?:MPEG)?(?:/(\d+[\.\d]+))?'
name: 'Winamp'
version: '$1'
- regex: '(?:Windows-Media-Player|NSPlayer)(?:/(\d+[\.\d]+))?'
name: 'Windows Media Player'
version: '$1'
- regex: 'XBMC(?:/([\d\.]+))?'
name: 'XBMC'
version: '$1'
- regex: 'Kodi(?:/([\d\.]+))?'
name: 'Kodi'
version: '$1'
- regex: 'stagefright(?:/([\d\.]+))?'
name: 'Stagefright'
version: '$1'
- regex: 'GoogleChirp(?:/(\d+[\.\d]+))?'
name: 'Google Podcasts'
version: '$1'
- regex: 'Music Player Daemon (?:(\d+[\.\d]+))?'
name: 'Music Player Daemon'
version: '$1'
- regex: 'mpv (?:(\d+[\.\d]+))?'
name: 'mpv'
version: '$1'

View File

@ -0,0 +1,216 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
# AndroidDownloadManager
- regex: 'AndroidDownloadManager(?:[ /]([\d\.]+))?'
name: 'AndroidDownloadManager'
version: '$1'
# Apple News
- regex: '(?:Apple)?News(?:[ /][\d\.]+)? Version(?:[ /]([\d\.]+))?'
name: 'Apple News'
version: '$1'
- regex: 'bPod'
name: 'bPod'
version: ''
# Facebook Messenger
- regex: '(?:MessengerForiOS|MESSENGER).(?:FBAV)(?:[ /]([\d\.]+))?'
name: 'Facebook Messenger'
version: '$1'
# Facebook
- regex: '(?:FBAV|com.facebook.katana)(?:[ /]([\d\.]+))?'
name: 'Facebook'
version: '$1'
# FeedR
- regex: 'FeedR(?:/([\d\.]+))?'
name: 'FeedR'
version: '$1'
# Google Go
- regex: 'com.google.android.apps.searchlite'
name: 'Google Go'
version: ''
# Google Play Kiosk
- regex: 'com.google.android.apps.magazines'
name: 'Google Play Newsstand'
version: ''
# Google Plus
- regex: 'com.google.GooglePlus'
name: 'Google Plus'
version: ''
# WeChat
- regex: 'MicroMessenger/([^ ]+)'
name: 'WeChat'
version: '$1'
# Sina Weibo
- regex: '.*__weibo__([0-9\.]+)__'
name: 'Sina Weibo'
version: '$1'
# Pinterest
- regex: 'Pinterest(?:/([\d\.]+))?'
name: 'Pinterest'
version: '$1'
# Podcatcher Deluxe
- regex: 'Podcatcher Deluxe'
name: 'Podcatcher Deluxe'
version: ''
# YouTube
- regex: 'com.google.android.youtube(?:/([\d\.]+))?'
name: 'YouTube'
version: '$1'
# AFNetworking generic
- regex: '([^/]+)/(\d+(?:\.\d+)+) \((?:iPhone|iPad); iOS [0-9\.]+; Scale/[0-9\.]+\)'
name: '$1'
version: '$2'
# WhatsApp
- regex: 'WhatsApp(?:[ /]([\d\.]+))?'
name: 'WhatsApp'
version: '$1'
# Line
- regex: 'Line(?:[ /]([\d\.]+))'
name: 'Line'
version: '$1'
# Instacast
- regex: 'Instacast(?:HD)?/(\d\.[\d\.abc]+) CFNetwork/([\d\.]+) Darwin/([\d\.]+)'
name: 'Instacast'
version: '$1'
-
regex: 'Podcasts/([\d\.]+)'
name: 'Podcasts'
version: '$1'
-
regex: 'Pocket Casts(?:, (?:Android|iOS) v([\d\.]+))?'
name: 'Pocket Casts'
version: '$1'
-
regex: 'Podcat/([\d\.]+)'
name: 'Podcat'
version: '$1'
-
regex: 'BeyondPod'
name: 'BeyondPod'
version:
-
regex: 'AntennaPod/?([\d\.]+)?'
name: 'AntennaPod'
version: '$1'
-
regex: 'Overcast/([\d\.]+)'
name: 'Overcast'
version: '$1'
-
regex: '(?:CastBox|fm.castbox.audiobook.radio.podcast)/?([\d\.]+)?'
name: 'CastBox'
version: '$1'
-
regex: 'Player FM'
name: 'Player FM'
version: ''
-
regex: 'Podkicker(?: Pro)?/([\d\.]+)'
name: 'Podkicker'
version: '$1'
-
regex: 'PodcastRepublic/([\d\.]+)'
name: 'Podcast Republic'
version: '$1'
-
regex: 'Castro/(\d+)'
name: 'Castro'
version: '$1'
-
regex: 'Castro 2 ([\d\.]+)/[\d]+ Like iTunes'
name: 'Castro 2'
version: '$1'
-
regex: 'Castro 2'
name: 'Castro 2'
version: ''
-
regex: 'DoggCatcher'
name: 'DoggCatcher'
version:
-
regex: 'PodcastAddict/v([\d]+)'
name: 'Podcast & Radio Addict'
version: '$1'
-
regex: 'Podcat/([\d]+) CFNetwork/([\d\.]+) Darwin/([\d\.]+)'
name: 'Podcat'
version: '$1'
-
regex: 'i[cC]atcher[^\d]+([\d\.]+)'
name: 'iCatcher'
version: '$1'
-
regex: 'YelpApp/([\d\.]+)'
name: 'Yelp Mobile'
version: '$1'
-
regex: 'jp.co.yahoo.android.yjtop/([\d\.]+)'
name: 'Yahoo! Japan'
version: '$1'
-
regex: 'RSSRadio/([\d]+)?'
name: 'RSSRadio'
version: '$1'
-
regex: 'SogouSearch Android[\d\.]+ version([\d\.]+)?'
name: 'SogouSearch App'
version: '$1'
-
regex: 'NewsArticle/([\d\.]+)?'
name: 'NewsArticle App'
version: '$1'
-
regex: 'tieba/([\d\.]+)?'
name: 'tieba'
version: '$1'
-
regex: 'com\.douban\.group/([\d\.]+)?'
name: 'douban App'
version: '$1'
-
regex: 'BingWeb/([\d\.]+)?'
name: 'BingWebApp'
version: '$1'
-
regex: 'GSA/([\d\.]+)?'
name: 'Google Search App'
version: '$1'
-
regex: 'Flipboard/([\d\.]+)?'
name: 'Flipboard App'
version: '$1'
-
regex: 'Instagram[ /]([\d\.]+)?'
name: 'Instagram App'
version: '$1'
-
regex: 'baiduboxapp/([\d\.]+)?'
name: 'Baidu Box App'
version: '$1'
-
regex: 'Crosswalk(?!.*Streamy)/([\d\.]+)?'
name: 'CrosswalkApp'
version: '$1'

View File

@ -0,0 +1,51 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
- regex: 'Outlook-Express(?:/(\d+[\.\d]+))?'
name: 'Outlook Express'
version: '$1'
- regex: 'Microsoft Outlook(?:[/ ](\d+[\.\d]+))?'
name: 'Microsoft Outlook'
version: '$1'
- regex: '(?:Thunderbird|Icedove|Shredder)(?:/(\d+[\.\d]+))?'
name: 'Thunderbird'
version: '$1'
- regex: 'Airmail(?: (\d+[\.\d]+))?'
name: 'Airmail'
version: '$1'
- regex: 'Lotus-Notes(?:/(\d+[\.\d]+))?'
name: 'Lotus Notes'
version: '$1'
- regex: 'Barca(?:Pro)?(?:[/ ](\d+[\.\d]+))?'
name: 'Barca'
version: '$1'
- regex: 'Postbox(?:[/ ](\d+[\.\d]+))?'
name: 'Postbox'
version: '$1'
- regex: 'MailBar(?:[/ ](\d+[\.\d]+))?'
name: 'MailBar'
version: '$1'
- regex: 'The Bat!(?: Voyager)?(?:[/ ](\d+[\.\d]+))?'
name: 'The Bat!'
version: '$1'
- regex: 'DAVdroid(?:/(\d+[\.\d]+))?'
name: 'DAVdroid'
version: '$1'
# SeaMonkey
- regex: '(?:SeaMonkey|Iceape)(?:/(\d+[\.\d]+))?'
name: 'SeaMonkey'
version: '$1'

View File

@ -0,0 +1,28 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
#Nikon
Nikon:
regex: 'Coolpix S800c'
device: 'camera'
model: 'Coolpix S800c'
# Samsung
Samsung:
regex: 'EK-G[CN][0-9]{3}'
device: 'camera'
models:
- regex: 'EK-GN120'
model: 'GALAXY NX'
- regex: 'EK-GC100'
model: 'GALAXY Camera'
- regex: 'EK-GC110'
model: 'GALAXY Camera WiFi only'
- regex: 'EK-GC200'
model: 'GALAXY Camera 2'
- regex: 'EK-GC([0-9]{3})'
model: 'GALAXY Camera $1'

View File

@ -0,0 +1,16 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
# Tesla Model S
Tesla:
regex: '(?:Tesla/[0-9.]+|QtCarBrowser)'
device: 'car browser'
models:
- regex: 'QtCarBrowser'
model: 'Model S'
- regex: 'Tesla/[0-9.]+'
model: ''

View File

@ -0,0 +1,40 @@
###############
# Device Detector - The Universal Device Detection library for parsing User Agents
#
# @link http://piwik.org
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
###############
Archos:
regex: 'Archos.*GAMEPAD([2]?)'
device: 'console'
model: 'Gamepad $1'
Microsoft:
regex: 'Xbox'
device: 'console'
models:
- regex: 'Xbox One'
model: 'Xbox One'
- regex: 'Xbox'
model: 'Xbox 360'
Nintendo:
regex: 'Nintendo (([3]?DS[i]?)|Wii[U]?|Switch)'
device: 'console'
model: '$1'
OUYA:
regex: 'OUYA'
device: 'console'
model: 'OUYA'
Sega:
regex: 'Dreamcast'
device: 'console'
model: 'Dreamcast'
Sony:
regex: 'PlayStation (3|4|Portable|Vita)'
device: 'console'
model: 'PlayStation $1'

Some files were not shown because too many files have changed in this diff Show More