if - else - elseif / else if - while

if

<?php
if ($a > $b)
echo "a больше b";
?>

else

<?php
if ($a > $b) {
echo "a больше, чем b";
} else {
echo "a НЕ больше, чем b";
}
?>

elseif / else if 

<?php
if ($a > $b) {
echo "a больше, чем b";
} elseif ($a == $b) {
echo "a равен b";
} else {
echo "a меньше, чем b";
}
?>

whileявляются простейшим видом циклов в PH

<?php
/* пример 1 */
$i = 1;
while ($i <= 10) {
echo $i++; /* выводиться будет значение переменной
$i перед её увеличением
(post-increment) */
}

/* пример 2 */
$i = 1;
while ($i <= 10):
echo $i;
$i++;
endwhile;
?>
php

Mobile_Detect. Автоматическое определение мобильных браузеров

Mobile Detect - это легкий класс PHP для обнаружения мобильных устройств (включая планшеты). Он использует строку User-Agent в сочетании с конкретными HTTP-заголовками для обнаружения мобильной среды.

  • Класс MobileDetect - это обнаружение на стороне сервера PHP класс, который может помочь вам с вашей стратегией RWD, он не является заменой медиазапросов CSS3 или других форм обнаружения функций на стороне клиента.
  • Может обнаружить разницу между мобильным телефоном и десктопами с помощью egexes.
  • Точность и релевантность обнаружения сохраняется при запуске испытания для проверки на наличие конфликтов обнаружения.

Использовать достаточно просто

Подключение

В самом начале страницы подключаем
<!-- ВЕРСИЯ 3.74.3 -->
<?php
 //include 'Mobile_Detect.php'
include_once $_SERVER['DOCUMENT_ROOT'] . '/Mobile_Detect.php';
$detect = new \Detection\MobileDetect;
?>


<!-- ВЕРСИЯ 2.8.3 -->
<?php
//include 'Mobile_Detect.php';
include_once $_SERVER["DOCUMENT_ROOT"]."/Mobile_Detect.php";
$detect = new Mobile_Detect();
?>

Применение

Добавляем класс к <body> (либо к <html>) в зависимости от устройства Пользователя

<body class="pageBody <?
if ($detect->isTablet() ) { // если планшет
echo 'tablet';}
else if ($detect->isMobile()) { // если мобильное устройство
echo 'mobile';}
else {echo 'desktop';} ?>">
Универсальный способ - получение информации об устройстве
<!-- ВЕРСИЯ 3.74.3 -->
$detect = new \Detection\MobileDetect;
$deviceType = ($detect->isMobile() ? ($detect->isTablet() ? 'tablet' : 'phone') : 'computer');

Далее использовать эти знания в своих целях.

Либо вот так.
Метод, который позволит определить, что пользователь зашел с мобильного устройства (смартфон, телефон и т.п.):

<!-- ВЕРСИЯ 3.74.3 -->
<?php
$detect = new \Detection\MobileDetect;
if ($detect->isMobile() and !($detect->isTablet())) {
// выводим мобильную версию сайта
}?>


<!-- ВЕРСИЯ 2.8.3 -->
<?php
$detect = new Mobile_Detect();
if ($detect->isMobile() and !($detect->isTablet())) {
// выводим мобильную версию сайта
}?>

С планшета

<!-- ВЕРСИЯ 3.74.3 -->
<?php
$detect = new \Detection\MobileDetect;
if($detect->isTablet()){
// выводим адаптированную версию браузера
}?>


<!-- ВЕРСИЯ 2.8.3 -->
<?php
$detect = new Mobile_Detect();
if($detect->isTablet()){
// выводим адаптированную версию браузера
}?>

Либо вот так

<!-- ВЕРСИЯ 3.74.3 -->
<?php
$detect = new \Detection\MobileDetect;
if ($detect->isMobile() or $detect->isTablet()){  // На мобильных и планшетах
// выводим адаптированную версию браузера
}?>

<?php  
$detect = new \Detection\MobileDetect;
if ($detect->isMobile() and !$detect->isTablet()){  // Только на мобильных
    // выводим мобильную версию 
}?>


<!-- ВЕРСИЯ 2.8.3 -->
<?php 
$detect = new Mobile_Detect(); 
if ($detect->isMobile() or $detect->isTablet()){ // На мобильных и планшетах
    // выводим мобильную версию
}?>

<?php  
$detect = new Mobile_Detect(); 
if ($detect->isMobile() and !$detect->isTablet()){  // Только на мобильных
    // выводим мобильную версию 
}?>
Если нужно показывать рекламу того приложения для устройства с которого зашел пользователь.
Т.е. зашел он с Android, предлагаем ему скачать Android программу из Play Store, зашел с Iphone даем ссылку на AppStore. С данным классом определить операционную систему можно так:
<?php
if($detect->isiOS()){
// выводим рекламу для яблокофонов
}

<?php
if($detect->isAndroidOS()){
// рекламируем приложение корпорации добра
}
Есть еще куча методов isIphone(), isIpad(), isBlackBerry(), isKindle(), isOpera() и т.д. полный список можно посмотреть запустив demo.php из скачанного архива, либо на demo странице официального сайта скрипта
Проект активно развивается, и используется уже в куче известных систем Drupal, WordPress и т.д.
В конце лишь напишу, что надо дать возможность пользователю решать, нужна ли ему мобильная версия. Я сделал так: в самом низу мобильной версии сайта, была ссылка на основной сайт, но с параметром http://sitename.ru/?nomobile
А на сервере просто ставим куку, чтобы в дальнейшем не перенаправлять этого пользователя. Примерно так:
if( isset($_GET['nomobile']) ){
setcookie('nomobile','1',time()+3600*24,'/');
$_COOKIE['nomobile'] = 1;
}
define('NOMOBILE',isset($_COOKIE['nomobile']));
и наш скрипт автоматического перенаправления будет выглядеть так:
//require_once 'Mobile_Detect.php';
include_once $_SERVER["DOCUMENT_ROOT"]."/Mobile_Detect.php";
$detect = new Mobile_Detect();
if(!NOMOBILE and ($detect->isMobile() or $detect->isTablet())){
header("Location: ".'http://m.'.$_SERVER['HTTP_HOST']);
exit();
}

Файл Mobile_Detect.php загружаем в корень сайта

PHP

<!-- ВЕРСИЯ 2.8.3 -->
<?php
/**
* Mobile Detect Library
* =====================
*
* Motto: "Every business should have a mobile detection script to detect mobile readers"
*
* Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
* It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
*
* @author Current authors: Serban Ghita <serbanghita@gmail.com>
* Nick Ilyin <nick.ilyin@gmail.com>
*
* Original author: Victor Stanciu <vic.stanciu@gmail.com>
*
* @license Code and contributions have 'MIT License'
* More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt
*
* @link Homepage: http://mobiledetect.net
* GitHub Repo: https://github.com/serbanghita/Mobile-Detect
* Google Code: http://code.google.com/p/php-mobile-detect/
* README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
* HOWTO: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
*
* @version 2.8.30
*/
class Mobile_Detect
{
/**
* Mobile detection type.
*
* @deprecated since version 2.6.9
*/
const DETECTION_TYPE_MOBILE = 'mobile';
/**
* Extended detection type.
*
* @deprecated since version 2.6.9
*/
const DETECTION_TYPE_EXTENDED = 'extended';
/**
* A frequently used regular expression to extract version #s.
*
* @deprecated since version 2.6.9
*/
const VER = '([\w._\+]+)';
/**
* Top-level device.
*/
const MOBILE_GRADE_A = 'A';
/**
* Mid-level device.
*/
const MOBILE_GRADE_B = 'B';
/**
* Low-level device.
*/
const MOBILE_GRADE_C = 'C';
/**
* Stores the version number of the current release.
*/
const VERSION = '2.8.30';
/**
* A type for the version() method indicating a string return value.
*/
const VERSION_TYPE_STRING = 'text';
/**
* A type for the version() method indicating a float return value.
*/
const VERSION_TYPE_FLOAT = 'float';
/**
* A cache for resolved matches
* @var array
*/
protected $cache = array();
/**
* The User-Agent HTTP header is stored in here.
* @var string
*/
protected $userAgent = null;
/**
* HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
* @var array
*/
protected $httpHeaders = array();
/**
* CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer.
* @var array
*/
protected $cloudfrontHeaders = array();
/**
* The matching Regex.
* This is good for debug.
* @var string
*/
protected $matchingRegex = null;
/**
* The matches extracted from the regex expression.
* This is good for debug.
* @var string
*/
protected $matchesArray = null;
/**
* The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED.
*
* @deprecated since version 2.6.9
*
* @var string
*/
protected $detectionType = self::DETECTION_TYPE_MOBILE;
/**
* HTTP headers that trigger the 'isMobile' detection
* to be true.
*
* @var array
*/
protected static $mobileHeaders = array(
'HTTP_ACCEPT' => array('matches' => array(
// Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
'application/x-obml2d',
// BlackBerry devices.
'application/vnd.rim.html',
'text/vnd.wap.wml',
'application/vnd.wap.xhtml+xml'
)),
'HTTP_X_WAP_PROFILE' => null,
'HTTP_X_WAP_CLIENTID' => null,
'HTTP_WAP_CONNECTION' => null,
'HTTP_PROFILE' => null,
// Reported by Opera on Nokia devices (eg. C3).
'HTTP_X_OPERAMINI_PHONE_UA' => null,
'HTTP_X_NOKIA_GATEWAY_ID' => null,
'HTTP_X_ORANGE_ID' => null,
'HTTP_X_VODAFONE_3GPDPCONTEXT' => null,
'HTTP_X_HUAWEI_USERID' => null,
// Reported by Windows Smartphones.
'HTTP_UA_OS' => null,
// Reported by Verizon, Vodafone proxy system.
'HTTP_X_MOBILE_GATEWAY' => null,
// Seen this on HTC Sensation. SensationXE_Beats_Z715e.
'HTTP_X_ATT_DEVICEID' => null,
// Seen this on a HTC.
'HTTP_UA_CPU' => array('matches' => array('ARM')),
);
/**
* List of mobile devices (phones).
*
* @var array
*/
protected static $phoneDevices = array(
'iPhone' => '\biPhone\b|\biPod\b', // |\biTunes
'BlackBerry' => 'BlackBerry|\bBB10\b|rim[0-9]+',
'HTC' => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel',
'Nexus' => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6',
// @todo: Is 'Dell Streak' a tablet or a phone? ;)
'Dell' => 'Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b',
'Motorola' => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b|XT1068|XT1092',
'Samsung' => '\bSamsung\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F',
'LG' => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323)',
'Sony' => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533',
'Asus' => 'Asus.*Galaxy|PadFone.*Mobile',
'NokiaLumia' => 'Lumia [0-9]{3,4}',
//http://www.micromaxinfo.com/mobiles/smartphones
// Added because the codes might conflict with Acer Tablets.
'Micromax' => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b',
// @todo Complete the regex.
'Palm' => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ;
'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;)
//http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH)
// Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android.
'Pantech' => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790',
//http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones.
'Fly' => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250',
//http://fr.wikomobile.com
'Wiko' => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM',
'iMobile' => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)',
// Added simvalley mobile just for fun. They have some interesting devices.
//http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html
'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b',
// Wolfgang - a brand that is sold by Aldi supermarkets.
//http://www.wolfgangmobile.com/
'Wolfgang' => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q',
'Alcatel' => 'Alcatel',
'Nintendo' => 'Nintendo 3DS',
//http://en.wikipedia.org/wiki/Amoi
'Amoi' => 'Amoi',
//http://en.wikipedia.org/wiki/INQ
'INQ' => 'INQ',
// @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039
'GenericPhone' => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser',
);
/**
* List of tablet devices.
*
* @var array
*/
protected static $tabletDevices = array(
// @todo: check for mobile friendly emails topic.
'iPad' => 'iPad|iPad.*Mobile',
// Removed |^.*Android.*Nexus(?!(?:Mobile).)*$
// @see #442
'NexusTablet' => 'Android.*Nexus[\s]+(7|9|10)',
'SamsungTablet' => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715|SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU|SM-T815Y', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone.
//http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
'Kindle' => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)',
// Only the Surface tablets with Windows RT are considered mobile.
//http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
'SurfaceTablet' => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)',
//http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT
'HPTablet' => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10',
// Watch out for PadFone, see #132.
//http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/
'AsusTablet' => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C|ME181C|P01Y|PO1MA|P01Z|\bP027\b',
'BlackBerryTablet' => 'PlayBook|RIM Tablet',
'HTCtablet' => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410',
'MotorolaTablet' => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617',
'NookTablet' => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2',
//http://www.acer.ro/ac/ro/RO/content/drivers
//http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer)
//http://us.acer.com/ac/en/US/content/group/tablets
//http://www.acer.de/ac/de/DE/content/models/tablets/
// Can conflict with Micromax and Motorola phones codes.
'AcerTablet' => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30',
//http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/
//http://us.toshiba.com/tablets/tablet-finder
//http://www.toshiba.co.jp/regza/tablet/
'ToshibaTablet' => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO',
//http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html
//http://www.lg.com/us/tablets
'LGTablet' => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b',
'FujitsuTablet' => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b',
// Prestigio Tablets http://www.prestigio.com/support
'PrestigioTablet' => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD|PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002',
//http://support.lenovo.com/en_GB/downloads/default.page?#
'LenovoTablet' => 'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)|TB-X103F|TB-X304F|TB-8703F',
//http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets
'DellTablet' => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7',
//http://www.yarvik.com/en/matrix/tablets/
'YarvikTablet' => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b',
'MedionTablet' => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB',
'ArnovaTablet' => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2',
//http://www.intenso.de/kategorie_en.php?kategorie=33
// @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate
'IntensoTablet' => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004',
// IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/
'IRUTablet' => 'M702pro',
'MegafonTablet' => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b',
//http://www.e-boda.ro/tablete-pc.html
'EbodaTablet' => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)',
//http://www.allview.ro/produse/droseries/lista-tablete-pc/
'AllViewTablet' => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)',
//http://wiki.archosfans.com/index.php?title=Main_Page
// @note Rewrite the regex format after we add more UAs.
'ArchosTablet' => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b',
//http://www.ainol.com/plugin.php?identifier=ainol&module=product
'AinolTablet' => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark',
'NokiaLumiaTablet' => 'Lumia 2520',
// @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER
// Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser
//http://www.sony.jp/support/tablet/
'SonyTablet' => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP612|SOT31',
//http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8
'PhilipsTablet' => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b',
// db + http://www.cube-tablet.com/buy-products.html
'CubeTablet' => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT',
//http://www.cobyusa.com/?p=pcat&pcat_id=3001
'CobyTablet' => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010',
//http://www.match.net.cn/products.asp
'MIDTablet' => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10',
//http://www.msi.com/support
// @todo Research the Windows Tablets.
'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b',
// @todo http://www.kyoceramobile.com/support/drivers/
// 'KyoceraTablet' => null,
// @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/
// 'IntextTablet' => null,
//http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets)
//http://www.imp3.net/14/show.php?itemid=20454
'SMiTTablet' => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)',
//http://www.rock-chips.com/index.php?do=prod&pid=2
'RockChipTablet' => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A',
//http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/
'FlyTablet' => 'IQ310|Fly Vision',
//http://www.bqreaders.com/gb/tablets-prices-sale.html
'bqTablet' => 'Android.*(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))|Maxwell.*Lite|Maxwell.*Plus',
//http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290
//http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets)
'HuaweiTablet' => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L',
// Nec or Medias Tab
'NecTablet' => '\bN-06D|\bN-08D',
// Pantech Tablets: http://www.pantechusa.com/phones/
'PantechTablet' => 'Pantech.*P4100',
// Broncho Tablets: http://www.broncho.cn/ (hard to find)
'BronchoTablet' => 'Broncho.*(N701|N708|N802|a710)',
//http://versusuk.com/support.html
'VersusTablet' => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b',
//http://www.zync.in/index.php/our-products/tablet-phablets
'ZyncTablet' => 'z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900',
//http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/
'PositivoTablet' => 'TB07STA|TB10STA|TB07FTA|TB10FTA',
//https://www.nabitablet.com/
'NabiTablet' => 'Android.*\bNabi',
'KoboTablet' => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build',
// French Danew Tablets http://www.danew.com/produits-tablette.php
'DanewTablet' => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b',
// Texet Tablets and Readers http://www.texet.ru/tablet/
'TexetTablet' => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE',
// Avoid detecting 'PLAYSTATION 3' as mobile.
'PlaystationTablet' => 'Playstation.*(Portable|Vita)',
//http://www.trekstor.de/surftabs.html
'TrekstorTablet' => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab',
//http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets
'PyleAudioTablet' => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b',
//http://www.advandigital.com/index.php?link=content-product&jns=JP001
// because of the short codenames we have to include whitespaces to reduce the possible conflicts.
'AdvanTablet' => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ',
//http://www.danytech.com/category/tablet-pc
'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1',
//http://www.galapad.net/product.html
'GalapadTablet' => 'Android.*\bG1\b',
//http://www.micromaxinfo.com/tablet/funbook
'MicromaxTablet' => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b',
//http://www.karbonnmobiles.com/products_tablet.php
'KarbonnTablet' => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b',
//http://www.myallfine.com/Products.asp
'AllFineTablet' => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide',
//http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr=
'PROSCANTablet' => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b',
//http://www.yonesnav.com/products/products.php
'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026',
//http://www.cjshowroom.com/eproducts.aspx?classcode=004001001
// China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html)
'ChangJiaTablet' => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503',
//http://www.gloryunion.cn/products.asp
//http://www.allwinnertech.com/en/apply/mobile.html
//http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB)
// @todo: Softwiner tablets?
// aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions.
'GUTablet' => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G
//http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118
'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10',
//http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/
// @todo: add more tests.
'OvermaxTablet' => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027',
//http://hclmetablet.com/India/index.php
'HCLTablet' => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync',
//http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html
'DPSTablet' => 'DPS Dream 9|DPS Dual 7',
//http://www.visture.com/index.asp
'VistureTablet' => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10',
//http://www.mijncresta.nl/tablet
'CrestaTablet' => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989',
// MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309
'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b',
// Concorde tab
'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan',
// GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/
'GoCleverTablet' => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042',
// Modecom Tablets - http://www.modecom.eu/tablets/portal/
'ModecomTablet' => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003',
// Vonino Tablets - http://www.vonino.eu/tablets
'VoninoTablet' => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b',
// ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0
'ECSTablet' => 'V07OT2|TM105A|S10OT1|TR10CS1',
// Storex Tablets - http://storex.fr/espace_client/support.html
// @note: no need to add all the tablet codes since they are guided by the first regex.
'StorexTablet' => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab',
// Generic Vodafone tablets.
'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497',
// French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb
// Aka: http://www.essentielb.fr/
'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2',
// Ross & Moor - http://ross-moor.ru/
'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711',
// i-mobile http://product.i-mobilephone.com/Mobile_Device
'iMobileTablet' => 'i-mobile i-note',
//http://www.tolino.de/de/vergleichen/
'TolinoTablet' => 'tolino tab [0-9.]+|tolino shine',
// AudioSonic - a Kmart brand
//http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72¤tPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1
'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b',
// AMPE Tablets - http://www.ampe.com.my/product-category/tablets/
// @todo: add them gradually to avoid conflicts.
'AMPETablet' => 'Android.* A78 ',
// Skk Mobile - http://skkmobile.com.ph/product_tablets.php
'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)',
// Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1
'TecnoTablet' => 'TECNO P9',
// JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3
'JXDTablet' => 'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b',
// i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/
'iJoyTablet' => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)',
//http://www.intracon.eu/tablet
'FX2Tablet' => 'FX2 PAD7|FX2 PAD10',
//http://www.xoro.de/produkte/
// @note: Might be the same brand with 'Simply tablets'
'XoroTablet' => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151',
//http://www1.viewsonic.com/products/computing/tablets/
'ViewsonicTablet' => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a',
//https://www.verizonwireless.com/tablets/verizon/
'VerizonTablet' => 'QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1',
//http://www.odys.de/web/internet-tablet_en.html
'OdysTablet' => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10',
//http://www.captiva-power.de/products.html#tablets-en
'CaptivaTablet' => 'CAPTIVA PAD',
// IconBIT - http://www.iconbit.com/products/tablets/
'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S',
//http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63
'TeclastTablet' => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi',
// Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price
'OndaTablet' => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+',
'JaytechTablet' => 'TPC-PA762',
'BlaupunktTablet' => 'Endeavour 800NG|Endeavour 1010',
//http://www.digma.ru/support/download/
// @todo: Ebooks also (if requested)
'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b',
//http://www.evolioshop.com/ro/tablete-pc.html
//http://www.evolio.ro/support/downloads_static.html?cat=2
// @todo: Research some more
'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b',
// @todo http://www.lavamobiles.com/tablets-data-cards
'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b',
//http://www.breezetablet.com/
'AocTablet' => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712',
//http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/
'MpmanTablet' => 'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71|MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77|MP709|MID701|MID711|MID170|MPDC703|MPQC1010',
//https://www.celkonmobiles.com/?_a=categoryphones&sid=2
'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b',
//http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab
'WolderTablet' => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b',
//http://www.mi.com/en
'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b',
//http://www.nbru.cn/index.html
'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One',
//http://navroad.com/products/produkty/tablety/
//http://navroad.com/products/produkty/tablety/
'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI',
//http://leader-online.com/new_site/product-category/tablets/
//http://www.leader-online.net.au/List/Tablet
'LeaderTablet' => 'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G|TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100',
//http://www.datawind.com/ubislate/
'UbislateTablet' => 'UbiSlate[\s]?7C',
//http://www.pocketbook-int.com/ru/support
'PocketBookTablet' => 'Pocketbook',
//http://www.kocaso.com/product_tablet.html
'KocasoTablet' => '\b(TB-1207)\b',
//http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm
'HisenseTablet' => '\b(F5281|E2371)\b',
//http://www.tesco.com/direct/hudl/
'Hudl' => 'Hudl HT7S3|Hudl 2',
//http://www.telstra.com.au/home-phone/thub-2/
'TelstraTablet' => 'T-Hub2',
'GenericTablet' => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b'
);
/**
* List of mobile Operating Systems.
*
* @var array
*/
protected static $operatingSystems = array(
'AndroidOS' => 'Android',
'BlackBerryOS' => 'blackberry|\bBB10\b|rim tablet os',
'PalmOS' => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino',
'SymbianOS' => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b',
// @reference: http://en.wikipedia.org/wiki/Windows_Mobile
'WindowsMobileOS' => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;',
// @reference: http://en.wikipedia.org/wiki/Windows_Phone
//http://wifeng.cn/?r=blog&a=view&id=106
//http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx
//http://msdn.microsoft.com/library/ms537503.aspx
//https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
'WindowsPhoneOS' => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;',
'iOS' => '\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia',
//http://en.wikipedia.org/wiki/MeeGo
// @todo: research MeeGo in UAs
'MeeGoOS' => 'MeeGo',
//http://en.wikipedia.org/wiki/Maemo
// @todo: research Maemo in UAs
'MaemoOS' => 'Maemo',
'JavaOS' => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135
'webOS' => 'webOS|hpwOS',
'badaOS' => '\bBada\b',
'BREWOS' => 'BREW',
);
/**
* List of mobile User Agents.
*
* IMPORTANT: This is a list of only mobile browsers.
* Mobile Detect 2.x supports only mobile browsers,
* it was never designed to detect all browsers.
* The change will come in 2017 in the 3.x release for PHP7.
*
* @var array
*/
protected static $browsers = array(
//'Vivaldi' => 'Vivaldi',
// @reference: https://developers.google.com/chrome/mobile/docs/user-agent
'Chrome' => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?',
'Dolfin' => '\bDolfin\b',
'Opera' => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+|Coast/[0-9.]+',
'Skyfire' => 'Skyfire',
'Edge' => 'Mobile Safari/[.0-9]* Edge',
'IE' => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+
'Firefox' => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS',
'Bolt' => 'bolt',
'TeaShark' => 'teashark',
'Blazer' => 'Blazer',
// @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3
'Safari' => 'Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari',
//http://en.wikipedia.org/wiki/Midori_(web_browser)
//'Midori' => 'midori',
//'Tizen' => 'Tizen',
'UCBrowser' => 'UC.*Browser|UCWEB',
'baiduboxapp' => 'baiduboxapp',
'baidubrowser' => 'baidubrowser',
//https://github.com/serbanghita/Mobile-Detect/issues/7
'DiigoBrowser' => 'DiigoBrowser',
//http://www.puffinbrowser.com/index.php
'Puffin' => 'Puffin',
//http://mercury-browser.com/index.html
'Mercury' => '\bMercury\b',
//http://en.wikipedia.org/wiki/Obigo_Browser
'ObigoBrowser' => 'Obigo',
//http://en.wikipedia.org/wiki/NetFront
'NetFront' => 'NF-Browser',
// @reference: http://en.wikipedia.org/wiki/Minimo
//http://en.wikipedia.org/wiki/Vision_Mobile_Browser
'GenericBrowser' => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger',
// @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser)
'PaleMoon' => 'Android.*PaleMoon|Mobile.*PaleMoon',
);
/**
* Utilities.
*
* @var array
*/
protected static $utilities = array(
// Experimental. When a mobile device wants to switch to 'Desktop Mode'.
//http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/
//https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011
//https://developers.facebook.com/docs/sharing/best-practices
'Bot' => 'Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|YandexMobileBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|Exabot|MJ12bot|YandexImages|TurnitinBot|Pingdom',
'MobileBot' => 'Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker/M1A1-R2D2',
'DesktopMode' => 'WPDesktop',
'TV' => 'SonyDTV|HbbTV', // experimental
'WebKit' => '(webkit)[ /]([\w.]+)',
// @todo: Include JXD consoles.
'Console' => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\b',
'Watch' => 'SM-V700',
);
/**
* All possible HTTP headers that represent the
* User-Agent string.
*
* @var array
*/
protected static $uaHttpHeaders = array(
// The default User-Agent string.
'HTTP_USER_AGENT',
// Header can occur on devices using Opera Mini.
'HTTP_X_OPERAMINI_PHONE_UA',
// Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
'HTTP_X_DEVICE_USER_AGENT',
'HTTP_X_ORIGINAL_USER_AGENT',
'HTTP_X_SKYFIRE_PHONE',
'HTTP_X_BOLT_PHONE_UA',
'HTTP_DEVICE_STOCK_UA',
'HTTP_X_UCBROWSER_DEVICE_UA'
);
/**
* The individual segments that could exist in a User-Agent string. VER refers to the regular
* expression defined in the constant self::VER.
*
* @var array
*/
protected static $properties = array(
// Build
'Mobile' => 'Mobile/[VER]',
'Build' => 'Build/[VER]',
'Version' => 'Version/[VER]',
'VendorID' => 'VendorID/[VER]',
// Devices
'iPad' => 'iPad.*CPU[a-z ]+[VER]',
'iPhone' => 'iPhone.*CPU[a-z ]+[VER]',
'iPod' => 'iPod.*CPU[a-z ]+[VER]',
//'BlackBerry' => array('BlackBerry[VER]', 'BlackBerry [VER];'),
'Kindle' => 'Kindle/[VER]',
// Browser
'Chrome' => array('Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'),
'Coast' => array('Coast/[VER]'),
'Dolfin' => 'Dolfin/[VER]',
// @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
'Firefox' => array('Firefox/[VER]', 'FxiOS/[VER]'),
'Fennec' => 'Fennec/[VER]',
//http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
//https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx
'Edge' => 'Edge/[VER]',
'IE' => array('IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'),
//http://en.wikipedia.org/wiki/NetFront
'NetFront' => 'NetFront/[VER]',
'NokiaBrowser' => 'NokiaBrowser/[VER]',
'Opera' => array( ' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]' ),
'Opera Mini' => 'Opera Mini/[VER]',
'Opera Mobi' => 'Version/[VER]',
'UCBrowser' => array( 'UCWEB[VER]', 'UC.*Browser/[VER]' ),
'MQQBrowser' => 'MQQBrowser/[VER]',
'MicroMessenger' => 'MicroMessenger/[VER]',
'baiduboxapp' => 'baiduboxapp/[VER]',
'baidubrowser' => 'baidubrowser/[VER]',
'SamsungBrowser' => 'SamsungBrowser/[VER]',
'Iron' => 'Iron/[VER]',
// @note: Safari 7534.48.3 is actually Version 5.1.
// @note: On BlackBerry the Version is overwriten by the OS.
'Safari' => array( 'Version/[VER]', 'Safari/[VER]' ),
'Skyfire' => 'Skyfire/[VER]',
'Tizen' => 'Tizen/[VER]',
'Webkit' => 'webkit[ /][VER]',
'PaleMoon' => 'PaleMoon/[VER]',
// Engine
'Gecko' => 'Gecko/[VER]',
'Trident' => 'Trident/[VER]',
'Presto' => 'Presto/[VER]',
'Goanna' => 'Goanna/[VER]',
// OS
'iOS' => ' \bi?OS\b [VER][ ;]{1}',
'Android' => 'Android [VER]',
'BlackBerry' => array('BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'),
'BREW' => 'BREW [VER]',
'Java' => 'Java/[VER]',
// @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx
// @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases
'Windows Phone OS' => array( 'Windows Phone OS [VER]', 'Windows Phone [VER]'),
'Windows Phone' => 'Windows Phone [VER]',
'Windows CE' => 'Windows CE/[VER]',
//http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd
'Windows NT' => 'Windows NT [VER]',
'Symbian' => array('SymbianOS/[VER]', 'Symbian/[VER]'),
'webOS' => array('webOS/[VER]', 'hpwOS/[VER];'),
);
/**
* Construct an instance of this class.
*
* @param array $headers Specify the headers as injection. Should be PHP _SERVER flavored.
* If left empty, will use the global _SERVER['HTTP_*'] vars instead.
* @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT
* from the $headers array instead.
*/
public function __construct(
array $headers = null,
$userAgent = null
) {
$this->setHttpHeaders($headers);
$this->setUserAgent($userAgent);
}
/**
* Get the current script version.
* This is useful for the demo.php file,
* so people can check on what version they are testing
* for mobile devices.
*
* @return string The version number in semantic version format.
*/
public static function getScriptVersion()
{
return self::VERSION;
}
/**
* Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
*
* @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
* the headers. The default null is left for backwards compatibility.
*/
public function setHttpHeaders($httpHeaders = null)
{
// use global _SERVER if $httpHeaders aren't defined
if (!is_array($httpHeaders) || !count($httpHeaders)) {
$httpHeaders = $_SERVER;
}
// clear existing headers
$this->httpHeaders = array();
// Only save HTTP headers. In PHP land, that means only _SERVER vars that
// start with HTTP_.
foreach ($httpHeaders as $key => $value) {
if (substr($key, 0, 5) === 'HTTP_') {
$this->httpHeaders[$key] = $value;
}
}
// In case we're dealing with CloudFront, we need to know.
$this->setCfHeaders($httpHeaders);
}
/**
* Retrieves the HTTP headers.
*
* @return array
*/
public function getHttpHeaders()
{
return $this->httpHeaders;
}
/**
* Retrieves a particular header. If it doesn't exist, no exception/error is caused.
* Simply null is returned.
*
* @param string $header The name of the header to retrieve. Can be HTTP compliant such as
* "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
* all-caps, HTTP_ prefixed, underscore seperated awesomeness.
*
* @return string|null The value of the header.
*/
public function getHttpHeader($header)
{
// are we using PHP-flavored headers?
if (strpos($header, '_') === false) {
$header = str_replace('-', '_', $header);
$header = strtoupper($header);
}
// test the alternate, too
$altHeader = 'HTTP_' . $header;
//Test both the regular and the HTTP_ prefix
if (isset($this->httpHeaders[$header])) {
return $this->httpHeaders[$header];
} elseif (isset($this->httpHeaders[$altHeader])) {
return $this->httpHeaders[$altHeader];
}
return null;
}
public function getMobileHeaders()
{
return self::$mobileHeaders;
}
/**
* Get all possible HTTP headers that
* can contain the User-Agent string.
*
* @return array List of HTTP headers.
*/
public function getUaHttpHeaders()
{
return self::$uaHttpHeaders;
}
/**
* Set CloudFront headers
* http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device
*
* @param array $cfHeaders List of HTTP headers
*
* @return boolean If there were CloudFront headers to be set
*/
public function setCfHeaders($cfHeaders = null) {
// use global _SERVER if $cfHeaders aren't defined
if (!is_array($cfHeaders) || !count($cfHeaders)) {
$cfHeaders = $_SERVER;
}
// clear existing headers
$this->cloudfrontHeaders = array();
// Only save CLOUDFRONT headers. In PHP land, that means only _SERVER vars that
// start with cloudfront-.
$response = false;
foreach ($cfHeaders as $key => $value) {
if (substr(strtolower($key), 0, 16) === 'http_cloudfront_') {
$this->cloudfrontHeaders[strtoupper($key)] = $value;
$response = true;
}
}
return $response;
}
/**
* Retrieves the cloudfront headers.
*
* @return array
*/
public function getCfHeaders()
{
return $this->cloudfrontHeaders;
}
/**
* @param string $userAgent
* @return string
*/
private function prepareUserAgent($userAgent) {
$userAgent = trim($userAgent);
$userAgent = substr($userAgent, 0, 500);
return $userAgent;
}
/**
* Set the User-Agent to be used.
*
* @param string $userAgent The user agent string to set.
*
* @return string|null
*/
public function setUserAgent($userAgent = null)
{
// Invalidate cache due to #375
$this->cache = array();
if (false === empty($userAgent)) {
return $this->userAgent = $this->prepareUserAgent($userAgent);
} else {
$this->userAgent = null;
foreach ($this->getUaHttpHeaders() as $altHeader) {
if (false === empty($this->httpHeaders[$altHeader])) { // @todo: should use getHttpHeader(), but it would be slow. (Serban)
$this->userAgent .= $this->httpHeaders[$altHeader] . " ";
}
}
if (!empty($this->userAgent)) {
return $this->userAgent = $this->prepareUserAgent($this->userAgent);
}
}
if (count($this->getCfHeaders()) > 0) {
return $this->userAgent = 'Amazon CloudFront';
}
return $this->userAgent = null;
}
/**
* Retrieve the User-Agent.
*
* @return string|null The user agent if it's set.
*/
public function getUserAgent()
{
return $this->userAgent;
}
/**
* Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or
* self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set.
*
* @deprecated since version 2.6.9
*
* @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default
* parameter is null which will default to self::DETECTION_TYPE_MOBILE.
*/
public function setDetectionType($type = null)
{
if ($type === null) {
$type = self::DETECTION_TYPE_MOBILE;
}
if ($type !== self::DETECTION_TYPE_MOBILE && $type !== self::DETECTION_TYPE_EXTENDED) {
return;
}
$this->detectionType = $type;
}
public function getMatchingRegex()
{
return $this->matchingRegex;
}
public function getMatchesArray()
{
return $this->matchesArray;
}
/**
* Retrieve the list of known phone devices.
*
* @return array List of phone devices.
*/
public static function getPhoneDevices()
{
return self::$phoneDevices;
}
/**
* Retrieve the list of known tablet devices.
*
* @return array List of tablet devices.
*/
public static function getTabletDevices()
{
return self::$tabletDevices;
}
/**
* Alias for getBrowsers() method.
*
* @return array List of user agents.
*/
public static function getUserAgents()
{
return self::getBrowsers();
}
/**
* Retrieve the list of known browsers. Specifically, the user agents.
*
* @return array List of browsers / user agents.
*/
public static function getBrowsers()
{
return self::$browsers;
}
/**
* Retrieve the list of known utilities.
*
* @return array List of utilities.
*/
public static function getUtilities()
{
return self::$utilities;
}
/**
* Method gets the mobile detection rules. This method is used for the magic methods $detect->is*().
*
* @deprecated since version 2.6.9
*
* @return array All the rules (but not extended).
*/
public static function getMobileDetectionRules()
{
static $rules;
if (!$rules) {
$rules = array_merge(
self::$phoneDevices,
self::$tabletDevices,
self::$operatingSystems,
self::$browsers
);
}
return $rules;
}
/**
* Method gets the mobile detection rules + utilities.
* The reason this is separate is because utilities rules
* don't necessary imply mobile. This method is used inside
* the new $detect->is('stuff') method.
*
* @deprecated since version 2.6.9
*
* @return array All the rules + extended.
*/
public function getMobileDetectionRulesExtended()
{
static $rules;
if (!$rules) {
// Merge all rules together.
$rules = array_merge(
self::$phoneDevices,
self::$tabletDevices,
self::$operatingSystems,
self::$browsers,
self::$utilities
);
}
return $rules;
}
/**
* Retrieve the current set of rules.
*
* @deprecated since version 2.6.9
*
* @return array
*/
public function getRules()
{
if ($this->detectionType == self::DETECTION_TYPE_EXTENDED) {
return self::getMobileDetectionRulesExtended();
} else {
return self::getMobileDetectionRules();
}
}
/**
* Retrieve the list of mobile operating systems.
*
* @return array The list of mobile operating systems.
*/
public static function getOperatingSystems()
{
return self::$operatingSystems;
}
/**
* Check the HTTP headers for signs of mobile.
* This is the fastest mobile check possible; it's used
* inside isMobile() method.
*
* @return bool
*/
public function checkHttpHeadersForMobile()
{
foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) {
if (isset($this->httpHeaders[$mobileHeader])) {
if (is_array($matchType['matches'])) {
foreach ($matchType['matches'] as $_match) {
if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false) {
return true;
}
}
return false;
} else {
return true;
}
}
}
return false;
}
/**
* Magic overloading method.
*
* @method boolean is[...]()
* @param string $name
* @param array $arguments
* @return mixed
* @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
*/
public function __call($name, $arguments)
{
// make sure the name starts with 'is', otherwise
if (substr($name, 0, 2) !== 'is') {
throw new BadMethodCallException("No such method exists: $name");
}
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
$key = substr($name, 2);
return $this->matchUAAgainstKey($key);
}
/**
* Find a detection rule that matches the current User-agent.
*
* @param null $userAgent deprecated
* @return boolean
*/
protected function matchDetectionRulesAgainstUA($userAgent = null)
{
// Begin general search.
foreach ($this->getRules() as $_regex) {
if (empty($_regex)) {
continue;
}
if ($this->match($_regex, $userAgent)) {
return true;
}
}
return false;
}
/**
* Search for a certain key in the rules array.
* If the key is found then try to match the corresponding
* regex against the User-Agent.
*
* @param string $key
*
* @return boolean
*/
protected function matchUAAgainstKey($key)
{
// Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc.
$key = strtolower($key);
if (false === isset($this->cache[$key])) {
// change the keys to lower case
$_rules = array_change_key_case($this->getRules());
if (false === empty($_rules[$key])) {
$this->cache[$key] = $this->match($_rules[$key]);
}
if (false === isset($this->cache[$key])) {
$this->cache[$key] = false;
}
}
return $this->cache[$key];
}
/**
* Check if the device is mobile.
* Returns true if any type of mobile device detected, including special ones
* @param null $userAgent deprecated
* @param null $httpHeaders deprecated
* @return bool
*/
public function isMobile($userAgent = null, $httpHeaders = null)
{
if ($httpHeaders) {
$this->setHttpHeaders($httpHeaders);
}
if ($userAgent) {
$this->setUserAgent($userAgent);
}
// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
if ($this->getUserAgent() === 'Amazon CloudFront') {
$cfHeaders = $this->getCfHeaders();
if(array_key_exists('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER'] === 'true') {
return true;
}
}
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
if ($this->checkHttpHeadersForMobile()) {
return true;
} else {
return $this->matchDetectionRulesAgainstUA();
}
}
/**
* Check if the device is a tablet.
* Return true if any type of tablet device is detected.
*
* @param string $userAgent deprecated
* @param array $httpHeaders deprecated
* @return bool
*/
public function isTablet($userAgent = null, $httpHeaders = null)
{
// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
if ($this->getUserAgent() === 'Amazon CloudFront') {
$cfHeaders = $this->getCfHeaders();
if(array_key_exists('HTTP_CLOUDFRONT_IS_TABLET_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_TABLET_VIEWER'] === 'true') {
return true;
}
}
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
foreach (self::$tabletDevices as $_regex) {
if ($this->match($_regex, $userAgent)) {
return true;
}
}
return false;
}
/**
* This method checks for a certain property in the
* userAgent.
* @todo: The httpHeaders part is not yet used.
*
* @param string $key
* @param string $userAgent deprecated
* @param string $httpHeaders deprecated
* @return bool|int|null
*/
public function is($key, $userAgent = null, $httpHeaders = null)
{
// Set the UA and HTTP headers only if needed (eg. batch mode).
if ($httpHeaders) {
$this->setHttpHeaders($httpHeaders);
}
if ($userAgent) {
$this->setUserAgent($userAgent);
}
$this->setDetectionType(self::DETECTION_TYPE_EXTENDED);
return $this->matchUAAgainstKey($key);
}
/**
* Some detection rules are relative (not standard),
* because of the diversity of devices, vendors and
* their conventions in representing the User-Agent or
* the HTTP headers.
*
* This method will be used to check custom regexes against
* the User-Agent string.
*
* @param $regex
* @param string $userAgent
* @return bool
*
* @todo: search in the HTTP headers too.
*/
public function match($regex, $userAgent = null)
{
$match = (bool) preg_match(sprintf('#%s#is', $regex), (false === empty($userAgent) ? $userAgent : $this->userAgent), $matches);
// If positive match is found, store the results for debug.
if ($match) {
$this->matchingRegex = $regex;
$this->matchesArray = $matches;
}
return $match;
}
/**
* Get the properties array.
*
* @return array
*/
public static function getProperties()
{
return self::$properties;
}
/**
* Prepare the version number.
*
* @todo Remove the error supression from str_replace() call.
*
* @param string $ver The string version, like "2.6.21.2152";
*
* @return float
*/
public function prepareVersionNo($ver)
{
$ver = str_replace(array('_', ' ', '/'), '.', $ver);
$arrVer = explode('.', $ver, 2);
if (isset($arrVer[1])) {
$arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
}
return (float) implode('.', $arrVer);
}
/**
* Check the version of the given property in the User-Agent.
* Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31)
*
* @param string $propertyName The name of the property. See self::getProperties() array
* keys for all possible properties.
* @param string $type Either self::VERSION_TYPE_STRING to get a string value or
* self::VERSION_TYPE_FLOAT indicating a float value. This parameter
* is optional and defaults to self::VERSION_TYPE_STRING. Passing an
* invalid parameter will default to the this type as well.
*
* @return string|float The version of the property we are trying to extract.
*/
public function version($propertyName, $type = self::VERSION_TYPE_STRING)
{
if (empty($propertyName)) {
return false;
}
// set the $type to the default if we don't recognize the type
if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) {
$type = self::VERSION_TYPE_STRING;
}
$properties = self::getProperties();
// Check if the property exists in the properties array.
if (true === isset($properties[$propertyName])) {
// Prepare the pattern to be matched.
// Make sure we always deal with an array (string is converted).
$properties[$propertyName] = (array) $properties[$propertyName];
foreach ($properties[$propertyName] as $propertyMatchString) {
$propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString);
// Identify and extract the version.
preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);
if (false === empty($match[1])) {
$version = ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);
return $version;
}
}
}
return false;
}
/**
* Retrieve the mobile grading, using self::MOBILE_GRADE_* constants.
*
* @return string One of the self::MOBILE_GRADE_* constants.
*/
public function mobileGrade()
{
$isMobile = $this->isMobile();
if (
// Apple iOS 4-7.0 – Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3 / 5.1 / 6.1), iPad 3 (5.1 / 6.0), iPad Mini (6.1), iPad Retina (7.0), iPhone 3GS (4.3), iPhone 4 (4.3 / 5.1), iPhone 4S (5.1 / 6.0), iPhone 5 (6.0), and iPhone 5S (7.0)
$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) >= 4.3 ||
$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) >= 4.3 ||
$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) >= 4.3 ||
// Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5)
// Android 3.1 (Honeycomb) - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM
// Android 4.0 (ICS) - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices
// Android 4.1 (Jelly Bean) - Tested on a Galaxy Nexus and Galaxy 7
( $this->version('Android', self::VERSION_TYPE_FLOAT)>2.1 && $this->is('Webkit') ) ||
// Windows Phone 7.5-8 - Tested on the HTC Surround (7.5), HTC Trophy (7.5), LG-E900 (7.5), Nokia 800 (7.8), HTC Mazaa (7.8), Nokia Lumia 520 (8), Nokia Lumia 920 (8), HTC 8x (8)
$this->version('Windows Phone OS', self::VERSION_TYPE_FLOAT) >= 7.5 ||
// Tested on the Torch 9800 (6) and Style 9670 (6), BlackBerry® Torch 9810 (7), BlackBerry Z10 (10)
$this->is('BlackBerry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 6.0 ||
// Blackberry Playbook (1.0-2.0) - Tested on PlayBook
$this->match('Playbook.*Tablet') ||
// Palm WebOS (1.4-3.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0), HP TouchPad (3.0)
( $this->version('webOS', self::VERSION_TYPE_FLOAT) >= 1.4 && $this->match('Palm|Pre|Pixi') ) ||
// Palm WebOS 3.0 - Tested on HP TouchPad
$this->match('hp.*TouchPad') ||
// Firefox Mobile 18 - Tested on Android 2.3 and 4.1 devices
( $this->is('Firefox') && $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 18 ) ||
// Chrome for Android - Tested on Android 4.0, 4.1 device
( $this->is('Chrome') && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 4.0 ) ||
// Skyfire 4.1 - Tested on Android 2.3 device
( $this->is('Skyfire') && $this->version('Skyfire', self::VERSION_TYPE_FLOAT) >= 4.1 && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) ||
// Opera Mobile 11.5-12: Tested on Android 2.3
( $this->is('Opera') && $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11.5 && $this->is('AndroidOS') ) ||
// Meego 1.2 - Tested on Nokia 950 and N9
$this->is('MeeGoOS') ||
// Tizen (pre-release) - Tested on early hardware
$this->is('Tizen') ||
// Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser
// @todo: more tests here!
$this->is('Dolfin') && $this->version('Bada', self::VERSION_TYPE_FLOAT) >= 2.0 ||
// UC Browser - Tested on Android 2.3 device
( ($this->is('UC Browser') || $this->is('Dolfin')) && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) ||
// Kindle 3 and Fire - Tested on the built-in WebKit browser for each
( $this->match('Kindle Fire') ||
$this->is('Kindle') && $this->version('Kindle', self::VERSION_TYPE_FLOAT) >= 3.0 ) ||
// Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet
$this->is('AndroidOS') && $this->is('NookTablet') ||
// Chrome Desktop 16-24 - Tested on OS X 10.7 and Windows 7
$this->version('Chrome', self::VERSION_TYPE_FLOAT) >= 16 && !$isMobile ||
// Safari Desktop 5-6 - Tested on OS X 10.7 and Windows 7
$this->version('Safari', self::VERSION_TYPE_FLOAT) >= 5.0 && !$isMobile ||
// Firefox Desktop 10-18 - Tested on OS X 10.7 and Windows 7
$this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 10.0 && !$isMobile ||
// Internet Explorer 7-9 - Tested on Windows XP, Vista and 7
$this->version('IE', self::VERSION_TYPE_FLOAT) >= 7.0 && !$isMobile ||
// Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7
$this->version('Opera', self::VERSION_TYPE_FLOAT) >= 10 && !$isMobile
){
return self::MOBILE_GRADE_A;
}
if (
$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT)<4.3 ||
$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT)<4.3 ||
$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT)<4.3 ||
// Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770
$this->is('Blackberry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 5 && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT)<6 ||
//Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3
($this->version('Opera Mini', self::VERSION_TYPE_FLOAT) >= 5.0 && $this->version('Opera Mini', self::VERSION_TYPE_FLOAT) <= 7.0 &&
($this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 || $this->is('iOS')) ) ||
// Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1)
$this->match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') ||
// @todo: report this (tested on Nokia N71)
$this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11 && $this->is('SymbianOS')
){
return self::MOBILE_GRADE_B;
}
if (
// Blackberry 4.x - Tested on the Curve 8330
$this->version('BlackBerry', self::VERSION_TYPE_FLOAT) <= 5.0 ||
// Windows Mobile - Tested on the HTC Leo (WinMo 5.2)
$this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT) <= 5.2 ||
// Tested on original iPhone (3.1), iPhone 3 (3.2)
$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) <= 3.2 ||
$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) <= 3.2 ||
$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) <= 3.2 ||
// Internet Explorer 7 and older - Tested on Windows XP
$this->version('IE', self::VERSION_TYPE_FLOAT) <= 7.0 && !$isMobile
){
return self::MOBILE_GRADE_C;
}
// All older smartphone platforms and featurephones - Any device that doesn't support media queries
// will receive the basic, C grade experience.
return self::MOBILE_GRADE_C;
}
}
<!-- Последняя версия - 4.8.06 -- НЕ ТЕСТИРОВАЛАСЬ! -->
<?php
/**
 * Mobile Detect Library
 * Motto: "Every business should have a mobile detection script to detect mobile readers"
 *
 * Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
 * It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
 *
 * Homepage: http://mobiledetect.net
 * GitHub: https://github.com/serbanghita/Mobile-Detect
 * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
 * CONTRIBUTING: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/CONTRIBUTING.md
 * KNOWN LIMITATIONS: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/KNOWN_LIMITATIONS.md
 * EXAMPLES: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
 *
 * @license https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE
 * @author  Serban Ghita <serbanghita@gmail.com> (since 2012)
 * @author  Nick Ilyin <nick.ilyin@gmail.com>
 * @author: Victor Stanciu <vic.stanciu@gmail.com> (original author)
 *
 * @version 4.8.06
 */
declare(strict_types=1);
namespace Detection;
use BadMethodCallException;
use Detection\Cache\Cache;
use Detection\Cache\CacheException;
use Detection\Exception\MobileDetectException;
use Psr\Cache\InvalidArgumentException;
/**
 * Auto-generated isXXXX() magic methods.
 * php export/dump_magic_methods.php
 *
 * @method bool isiPhone()
 * @method bool isBlackBerry()
 * @method bool isPixel()
 * @method bool isHTC()
 * @method bool isNexus()
 * @method bool isDell()
 * @method bool isMotorola()
 * @method bool isSamsung()
 * @method bool isLG()
 * @method bool isSony()
 * @method bool isAsus()
 * @method bool isXiaomi()
 * @method bool isNokiaLumia()
 * @method bool isMicromax()
 * @method bool isPalm()
 * @method bool isVertu()
 * @method bool isPantech()
 * @method bool isFly()
 * @method bool isWiko()
 * @method bool isiMobile()
 * @method bool isSimValley()
 * @method bool isWolfgang()
 * @method bool isAlcatel()
 * @method bool isNintendo()
 * @method bool isAmoi()
 * @method bool isINQ()
 * @method bool isOnePlus()
 * @method bool isGenericPhone()
 * @method bool isiPad()
 * @method bool isNexusTablet()
 * @method bool isGoogleTablet()
 * @method bool isSamsungTablet()
 * @method bool isKindle()
 * @method bool isSurfaceTablet()
 * @method bool isHPTablet()
 * @method bool isAsusTablet()
 * @method bool isBlackBerryTablet()
 * @method bool isHTCtablet()
 * @method bool isMotorolaTablet()
 * @method bool isNookTablet()
 * @method bool isAcerTablet()
 * @method bool isToshibaTablet()
 * @method bool isLGTablet()
 * @method bool isFujitsuTablet()
 * @method bool isPrestigioTablet()
 * @method bool isLenovoTablet()
 * @method bool isDellTablet()
 * @method bool isYarvikTablet()
 * @method bool isMedionTablet()
 * @method bool isArnovaTablet()
 * @method bool isIntensoTablet()
 * @method bool isIRUTablet()
 * @method bool isMegafonTablet()
 * @method bool isEbodaTablet()
 * @method bool isAllViewTablet()
 * @method bool isArchosTablet()
 * @method bool isAinolTablet()
 * @method bool isNokiaLumiaTablet()
 * @method bool isSonyTablet()
 * @method bool isPhilipsTablet()
 * @method bool isCubeTablet()
 * @method bool isCobyTablet()
 * @method bool isMIDTablet()
 * @method bool isMSITablet()
 * @method bool isSMiTTablet()
 * @method bool isRockChipTablet()
 * @method bool isFlyTablet()
 * @method bool isbqTablet()
 * @method bool isHuaweiTablet()
 * @method bool isNecTablet()
 * @method bool isPantechTablet()
 * @method bool isBronchoTablet()
 * @method bool isVersusTablet()
 * @method bool isZyncTablet()
 * @method bool isPositivoTablet()
 * @method bool isNabiTablet()
 * @method bool isKoboTablet()
 * @method bool isDanewTablet()
 * @method bool isTexetTablet()
 * @method bool isPlaystationTablet()
 * @method bool isTrekstorTablet()
 * @method bool isPyleAudioTablet()
 * @method bool isAdvanTablet()
 * @method bool isDanyTechTablet()
 * @method bool isGalapadTablet()
 * @method bool isMicromaxTablet()
 * @method bool isKarbonnTablet()
 * @method bool isAllFineTablet()
 * @method bool isPROSCANTablet()
 * @method bool isYONESTablet()
 * @method bool isChangJiaTablet()
 * @method bool isGUTablet()
 * @method bool isPointOfViewTablet()
 * @method bool isOvermaxTablet()
 * @method bool isHCLTablet()
 * @method bool isDPSTablet()
 * @method bool isVistureTablet()
 * @method bool isCrestaTablet()
 * @method bool isMediatekTablet()
 * @method bool isConcordeTablet()
 * @method bool isGoCleverTablet()
 * @method bool isModecomTablet()
 * @method bool isVoninoTablet()
 * @method bool isECSTablet()
 * @method bool isStorexTablet()
 * @method bool isVodafoneTablet()
 * @method bool isEssentielBTablet()
 * @method bool isRossMoorTablet()
 * @method bool isiMobileTablet()
 * @method bool isTolinoTablet()
 * @method bool isAudioSonicTablet()
 * @method bool isAMPETablet()
 * @method bool isSkkTablet()
 * @method bool isTecnoTablet()
 * @method bool isJXDTablet()
 * @method bool isiJoyTablet()
 * @method bool isFX2Tablet()
 * @method bool isXoroTablet()
 * @method bool isViewsonicTablet()
 * @method bool isVerizonTablet()
 * @method bool isOdysTablet()
 * @method bool isCaptivaTablet()
 * @method bool isIconbitTablet()
 * @method bool isTeclastTablet()
 * @method bool isOndaTablet()
 * @method bool isJaytechTablet()
 * @method bool isBlaupunktTablet()
 * @method bool isDigmaTablet()
 * @method bool isEvolioTablet()
 * @method bool isLavaTablet()
 * @method bool isAocTablet()
 * @method bool isMpmanTablet()
 * @method bool isCelkonTablet()
 * @method bool isWolderTablet()
 * @method bool isMediacomTablet()
 * @method bool isMiTablet()
 * @method bool isNibiruTablet()
 * @method bool isNexoTablet()
 * @method bool isLeaderTablet()
 * @method bool isUbislateTablet()
 * @method bool isPocketBookTablet()
 * @method bool isKocasoTablet()
 * @method bool isHisenseTablet()
 * @method bool isHudl()
 * @method bool isTelstraTablet()
 * @method bool isGenericTablet()
 * @method bool isAndroidOS()
 * @method bool isBlackBerryOS()
 * @method bool isPalmOS()
 * @method bool isSymbianOS()
 * @method bool isWindowsMobileOS()
 * @method bool isWindowsPhoneOS()
 * @method bool isiOS()
 * @method bool isiPadOS()
 * @method bool isSailfishOS()
 * @method bool isMeeGoOS()
 * @method bool isMaemoOS()
 * @method bool isJavaOS()
 * @method bool iswebOS()
 * @method bool isbadaOS()
 * @method bool isBREWOS()
 * @method bool isChrome()
 * @method bool isDolfin()
 * @method bool isOpera()
 * @method bool isSkyfire()
 * @method bool isEdge()
 * @method bool isIE()
 * @method bool isFirefox()
 * @method bool isBolt()
 * @method bool isTeaShark()
 * @method bool isBlazer()
 * @method bool isSafari()
 * @method bool isWeChat()
 * @method bool isUCBrowser()
 * @method bool isbaiduboxapp()
 * @method bool isbaidubrowser()
 * @method bool isDiigoBrowser()
 * @method bool isMercury()
 * @method bool isObigoBrowser()
 * @method bool isNetFront()
 * @method bool isGenericBrowser()
 * @method bool isPaleMoon()
 * @method bool isWebKit()
 * @method bool isConsole()
 * @method bool isWatch()
 */
class MobileDetect
{
    /**
     * A cache for resolved matches
     *  Implementation of PSR-16: Common Interface for Caching Libraries
     *  https://www.php-fig.org/psr/psr-16/
     *
     * Replace this with your own implementation.
     */
    protected Cache $cache;
    /**
     * Stores the version number of the current release.
     */
    protected string $VERSION = '4.8.06';
    protected array $config = [
        // Auto-initialization on HTTP headers from $_SERVER['HTTP...']
        // Disable this if you're going for performance and set the
        // User-Agent via $detect->setUserAgent("...").
        // @var boolean
        'autoInitOfHttpHeaders' => true,
        // Maximum HTTP User-Agent value allowed.
        // @var int
        'maximumUserAgentLength' => 500
    ];
    /**
     * A frequently used regular expression to extract version #s.
     */
    protected const VERSION_REGEX = '([\w._\+]+)';
    /**
     * A type for the version() method indicating a string return value.
     */
    private const VERSION_TYPE_STRING = 'text';
    /**
     * A type for the version() method indicating a float return value.
     */
    private const VERSION_TYPE_FLOAT = 'float';
    /**
     * The User-Agent HTTP header is stored in here.
     * @var string|null
     */
    protected ?string $userAgent = null;
    /**
     * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
     * @var array
     */
    protected array $httpHeaders = [];
    /**
     * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer.
     * @var array
     */
    protected static array $knownCloudFrontHeaders = [
        'HTTP_CLOUDFRONT_IS_MOBILE_VIEWER',
        'HTTP_CLOUDFRONT_IS_TABLET_VIEWER',
        'HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'
    ];
    protected static string $cloudFrontUA = 'Amazon CloudFront';
    /**
     * The matching regex string. Used only for debugging.
     * @var string
     */
    protected string $matchingRegex = "";
    /**
     * The matches extracted from the regex expression. Used only for debugging.
     * @var array
     */
    protected array $matchesArray = [];
    /**
     * HTTP headers that trigger the 'isMobile' detection to be true.
     * @var array
     */
    protected static array $knownMobilePositiveHeaders = [
        'HTTP_ACCEPT'                  => [
            'matches' => [
                // Opera Mini
                // @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
                'application/x-obml2d',
                // BlackBerry devices.
                'application/vnd.rim.html',
                'text/vnd.wap.wml',
                'application/vnd.wap.xhtml+xml'
            ]],
        'HTTP_X_WAP_PROFILE'           => null,
        'HTTP_X_WAP_CLIENTID'          => null,
        'HTTP_WAP_CONNECTION'          => null,
        'HTTP_PROFILE'                 => null,
        // Reported by Opera on Nokia devices (e.g. C3).
        'HTTP_X_OPERAMINI_PHONE_UA'    => null,
        'HTTP_X_NOKIA_GATEWAY_ID'      => null,
        'HTTP_X_ORANGE_ID'             => null,
        'HTTP_X_VODAFONE_3GPDPCONTEXT' => null,
        'HTTP_X_HUAWEI_USERID'         => null,
        // Reported by Windows Smartphones.
        'HTTP_UA_OS'                   => null,
        // Reported by Verizon, Vodafone proxy system.
        'HTTP_X_MOBILE_GATEWAY'        => null,
        // Seen this on HTC Sensation. SensationXE_Beats_Z715e.
        'HTTP_X_ATT_DEVICEID'          => null,
        // Seen this on a HTC.
        'HTTP_UA_CPU'                  => ['matches' => ['ARM']],
    ];
    /**
     * List of mobile devices (phones).
     * @var array
     */
    protected static array $phoneDevices = [
        'iPhone'        => '\biPhone\b|\biPod\b', // |\biTunes
        'BlackBerry'    => 'BlackBerry|\bBB10\b|rim[0-9]+|\b(BBA100|BBB100|BBD100|BBE100|BBF100|STH100)\b-[0-9]+',
        'Pixel'         => '; \bPixel\b',
        'HTC'           => [
            'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)',
            'APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200',
            'ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel'
        ],
        'Nexus'         => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 5X|Nexus 6',
        // @todo: Is 'Dell Streak' a tablet or a phone? ;)
        'Dell'          => 'Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b',
        'Motorola'      => [
            'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955',
            'A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611',
            'MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863',
            'ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317',
            'XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800',
            'XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b|XT1068|XT1092|XT1052',
        ],
        'Samsung'       => [
            '\bSamsung\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310',
            'SM-F946B|SM-A127F',
            'SM-S908E|SM-G955N|SM-S918U1|SM-G998B|SM-G970N|SM-G973U|SM-S901U|SM-A515F|SM-S901E|SM-G980F|SM-S901B',
            'GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510',
            'GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K',
            'GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010',
            'GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100',
            'GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210',
            'GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250',
            'GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420',
            'GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700',
            'GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103',
            'GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800',
            'GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210',
            'GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350',
            'GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660',
            'GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230',
            'GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600',
            'SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930',
            'SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730',
            'SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351',
            'SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430',
            'SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740',
            'SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157',
            'SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657',
            'SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817',
            'SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220',
            'SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225',
            'SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105',
            'SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200',
            'SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717',
            'SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917',
            'SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500',
            'SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777',
            'SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229',
            'SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409',
            'SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609',
            'SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749',
            'SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200',
            'SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497',
            'SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10',
            'SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700',
            'SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700',
            'SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220',
            'SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550',
            'SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920',
            'SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535',
            'SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510',
            'GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582',
            'GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K',
            'SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558',
            'GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F|SM-J330F',
            'SM-G610F|SM-G981B|SM-G892A|SM-A530F|SM-G988N|SM-G781B|SM-A805N|SM-G965F',
        ],
        'LG'            => [
            '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200)',
            'LG[- ]?(MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK)',
            'LG[- ]?(E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690)',
            'LG[- ]?(MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740)',
            'LG[- ]?(VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323|M257)|LM-G710',
        ],
        'Sony'          => [
            'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i',
            'C5303|C6902|C6903|C6906|C6943|D2533|SOV34|601SO|F8332',
        ],
        'Asus'          => 'Asus.*Galaxy|PadFone.*Mobile|ASUS_Z01QD|ASUS_X00TD',
        'Xiaomi'        => [
            '^(?!.*\bx11\b).*xiaomi.*$|POCOPHONE F1|\bMI\b 8|\bMi\b 10|Redmi Note 9S|Redmi 5A|Redmi Note 5A Prime|Redmi Note 7 Pro',
            'N2G47H|M2001J2G|M2001J2I|M1805E10A|M2004J11G|M1902F1G|M2002J9G|M2004J19G|M2003J6A1G|M2012K11C|M2007J1SC',
        ],
        'NokiaLumia'    => 'Lumia [0-9]{3,4}',
        // http://www.micromaxinfo.com/mobiles/smartphones
        // Added because the codes might conflict with Acer Tablets.
        'Micromax'  => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b',
        // @todo Complete the regex.
        'Palm'  => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ;
        // Just for fun ;)
        'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature',
        // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH)
        // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android.
        'Pantech'   => [
            'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L',
            'IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S',
            'IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995',
            'IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790',
        ],
        // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones.
        'Fly'   => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250',
        // http://fr.wikomobile.com
        'Wiko'  => [
            'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY',
            'BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM'
        ],
        'iMobile'   => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)',
        // Added simvalley mobile just for fun. They have some interesting devices.
        // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html
        'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b',
         // Wolfgang - a brand that is sold by Aldi supermarkets.
         // http://www.wolfgangmobile.com/
        'Wolfgang'  => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q',
        'Alcatel'   => 'Alcatel',
        'Nintendo'  => 'Nintendo (3DS|Switch)',
        // http://en.wikipedia.org/wiki/Amoi
        'Amoi'  => 'Amoi',
        // http://en.wikipedia.org/wiki/INQ
        'INQ'   => 'INQ',
        'OnePlus'   => 'ONEPLUS',
        // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039
        'GenericPhone'  => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser',
    ];
    /**
     * List of tablet devices.
     * @var array
     */
    protected static array $tabletDevices = [
        // @todo: check for mobile friendly emails topic.
        'iPad'              => 'iPad|iPad.*Mobile',
        // Removed |^.*Android.*Nexus(?!(?:Mobile).)*$
        // @see #442
        // @todo Merge NexusTablet into GoogleTablet.
        'NexusTablet'       => 'Android.*Nexus[\s]+(7|9|10)',
        // https://en.wikipedia.org/wiki/Pixel_C
        'GoogleTablet'           => 'Android.*Pixel C',
        'SamsungTablet'     => [
            'SM-X616B|SM-X610|SM-X516B|SM-X910|SM-X916B|SM-X816B|SM-X810|SM-X710|SM-X716B|SM-X510|SM-P619|SM-T225|SM-T225N|SM-T736B|SM-T505|SM-T733|SM-X205|SM-X210|SM-X216B',
            'SM-X700|SM-X706|SM-X706B|SM-X706U|SM-X706N|SM-X800|SM-X806|SM-X806B|SM-X806U|SM-X806N|SM-X900|SM-X906|SM-X906B|SM-X906U|SM-X906N|SM-P613|SM-X110|SM-X115',
            'SM-T970|SM-T380|SM-T5950|SM-T905|SM-T231|SM-T500|SM-T860|SM-T536|SM-T837A|SM-X200|SM-T220|SM-T870|SM-X906C', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone.
            'SM-T815Y|SM-T585|SM-T285|SM-T825|SM-W708|SM-T835|SM-T830|SM-T837V|SM-T720|SM-T510|SM-T387V|SM-P610|SM-T290|SM-T515|SM-T590|SM-T595|SM-T725|SM-T817P|SM-P585N0|SM-T395|SM-T295|SM-T865|SM-P610N|SM-P615',
            'SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU',
            'SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715',
            'SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237',
            'GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X',
            'GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X',
            'SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210',
            'GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L',
            'SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113',
            'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987',
        ],
        // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
        'Kindle'            => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)',
        // Only the Surface tablets with Windows RT are considered mobile.
        // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
        'SurfaceTablet'     => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)',
        // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT
        'HPTablet'          => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10',
        // Watch out for PadFone, see #132.
        // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/
        'AsusTablet'        => [
            'ME181C|P01Y|PO1MA|P01Z|\bP027\b|\bP024\b|\bP00C\b',
            '\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K01A | K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C',
            '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b',
        ],
        'BlackBerryTablet'  => 'PlayBook|RIM Tablet',
        'HTCtablet'         => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410',
        'MotorolaTablet'    => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617',
        'NookTablet'        => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2',
        // http://www.acer.ro/ac/ro/RO/content/drivers
        // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer)
        // http://us.acer.com/ac/en/US/content/group/tablets
        // http://www.acer.de/ac/de/DE/content/models/tablets/
        // Can conflict with Micromax and Motorola phones codes.
        'AcerTablet'        => [
            'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b',
            'W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30|A3-A40'
        ],
        // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/
        // http://us.toshiba.com/tablets/tablet-finder
        // http://www.toshiba.co.jp/regza/tablet/
        'ToshibaTablet'     => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO',
        // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html
        // http://www.lg.com/us/tablets
        'LGTablet'          => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b',
        'FujitsuTablet'     => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b',
        // Prestigio Tablets http://www.prestigio.com/support
        'PrestigioTablet'   => [
            'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C',
            'PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD',
            'PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002',
        ],
        // http://support.lenovo.com/en_GB/downloads/default.page?#
        'LenovoTablet'      => [
            'TB-X704L|TB-J606F|TB-X606F|TB-X306X|YT-J706X|TB128FU',
            'YT3-X50M|YT-X705F|YT-X703F|YT-X703L|YT-X705L|YT-X705X|TB2-X30F|TB2-X30L|TB2-X30M|A2107A-F|A2107A-H|TB3-730F|TB3-730M|TB3-730X|TB-7504F|TB-7504X|TB-X704F|TB-X104F|TB3-X70F|TB-X705F|TB-8504F|TB3-X70L|TB3-710F',
            'TB-X103F|TB-X304X|TB-X304F|TB-X304L|TB-X505F|TB-X505L|TB-X505X|TB-X605F|TB-X605L|TB-8703F|TB-8703X|TB-8703N|TB-8704N|TB-8704F|TB-8704X|TB-8704V|TB-7304F|TB-7304I|TB-7304X|Tab2A7-10F|Tab2A7-20F|TB2-X30L|YT3-X50L|YT3-X50F',
            'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)',
        ],
        // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets
        'DellTablet'        => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7',
        'XiaomiTablet'      => '21051182G',
        // http://www.yarvik.com/en/matrix/tablets/
        'YarvikTablet'      => [
            'Android.*\b(TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b',
            'Android.*\b(TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211)\b',
            'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152)\b',
        ],
        'MedionTablet'      => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB',
        'ArnovaTablet'      => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2',
        // http://www.intenso.de/kategorie_en.php?kategorie=33
        // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate
        'IntensoTablet'     => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004',
        // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/
        'IRUTablet'         => 'M702pro',
        'MegafonTablet'     => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b',
        // http://www.e-boda.ro/tablete-pc.html
        'EbodaTablet'       => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)',
        // http://www.allview.ro/produse/droseries/lista-tablete-pc/
        'AllViewTablet'           => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)',
        // http://wiki.archosfans.com/index.php?title=Main_Page
        // @note Rewrite the regex format after we add more UAs.
        'ArchosTablet'      => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b',
        // http://www.ainol.com/plugin.php?identifier=ainol&module=product
        'AinolTablet'       => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark',
        'NokiaLumiaTablet'  => 'Lumia 2520',
        // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER
        // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser
        // http://www.sony.jp/support/tablet/
        'SonyTablet'        => [
            'EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP641|SGP612|SOT31|SGP771|SGP611|SGP612|SGP712',
            'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321',
        ],
        // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8
        'PhilipsTablet'     => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b',
        // db + http://www.cube-tablet.com/buy-products.html
        'CubeTablet'        => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT',
        // http://www.cobyusa.com/?p=pcat&pcat_id=3001
        'CobyTablet'        => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010',
        // http://www.match.net.cn/products.asp
        'MIDTablet'         => [
            'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800',
            'MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10',
        ],
        // http://www.msi.com/support
        // @todo Research the Windows Tablets.
        'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b',
        // @todo http://www.kyoceramobile.com/support/drivers/
    //    'KyoceraTablet' => null,
        // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/
    //    'IntextTablet' => null,
        // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets)
        // http://www.imp3.net/14/show.php?itemid=20454
        'SMiTTablet'        => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)',
        // http://www.rock-chips.com/index.php?do=prod&pid=2
        'RockChipTablet'    => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A',
        // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/
        'FlyTablet'         => 'IQ310|Fly Vision',
        // http://www.bqreaders.com/gb/tablets-prices-sale.html
        'bqTablet'          => 'Android.*(bq)?.*\b(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))\b|Maxwell.*Lite|Maxwell.*Plus',
        // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290
        // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets)
        'HuaweiTablet'      => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L|BAH-L09|BAH-W09|AGS-L09|CMR-AL19|KOB2-L09|BG2-U01|BG2-W09|BG2-U03',
        // Nec or Medias Tab
        'NecTablet'         => '\bN-06D|\bN-08D',
        // Pantech Tablets: http://www.pantechusa.com/phones/
        'PantechTablet'     => 'Pantech.*P4100',
        // Broncho Tablets: http://www.broncho.cn/ (hard to find)
        'BronchoTablet'     => 'Broncho.*(N701|N708|N802|a710)',
        // http://versusuk.com/support.html
        'VersusTablet'      => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b',
        // http://www.zync.in/index.php/our-products/tablet-phablets
        'ZyncTablet'        => 'z1000|Z99 2G|z930|z990|z909|Z919|z900', // Removed "z999" because of https://github.com/serbanghita/Mobile-Detect/issues/717
        // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/
        'PositivoTablet'    => 'TB07STA|TB10STA|TB07FTA|TB10FTA',
        // https://www.nabitablet.com/
        'NabiTablet'        => 'Android.*\bNabi',
        'KoboTablet'        => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build',
        // French Danew Tablets http://www.danew.com/produits-tablette.php
        'DanewTablet'       => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b',
        // Texet Tablets and Readers http://www.texet.ru/tablet/
        'TexetTablet'       => [
            'TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE',
            'TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD',
            'TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A',
            'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020',
        ],
        // Avoid detecting 'PLAYSTATION 3' as mobile.
        'PlaystationTablet' => 'Playstation.*(Portable|Vita)',
        // http://www.trekstor.de/surftabs.html
        'TrekstorTablet'    => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab',
        // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets
        'PyleAudioTablet'   => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b',
        // http://www.advandigital.com/index.php?link=content-product&jns=JP001
        // because of the short codenames we have to include whitespaces to reduce the possible conflicts.
        'AdvanTablet'       => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ',
        // http://www.danytech.com/category/tablet-pc
        'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1',
        // http://www.galapad.net/product.html ; https://github.com/serbanghita/Mobile-Detect/issues/761
        'GalapadTablet'     => 'Android [0-9.]+; [a-z-]+; \bG1\b',
        // http://www.micromaxinfo.com/tablet/funbook
        'MicromaxTablet'    => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b',
        // http://www.karbonnmobiles.com/products_tablet.php
        'KarbonnTablet'     => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b',
        // http://www.myallfine.com/Products.asp
        'AllFineTablet'     => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide',
        // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr=
        'PROSCANTablet'     => [
            '\b(PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b',
            '\b(PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082)\b',
            '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K)\b',
        ],
        // http://www.yonesnav.com/products/products.php
        'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026',
        // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001
        // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html)
        'ChangJiaTablet'    => [
            'TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503',
            'TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205',
            'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106',
        ],
        // http://www.gloryunion.cn/products.asp
        // http://www.allwinnertech.com/en/apply/mobile.html
        // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB)
        // @todo: Softwiner tablets?
        // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions.
        'GUTablet'          => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G
        // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118
        'PointOfViewTablet' => [
            'TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10',
            'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945',
        ],
        // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/
        // @todo: add more tests.
        'OvermaxTablet'     => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027',
        // http://hclmetablet.com/India/index.php
        'HCLTablet'         => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync',
        // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html
        'DPSTablet'         => 'DPS Dream 9|DPS Dual 7',
        // http://www.visture.com/index.asp
        'VistureTablet'     => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10',
        // http://www.mijncresta.nl/tablet
        'CrestaTablet'     => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989',
        // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309
        'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b',
        // Concorde tab
        'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan',
        // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/
        'GoCleverTablet' => [
            'TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042',
            'TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2',
            'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76',
        ],
        // Modecom Tablets - http://www.modecom.eu/tablets/portal/
        'ModecomTablet' => [
            'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702',
            'FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003',
        ],
        // Vonino Tablets
        'VoninoTablet'  => [
            '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS)\b',
            '\b(Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b',
        ],
        // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0
        'ECSTablet'     => 'V07OT2|TM105A|S10OT1|TR10CS1',
        // Storex Tablets - http://storex.fr/espace_client/support.html
        // @note: no need to add all the tablet codes since they are guided by the first regex.
        'StorexTablet'  => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab',
        // Generic Vodafone tablets.
        'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497|VFD 1400',
        // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb
        // Aka: http://www.essentielb.fr/
        'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2',
        // Ross & Moor - http://ross-moor.ru/
        'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711',
        // i-mobile http://product.i-mobilephone.com/Mobile_Device
        'iMobileTablet'        => 'i-mobile i-note',
        // http://www.tolino.de/de/vergleichen/
        'TolinoTablet'  => 'tolino tab [0-9.]+|tolino shine',
        // AudioSonic - a Kmart brand
        // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72&currentPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1
        'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b',
        // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/
        // @todo: add them gradually to avoid conflicts.
        'AMPETablet' => 'Android.* A78 ',
        // Skk Mobile - http://skkmobile.com.ph/product_tablets.php
        'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)',
        // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1
        'TecnoTablet' => 'TECNO P9|TECNO DP8D',
        // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3
        'JXDTablet' => [
            'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603)\b',
            'Android.* \b(S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b',
        ],
        // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/
        'iJoyTablet' => [
            'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7)',
            'Tablet (Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst)',
            'Tablet (Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam)',
            'Tablet (Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)',
        ],
        // http://www.intracon.eu/tablet
        'FX2Tablet' => 'FX2 PAD7|FX2 PAD10',
        // http://www.xoro.de/produkte/
        // @note: Might be the same brand with 'Simply tablets'
        'XoroTablet'        => [
            'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790',
            'PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032',
            'TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151',
        ],
        // http://www1.viewsonic.com/products/computing/tablets/
        'ViewsonicTablet'   => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a',
        // https://www.verizonwireless.com/tablets/verizon/
        'VerizonTablet' => 'QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1',
        // http://www.odys.de/web/internet-tablet_en.html
        'OdysTablet'        => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10',
        // http://www.captiva-power.de/products.html#tablets-en
        'CaptivaTablet'     => 'CAPTIVA PAD',
        // IconBIT - http://www.iconbit.com/products/tablets/
        'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S',
        // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63
        'TeclastTablet' => [
            'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G',
            '\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G',
            '\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b',
            '\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b',
            '\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b',
            '\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi',
        ],
        // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price
        'OndaTablet' => [
            '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811)\b[\s]+',
            '\b(V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819)\b[\s]+',
            '\b(V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+|V10 \b4G\b',
        ],
        'JaytechTablet'     => 'TPC-PA762',
        'BlaupunktTablet'   => 'Endeavour 800NG|Endeavour 1010',
        // http://www.digma.ru/support/download/
        // @todo: Ebooks also (if requested)
        'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b',
        // http://www.evolioshop.com/ro/tablete-pc.html
        // http://www.evolio.ro/support/downloads_static.html?cat=2
        // @todo: Research some more
        'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b',
        // @todo http://www.lavamobiles.com/tablets-data-cards
        'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b',
        // http://www.breezetablet.com/
        'AocTablet' => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712',
        // http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/
        'MpmanTablet' => [
            'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71',
            'MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77',
            'MP709|MID701|MID711|MID170|MPDC703|MPQC1010',
        ],
        // https://www.celkonmobiles.com/?_a=categoryphones&sid=2
        'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b',
        // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab
        'WolderTablet' => [
            'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT)\b',
            'miTab \b(EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND)\b',
            'miTab \b(BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b',
        ],
        'MediacomTablet' => 'M-MPI10C3G|M-SP10EG|M-SP10EGP|M-SP10HXAH|M-SP7HXAH|M-SP10HXBH|M-SP8HXAH|M-SP8MXA',
        // http://www.mi.com/en
        'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b',
        // http://www.nbru.cn/index.html
        'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One',
        // http://navroad.com/products/produkty/tablety/
        // http://navroad.com/products/produkty/tablety/
        'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI',
        // http://leader-online.com/new_site/product-category/tablets/
        // http://www.leader-online.net.au/List/Tablet
        'LeaderTablet' => [
            'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G',
            'TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100',
        ],
        // http://www.datawind.com/ubislate/
        'UbislateTablet' => 'UbiSlate[\s]?7C',
        // http://www.pocketbook-int.com/ru/support
        'PocketBookTablet' => 'Pocketbook',
        // http://www.kocaso.com/product_tablet.html
        'KocasoTablet' => '\b(TB-1207)\b',
        // http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm
        'HisenseTablet' => '\b(F5281|E2371)\b',
        // http://www.tesco.com/direct/hudl/
        'Hudl'              => 'Hudl HT7S3|Hudl 2',
        // http://www.telstra.com.au/home-phone/thub-2/
        'TelstraTablet'     => 'T-Hub2',
        'GenericTablet'     => [
            'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002',
            '\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab',
            '\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b|WVT101|TM1088|KT107',
        ],
    ];
    /**
     * List of mobile Operating Systems.
     * @var array
     */
    protected static array $operatingSystems = [
        'AndroidOS'         => 'Android',
        'BlackBerryOS'      => 'blackberry|\bBB10\b|rim tablet os',
        'PalmOS'            => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino',
        'SymbianOS'         => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b',
        // @reference: http://en.wikipedia.org/wiki/Windows_Mobile
        'WindowsMobileOS'   => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Windows Mobile|Windows Phone [0-9.]+|WCE;',
        // @reference: http://en.wikipedia.org/wiki/Windows_Phone
        // http://wifeng.cn/?r=blog&a=view&id=106
        // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx
        // http://msdn.microsoft.com/library/ms537503.aspx
        // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
        'WindowsPhoneOS'   => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;',
        'iOS'               => '\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia',
        // https://en.wikipedia.org/wiki/IPadOS
        'iPadOS' => 'CPU OS 13',
        // @reference https://en.m.wikipedia.org/wiki/Sailfish_OS
        // https://sailfishos.org/
        'SailfishOS'        => 'Sailfish',
        // http://en.wikipedia.org/wiki/MeeGo
        // @todo: research MeeGo in UAs
        'MeeGoOS'           => 'MeeGo',
        // http://en.wikipedia.org/wiki/Maemo
        // @todo: research Maemo in UAs
        'MaemoOS'           => 'Maemo',
        'JavaOS'            => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135
        'webOS'             => 'webOS|hpwOS',
        'badaOS'            => '\bBada\b',
        'BREWOS'            => 'BREW',
    ];
    /**
     * List of mobile User Agents.
     *
     * IMPORTANT: This is a list of mobile browsers only.
     * Since Mobile Detect 2.x.x, this list supports mobile browsers only.
     * Mobile Detect was never designed to detect all browsers.
     * @var array
     */
    protected static array $browsers = [
        //'Vivaldi'         => 'Vivaldi',
        // @reference: https://developers.google.com/chrome/mobile/docs/user-agent
        'Chrome'          => '\bCrMo\b|CriOS.*Mobile|Android.*Chrome/[.0-9]* Mobile',
        'Dolfin'          => '\bDolfin\b',
        'Opera'           => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+$|Coast/[0-9.]+',
        'Skyfire'         => 'Skyfire',
        // Added "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Edge'             => 'EdgiOS.*Mobile|Mobile Safari/[.0-9]* Edge',
        'IE'              => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+
        'Firefox'         => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS.*Mobile',
        'Bolt'            => 'bolt',
        'TeaShark'        => 'teashark',
        'Blazer'          => 'Blazer',
        // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3
        // Excluded "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Safari'          => 'Version((?!\bEdgiOS\b).)*Mobile.*Safari|Safari.*Mobile|MobileSafari',
        // http://en.wikipedia.org/wiki/Midori_(web_browser)
        //'Midori'          => 'midori',
        //'Tizen'           => 'Tizen',
        'WeChat'          => '\bMicroMessenger\b',
        'UCBrowser'       => 'UC.*Browser|UCWEB',
        'baiduboxapp'     => 'baiduboxapp',
        'baidubrowser'    => 'baidubrowser',
        // https://github.com/serbanghita/Mobile-Detect/issues/7
        'DiigoBrowser'    => 'DiigoBrowser',
        // http://www.puffinbrowser.com/index.php
        // https://github.com/serbanghita/Mobile-Detect/issues/752
        // 'Puffin'            => 'Puffin',
        // http://mercury-browser.com/index.html
        'Mercury'          => '\bMercury\b',
        // http://en.wikipedia.org/wiki/Obigo_Browser
        'ObigoBrowser' => 'Obigo',
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront' => 'NF-Browser',
        // @reference: http://en.wikipedia.org/wiki/Minimo
        // http://en.wikipedia.org/wiki/Vision_Mobile_Browser
        'GenericBrowser'  => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger',
        // @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser)
        'PaleMoon'        => 'Android.*PaleMoon|Mobile.*PaleMoon',
    ];
    /**
     * All possible HTTP headers that represent the
     * User-Agent string.
     * @var array
     */
    protected static array $knownUserAgentHttpHeaders = [
        // The default User-Agent string.
        'HTTP_USER_AGENT',
        // Header can occur on devices using Opera Mini.
        'HTTP_X_OPERAMINI_PHONE_UA',
        // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
        'HTTP_X_DEVICE_USER_AGENT',
        'HTTP_X_ORIGINAL_USER_AGENT',
        'HTTP_X_SKYFIRE_PHONE',
        'HTTP_X_BOLT_PHONE_UA',
        'HTTP_DEVICE_STOCK_UA',
        'HTTP_X_UCBROWSER_DEVICE_UA'
    ];
    /**
     * The individual segments that could exist in a User-Agent string. VER refers to the regular
     * expression defined in the constant self::VERSION_REGEX.
     * @var array
     */
    protected static array $properties = [
        // Build
        'Mobile'        => 'Mobile/[VER]',
        'Build'         => 'Build/[VER]',
        'Version'       => 'Version/[VER]',
        'VendorID'      => 'VendorID/[VER]',
        // Devices
        'iPad'          => 'iPad.*CPU[a-z ]+[VER]',
        'iPhone'        => 'iPhone.*CPU[a-z ]+[VER]',
        'iPod'          => 'iPod.*CPU[a-z ]+[VER]',
        //'BlackBerry'    => array('BlackBerry[VER]', 'BlackBerry [VER];'),
        'Kindle'        => 'Kindle/[VER]',
        // Browser
        'Chrome'        => ['Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'],
        'Coast'         => ['Coast/[VER]'],
        'Dolfin'        => 'Dolfin/[VER]',
        // @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
        'Firefox'       => ['Firefox/[VER]', 'FxiOS/[VER]'],
        'Fennec'        => 'Fennec/[VER]',
        // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
        // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx
        'Edge' => 'Edge/[VER]',
        'IE'      => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'],
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront'      => 'NetFront/[VER]',
        'NokiaBrowser'  => 'NokiaBrowser/[VER]',
        'Opera'         => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]'],
        'Opera Mini'    => 'Opera Mini/[VER]',
        'Opera Mobi'    => 'Version/[VER]',
        'UCBrowser'    => ['UCWEB[VER]', 'UC.*Browser/[VER]'],
        'MQQBrowser'    => 'MQQBrowser/[VER]',
        'MicroMessenger' => 'MicroMessenger/[VER]',
        'baiduboxapp'   => 'baiduboxapp/[VER]',
        'baidubrowser'  => 'baidubrowser/[VER]',
        'SamsungBrowser' => 'SamsungBrowser/[VER]',
        'Iron'          => 'Iron/[VER]',
        // @note: Safari 7534.48.3 is actually Version 5.1.
        // @note: On BlackBerry the Version is overwriten by the OS.
        'Safari'        => ['Version/[VER]', 'Safari/[VER]'],
        'Skyfire'       => 'Skyfire/[VER]',
        'Tizen'         => 'Tizen/[VER]',
        'Webkit'        => 'webkit[ /][VER]',
        'PaleMoon'         => 'PaleMoon/[VER]',
        'SailfishBrowser'  => 'SailfishBrowser/[VER]',
        // Engine
        'Gecko'         => 'Gecko/[VER]',
        'Trident'       => 'Trident/[VER]',
        'Presto'        => 'Presto/[VER]',
        'Goanna'           => 'Goanna/[VER]',
        // OS
        'iOS'              => ' \bi?OS\b [VER][ ;]{1}',
        'Android'          => 'Android [VER]',
        'Sailfish'         => 'Sailfish [VER]',
        'BlackBerry'       => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
        'BREW'             => 'BREW [VER]',
        'Java'             => 'Java/[VER]',
        // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx
        // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases
        'Windows Phone OS' => ['Windows Phone OS [VER]', 'Windows Phone [VER]'],
        'Windows Phone'    => 'Windows Phone [VER]',
        'Windows CE'       => 'Windows CE/[VER]',
        // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd
        'Windows NT'       => 'Windows NT [VER]',
        'Symbian'          => ['SymbianOS/[VER]', 'Symbian/[VER]'],
        'webOS'            => ['webOS/[VER]', 'hpwOS/[VER];'],
    ];
    /**
     * Construct an instance of this class.
     */
    public function __construct(
        Cache $cache = null,
        array $config = [],
    ) {
        // If no custom cache provided then use our own.
        $this->cache = $cache == null ? new Cache() : $cache;
        // Override config from user.
        $this->config = array_merge($this->config, $config);
        // Beware that if you use "autoInitOfHttpHeaders: false" and you forget to setUserAgent
        // to something other than a string, an MobileDetectException exception will be thrown.
        if ($this->config['autoInitOfHttpHeaders']) {
            $this->autoInitKnownHttpHeaders();
        }
    }
    /**
     * Get the current script version.
     * Used in demo.php file.
     *
     * @return string The version number in semantic version format.
     */
    public function getVersion(): string
    {
        return $this->VERSION;
    }
    /**
     * On startup Mobile Detect library will auto-initiate from the existing
     * HTTP headers extracted from $_SERVER.
     *
     * @return void
     */
    public function autoInitKnownHttpHeaders(): void
    {
        // Go through known HTTP headers that we care about.
        // See "4.1.18. Protocol-Specific Meta-Variables" of http://www.faqs.org/rfcs/rfc3875.html
        $knownHttpHeaders = array_merge(
            array_values(self::$knownUserAgentHttpHeaders),
            array_keys(self::$knownMobilePositiveHeaders),
            array_values(self::$knownCloudFrontHeaders)
        );
        // Did not iterate through global $_SERVER to find ['HTTP...'] header values
        // because it's very slow and on some servers it can have more than 50 worthless keys.
//        $httpHeaders = array_filter($_SERVER, function ($key) {
//            return str_starts_with($key, 'HTTP_');
//        }, ARRAY_FILTER_USE_KEY);
        $httpHeaders = [];
        foreach ($knownHttpHeaders as $headerName) {
            if (isset($_SERVER[$headerName])) {
                $httpHeaders[$headerName] = $_SERVER[$headerName];
            }
        }
        $this->setHttpHeaders($httpHeaders);
        // Set the User-Agent even if it's an empty string so that "autoInitOfHttpHeaders" doesn't throw an exception.
        // https://github.com/serbanghita/Mobile-Detect/issues/946#issuecomment-1885675939
        if (!$this->hasUserAgent()) {
            $this->setUserAgent("");
        }
    }
    /**
     * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
     *
     * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
     *                           the headers. The default null is left for backwards compatibility.
     */
    public function setHttpHeaders(array $httpHeaders = []): void
    {
        $this->httpHeaders = $httpHeaders;
        // Don't process any further if no actual HTTP headers were set.
        if (count($httpHeaders) === 0) {
            return;
        }
        // Setting new HTTP headers automatically resets the User-Agent.
        // Set current User-Agent from known User-Agent-like HTTP header(s).
        $userAgent = "";
        foreach ($this->getUaHttpHeaders() as $altHeader) {
            if (!empty($this->httpHeaders[$altHeader])) {
                $userAgent .= $this->httpHeaders[$altHeader] . " ";
            }
        }
        if (!empty($userAgent)) {
            $this->setUserAgent($userAgent);
        }
        // Override User-Agent string if 'Amazon Cloudfront' specific HTTP headers are present.
        if (
            $this->hasHttpHeader(self::$knownCloudFrontHeaders[0]) ||
            $this->hasHttpHeader(self::$knownCloudFrontHeaders[1])
        ) {
            $this->setUserAgent(self::$cloudFrontUA);
        }
    }
    /**
     * Retrieves the HTTP headers.
     *
     * @return array
     */
    public function getHttpHeaders(): array
    {
        return $this->httpHeaders;
    }
    public function hasHttpHeaders(): bool
    {
        return count($this->httpHeaders) > 0;
    }
    protected function hasHttpHeader(string $name): bool
    {
        return !empty($this->httpHeaders[$name]);
    }
    /**
     * Retrieves a particular header. If it doesn't exist, no exception/error is caused.
     * Simply null is returned.
     *
     * @param string $header The name of the header to retrieve. Can be HTTP compliant such as
     *                       "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
     *                       all-caps, HTTP_ prefixed, underscore separated awesomeness.
     *
     * @return string|null The value of the header.
     */
    public function getHttpHeader(string $header): ?string
    {
        // are we using PHP-flavored headers?
        if (!str_contains($header, '_')) {
            $header = str_replace('-', '_', $header);
            $header = strtoupper($header);
        }
        // test the alternate, too
        $altHeader = 'HTTP_' . $header;
        //Test both the regular and the HTTP_ prefix
        if (isset($this->httpHeaders[$header])) {
            return $this->httpHeaders[$header];
        } elseif (isset($this->httpHeaders[$altHeader])) {
            return $this->httpHeaders[$altHeader];
        }
        return null;
    }
    public function getMobileHeaders(): array
    {
        return static::$knownMobilePositiveHeaders;
    }
    /**
     * Get all possible HTTP headers that
     * can contain the User-Agent string.
     *
     * @return array List of HTTP headers.
     */
    public function getUaHttpHeaders(): array
    {
        return static::$knownUserAgentHttpHeaders;
    }
    /**
     * Retrieves the HTTP CloudFront headers
     * that trigger a mobile detection.
     *
     * @return array
     */
    public function getCloudFrontHttpHeaders(): array
    {
        return static::$knownCloudFrontHeaders;
    }
    /**
     * Prepare the User-Agent string for matching phase.
     *
     * @param string $userAgent The User-Agent string.
     * @return string
     */
    private function prepareUserAgent(string $userAgent): string
    {
        $userAgent = trim($userAgent);
        return substr($userAgent, 0, $this->config['maximumUserAgentLength']);
    }
    /**
     * Set the User-Agent to be used.
     *
     * @param string $userAgent The User-Agent string.
     * @return string
     */
    public function setUserAgent(string $userAgent): string
    {
        $preparedUserAgent = $this->prepareUserAgent($userAgent);
        return $this->userAgent = $preparedUserAgent;
    }
    /**
     * Retrieve the User-Agent.
     *
     * @return string|null The user agent if it's set.
     */
    public function getUserAgent(): ?string
    {
        return $this->userAgent;
    }
    public function hasUserAgent(): bool
    {
        return is_string($this->userAgent);
    }
    public function isUserAgentEmpty(): bool
    {
        return $this->hasUserAgent() && $this->userAgent === '';
    }
    public function getMatchingRegex(): ?string
    {
        return $this->matchingRegex;
    }
    public function getMatchesArray(): ?array
    {
        return $this->matchesArray;
    }
    /**
     * Retrieve the list of known phone devices.
     *
     * @return array List of phone devices.
     */
    public static function getPhoneDevices(): array
    {
        return static::$phoneDevices;
    }
    /**
     * Retrieve the list of known tablet devices.
     *
     * @return array List of tablet devices.
     */
    public static function getTabletDevices(): array
    {
        return static::$tabletDevices;
    }
    /**
     * Retrieve the list of known browsers. Specifically, the user agents.
     *
     * @return array List of browsers / user agents.
     */
    public static function getBrowsers(): array
    {
        return static::$browsers;
    }
    /**
     * Method gets the mobile detection rules.
     * This method is used for the magic methods $detect->is*().
     * Retrieve the current set of rules.
     *
     * @return array
     */
    public function getRules(): array
    {
        static $rules;
        if (!$rules) {
            $rules = array_merge(
                static::$browsers,
                static::$operatingSystems,
                static::$phoneDevices,
                static::$tabletDevices
            );
        }
        return $rules;
    }
    /**
     * Retrieve the list of mobile operating systems.
     *
     * @return array The list of mobile operating systems.
     */
    public static function getOperatingSystems(): array
    {
        return static::$operatingSystems;
    }
    /**
     * Check the HTTP headers for signs of mobile.
     * This is the fastest mobile check possible; it's used
     * inside isMobile() method.
     *
     * @return bool
     */
    public function checkHttpHeadersForMobile(): bool
    {
        foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) {
            if (isset($this->httpHeaders[$mobileHeader])) {
                if (isset($matchType['matches']) && is_array($matchType['matches'])) {
                    foreach ($matchType['matches'] as $_match) {
                        if (str_contains($this->httpHeaders[$mobileHeader], $_match)) {
                            return true;
                        }
                    }
                    return false;
                } else {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Magic overloading method.
     *
     * @method boolean is[...]()
     * @param string $name
     * @param array $arguments
     * @return bool
     * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
     * @throws \Exception
     * @throws InvalidArgumentException
     */
    public function __call(string $name, array $arguments)
    {
        // make sure the name starts with 'is', otherwise
        if (!str_starts_with($name, 'is')) {
            throw new BadMethodCallException("No such method exists: $name");
        }
        $ruleName = substr($name, 2);
        return $this->is($ruleName);
    }
    /**
     * Check if the device is mobile.
     * Returns true if any type of mobile device detected, including special ones
     * @return bool
     * @throws MobileDetectException
     */
    public function isMobile(): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No valid user-agent has been set.');
        }
        if ($this->isUserAgentEmpty()) {
            return false;
        }
        // Cache check.
        try {
            $cacheKey = $this->createCacheKey("mobile");
            $cacheItem = $this->cache->get($cacheKey);
            if (!is_null($cacheItem)) {
                return $cacheItem->get();
            }
            // Special case: Amazon CloudFront mobile viewer
            if (
                $this->getUserAgent() === self::$cloudFrontUA &&
                $this->getHttpHeader('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER') === 'true'
            ) {
                $this->cache->set($cacheKey, true);
                return true;
            }
            if ($this->hasHttpHeaders() && $this->checkHttpHeadersForMobile()) {
                $this->cache->set($cacheKey, true);
                return true;
            } else {
                $result = $this->matchUserAgentWithFirstFoundMatchingRule();
                $this->cache->set($cacheKey, $result);
                return $result;
            }
        } catch (CacheException $e) {
            throw new MobileDetectException("Cache problem in isMobile(): {$e->getMessage()}");
        }
    }
    /**
     * Check if the device is a tablet.
     * Return true if any type of tablet device is detected.
     * @return bool
     * @throws MobileDetectException
     */
    public function isTablet(): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No user-agent has been set.');
        }
        if ($this->isUserAgentEmpty()) {
            return false;
        }
        // Cache check.
        try {
            $cacheKey = $this->createCacheKey("tablet");
            $cacheItem = $this->cache->get($cacheKey);
            if (!is_null($cacheItem)) {
                return $cacheItem->get();
            }
            // Special case: Amazon CloudFront mobile viewer
            if (
                $this->getUserAgent() === self::$cloudFrontUA &&
                $this->getHttpHeader('HTTP_CLOUDFRONT_IS_TABLET_VIEWER') === 'true'
            ) {
                $this->cache->set($cacheKey, true);
                return true;
            }
            foreach (static::$tabletDevices as $_regex) {
                $regexString = $_regex;
                // "regex" is array of "strings"
                if (is_array($_regex)) {
                    $regexString = implode("|", $_regex);
                }
                if ($this->match($regexString, $this->getUserAgent())) {
                    $this->cache->set($cacheKey, true);
                    return true;
                }
//                if (is_array($_regex)) {
//                    foreach ($_regex as $regexString) {
//                        $result = $this->match($regexString, $this->getUserAgent());
//                        if ($result) {
//                            $this->cache->set($cacheKey, true);
//                            return true;
//                        }
//                    }
//                } else {
//                    // assume the regex is a "string"
//                    if ($this->match($_regex, $this->getUserAgent())) {
//                        $this->cache->set($cacheKey, true);
//                        return true;
//                    }
//                }
            }
            $this->cache->set($cacheKey, false);
            return false;
        } catch (CacheException $e) {
            throw new MobileDetectException("Cache problem in isTablet(): {$e->getMessage()}");
        }
    }
    /**
     * Checks if a rule (e.g. isIphone, isIOS, etc.) matches its regex against the User-Agent.
     *
     * @param string $ruleName
     * @return bool
     * @throws MobileDetectException
     */
    public function is(string $ruleName): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No user-agent has been set.');
        }
        if ($this->isUserAgentEmpty()) {
            return false;
        }
        // Cache check.
        try {
            $cacheKey = $this->createCacheKey($ruleName);
            $cacheItem = $this->cache->get($cacheKey);
            if (!is_null($cacheItem)) {
                return $cacheItem->get();
            }
            $result = $this->matchUserAgentWithRule($ruleName);
            // Cache save.
            $this->cache->set($cacheKey, $result);
            return $result;
        } catch (CacheException $e) {
            throw new MobileDetectException("Cache problem in is(): {$e->getMessage()}");
        }
    }
    /**
     * Some detection rules are relative (not standard),
     * because of the diversity of devices, vendors and
     * their conventions in representing the User-Agent or
     * the HTTP headers.
     *
     * This method will be used to check custom regexes against
     * the User-Agent string.
     *
     * @param string $regex
     * @param string $userAgent
     * @return bool
     *
     * @todo: search in the HTTP headers too.
     */
    public function match(string $regex, string $userAgent): bool
    {
        $match = (bool) preg_match(sprintf('#%s#is', $regex), $userAgent, $matches);
        // If positive match is found, store the results for debug.
        if ($match) {
            $this->matchingRegex = $regex;
            $this->matchesArray = $matches;
        }
        return $match;
    }
    /**
     * Find a detection rule that matches the current User-agent.
     * @return bool
     */
    protected function matchUserAgentWithFirstFoundMatchingRule(): bool
    {
        // Begin general search.
        foreach ($this->getRules() as $_regex) {
            if (empty($_regex)) {
                continue;
            }
            // regex is an array of "strings"
            if (is_array($_regex)) {
                foreach ($_regex as $regexString) {
                    if ($this->match($regexString, $this->getUserAgent())) {
                        return true;
                    }
                }
            } else {
                // assume regex is "string"
                if ($this->match($_regex, $this->getUserAgent())) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Search for a certain key in the rules array.
     * If the key is found then try to match the corresponding
     * regex against the User-Agent.
     *
     * @param string $ruleName
     * @return bool
     */
    protected function matchUserAgentWithRule(string $ruleName): bool
    {
        $result = false;
        // Make the keys lowercase, so we can match: isIphone(), isiPhone(), isiphone(), etc.
        $ruleName = strtolower($ruleName);
        // change the keys to lower case
        $_rules = array_change_key_case($this->getRules());
        if (false === empty($_rules[$ruleName])) {
            $regexString = $_rules[$ruleName];
            if (is_array($_rules[$ruleName])) {
                $regexString = implode("|", $_rules[$ruleName]);
            }
            $result = $this->match($regexString, $this->getUserAgent());
//            if (is_array($_rules[$ruleName])) {
//             foreach($_rules[$ruleName] as $ruleRegex) {
//                 $result = $this->match($ruleRegex, $this->getUserAgent());
//                 if ($result) {
//                     return true;
//                 }
//             }
//            } else {
//                $result = $this->match($_rules[$ruleName], $this->getUserAgent());
//            }
        }
        return $result;
    }
    /**
     * Prepare the version number.
     * @todo Remove the error suppression from str_replace() call.
     *
     * @param string $ver The string version, like "2.6.21.2152";
     * @return float
     */
    public function prepareVersionNo(string $ver): float
    {
        $ver = str_replace(array('_', ' ', '/'), '.', $ver);
        $arrVer = explode('.', $ver, 2);
        if (isset($arrVer[1])) {
            $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
        }
        return (float) implode('.', $arrVer);
    }
    /**
     * Check the version of the given property in the User-Agent.
     * Will return a float number. (e.g. 2_0 will return 2.0, 4.3.1 will return 4.31)
     *
     * @param string $propertyName The name of the property. See self::getProperties() array
     *                             keys for all possible properties.
     * @param string $type         Either self::VERSION_TYPE_STRING to get a string value or
     *                             self::VERSION_TYPE_FLOAT indicating a float value. This parameter
     *                             is optional and defaults to self::VERSION_TYPE_STRING. Passing an
     *                             invalid parameter will default to the type as well.
     *
     * @return string|float|false The version of the property we are trying to extract.
     */
    public function version(string $propertyName, string $type = self::VERSION_TYPE_STRING): float|bool|string
    {
        if (empty($propertyName) || !$this->hasUserAgent()) {
            return false;
        }
        // set the $type to the default if we don't recognize the type
        if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) {
            $type = self::VERSION_TYPE_STRING;
        }
        $properties = self::getProperties();
        // Check if the property exists in the properties array.
        if (true === isset($properties[$propertyName])) {
            // Prepare the pattern to be matched.
            // Make sure we always deal with an array (string is converted).
            $properties[$propertyName] = (array) $properties[$propertyName];
            foreach ($properties[$propertyName] as $propertyMatchString) {
                $propertyPattern = str_replace('[VER]', self::VERSION_REGEX, $propertyMatchString);
                // Identify and extract the version.
                preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);
                if (false === empty($match[1])) {
                    return ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);
                }
            }
        }
        return false;
    }
    public function getCache(): Cache
    {
        return $this->cache;
    }
    protected function createCacheKey(string $key): string
    {
        $userAgentKey = $this->hasUserAgent() ? $this->userAgent : '';
        $httpHeadersKey = $this->hasHttpHeaders() ? static::flattenHeaders($this->httpHeaders) : '';
        return base64_encode("$key:$userAgentKey:$httpHeadersKey");
    }
    public static function flattenHeaders(array $httpHeaders): string
    {
        $key = '';
        foreach ($httpHeaders as $name => $value) {
            $key .= "$name: $value" . PHP_EOL;
        }
        return trim($key);
    }
    /**
     * Get the properties array.
     *
     * @return array
     */
    public static function getProperties(): array
    {
        return static::$properties;
    }
}
<!-- ВЕРСИЯ 3.74.3-->
<?php
/**
 * Mobile Detect Library
 * Motto: "Every business should have a mobile detection script to detect mobile readers"
 *
 * Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
 * It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
 *
 * Homepage: http://mobiledetect.net
 * GitHub: https://github.com/serbanghita/Mobile-Detect
 * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
 * CONTRIBUTING: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/CONTRIBUTING.md
 * KNOWN LIMITATIONS: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/KNOWN_LIMITATIONS.md
 * EXAMPLES: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
 *
 * @license https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE
 * @author  Serban Ghita <serbanghita@gmail.com> (since 2012)
 * @author  Nick Ilyin <nick.ilyin@gmail.com>
 * @author: Victor Stanciu <vic.stanciu@gmail.com> (original author)
 *
 * @version 3.74.3
 */
namespace Detection;
use BadMethodCallException;
/**
 * Auto-generated isXXXX() magic methods.
 * php export/dump_magic_methods.php
 *
 * @method bool isiPhone()
 * @method bool isBlackBerry()
 * @method bool isPixel()
 * @method bool isHTC()
 * @method bool isNexus()
 * @method bool isDell()
 * @method bool isMotorola()
 * @method bool isSamsung()
 * @method bool isLG()
 * @method bool isSony()
 * @method bool isAsus()
 * @method bool isXiaomi()
 * @method bool isNokiaLumia()
 * @method bool isMicromax()
 * @method bool isPalm()
 * @method bool isVertu()
 * @method bool isPantech()
 * @method bool isFly()
 * @method bool isWiko()
 * @method bool isiMobile()
 * @method bool isSimValley()
 * @method bool isWolfgang()
 * @method bool isAlcatel()
 * @method bool isNintendo()
 * @method bool isAmoi()
 * @method bool isINQ()
 * @method bool isOnePlus()
 * @method bool isGenericPhone()
 * @method bool isiPad()
 * @method bool isNexusTablet()
 * @method bool isGoogleTablet()
 * @method bool isSamsungTablet()
 * @method bool isKindle()
 * @method bool isSurfaceTablet()
 * @method bool isHPTablet()
 * @method bool isAsusTablet()
 * @method bool isBlackBerryTablet()
 * @method bool isHTCtablet()
 * @method bool isMotorolaTablet()
 * @method bool isNookTablet()
 * @method bool isAcerTablet()
 * @method bool isToshibaTablet()
 * @method bool isLGTablet()
 * @method bool isFujitsuTablet()
 * @method bool isPrestigioTablet()
 * @method bool isLenovoTablet()
 * @method bool isDellTablet()
 * @method bool isYarvikTablet()
 * @method bool isMedionTablet()
 * @method bool isArnovaTablet()
 * @method bool isIntensoTablet()
 * @method bool isIRUTablet()
 * @method bool isMegafonTablet()
 * @method bool isEbodaTablet()
 * @method bool isAllViewTablet()
 * @method bool isArchosTablet()
 * @method bool isAinolTablet()
 * @method bool isNokiaLumiaTablet()
 * @method bool isSonyTablet()
 * @method bool isPhilipsTablet()
 * @method bool isCubeTablet()
 * @method bool isCobyTablet()
 * @method bool isMIDTablet()
 * @method bool isMSITablet()
 * @method bool isSMiTTablet()
 * @method bool isRockChipTablet()
 * @method bool isFlyTablet()
 * @method bool isbqTablet()
 * @method bool isHuaweiTablet()
 * @method bool isNecTablet()
 * @method bool isPantechTablet()
 * @method bool isBronchoTablet()
 * @method bool isVersusTablet()
 * @method bool isZyncTablet()
 * @method bool isPositivoTablet()
 * @method bool isNabiTablet()
 * @method bool isKoboTablet()
 * @method bool isDanewTablet()
 * @method bool isTexetTablet()
 * @method bool isPlaystationTablet()
 * @method bool isTrekstorTablet()
 * @method bool isPyleAudioTablet()
 * @method bool isAdvanTablet()
 * @method bool isDanyTechTablet()
 * @method bool isGalapadTablet()
 * @method bool isMicromaxTablet()
 * @method bool isKarbonnTablet()
 * @method bool isAllFineTablet()
 * @method bool isPROSCANTablet()
 * @method bool isYONESTablet()
 * @method bool isChangJiaTablet()
 * @method bool isGUTablet()
 * @method bool isPointOfViewTablet()
 * @method bool isOvermaxTablet()
 * @method bool isHCLTablet()
 * @method bool isDPSTablet()
 * @method bool isVistureTablet()
 * @method bool isCrestaTablet()
 * @method bool isMediatekTablet()
 * @method bool isConcordeTablet()
 * @method bool isGoCleverTablet()
 * @method bool isModecomTablet()
 * @method bool isVoninoTablet()
 * @method bool isECSTablet()
 * @method bool isStorexTablet()
 * @method bool isVodafoneTablet()
 * @method bool isEssentielBTablet()
 * @method bool isRossMoorTablet()
 * @method bool isiMobileTablet()
 * @method bool isTolinoTablet()
 * @method bool isAudioSonicTablet()
 * @method bool isAMPETablet()
 * @method bool isSkkTablet()
 * @method bool isTecnoTablet()
 * @method bool isJXDTablet()
 * @method bool isiJoyTablet()
 * @method bool isFX2Tablet()
 * @method bool isXoroTablet()
 * @method bool isViewsonicTablet()
 * @method bool isVerizonTablet()
 * @method bool isOdysTablet()
 * @method bool isCaptivaTablet()
 * @method bool isIconbitTablet()
 * @method bool isTeclastTablet()
 * @method bool isOndaTablet()
 * @method bool isJaytechTablet()
 * @method bool isBlaupunktTablet()
 * @method bool isDigmaTablet()
 * @method bool isEvolioTablet()
 * @method bool isLavaTablet()
 * @method bool isAocTablet()
 * @method bool isMpmanTablet()
 * @method bool isCelkonTablet()
 * @method bool isWolderTablet()
 * @method bool isMediacomTablet()
 * @method bool isMiTablet()
 * @method bool isNibiruTablet()
 * @method bool isNexoTablet()
 * @method bool isLeaderTablet()
 * @method bool isUbislateTablet()
 * @method bool isPocketBookTablet()
 * @method bool isKocasoTablet()
 * @method bool isHisenseTablet()
 * @method bool isHudl()
 * @method bool isTelstraTablet()
 * @method bool isGenericTablet()
 * @method bool isAndroidOS()
 * @method bool isBlackBerryOS()
 * @method bool isPalmOS()
 * @method bool isSymbianOS()
 * @method bool isWindowsMobileOS()
 * @method bool isWindowsPhoneOS()
 * @method bool isiOS()
 * @method bool isiPadOS()
 * @method bool isSailfishOS()
 * @method bool isMeeGoOS()
 * @method bool isMaemoOS()
 * @method bool isJavaOS()
 * @method bool iswebOS()
 * @method bool isbadaOS()
 * @method bool isBREWOS()
 * @method bool isChrome()
 * @method bool isDolfin()
 * @method bool isOpera()
 * @method bool isSkyfire()
 * @method bool isEdge()
 * @method bool isIE()
 * @method bool isFirefox()
 * @method bool isBolt()
 * @method bool isTeaShark()
 * @method bool isBlazer()
 * @method bool isSafari()
 * @method bool isWeChat()
 * @method bool isUCBrowser()
 * @method bool isbaiduboxapp()
 * @method bool isbaidubrowser()
 * @method bool isDiigoBrowser()
 * @method bool isMercury()
 * @method bool isObigoBrowser()
 * @method bool isNetFront()
 * @method bool isGenericBrowser()
 * @method bool isPaleMoon()
 * @method bool isWebKit()
 * @method bool isConsole()
 * @method bool isWatch()
 */
class MobileDetect
{
    /**
     * A frequently used regular expression to extract version #s.
     *
     * @deprecated since version 2.6.9
     */
    const VER                       = '([\w._\+]+)';
    /**
     * Stores the version number of the current release.
     */
    const VERSION                   = '3.74.3';
    /**
     * A type for the version() method indicating a string return value.
     */
    const VERSION_TYPE_STRING       = 'text';
    /**
     * A type for the version() method indicating a float return value.
     */
    const VERSION_TYPE_FLOAT        = 'float';
    /**
     * A cache for resolved matches
     * @var array
     */
    protected array $cache = [];
    /**
     * The User-Agent HTTP header is stored in here.
     * @var string|null
     */
    protected ?string $userAgent = null;
    /**
     * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
     * @var array
     */
    protected array $httpHeaders = [];
    /**
     * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer.
     * @var array
     */
    protected array $cloudfrontHeaders = [];
    /**
     * The matching Regex.
     * This is good for debug.
     * @var string|null
     */
    protected ?string $matchingRegex = null;
    /**
     * The matches extracted from the regex expression.
     * This is good for debug.
     *
     * @var array|null
     */
    protected ?array $matchesArray = null;
    /**
     * HTTP headers that trigger the 'isMobile' detection
     * to be true.
     *
     * @var array
     */
    protected static array $mobileHeaders = [
            'HTTP_ACCEPT'                  => [
                'matches' => [
                    // Opera Mini
                    // @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
                    'application/x-obml2d',
                    // BlackBerry devices.
                    'application/vnd.rim.html',
                    'text/vnd.wap.wml',
                    'application/vnd.wap.xhtml+xml'
                ]],
            'HTTP_X_WAP_PROFILE'           => null,
            'HTTP_X_WAP_CLIENTID'          => null,
            'HTTP_WAP_CONNECTION'          => null,
            'HTTP_PROFILE'                 => null,
            // Reported by Opera on Nokia devices (eg. C3).
            'HTTP_X_OPERAMINI_PHONE_UA'    => null,
            'HTTP_X_NOKIA_GATEWAY_ID'      => null,
            'HTTP_X_ORANGE_ID'             => null,
            'HTTP_X_VODAFONE_3GPDPCONTEXT' => null,
            'HTTP_X_HUAWEI_USERID'         => null,
            // Reported by Windows Smartphones.
            'HTTP_UA_OS'                   => null,
            // Reported by Verizon, Vodafone proxy system.
            'HTTP_X_MOBILE_GATEWAY'        => null,
            // Seen this on HTC Sensation. SensationXE_Beats_Z715e.
            'HTTP_X_ATT_DEVICEID'          => null,
            // Seen this on a HTC.
            'HTTP_UA_CPU'                  => ['matches' => ['ARM']],
    ];
    /**
     * List of mobile devices (phones).
     *
     * @var array
     */
    protected static array $phoneDevices = [
        'iPhone'        => '\biPhone\b|\biPod\b', // |\biTunes
        'BlackBerry'    => 'BlackBerry|\bBB10\b|rim[0-9]+|\b(BBA100|BBB100|BBD100|BBE100|BBF100|STH100)\b-[0-9]+',
        'Pixel'         => '; \bPixel\b',
        'HTC'           => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel',
        'Nexus'         => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 5X|Nexus 6',
        // @todo: Is 'Dell Streak' a tablet or a phone? ;)
        'Dell'          => 'Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b',
        'Motorola'      => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b|XT1068|XT1092|XT1052',
        'Samsung'       => '\bSamsung\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F|SM-J330F|SM-G610F|SM-G981B|SM-G892A|SM-A530F|SM-G988N|SM-G781B|SM-A805N|SM-G965F',
        'LG'            => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323|M257)|LM-G710',
        'Sony'          => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533|SOV34|601SO|F8332',
        'Asus'          => 'Asus.*Galaxy|PadFone.*Mobile|ASUS_Z01QD|ASUS_X00TD',
        'Xiaomi'        => '^(?!.*\bx11\b).*xiaomi.*$|POCOPHONE F1|\bMI\b 8|\bMi\b 10|Redmi Note 9S|Redmi 5A|Redmi Note 5A Prime|Redmi Note 7 Pro|N2G47H|M2001J2G|M2001J2I|M1805E10A|M2004J11G|M1902F1G|M2002J9G|M2004J19G|M2003J6A1G|M2012K11C|M2007J1SC',
        'NokiaLumia'    => 'Lumia [0-9]{3,4}',
        // http://www.micromaxinfo.com/mobiles/smartphones
        // Added because the codes might conflict with Acer Tablets.
        'Micromax'      => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b',
        // @todo Complete the regex.
        'Palm'          => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ;
        'Vertu'         => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;)
        // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH)
        // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android.
        'Pantech'       => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790',
        // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones.
        'Fly'           => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250',
        // http://fr.wikomobile.com
        'Wiko'          => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM',
        'iMobile'        => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)',
        // Added simvalley mobile just for fun. They have some interesting devices.
        // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html
        'SimValley'     => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b',
         // Wolfgang - a brand that is sold by Aldi supermarkets.
         // http://www.wolfgangmobile.com/
        'Wolfgang'      => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q',
        'Alcatel'       => 'Alcatel',
        'Nintendo'      => 'Nintendo (3DS|Switch)',
        // http://en.wikipedia.org/wiki/Amoi
        'Amoi'          => 'Amoi',
        // http://en.wikipedia.org/wiki/INQ
        'INQ'           => 'INQ',
        'OnePlus'       => 'ONEPLUS',
        // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039
        'GenericPhone'  => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser',
    ];
    /**
     * List of tablet devices.
     *
     * @var array
     */
    protected static array $tabletDevices = [
        // @todo: check for mobile friendly emails topic.
        'iPad'              => 'iPad|iPad.*Mobile',
        // Removed |^.*Android.*Nexus(?!(?:Mobile).)*$
        // @see #442
        // @todo Merge NexusTablet into GoogleTablet.
        'NexusTablet'       => 'Android.*Nexus[\s]+(7|9|10)',
        // https://en.wikipedia.org/wiki/Pixel_C
        'GoogleTablet'           => 'Android.*Pixel C',
        'SamsungTablet'     => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715|SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU|SM-T815Y|SM-T585|SM-T285|SM-T825|SM-W708|SM-T835|SM-T830|SM-T837V|SM-T720|SM-T510|SM-T387V|SM-P610|SM-T290|SM-T515|SM-T590|SM-T595|SM-T725|SM-T817P|SM-P585N0|SM-T395|SM-T295|SM-T865|SM-P610N|SM-P615|SM-T970|SM-T380|SM-T5950|SM-T905|SM-T231|SM-T500|SM-T860|SM-T536|SM-T837A|SM-X200|SM-T220|SM-T870|SM-X906C|SM-X700|SM-X706|SM-X706B|SM-X706U|SM-X706N|SM-X800|SM-X806|SM-X806B|SM-X806U|SM-X806N|SM-X900|SM-X906|SM-X906B|SM-X906U|SM-X906N|SM-P613', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone.
        // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
        'Kindle'            => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)',
        // Only the Surface tablets with Windows RT are considered mobile.
        // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
        'SurfaceTablet'     => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)',
        // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT
        'HPTablet'          => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10',
        // Watch out for PadFone, see #132.
        // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/
        'AsusTablet'        => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K01A | K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C|ME181C|P01Y|PO1MA|P01Z|\bP027\b|\bP024\b|\bP00C\b',
        'BlackBerryTablet'  => 'PlayBook|RIM Tablet',
        'HTCtablet'         => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410',
        'MotorolaTablet'    => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617',
        'NookTablet'        => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2',
        // http://www.acer.ro/ac/ro/RO/content/drivers
        // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer)
        // http://us.acer.com/ac/en/US/content/group/tablets
        // http://www.acer.de/ac/de/DE/content/models/tablets/
        // Can conflict with Micromax and Motorola phones codes.
        'AcerTablet'        => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30|A3-A40',
        // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/
        // http://us.toshiba.com/tablets/tablet-finder
        // http://www.toshiba.co.jp/regza/tablet/
        'ToshibaTablet'     => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO',
        // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html
        // http://www.lg.com/us/tablets
        'LGTablet'          => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b',
        'FujitsuTablet'     => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b',
        // Prestigio Tablets http://www.prestigio.com/support
        'PrestigioTablet'   => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD|PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002',
        // http://support.lenovo.com/en_GB/downloads/default.page?#
        'LenovoTablet'      => 'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)|TB-X103F|TB-X304X|TB-X304F|TB-X304L|TB-X505F|TB-X505L|TB-X505X|TB-X605F|TB-X605L|TB-8703F|TB-8703X|TB-8703N|TB-8704N|TB-8704F|TB-8704X|TB-8704V|TB-7304F|TB-7304I|TB-7304X|Tab2A7-10F|Tab2A7-20F|TB2-X30L|YT3-X50L|YT3-X50F|YT3-X50M|YT-X705F|YT-X703F|YT-X703L|YT-X705L|YT-X705X|TB2-X30F|TB2-X30L|TB2-X30M|A2107A-F|A2107A-H|TB3-730F|TB3-730M|TB3-730X|TB-7504F|TB-7504X|TB-X704F|TB-X104F|TB3-X70F|TB-X705F|TB-8504F|TB3-X70L|TB3-710F|TB-X704L|TB-J606F|TB-X606F|TB-X306X|YT-J706X|TB128FU',
        // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets
        'DellTablet'        => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7',
        'XiaomiTablet'      => '21051182G',
        // http://www.yarvik.com/en/matrix/tablets/
        'YarvikTablet'      => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b',
        'MedionTablet'      => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB',
        'ArnovaTablet'      => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2',
        // http://www.intenso.de/kategorie_en.php?kategorie=33
        // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate
        'IntensoTablet'     => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004',
        // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/
        'IRUTablet'         => 'M702pro',
        'MegafonTablet'     => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b',
        // http://www.e-boda.ro/tablete-pc.html
        'EbodaTablet'       => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)',
        // http://www.allview.ro/produse/droseries/lista-tablete-pc/
        'AllViewTablet'           => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)',
        // http://wiki.archosfans.com/index.php?title=Main_Page
        // @note Rewrite the regex format after we add more UAs.
        'ArchosTablet'      => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b',
        // http://www.ainol.com/plugin.php?identifier=ainol&module=product
        'AinolTablet'       => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark',
        'NokiaLumiaTablet'  => 'Lumia 2520',
        // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER
        // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser
        // http://www.sony.jp/support/tablet/
        'SonyTablet'        => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP641|SGP612|SOT31|SGP771|SGP611|SGP612|SGP712',
        // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8
        'PhilipsTablet'     => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b',
        // db + http://www.cube-tablet.com/buy-products.html
        'CubeTablet'        => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT',
        // http://www.cobyusa.com/?p=pcat&pcat_id=3001
        'CobyTablet'        => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010',
        // http://www.match.net.cn/products.asp
        'MIDTablet'         => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10',
        // http://www.msi.com/support
        // @todo Research the Windows Tablets.
        'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b',
        // @todo http://www.kyoceramobile.com/support/drivers/
    //    'KyoceraTablet' => null,
        // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/
    //    'IntextTablet' => null,
        // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets)
        // http://www.imp3.net/14/show.php?itemid=20454
        'SMiTTablet'        => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)',
        // http://www.rock-chips.com/index.php?do=prod&pid=2
        'RockChipTablet'    => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A',
        // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/
        'FlyTablet'         => 'IQ310|Fly Vision',
        // http://www.bqreaders.com/gb/tablets-prices-sale.html
        'bqTablet'          => 'Android.*(bq)?.*\b(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))\b|Maxwell.*Lite|Maxwell.*Plus',
        // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290
        // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets)
        'HuaweiTablet'      => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L|BAH-L09|BAH-W09|AGS-L09|CMR-AL19|KOB2-L09|BG2-U01|BG2-W09|BG2-U03',
        // Nec or Medias Tab
        'NecTablet'         => '\bN-06D|\bN-08D',
        // Pantech Tablets: http://www.pantechusa.com/phones/
        'PantechTablet'     => 'Pantech.*P4100',
        // Broncho Tablets: http://www.broncho.cn/ (hard to find)
        'BronchoTablet'     => 'Broncho.*(N701|N708|N802|a710)',
        // http://versusuk.com/support.html
        'VersusTablet'      => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b',
        // http://www.zync.in/index.php/our-products/tablet-phablets
        'ZyncTablet'        => 'z1000|Z99 2G|z930|z990|z909|Z919|z900', // Removed "z999" because of https://github.com/serbanghita/Mobile-Detect/issues/717
        // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/
        'PositivoTablet'    => 'TB07STA|TB10STA|TB07FTA|TB10FTA',
        // https://www.nabitablet.com/
        'NabiTablet'        => 'Android.*\bNabi',
        'KoboTablet'        => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build',
        // French Danew Tablets http://www.danew.com/produits-tablette.php
        'DanewTablet'       => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b',
        // Texet Tablets and Readers http://www.texet.ru/tablet/
        'TexetTablet'       => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE',
        // Avoid detecting 'PLAYSTATION 3' as mobile.
        'PlaystationTablet' => 'Playstation.*(Portable|Vita)',
        // http://www.trekstor.de/surftabs.html
        'TrekstorTablet'    => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab',
        // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets
        'PyleAudioTablet'   => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b',
        // http://www.advandigital.com/index.php?link=content-product&jns=JP001
        // because of the short codenames we have to include whitespaces to reduce the possible conflicts.
        'AdvanTablet'       => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ',
        // http://www.danytech.com/category/tablet-pc
        'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1',
        // http://www.galapad.net/product.html ; https://github.com/serbanghita/Mobile-Detect/issues/761
        'GalapadTablet'     => 'Android [0-9.]+; [a-z-]+; \bG1\b',
        // http://www.micromaxinfo.com/tablet/funbook
        'MicromaxTablet'    => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b',
        // http://www.karbonnmobiles.com/products_tablet.php
        'KarbonnTablet'     => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b',
        // http://www.myallfine.com/Products.asp
        'AllFineTablet'     => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide',
        // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr=
        'PROSCANTablet'     => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b',
        // http://www.yonesnav.com/products/products.php
        'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026',
        // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001
        // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html)
        'ChangJiaTablet'    => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503',
        // http://www.gloryunion.cn/products.asp
        // http://www.allwinnertech.com/en/apply/mobile.html
        // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB)
        // @todo: Softwiner tablets?
        // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions.
        'GUTablet'          => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G
        // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118
        'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10',
        // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/
        // @todo: add more tests.
        'OvermaxTablet'     => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027',
        // http://hclmetablet.com/India/index.php
        'HCLTablet'         => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync',
        // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html
        'DPSTablet'         => 'DPS Dream 9|DPS Dual 7',
        // http://www.visture.com/index.asp
        'VistureTablet'     => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10',
        // http://www.mijncresta.nl/tablet
        'CrestaTablet'     => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989',
        // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309
        'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b',
        // Concorde tab
        'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan',
        // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/
        'GoCleverTablet' => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042',
        // Modecom Tablets - http://www.modecom.eu/tablets/portal/
        'ModecomTablet' => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003',
        // Vonino Tablets
        'VoninoTablet'  => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b',
        // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0
        'ECSTablet'     => 'V07OT2|TM105A|S10OT1|TR10CS1',
        // Storex Tablets - http://storex.fr/espace_client/support.html
        // @note: no need to add all the tablet codes since they are guided by the first regex.
        'StorexTablet'  => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab',
        // Generic Vodafone tablets.
        'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497|VFD 1400',
        // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb
        // Aka: http://www.essentielb.fr/
        'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2',
        // Ross & Moor - http://ross-moor.ru/
        'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711',
        // i-mobile http://product.i-mobilephone.com/Mobile_Device
        'iMobileTablet'        => 'i-mobile i-note',
        // http://www.tolino.de/de/vergleichen/
        'TolinoTablet'  => 'tolino tab [0-9.]+|tolino shine',
        // AudioSonic - a Kmart brand
        // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72&currentPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1
        'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b',
        // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/
        // @todo: add them gradually to avoid conflicts.
        'AMPETablet' => 'Android.* A78 ',
        // Skk Mobile - http://skkmobile.com.ph/product_tablets.php
        'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)',
        // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1
        'TecnoTablet' => 'TECNO P9|TECNO DP8D',
        // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3
        'JXDTablet' => 'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b',
        // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/
        'iJoyTablet' => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)',
        // http://www.intracon.eu/tablet
        'FX2Tablet' => 'FX2 PAD7|FX2 PAD10',
        // http://www.xoro.de/produkte/
        // @note: Might be the same brand with 'Simply tablets'
        'XoroTablet'        => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151',
        // http://www1.viewsonic.com/products/computing/tablets/
        'ViewsonicTablet'   => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a',
        // https://www.verizonwireless.com/tablets/verizon/
        'VerizonTablet' => 'QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1',
        // http://www.odys.de/web/internet-tablet_en.html
        'OdysTablet'        => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10',
        // http://www.captiva-power.de/products.html#tablets-en
        'CaptivaTablet'     => 'CAPTIVA PAD',
        // IconBIT - http://www.iconbit.com/products/tablets/
        'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S',
        // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63
        'TeclastTablet' => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi',
        // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price
        'OndaTablet' => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+|V10 \b4G\b',
        'JaytechTablet'     => 'TPC-PA762',
        'BlaupunktTablet'   => 'Endeavour 800NG|Endeavour 1010',
        // http://www.digma.ru/support/download/
        // @todo: Ebooks also (if requested)
        'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b',
        // http://www.evolioshop.com/ro/tablete-pc.html
        // http://www.evolio.ro/support/downloads_static.html?cat=2
        // @todo: Research some more
        'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b',
        // @todo http://www.lavamobiles.com/tablets-data-cards
        'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b',
        // http://www.breezetablet.com/
        'AocTablet' => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712',
        // http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/
        'MpmanTablet' => 'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71|MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77|MP709|MID701|MID711|MID170|MPDC703|MPQC1010',
        // https://www.celkonmobiles.com/?_a=categoryphones&sid=2
        'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b',
        // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab
        'WolderTablet' => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b',
        'MediacomTablet' => 'M-MPI10C3G|M-SP10EG|M-SP10EGP|M-SP10HXAH|M-SP7HXAH|M-SP10HXBH|M-SP8HXAH|M-SP8MXA',
        // http://www.mi.com/en
        'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b',
        // http://www.nbru.cn/index.html
        'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One',
        // http://navroad.com/products/produkty/tablety/
        // http://navroad.com/products/produkty/tablety/
        'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI',
        // http://leader-online.com/new_site/product-category/tablets/
        // http://www.leader-online.net.au/List/Tablet
        'LeaderTablet' => 'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G|TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100',
        // http://www.datawind.com/ubislate/
        'UbislateTablet' => 'UbiSlate[\s]?7C',
        // http://www.pocketbook-int.com/ru/support
        'PocketBookTablet' => 'Pocketbook',
        // http://www.kocaso.com/product_tablet.html
        'KocasoTablet' => '\b(TB-1207)\b',
        // http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm
        'HisenseTablet' => '\b(F5281|E2371)\b',
        // http://www.tesco.com/direct/hudl/
        'Hudl'              => 'Hudl HT7S3|Hudl 2',
        // http://www.telstra.com.au/home-phone/thub-2/
        'TelstraTablet'     => 'T-Hub2',
        'GenericTablet'     => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b|WVT101|TM1088|KT107'
    ];
    /**
     * List of mobile Operating Systems.
     *
     * @var array
     */
    protected static array $operatingSystems = [
        'AndroidOS'         => 'Android',
        'BlackBerryOS'      => 'blackberry|\bBB10\b|rim tablet os',
        'PalmOS'            => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino',
        'SymbianOS'         => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b',
        // @reference: http://en.wikipedia.org/wiki/Windows_Mobile
        'WindowsMobileOS'   => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Windows Mobile|Windows Phone [0-9.]+|WCE;',
        // @reference: http://en.wikipedia.org/wiki/Windows_Phone
        // http://wifeng.cn/?r=blog&a=view&id=106
        // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx
        // http://msdn.microsoft.com/library/ms537503.aspx
        // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
        'WindowsPhoneOS'   => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;',
        'iOS'               => '\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia',
        // https://en.wikipedia.org/wiki/IPadOS
        'iPadOS' => 'CPU OS 13',
        // @reference https://en.m.wikipedia.org/wiki/Sailfish_OS
        // https://sailfishos.org/
        'SailfishOS'        => 'Sailfish',
        // http://en.wikipedia.org/wiki/MeeGo
        // @todo: research MeeGo in UAs
        'MeeGoOS'           => 'MeeGo',
        // http://en.wikipedia.org/wiki/Maemo
        // @todo: research Maemo in UAs
        'MaemoOS'           => 'Maemo',
        'JavaOS'            => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135
        'webOS'             => 'webOS|hpwOS',
        'badaOS'            => '\bBada\b',
        'BREWOS'            => 'BREW',
    ];
    /**
     * List of mobile User Agents.
     *
     * IMPORTANT: This is a list of only mobile browsers.
     * Mobile Detect 2.x supports only mobile browsers,
     * it was never designed to detect all browsers.
     * The change will come in 2017 in the 3.x release for PHP7.
     *
     * @var array
     */
    protected static array $browsers = [
        //'Vivaldi'         => 'Vivaldi',
        // @reference: https://developers.google.com/chrome/mobile/docs/user-agent
        'Chrome'          => '\bCrMo\b|CriOS.*Mobile|Android.*Chrome/[.0-9]* Mobile',
        'Dolfin'          => '\bDolfin\b',
        'Opera'           => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+$|Coast/[0-9.]+',
        'Skyfire'         => 'Skyfire',
        // Added "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Edge'             => 'EdgiOS.*Mobile|Mobile Safari/[.0-9]* Edge',
        'IE'              => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+
        'Firefox'         => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS.*Mobile',
        'Bolt'            => 'bolt',
        'TeaShark'        => 'teashark',
        'Blazer'          => 'Blazer',
        // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3
        // Excluded "Edge on iOS" https://github.com/serbanghita/Mobile-Detect/issues/764
        'Safari'          => 'Version((?!\bEdgiOS\b).)*Mobile.*Safari|Safari.*Mobile|MobileSafari',
        // http://en.wikipedia.org/wiki/Midori_(web_browser)
        //'Midori'          => 'midori',
        //'Tizen'           => 'Tizen',
        'WeChat'          => '\bMicroMessenger\b',
        'UCBrowser'       => 'UC.*Browser|UCWEB',
        'baiduboxapp'     => 'baiduboxapp',
        'baidubrowser'    => 'baidubrowser',
        // https://github.com/serbanghita/Mobile-Detect/issues/7
        'DiigoBrowser'    => 'DiigoBrowser',
        // http://www.puffinbrowser.com/index.php
        // https://github.com/serbanghita/Mobile-Detect/issues/752
        // 'Puffin'            => 'Puffin',
        // http://mercury-browser.com/index.html
        'Mercury'          => '\bMercury\b',
        // http://en.wikipedia.org/wiki/Obigo_Browser
        'ObigoBrowser' => 'Obigo',
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront' => 'NF-Browser',
        // @reference: http://en.wikipedia.org/wiki/Minimo
        // http://en.wikipedia.org/wiki/Vision_Mobile_Browser
        'GenericBrowser'  => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger',
        // @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser)
        'PaleMoon'        => 'Android.*PaleMoon|Mobile.*PaleMoon',
    ];
    /**
     * All possible HTTP headers that represent the
     * User-Agent string.
     *
     * @var array
     */
    protected static array $uaHttpHeaders = [
        // The default User-Agent string.
        'HTTP_USER_AGENT',
        // Header can occur on devices using Opera Mini.
        'HTTP_X_OPERAMINI_PHONE_UA',
        // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
        'HTTP_X_DEVICE_USER_AGENT',
        'HTTP_X_ORIGINAL_USER_AGENT',
        'HTTP_X_SKYFIRE_PHONE',
        'HTTP_X_BOLT_PHONE_UA',
        'HTTP_DEVICE_STOCK_UA',
        'HTTP_X_UCBROWSER_DEVICE_UA'
    ];
    /**
     * The individual segments that could exist in a User-Agent string. VER refers to the regular
     * expression defined in the constant self::VER.
     *
     * @var array
     */
    protected static array $properties = [
        // Build
        'Mobile'        => 'Mobile/[VER]',
        'Build'         => 'Build/[VER]',
        'Version'       => 'Version/[VER]',
        'VendorID'      => 'VendorID/[VER]',
        // Devices
        'iPad'          => 'iPad.*CPU[a-z ]+[VER]',
        'iPhone'        => 'iPhone.*CPU[a-z ]+[VER]',
        'iPod'          => 'iPod.*CPU[a-z ]+[VER]',
        //'BlackBerry'    => array('BlackBerry[VER]', 'BlackBerry [VER];'),
        'Kindle'        => 'Kindle/[VER]',
        // Browser
        'Chrome'        => ['Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'],
        'Coast'         => ['Coast/[VER]'],
        'Dolfin'        => 'Dolfin/[VER]',
        // @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
        'Firefox'       => ['Firefox/[VER]', 'FxiOS/[VER]'],
        'Fennec'        => 'Fennec/[VER]',
        // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
        // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx
        'Edge' => 'Edge/[VER]',
        'IE'      => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'],
        // http://en.wikipedia.org/wiki/NetFront
        'NetFront'      => 'NetFront/[VER]',
        'NokiaBrowser'  => 'NokiaBrowser/[VER]',
        'Opera'         => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]'],
        'Opera Mini'    => 'Opera Mini/[VER]',
        'Opera Mobi'    => 'Version/[VER]',
        'UCBrowser'    => ['UCWEB[VER]', 'UC.*Browser/[VER]'],
        'MQQBrowser'    => 'MQQBrowser/[VER]',
        'MicroMessenger' => 'MicroMessenger/[VER]',
        'baiduboxapp'   => 'baiduboxapp/[VER]',
        'baidubrowser'  => 'baidubrowser/[VER]',
        'SamsungBrowser' => 'SamsungBrowser/[VER]',
        'Iron'          => 'Iron/[VER]',
        // @note: Safari 7534.48.3 is actually Version 5.1.
        // @note: On BlackBerry the Version is overwriten by the OS.
        'Safari'        => ['Version/[VER]', 'Safari/[VER]'],
        'Skyfire'       => 'Skyfire/[VER]',
        'Tizen'         => 'Tizen/[VER]',
        'Webkit'        => 'webkit[ /][VER]',
        'PaleMoon'         => 'PaleMoon/[VER]',
        'SailfishBrowser'  => 'SailfishBrowser/[VER]',
        // Engine
        'Gecko'         => 'Gecko/[VER]',
        'Trident'       => 'Trident/[VER]',
        'Presto'        => 'Presto/[VER]',
        'Goanna'           => 'Goanna/[VER]',
        // OS
        'iOS'              => ' \bi?OS\b [VER][ ;]{1}',
        'Android'          => 'Android [VER]',
        'Sailfish'         => 'Sailfish [VER]',
        'BlackBerry'       => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
        'BREW'             => 'BREW [VER]',
        'Java'             => 'Java/[VER]',
        // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx
        // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases
        'Windows Phone OS' => ['Windows Phone OS [VER]', 'Windows Phone [VER]'],
        'Windows Phone'    => 'Windows Phone [VER]',
        'Windows CE'       => 'Windows CE/[VER]',
        // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd
        'Windows NT'       => 'Windows NT [VER]',
        'Symbian'          => ['SymbianOS/[VER]', 'Symbian/[VER]'],
        'webOS'            => ['webOS/[VER]', 'hpwOS/[VER];'],
    ];
    /**
     * Construct an instance of this class.
     *
     * @param array|null $headers Specify the headers as injection. Should be PHP _SERVER flavored.
     *                            If left empty, will use the global _SERVER['HTTP_*'] vars instead.
     * @param string|null $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT
     *                               from the $headers array instead.
     */
    public function __construct(array $headers = null, string $userAgent = null)
    {
        $this->setHttpHeaders($headers);
        $this->setUserAgent($userAgent);
    }
    /**
     * Get the current script version.
     * This is useful for the demo.php file,
     * so people can check on what version they are testing
     * for mobile devices.
     *
     * @return string The version number in semantic version format.
     */
    public static function getScriptVersion(): string
    {
        return self::VERSION;
    }
    /**
     * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
     *
     * @param array|null $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
     *                           the headers. The default null is left for backwards compatibility.
     */
    public function setHttpHeaders(array $httpHeaders = null)
    {
        // use global _SERVER if $httpHeaders aren't defined
        if (!is_array($httpHeaders) || !count($httpHeaders)) {
            $httpHeaders = $_SERVER;
        }
        // clear existing headers
        $this->httpHeaders = array();
        // Only save HTTP headers. In PHP land, that means only _SERVER vars that
        // start with HTTP_.
        foreach ($httpHeaders as $key => $value) {
            if (substr($key, 0, 5) === 'HTTP_') {
                $this->httpHeaders[$key] = $value;
            }
        }
        // In case we're dealing with CloudFront, we need to know.
        $this->setCfHeaders($httpHeaders);
    }
    /**
     * Retrieves the HTTP headers.
     *
     * @return array
     */
    public function getHttpHeaders(): array
    {
        return $this->httpHeaders;
    }
    /**
     * Retrieves a particular header. If it doesn't exist, no exception/error is caused.
     * Simply null is returned.
     *
     * @param string $header The name of the header to retrieve. Can be HTTP compliant such as
     *                       "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
     *                       all-caps, HTTP_ prefixed, underscore separated awesomeness.
     *
     * @return string|null The value of the header.
     */
    public function getHttpHeader(string $header): ?string
    {
        // are we using PHP-flavored headers?
        if (strpos($header, '_') === false) {
            $header = str_replace('-', '_', $header);
            $header = strtoupper($header);
        }
        // test the alternate, too
        $altHeader = 'HTTP_' . $header;
        //Test both the regular and the HTTP_ prefix
        if (isset($this->httpHeaders[$header])) {
            return $this->httpHeaders[$header];
        } elseif (isset($this->httpHeaders[$altHeader])) {
            return $this->httpHeaders[$altHeader];
        }
        return null;
    }
    public function getMobileHeaders(): array
    {
        return static::$mobileHeaders;
    }
    /**
     * Get all possible HTTP headers that
     * can contain the User-Agent string.
     *
     * @return array List of HTTP headers.
     */
    public function getUaHttpHeaders(): array
    {
        return static::$uaHttpHeaders;
    }

    /**
     * Set CloudFront headers
     * http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device
     *
     * @param array|null $cfHeaders List of HTTP headers
     *
     * @return bool If there were CloudFront headers to be set
     */
    public function setCfHeaders(array $cfHeaders = null): bool
    {
        // use global _SERVER if $cfHeaders aren't defined
        if (!is_array($cfHeaders) || !count($cfHeaders)) {
            $cfHeaders = $_SERVER;
        }
        // clear existing headers
        $this->cloudfrontHeaders = array();
        // Only save CLOUDFRONT headers. In PHP land, that means only _SERVER vars that
        // start with cloudfront-.
        $response = false;
        foreach ($cfHeaders as $key => $value) {
            if (substr(strtolower($key), 0, 16) === 'http_cloudfront_') {
                $this->cloudfrontHeaders[strtoupper($key)] = $value;
                $response = true;
            }
        }
        return $response;
    }
    /**
     * Retrieves the cloudfront headers.
     *
     * @return array
     */
    public function getCfHeaders(): array
    {
        return $this->cloudfrontHeaders;
    }
    /**
     * @param string $userAgent
     * @return string
     */
    private function prepareUserAgent(string $userAgent): string
    {
        $userAgent = trim($userAgent);
        return substr($userAgent, 0, 500);
    }
    /**
     * Set the User-Agent to be used.
     *
     * @param string|null $userAgent The user agent string to set.
     *
     * @return string|null
     */
    public function setUserAgent(string $userAgent = null): ?string
    {
        // Invalidate cache due to #375
        $this->cache = array();
        if (false === empty($userAgent)) {
            return $this->userAgent = $this->prepareUserAgent($userAgent);
        } else {
            $this->userAgent = null;
            foreach ($this->getUaHttpHeaders() as $altHeader) {
                // @todo: should use getHttpHeader(), but it would be slow. (Serban)
                if (false === empty($this->httpHeaders[$altHeader])) {
                    $this->userAgent .= $this->httpHeaders[$altHeader] . " ";
                }
            }
            if (!empty($this->userAgent)) {
                return $this->userAgent = $this->prepareUserAgent($this->userAgent);
            }
        }
        if (count($this->getCfHeaders()) > 0) {
            return $this->userAgent = 'Amazon CloudFront';
        }
        return $this->userAgent = null;
    }
    /**
     * Retrieve the User-Agent.
     *
     * @return string|null The user agent if it's set.
     */
    public function getUserAgent(): ?string
    {
        return $this->userAgent;
    }
    public function getMatchingRegex(): ?string
    {
        return $this->matchingRegex;
    }
    public function getMatchesArray(): ?array
    {
        return $this->matchesArray;
    }
    /**
     * Retrieve the list of known phone devices.
     *
     * @return array List of phone devices.
     */
    public static function getPhoneDevices(): array
    {
        return static::$phoneDevices;
    }
    /**
     * Retrieve the list of known tablet devices.
     *
     * @return array List of tablet devices.
     */
    public static function getTabletDevices(): array
    {
        return static::$tabletDevices;
    }
    /**
     * Alias for getBrowsers() method.
     *
     * @return array List of user agents.
     */
    public static function getUserAgents(): array
    {
        return static::getBrowsers();
    }
    /**
     * Retrieve the list of known browsers. Specifically, the user agents.
     *
     * @return array List of browsers / user agents.
     */
    public static function getBrowsers(): array
    {
        return static::$browsers;
    }
    /**
     * Method gets the mobile detection rules. This method is used for the magic methods $detect->is*().
     * Retrieve the current set of rules.
     *
     * @return array
     */
    public function getRules(): array
    {
        static $rules;
        if (!$rules) {
            $rules = array_merge(
                static::$phoneDevices,
                static::$tabletDevices,
                static::$operatingSystems,
                static::$browsers
            );
        }
        return $rules;
    }
    /**
     * Retrieve the list of mobile operating systems.
     *
     * @return array The list of mobile operating systems.
     */
    public static function getOperatingSystems(): array
    {
        return static::$operatingSystems;
    }
    /**
     * Check the HTTP headers for signs of mobile.
     * This is the fastest mobile check possible; it's used
     * inside isMobile() method.
     *
     * @return bool
     */
    public function checkHttpHeadersForMobile(): bool
    {
        foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) {
            if (isset($this->httpHeaders[$mobileHeader])) {
                if (isset($matchType['matches']) && is_array($matchType['matches'])) {
                    foreach ($matchType['matches'] as $_match) {
                        if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false) {
                            return true;
                        }
                    }
                    return false;
                } else {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Magic overloading method.
     *
     * @method boolean is[...]()
     * @param string $name
     * @param array $arguments
     * @return bool
     * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
     */
    public function __call(string $name, array $arguments)
    {
        // make sure the name starts with 'is', otherwise
        if (substr($name, 0, 2) !== 'is') {
            throw new BadMethodCallException("No such method exists: $name");
        }
        $key = substr($name, 2);
        return $this->matchUAAgainstKey($key);
    }
    /**
     * Find a detection rule that matches the current User-agent.
     *
     * @param string|null $userAgent deprecated
     * @return bool
     */
    protected function matchDetectionRulesAgainstUA(string $userAgent = null): bool
    {
        // Begin general search.
        foreach ($this->getRules() as $_regex) {
            if (empty($_regex)) {
                continue;
            }
            if ($this->match($_regex, $userAgent)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Search for a certain key in the rules array.
     * If the key is found then try to match the corresponding
     * regex against the User-Agent.
     *
     * @param string $key
     *
     * @return bool
     */
    protected function matchUAAgainstKey(string $key): bool
    {
        // Make the keys lowercase, so we can match: isIphone(), isiPhone(), isiphone(), etc.
        $key = strtolower($key);
        if (false === isset($this->cache[$key])) {
            // change the keys to lower case
            $_rules = array_change_key_case($this->getRules());
            if (false === empty($_rules[$key])) {
                $this->cache[$key] = $this->match($_rules[$key]);
            }
            if (false === isset($this->cache[$key])) {
                $this->cache[$key] = false;
            }
        }
        return $this->cache[$key];
    }
    /**
     * Check if the device is mobile.
     * Returns true if any type of mobile device detected, including special ones
     * @param string|null $userAgent  deprecated
     * @param array|null $httpHeaders deprecated
     * @return bool
     */
    public function isMobile(string $userAgent = null, array $httpHeaders = null): bool
    {
        if ($httpHeaders) {
            $this->setHttpHeaders($httpHeaders);
        }
        if ($userAgent) {
            $this->setUserAgent($userAgent);
        }
        // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
        if ($this->getUserAgent() === 'Amazon CloudFront') {
            $cfHeaders = $this->getCfHeaders();
            if (array_key_exists('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER', $cfHeaders) &&
                $cfHeaders['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER'] === 'true'
            ) {
                return true;
            }
        }
        if ($this->checkHttpHeadersForMobile()) {
            return true;
        } else {
            return $this->matchDetectionRulesAgainstUA();
        }
    }
    /**
     * Check if the device is a tablet.
     * Return true if any type of tablet device is detected.
     *
     * @param string|null $userAgent   deprecated
     * @param array|null $httpHeaders deprecated
     * @return bool
     */
    public function isTablet(string $userAgent = null, array $httpHeaders = null): bool
    {
        // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
        if ($this->getUserAgent() === 'Amazon CloudFront') {
            $cfHeaders = $this->getCfHeaders();
            if (array_key_exists('HTTP_CLOUDFRONT_IS_TABLET_VIEWER', $cfHeaders) &&
                $cfHeaders['HTTP_CLOUDFRONT_IS_TABLET_VIEWER'] === 'true'
            ) {
                return true;
            }
        }
        foreach (static::$tabletDevices as $_regex) {
            if ($this->match($_regex, $userAgent)) {
                return true;
            }
        }
        return false;
    }
    /**
     * This method checks for a certain property in the
     * userAgent.
     * @param  string        $key
     * @param string|null $userAgent   deprecated
     * @param array|null $httpHeaders deprecated
     * @return bool
     *
     * @todo: The httpHeaders part is not yet used.
     */
    public function is(string $key, string $userAgent = null, array $httpHeaders = null): bool
    {
        // Set the UA and HTTP headers only if needed (eg. batch mode).
        if ($httpHeaders) {
            $this->setHttpHeaders($httpHeaders);
        }
        if ($userAgent) {
            $this->setUserAgent($userAgent);
        }
        return $this->matchUAAgainstKey($key);
    }
    /**
     * Some detection rules are relative (not standard),
     * because of the diversity of devices, vendors and
     * their conventions in representing the User-Agent or
     * the HTTP headers.
     *
     * This method will be used to check custom regexes against
     * the User-Agent string.
     *
     * @param string $regex
     * @param string|null $userAgent
     * @return bool
     *
     * @todo: search in the HTTP headers too.
     */
    public function match(string $regex, string $userAgent = null): bool
    {
        if (!\is_string($userAgent) && !\is_string($this->userAgent)) {
            return false;
        }
        $match = (bool) preg_match(
            sprintf('#%s#is', $regex),
            (false === empty($userAgent) ? $userAgent : $this->userAgent),
            $matches
        );
        // If positive match is found, store the results for debug.
        if ($match) {
            $this->matchingRegex = $regex;
            $this->matchesArray = $matches;
        }
        return $match;
    }
    /**
     * Get the properties array.
     *
     * @return array
     */
    public static function getProperties(): array
    {
        return static::$properties;
    }
    /**
     * Prepare the version number.
     *
     * @param string $ver The string version, like "2.6.21.2152";
     *
     * @return float
     *
     * @todo Remove the error suppression from str_replace() call.
     */
    public function prepareVersionNo(string $ver): float
    {
        $ver = str_replace(array('_', ' ', '/'), '.', $ver);
        $arrVer = explode('.', $ver, 2);
        if (isset($arrVer[1])) {
            $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
        }
        return (float) implode('.', $arrVer);
    }
    /**
     * Check the version of the given property in the User-Agent.
     * Will return a float number. (e.g. 2_0 will return 2.0, 4.3.1 will return 4.31)
     *
     * @param string $propertyName The name of the property. See self::getProperties() array
     *                             keys for all possible properties.
     * @param string $type         Either self::VERSION_TYPE_STRING to get a string value or
     *                             self::VERSION_TYPE_FLOAT indicating a float value. This parameter
     *                             is optional and defaults to self::VERSION_TYPE_STRING. Passing an
     *                             invalid parameter will default to the type as well.
     *
     * @return string|float|false The version of the property we are trying to extract.
     */
    public function version(string $propertyName, string $type = self::VERSION_TYPE_STRING)
    {
        if (empty($propertyName)) {
            return false;
        }
        if (!\is_string($this->userAgent)) {
            return false;
        }
        // set the $type to the default if we don't recognize the type
        if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) {
            $type = self::VERSION_TYPE_STRING;
        }
        $properties = self::getProperties();
        // Check if the property exists in the properties array.
        if (true === isset($properties[$propertyName])) {
            // Prepare the pattern to be matched.
            // Make sure we always deal with an array (string is converted).
            $properties[$propertyName] = (array) $properties[$propertyName];
            foreach ($properties[$propertyName] as $propertyMatchString) {
                $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString);
                // Identify and extract the version.
                preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);
                if (false === empty($match[1])) {
                    return ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);
                }
            }
        }
        return false;
    }
}
php

Open Graph

#Вариант 1

Обязательно добавить в head префиксы для валидности
<head prefix=
    "og: http://ogp.me/ns#
     fb: http://ogp.me/ns/fb#  
     product: http://ogp.me/ns/product#">
Сам код OpenGraph
<?php
$bOpenGraph = FALSE;
// Open Graph
if (is_object(Core_Page::instance()->object))
{
    $bInformationItem = Core_Page::instance()->object instanceof Informationsystem_Controller_Show;
    $bShopItem = Core_Page::instance()->object instanceof Shop_Controller_Show;
   
    if ($bInformationItem || $bShopItem)
    {
        echo'<meta property="og:site_name" content="inikSite.ru">';    // ЗАМЕНИТЬ НА URL СВОЕГО САЙТА
        if (Core_Page::instance()->object->item)
        {
   $bOpenGraph = TRUE;
         
            $aOpenGraph = array();            
            $oEntity = $bInformationItem
                ? Core_Entity::factory('Informationsystem_Item', Core_Page::instance()->object->item)
                : Core_Entity::factory('Shop_Item', Core_Page::instance()->object->item);
            
            $type = $bInformationItem
                ? 'article'
                : 'product';
            
            $aOpenGraph['og:type'] = $type;
            $aOpenGraph['og:title'] =  $oEntity->name;
            $aOpenGraph['og:description'] = strip_tags(Core_Str::cutSentences($oEntity->description));


            $oSite = Core_Entity::factory('Site', CURRENT_SITE);
            $oSite_Alias = $oSite->getCurrentAlias();
        
            if ($oSite_Alias)
            {
                $sSiteURL = $oSite_Alias->name;
                
                $protocol = Core_Page::instance()->structure->https
                    ? 'https://'
                    : 'http://';
            
                $aOpenGraph['og:url'] = $protocol . $sSiteURL
                    . Core_Page::instance()->structure->getPath()
                    . $oEntity->getPath();
            
                if ($oEntity->image_large != '')
                {
                    $aOpenGraph['og:image'] = $protocol . $sSiteURL . $oEntity->getLargeFileHref();
                              $aOpenGraph['og:image:width'] = $oEntity->image_large_width;
                              $aOpenGraph['og:image:height'] = $oEntity->image_large_height;
                }
               
               if ($bShopItem)
               {
                  $aOpenGraph['product:price:amount'] = $oEntity->price;
                  $aOpenGraph['product:price:currency'] = $oEntity->shop_currency->code;
               }
                     
            }
            
            foreach ($aOpenGraph as $sProperty => $sContent)
            {
         ?><meta property="<?php echo htmlspecialchars($sProperty)?>" content="<?php echo htmlspecialchars($sContent)?>" /><?php
      
         echo PHP_EOL;
            }
      }
      elseif (Core_Page::instance()->object->group)
      {
         $bOpenGraph = TRUE;


            $aOpenGraph = array();
            
            $oEntity = $bInformationItem
                ? Core_Entity::factory('Informationsystem_Group', Core_Page::instance()->object->group)
                : Core_Entity::factory('Shop_Group', Core_Page::instance()->object->group);
            
            $type = $bInformationItem
                ? 'article'
                : 'website';
            
            $aOpenGraph['og:type'] = $type;
            $aOpenGraph['og:title'] = $oEntity->name;
            $aOpenGraph['og:description'] = $oEntity->seo_description;


            $oSite = Core_Entity::factory('Site', CURRENT_SITE);
            $oSite_Alias = $oSite->getCurrentAlias();
        
            if ($oSite_Alias)
            {
                $sSiteURL = $oSite_Alias->name;
                
                $protocol = Core_Page::instance()->structure->https
                    ? 'https://'
                    : 'http://';
            
                $aOpenGraph['og:url'] = $protocol . $sSiteURL
                    . Core_Page::instance()->structure->getPath()
                    . $oEntity->getPath();
            
                if ($oEntity->image_large != '')
                {
                    $aOpenGraph['og:image'] = $protocol . $sSiteURL . $oEntity->getLargeFileHref();
                }else{
                    $aOpenGraph['og:image'] = 'https://inikSite.ru/images/logo.jpg';   // ЗАМЕНИТЬ НА ПУТЬ К ЛОГОТИПУ СВОЕГО САЙТА
                }
            }
            
            foreach ($aOpenGraph as $sProperty => $sContent)
            {
         ?><meta property="<?php echo htmlspecialchars($sProperty)?>" content="<?php echo htmlspecialchars($sContent)?>" /><?php
      
         echo PHP_EOL;
         }
      }
   }
}
   
if (!$bOpenGraph && Core_Page::instance()->structure)
{
   $aOpenGraph = array();
   
   $oEntity = Core_Page::instance()->structure;
   
   $type = 'website';
   
   $aOpenGraph['og:type'] = $type;
   $aOpenGraph['og:title'] = $oEntity->Site->name;
   $aOpenGraph['og:description'] = strip_tags(Core_Str::cutSentences($oEntity->seo_description));


   $oSite = Core_Entity::factory('Site', CURRENT_SITE);
   $oSite_Alias = $oSite->getCurrentAlias();


   if ($oSite_Alias)
   {
      $sSiteURL = $oSite_Alias->name;
      
      $protocol = Core_Page::instance()->structure->https
         ? 'https://'
         : 'http://';
   
      $aOpenGraph['og:url'] = $protocol . $sSiteURL
         . $oEntity->getPath();
   
     $aOpenGraph['og:image'] = 'https://inikSite.ru/images/logo.jpg';   // ЗАМЕНИТЬ НА ПУТЬ К ЛОГОТИПУ СВОЕГО САЙТА
               
   }
   
   foreach ($aOpenGraph as $sProperty => $sContent)
   {
      ?><meta property="<?php echo htmlspecialchars($sProperty)?>" content="<?php echo htmlspecialchars($sContent)?>" /><?php


      echo PHP_EOL;
   }
}
?>

#Вариант 2

<?php
    // Open Graph
    if (is_object(Core_Page::instance()->object))
    {
        $bInformationItem = Core_Page::instance()->object instanceof Informationsystem_Controller_Show;
        $bShopItem = Core_Page::instance()->object instanceof Shop_Controller_Show;
        if ($bInformationItem || $bShopItem)
        {
            if (Core_Page::instance()->object->item)
            {
                $aOpenGraph = array();
                
                $oEntity = $bInformationItem
                    ? Core_Entity::factory('Informationsystem_Item', Core_Page::instance()->object->item)
                    : Core_Entity::factory('Shop_Item', Core_Page::instance()->object->item);
                
                $type = $bInformationItem
                    ? 'article'
                    : 'website';
                
                $aOpenGraph['og:type'] = $type;
                $aOpenGraph['og:title'] = $oEntity->name;
                $aOpenGraph['og:description'] = strip_tags(Core_Str::cutSentences($oEntity->description));
                                

                if ($oEntity->image_large != '')
                {
                    $oSite = Core_Entity::factory('Site', CURRENT_SITE);
                    $oSite_Alias = $oSite->getCurrentAlias();
                
                    if ($oSite_Alias)
                    {
                        $sSiteURL = $oSite_Alias->name;
                        
                        $protocol = Core_Page::instance()->structure->https
                            ? 'https://'
                            : 'http://';
                            
                        $aOpenGraph['og:image'] = $protocol . $sSiteURL . $oEntity->getLargeFileHref();
                    }
                }
                
                foreach ($aOpenGraph as $sProperty => $sContent)
                {
                    ?><meta property="<?php echo htmlspecialchars($sProperty)?>" content="<?php echo htmlspecialchars($sContent)?>" /><?php
                    echo PHP_EOL;
                }
            }
        }
    }
?>

PHP - ИНФОРМАЦИЯ

$GLOBALS — Ссылки на все переменные глобальной области видимости
Ассоциативный массив (array), содержащий ссылки на все переменные глобальной области видимости скрипта, определенные в данный момент. Имена переменных являются ключами массива.
Пример:
<?php
function test() {
    $foo = "local variable";
    echo '$foo in global scope: ' . $GLOBALS["foo"] . "\n";
    echo '$foo in current scope: ' . $foo . "\n";
}

$foo = "Example content";
test();
?>
$_REQUEST — Переменные HTTP-запроса
Ассоциативный массив (array), который по умолчанию содержит данные переменных $_GET, $_POST и $_COOKIE.
$_GET -- $HTTP_GET_VARS [deprecated] — GET-переменные HTTP
Ассоциативный массив параметров, переданных скрипту через URL.
$HTTP_GET_VARS содержит аналогичный набор данных, но не является суперглобальным. (Заметьте, что $HTTP_GET_VARS и $_GET являются разными переменными и обрабатываются PHP независимо друг от друга)
$_POST -- $HTTP_POST_VARS [deprecated] — HTTP POST variables
Ассоциативный массив данных, переданных скрипту через HTTP метод POST.
$HTTP_POST_VARS содержит аналогичный набор данных, но не является суперглобальным. ($HTTP_POST_VARS и $_POST являются разными переменными и обрабатываются PHP независимо друг от друга)
$_SESSION -- $HTTP_SESSION_VARS [устаревшее] — Переменные сессии
Ассоциативный массив, содержащий переменные сессии, которые доступны для текущего скрипта. Смотрите документацию по функциям сессии для получения дополнительной информации.
$HTTP_SESSION_VARS первоначально содержит ту же информацию, но она не является суперглобальной переменной. (Обратите внимание, что $HTTP_SESSION_VARS и $_SESSION являются различными переменными и в таковом качестве обрабатываются PHP).
php

UTM-метки скрипт на PHP

UTM-метки — это небольшие фрагменты кода, которые добавляются в URL-адреса ссылок или рекламных материалов для отслеживания эффективности рекламной кампании. Они позволяют веб-аналитикам получить информацию о том, откуда посетитель пришел на сайт, по каким источникам и рекламным кампаниям.

Но что, если вы хотите использовать UTM-метки в своем скрипте на языке php? В этой статье мы рассмотрим, как использовать и настроить UTM-метки в скрипте на языке php.

Начнем с основ — как добавить UTM-метки в URL-адрес ссылки. Вам понадобится изменить URL и добавить параметры UTM-меток. Например:

http://example.com/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale

В этом примере мы добавляем следующие UTM-метки: utm_source (источник), utm_medium (тип рекламного материала) и utm_campaign (название рекламной кампании). Каждая метка имеет свое значение, которое вы можете настроить в соответствии с вашими потребностями.

Далее, вы можете использовать эти UTM-метки в своем скрипте на языке php для отслеживания и анализа данных. Например, вы можете получить значение определенной UTM-метки с помощью глобального массива $_GET. Например:

$utm_source = $_GET[‘utm_source’];

Теперь вы можете использовать это значение для анализа данных и определения эффективности рекламной кампании. Вы также можете настроить свои собственные функции и скрипты, чтобы автоматически обрабатывать и анализировать данные UTM-меток.

Таким образом, использование и настройка UTM-меток в скрипте на языке php является важным инструментом для отслеживания и анализа эффективности ваших рекламных кампаний. Они позволяют вам получить ценные данные о посетителях и максимально эффективно использовать свои рекламные ресурсы.


UTM-метки используются веб-аналитическими системами, такими как Google Analytics, для сбора данных о поведении пользователей. Они состоят из нескольких параметров, которые добавляются к конечной части URL-адреса. Эти параметры обычно включают:


utm_source   -  Определяет источник трафика (например, google, newsletter)
utm_medium  -  Определяет тип маркетингового канала (например, cpc, email)
utm_campaign  -  Определяет название кампании или акции
utm_term  -  Определяет ключевое слово или ключевую фразу в рекламной кампании
utm_content  -  Определяет конкретное объявление или ссылку, которая привлекла посетителя


Когда посетитель переходит по URL-адресу с UTM-метками, система аналитики записывает эти метки и отображает соответствующую информацию в отчетах. Это позволяет более точно определить эффективность каждого маркетингового канала и принять меры для оптимизации кампании.

Чтобы использовать UTM-метки, вы можете создать специальные ссылки с метками в рекламных объявлениях, электронных письмах или на любых других источниках трафика. Важно следить за правильностью написания и форматирования меток, чтобы они успешно передавали информацию системам аналитики.

Функции UTM-меток в скрипте на php
Ниже несколько полезных функций UTM-меток, которые можно использовать в скрипте на php.

1. Получение значения UTM-метки по имени:

Если вам нужно получить значение определенной UTM-метки, вы можете использовать функцию $_GET. Ниже приведен пример кода, который позволяет получить значение UTM-метки «utm_source»:


if(isset($_GET['utm_source'])){
utm_source = $_GET['utm_source'];
// Дальнейшая обработка значения UTM-метки...
}

2. Проверка наличия UTM-меток:

Если вы хотите проверить, содержит ли URL-адрес UTM-метки, вы можете использовать функцию isset(). В следующем примере кода проверяется наличие меток «utm_source» и «utm_medium»:

if(isset($_GET['utm_source']) && isset($_GET['utm_medium'])){
// В URL-адресе присутствуют UTM-метки "utm_source" и "utm_medium"
// Выполнение соответствующих действий...
}

3. Генерация URL-адресов с UTM-метками:

Вы также можете использовать функцию http_build_query() для генерации URL-адресов, содержащих UTM-метки. В следующем примере кода показано, как сгенерировать URL-адрес со значениями «utm_source» и «utm_medium»:

$utm_params = array(
'utm_source' => 'google',
'utm_medium' => 'cpc'
);
url = 'http://example.com/?' . http_build_query($utm_params);
echo $url;

Этот код выведет следующий URL-адрес: http://example.com/?utm_source=google&utm_medium=cpc

Использование этих функций UTM-меток в скрипте на php поможет вам улучшить отслеживание и анализ эффективности маркетинговых кампаний. Убедитесь, что вы правильно настраиваете и обрабатываете UTM-метки для получения максимальной информации о пользователях и источниках трафика.

Преимущества использования UTM-меток для аналитики сайта

  • 1. Полная информация о источнике трафика.
    UTM-метки позволяют узнать, откуда пришли посетители на ваш сайт: из поисковиков, рекламных кампаний, социальных сетей или других источников.
    Зная источник трафика, вы можете определить, какие источники приносят больше посетителей и являются более эффективными для вашего бизнеса.
  • 2. Оценка эффективности рекламных кампаний.
    UTM-метки помогают отследить, какие рекламные кампании или объявления привлекают больше посетителей на ваш сайт.
    Вы можете сравнить эффективность разных рекламных кампаний и принять решение о том, какую рекламу стоит продолжать и оптимизировать.
  • 3. Создание понятных отчетов.
    UTM-метки позволяют создать понятные отчеты об источниках трафика и эффективности рекламных кампаний.
    Вы можете легко отследить, какие каналы приводят больше посетителей и какая реклама приводит к большему количеству конверсий.
  • 4. Улучшение качества аналитики.
    UTM-метки позволяют более точно определить конверсии и отслеживать их до источника трафика.
    Вы можете узнать, какие конкретные источники приводят к продажам или другим целевым действиям на вашем сайте.
  • 5. Повышение эффективности маркетинговых кампаний.
    Благодаря UTM-меткам вы можете оптимизировать свои маркетинговые кампании и привлекать больше качественного трафика.
    Зная, какие источники приводят к наиболее высокому проценту конверсий, вы можете перераспределить свой бюджет и сосредоточиться на самых эффективных источниках.
  • 6. Максимальная гибкость настроек.
    UTM-метки позволяют настроить максимально гибкий отчет по различным параметрам: источникам трафика, кампаниям, ключевым словам и т. д.
    Вы можете настроить сегментацию по UTM-меткам и получать более детальную информацию о своих посетителях и действиях на сайте.
  • 7. Лучшая ориентация на целевую аудиторию.
    Благодаря UTM-меткам вы можете более точно определить свою целевую аудиторию и создавать более персонализированные маркетинговые кампании.
    Вы можете анализировать, какие источники привлекают тех посетителей, которые наиболее вероятно станут вашими клиентами.
    В целом, использование UTM-меток позволяет более точно анализировать и измерять эффективность маркетинговых кампаний, оптимизировать бюджет и повысить уровень конверсии на вашем сайте.

Как правильно настроить UTM-метки в скрипте на php

Чтобы правильно настроить UTM-метки в скрипте на php, вам понадобится знать несколько ключевых моментов:
  • 1. Формирование ссылок с UTM-метками.
    Создайте шаблон ссылки, в которой будут содержаться UTM-метки (например, http://www.example.com/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale). Замените значения параметров utm_source, utm_medium и utm_campaign на соответствующие маркетинговые каналы и кампании.
  • 2. Парсинг и сохранение UTM-меток.
    В вашем скрипте на php создайте код для парсинга и сохранения UTM-меток из URL-адреса. Используйте функцию parse_url() для разбора URL-адреса и функцию parse_str() для извлечения значений параметров UTM-меток.
    $url = $_SERVER['REQUEST_URI'];
    $parsed_url = parse_url($url);
    if(isset($parsed_url['query'])){
    parse_str($parsed_url['query'], $query_params);
    $utm_source = isset($query_params['utm_source']) ? $query_params['utm_source'] : '';
    $utm_medium = isset($query_params['utm_medium']) ? $query_params['utm_medium'] : '';
    $utm_campaign = isset($query_params['utm_campaign']) ? $query_params['utm_campaign'] : '';
    // Сохраните значения UTM-меток в базу данных или переменные для последующей обработки.
    }
  • 3. Использование UTM-меток для анализа.
    Полученные UTM-метки можно использовать для анализа эффективности маркетинговых кампаний. Сохраните значения меток в базу данных или используйте их напрямую для отображения в аналитических отчетах. Например, вы можете получить общую информацию о том, сколько посетителей пришло из каждого маркетингового канала и какие из них привлекли больше покупателей.
    Уникальные метки помогут вам определить наиболее эффективные и доходные маркетинговые каналы или кампании, а также улучшить вашу маркетинговую стратегию.
Важно помнить, что использование UTM-меток требует внесения изменений в ваши ссылки и скрипты, поэтому убедитесь, что они работают корректно на вашем сайте. Также рекомендуется регулярно проверять и анализировать данные, полученные с помощью UTM-меток, чтобы оптимизировать ваши маркетинговые усилия и достичь максимально возможных результатов.

Примеры использования UTM-меток и рекомендации по их применению

Ниже приведены примеры использования UTM-меток и рекомендации по их применению:
  • 1. Отслеживание объявлений в поисковых системах:
    Рекомендуется использовать UTM-метки для отслеживания рекламных объявлений и ключевых слов в поисковых системах, например, Google Ads или Яндекс.Директ. При создании объявления вы можете добавить UTM-метки в URL-адрес ссылки, чтобы получить информацию о рекламе, ключевых словах и кампании.
  • 2. Отслеживание рекламы в социальных сетях:
    При проведении рекламных кампаний в социальных сетях, таких как Facebook, Instagram или ВКонтакте, рекомендуется использовать UTM-метки для отслеживания эффективности рекламных материалов, аудитории и кампании. Добавление UTM-меток в URL-адресы ссылок позволяет получить подробную информацию о том, как пользователи переходят на ваш сайт через рекламу в социальных сетях.
  • 3. Отслеживание электронной рассылки:
    Для отслеживания того, какие ссылки в электронной рассылке приводят к посещениям вашего сайта, рекомендуется использовать UTM-метки. Добавление UTM-меток в URL-адреса ссылок в письмах позволяет узнать, какие рекламные акции или сообщения в рассылке привлекают пользователей.
  • 4. Отслеживание партнерского трафика:
    Если вы работаете с партнерами или аффилиатами, UTM-метки могут быть полезными для отслеживания трафика, генерируемого ими. Вы можете создать уникальные UTM-метки для каждого партнера или рекламной площадки и отследить, сколько посетителей и продаж поступает от них.
  • 5. Рекомендации по применению UTM-меток:
    — Используйте согласованную систему именования UTM-меток для удобства анализа данных.
    — Не добавляйте слишком много UTM-меток, так как это может затруднить анализ данных и снизить производительность вашего сайта.
    — Используйте уникальные UTM-метки для каждого источника трафика, чтобы более точно отслеживать его эффективность.
    — Следите за свежестью UTM-меток: регулярно обновляйте их, чтобы избежать удаления и ошибочного отображения данных.
    Успешное использование UTM-меток позволит вам получить детальные данные о том, как и откуда приходит трафик на ваш веб-сайт, и принимать более обоснованные решения в области маркетинга и рекламы.

Авторизация Пользователя сайта на любой странице

Делаем Блок авторизации на сайте без перехода в Личный кабинет.

В шапку Основного Макета сайта добавляем код, который будет показывать кнопку с иконкой для открытия формы авторизации

<?php
               // Если модуль пользователей сайта доступен
               if (Core::moduleIsActive('siteuser'))
               {
                    if (is_null(Core_Entity::factory('Siteuser')->getCurrent()))
                    {
                         ?>
                             <button type="button" class="site_user me-3">
                             <span class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="17px"><path style="fill:#444" d="M313.6 288c-28.7 0-42.5 16-89.6 16-47.1 0-60.8-16-89.6-16C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4zM416 464c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16v-41.6C32 365.9 77.9 320 134.4 320c19.6 0 39.1 16 89.6 16 50.4 0 70-16 89.6-16 56.5 0 102.4 45.9 102.4 102.4V464zM224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm0-224c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z"/></svg></span>
                             <span class="small">Войти</span>
                            </button>
                         
                            <div id="site_user_autorisation" class="d-none">
                            <form method="post" action="/users/">
                              <div><input type="text" name="login" id="login" class="form-control mb-2" placeholder="Логин" /></div>
                              <div><input type="password" name="password" id="password" class="form-control mb-2" placeholder="Пароль" /></div>
                              <div>
                                  <button type="submit" name="apply" class="btn btn-primary btn-sm">Авторизоваться</button>
                              </div>
                              <a href="/users/registration/" class="small"> +Зарегистироваться</a>
                            </form>
                            </div>
                            <div id="site_user_autorisation" class="d-none">
                             
                            </div>
                         <?php
                    }
                    else
                    {?>
                        <a class="site_user" href="/users/">
                             <span class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="18px"><path d="M313.6 288c-28.7 0-42.5 16-89.6 16-47.1 0-60.8-16-89.6-16C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4zM416 464c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16v-41.6C32 365.9 77.9 320 134.4 320c19.6 0 39.1 16 89.6 16 50.4 0 70-16 89.6-16 56.5 0 102.4 45.9 102.4 102.4V464zM224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm0-224c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z"/></svg></span>
                             <span class="small">Кабинет</span>
                         </a>
                    <?php
                    }
                 }  
               ?>

В ТДС "ПользователиСайта" добавляем код

// ADD АВТОРИЗАЦИЯ ВО ВСПЛЫВАЮЩЕМ ОКНЕ
// Ajax Авторизация по логину и паролю
if (Core_Array::getRequest('ajaxapply')){
$oSiteuser = $oSiteuser->Site->Siteusers->getByLoginAndPassword(
strval(Core_Array::getPost('login')), strval(Core_Array::getPost('password'))
);
if (!is_null($oSiteuser)){
if ($oSiteuser->active){
$expires = Core_Array::getPost('remember')
? 2678400 // 31 день
: 86400; // 1 день
$oSiteuser->setCurrent($expires);
}
else{
$sError = 'Пользователь не активирован!';
}
}
else{
$sError = 'Введите корректный логин и пароль!';
}
$aResult = array();
if (!empty($sError)){
$aResult['error'] = $sError;
}
else{
$aResult['success'] = 1;
$aResult['userName'] = $oSiteuser->login;
}
echo json_encode($aResult);
exit();
}
//~END ADD
Пишем код jQuery, который будет у нас обрабатывать запрос на авторизацию с помощью ajax
$(function() {
$('#site_user_autorisation [type="submit"]').on('click', function(){
var form = $(this).parents('form:eq(0)');
form.parents('div:eq(0)').find('.alert-danger').remove();
$.loadingScreen('show');


$.ajax({
url : form.attr('action') + '?ajaxapply=1',
type : 'POST',
data : form.serialize(),
dataType : 'json',
success : function (ajaxData) {
if (ajaxData.success == 1){
form.parents('.fancybox-skin:eq(0)').find('.fancybox-close').click();
$('.register').hide();
$('#authorization').empty().append($('<a>', {href:form.attr('action'), class:'fancy'}).text(' Здравствуйте, '+ajaxData.userName));
}
else{
if (ajaxData.error != undefined){
form.before($('<div>', {class: 'alert alert-danger', role:'alert'}).html(ajaxData.error));
}
}
$.loadingScreen('hide');
},
error : function (){$.loadingScreen('hide');return false}
});
return false;
});
});
php,

Выводим свойство структуры в макете

К примеру, нам нужно вывести в макете страницы текущей Структуры дополнительное свойство с ID 33
 <?php
                        $oProperty = Core_Entity::factory('Property', 33); // ID св-ва
                        $aPropertyValues = $oProperty->getValues(CURRENT_STRUCTURE_ID);
                        if (isset($aPropertyValues[0]))
                            {
                              echo $aPropertyValues[0]->value;
                            }
                        ?>
php

Где управлять разрешенными для загрузки типами файлов?

Открываем фал  \modules\core\config\config.php

Находим строчку:
'availableExtension' => array ('JPG', 'JPEG', 'GIF', 'PNG', 'PDF', 'ZIP', 'DOC'),

и меняем на эту: 

'availableExtension' => array ('JPG', 'JPEG', 'GIF', 'PNG', 'PDF', 'ZIP', 'TXT', 'CSV', 'DOC', 'DOCX', 'RTF', 'PDF', 'PPT', 'PPTX', 'XLS', 'XLSX')
php

Для показа групп товаров при переносе групп и товаров в админке сайта

Для показа групп товаров при переносе групп и товаров в админке сайта, в файле \modules\core\config\config.php укажите параметр
'switchSelectToAutocomplete' => 100,
вместо 100 укажите значительно большее число, при этом значении выбор папок будет заменяться на автокомплит
php

Информационный блок в карточке товара

Добавляем в карточку товара блок с призывом задать вопрос

1. В XSL шаблон вывода карточки товара в нужном месте размещаем данный код: 

<div class="ask-question">
            <div class="d-flex justify-content-center">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="40px" height="40px">
                <path style="fill:#ccc" d="M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 7.1 5.8 12 12 12 2.4 0 4.9-.7 7.1-2.4L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64zm32 352c0 17.6-14.4 32-32 32H293.3l-8.5 6.4L192 460v-76H64c-17.6 0-32-14.4-32-32V64c0-17.6 14.4-32 32-32h384c17.6 0 32 14.4 32 32v288zm-224-88c-13.3 0-24 10.7-24 24s10.7 24 24 24 24-10.7 24-24-10.7-24-24-24zm-8.5-24h17c4.2 0 7.7-3.3 8-7.5l7-112c.3-4.6-3.4-8.5-8-8.5h-31c-4.6 0-8.3 3.9-8 8.5l7 112c.3 4.2 3.8 7.5 8 7.5z"></path>
              </svg>
              <span class="ps-3 fs-4 fw-bold title">Есть вопросы?</span>
            </div>
            <div class="text">
						Если у вас есть вопросы по товару, то задавайте их и мы вам с радостью ответим.
					</div>
            <div class="last">
              <button type="button" id="questionModalOpen" data-bs-toggle="modal" data-bs-target="#questionModal" class="btn btn-transparent">Задать вопрос о товаре</button>
            </div>
          </div>

2. В макет добавляем код загрузки формы (в примере код формы с отправкой уведомления Пользователю). Форма будет в модальном окне. Нажатие на кнопку "Задать вопрос" вызовет открытие формы.

<?php
        // ФОРМА ЗАЯВКИ
        $oForm = Core_Entity::factory('Form', 1);  
        $Form_Controller_Show = new Form_Controller_Show($oForm);
       if (!is_null(Core_Array::getPost($oForm->button_name)))
          {
         $Form_Controller_Show
         ->values($_POST + $_FILES)
         // 0 - html, 1- plain text
         ->mailType(0)
         ->mailXsl(
            Core_Entity::factory('Xsl')->getByName('ПисьмоКураторуФормыВФорматеHTML')
         )
         ->mailFromFieldName('euronasos19@gmail.com')
         ->process();

        // Отправляем письмо-подтверждение пользователю
        $Form_Controller_Show1 = clone $Form_Controller_Show;
        $sEmail = Core_Array::get($Form_Controller_Show1->values, 'email');  // здесь указать название поля input для email в форме
        if (Core_Valid::email($sEmail)){
            ob_start();
            $Form_Controller_Show1
                ->xsl(
                    Core_Entity::factory('Xsl')->getByName('ПисьмоПодтверждениеПользователюФормыВФорматеHTML')
                )
                ->show();
            $sMailText = ob_get_clean();
            if (mb_strpos($sMailText, 'ERROR TRUE') === FALSE){
                $subject = 'На сайте site.ru Вами была заполнена форма Заявки';// Тему письма отредактировать
                $oCore_Mail = Core_Mail::instance()
                    ->to($sEmail)
                    ->from('info@site.ru')
                    ->subject($subject)
                    ->message(trim($sMailText))
                    ->contentType('text/html')
                    ->header('X-HostCMS-Reason', 'Form');

                $oCore_Mail->send();
                      }
                  }
              }
                $Form_Controller_Show
                  ->xsl(
                  Core_Entity::factory('Xsl')->getByName('ОтобразитьФормуЗаявкиВМодальномОкне')
                 )
                  ->show();
?>

XSL . ФормаЗаявкиВМодальномОкне

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xsl:stylesheet SYSTEM "lang://351">
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:hostcms="http://www.hostcms.ru/"
exclude-result-prefixes="hostcms">
<xsl:output xmlns="http://www.w3.org/TR/xhtml1/strict" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" encoding="utf-8" indent="yes" method="html" omit-xml-declaration="no" version="1.0" media-type="text/xml" />
<!-- ОтобразитьФорму -->
<xsl:template match="/">
<xsl:apply-templates select="/form" />
</xsl:template>
<xsl:template match="/form">
<xsl:choose>
<xsl:when test="success/node() and success = 1">
<!--div id="fade" style="display: block;"></div-->
<div class="modal fade form-success-result" id="modalSuccess" tabindex="-1" role="dialog">
<div class="modal-dialog  modal-dialog-centered">
<div class="modal-content p-5">
<div class="modal-header text-center">
<!--div class="modal-title w-100 fs-4 fw-bold">&labelModalTitle1;</div-->
<button class="btn-close" type="button" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<xsl:value-of disable-output-escaping="yes" select="success_text" />
</div>
</div>
</div>
</div>
<script>
let modalSuccess = new bootstrap.Modal(document.getElementById('modalSuccess'));
modalSuccess.show();
</script>
</xsl:when>
<!-- Выводим ошибку (error), если она была передана через внешний параметр -->
<xsl:when test="error != ''">
<div class="modal fade form-success-result" id="modalError" tabindex="-1" role="dialog">
<div class="modal-dialog  modal-dialog-centered">
<div class="modal-content p-5">
<div class="modal-header text-center">
<div class="modal-title w-100 fs-3 fw-bold">ОШИБКА!</div>
<button class=" " type="button" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body"><b><xsl:value-of disable-output-escaping="yes" select="error" /></b></div>
</div>
</div>
</div>
<script>
let modalError = new bootstrap.Modal(document.getElementById('modalError'));
modalError.show();
</script>
</xsl:when>
<xsl:when test="errorId/node()">
<div class="modal fade form-success-result" id="modalError" tabindex="-1" role="dialog">
<div class="modal-dialog  modal-dialog-centered">
<div class="modal-content p-5">
<div class="modal-header text-center">
<div class="modal-title w-100 fs-3 fw-bold">&labelERROR;</div>
<button class="btn-close" type="button" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<xsl:choose>
<xsl:when test="errorId = 0">
&labelTextErrorId0;
</xsl:when>
<xsl:when test="errorId = 1">
&labelTextErrorId1;
</xsl:when>
<xsl:when test="errorId = 2">
&labelTextErrorId2;
</xsl:when>
</xsl:choose>
</div>
</div>
</div>
</div>
<script>
let modalError = new bootstrap.Modal(document.getElementById('modalError'));
modalError.show();
</script>
</xsl:when>
<xsl:otherwise>
 <div class="modal fade" id="questionModal" tabindex="-1" aria-labelledby="questionModalLabel_{@id}" aria-hidden="true">
<div class="modal-dialog  modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header justify-content-center">
<div class="h2 m-0 modal-title" id="questionModalLabel_{@id}"><xsl:value-of disable-output-escaping="yes" select="name" /></div>
<button class="btn-close" type="button" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<xsl:if test="description !=''">
<xsl:value-of disable-output-escaping="yes" select="description" />
</xsl:if>
<div id="add-text" class="text-center"> </div>
<!-- Параметр action формы должен быть "./", если обработчик на этой же странице, либо "./form/", если обработчик на другой странице, например ./form/ -->
<form name="form{@id}" id="form{@id}" class="validate" action="./" method="post" enctype="multipart/form-data" >
<!-- Вывод разделов формы 0-го уровня -->
<xsl:apply-templates select="form_field_dir" />
<!-- Вывод списка полей формы 0-го уровня -->
<xsl:apply-templates select="form_field" />
<!-- Код подтверждения -->
<xsl:if test="captcha_id != 0">
<div class="form-group mb-3">
<div class="row">
<div class="col-6 caption">
<input type="hidden" name="captcha_id" value="{/form/captcha_id}"/>
<input type="text" name="captcha" size="15" class="form-control required" minlength="4" title="Введите число, которое указано на картинке."/>
<div class="padding-5">&labelCheckNumber1;<sup><font color="red">*</font></sup></div>
</div>
<div class="col-6 field">
<img id="formCaptcha_{/form/@id}_{/form/captcha_id}" src="/captcha.php?id={captcha_id}&amp;height=30&amp;width=100" class="captcha" name="captcha" />
<div class="captcha">
<img src="/images/refresh.png" /> <span class="small" onclick="$('#formCaptcha_{/form/@id}_{/form/captcha_id}').updateCaptcha('{/form/captcha_id}', 30); return false" style="cursor:pointer">&labelCheckNumber2;</span>
</div>
</div>
</div>
</div>
</xsl:if>
<!--div class="margin-bottom-10">
<div class="g-recaptcha" data-sitekey="6LfemXMiAAAAAPtn9tHaYj5z-0yKJq4Tmi3Ph1Cw"></div>
</div-->
<xsl:if test="csrf_token/node() and csrf_token != ''">
<input type="hidden" name="{csrf_field}" value="{csrf_token}" />
</xsl:if>
<div class="form-group">
<input type="hidden" name="{button_name}" value="submit"/>
<button id="submit_{@id}" value="submit" name="{button_name}" type="submit" class="btn btn-black mt-4 w-100"><xsl:value-of select="button_value" /></button>
</div>
</form>
<div class="mt-4">
<div class="form-check">
<input id="checkbox{@id}" class="form-check-input" type="checkbox" name="checkbox" checked="checked" onchange="document.getElementById('submit_{@id}').disabled = !this.checked;"/>
<label for="checkbox{@id}" class="form-check-label small" style="text-align:justify">Я соглашаюсь на обработку своих персональных данных в соответствии с <a href="/private" class="link-border"> Политикой конфиденциальности</a></label>
</div>
</div>
</div>
</div>
</div>
</div>
<SCRIPT>
<xsl:comment>
<xsl:text disable-output-escaping="yes">
    <![CDATA[
    $(function() {
    $("#fileUpload").on('change', function () {
 
     //Get count of selected files
     var countFiles = $(this)[0].files.length;
     var imgPath = $(this)[0].value;
     var extn = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase();
     var image_holder = $("#image-holder");
     image_holder.empty();
 
     if (extn == "png" || extn == "jpg" || extn == "jpeg" || extn == "webp") {
         if (typeof (FileReader) != "undefined") {
 
             //loop for each file selected for uploaded.
             for (var i = 0; i < countFiles; i++) {
 
                 var reader = new FileReader();
                 reader.onload = function (e) {
                     $("<img />", {
                         "src": e.target.result,
                             "class": "thumb-image"
                     }).appendTo(image_holder);
                 }
 
                 image_holder.show();
                 reader.readAsDataURL($(this)[0].files[i]);
             }
 
         } else {
             alert("Ваш брузер не поддерживает FileReader!");
         }
     } else {
         alert("Выберите файл в формате JPG, JPEG, PNG или WEBP!");
     }
    });
    });
]]>
</xsl:text>
</xsl:comment>
</SCRIPT>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="form_field_dir">
<!--fieldset class="maillist_fieldset"-->
<!--legend><xsl:value-of select="name" /></legend-->
<!-- Вывод списка полей формы -->
<xsl:apply-templates select="form_field" />
<!-- Вывод разделов формы -->
<xsl:apply-templates select="form_field_dir" />
<!--/fieldset-->
</xsl:template>
<xsl:template match="form_field">
<!-- Не скрытое поле и не надпись -->
<xsl:if test="type != 7 and type != 8">
<div class="form-group mb-3">
<xsl:if test="type = 2">
<xsl:attribute name="class">form-group mb-2 me-4</xsl:attribute>
</xsl:if>
<xsl:variable name="caption">
<xsl:choose>
<xsl:when test="obligatory = 1">
<xsl:value-of select="caption" />*
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="caption" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<!-- Радиокнопки -->
<xsl:when test="type = 3 or type = 9">
<xsl:apply-templates select="list/list_item" />
</xsl:when>
<!-- Checkbox -->
<xsl:when test="type = 4">
<div class="pretty p-default p-pulse">
<input type="checkbox" name="{name}">
<xsl:if test="checked = 1 or value = 1">
<xsl:attribute name="checked">checked</xsl:attribute>
</xsl:if>
</input>
<div class="state p-danger-o">
<label><xsl:value-of select="value" /></label>
</div>
</div>
</xsl:when>
<!-- Textarea -->
<xsl:when test="type = 5">
<textarea class="form-control" name="{name}" cols="{cols}" rows="{rows}" placeholder="{$caption}">
<xsl:if test="obligatory = 1">
<xsl:attribute name="class">form-control required</xsl:attribute>
</xsl:if>
<xsl:value-of select="value" />
</textarea>
</xsl:when>
<!-- Список -->
<xsl:when test="type = 6">
<select name="{name}" class="wide">
<xsl:if test="obligatory = 1">
<xsl:attribute name="class">wide required</xsl:attribute>
<xsl:attribute name="title"><xsl:value-of select="caption" /></xsl:attribute>
</xsl:if>
<option value="">...</option>
<xsl:apply-templates select="list/list_item" />
</select>
</xsl:when>
<!-- Текстовые поля -->
<xsl:otherwise>
<xsl:if test="type = 2">
<label for="fileUpload" class="download-file">
<i class="svg_icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="21px" height="21px" ><path style="fill:#797979" d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm16 336c0 8.822-7.178 16-16 16H48c-8.822 0-16-7.178-16-16V112c0-8.822 7.178-16 16-16h416c8.822 0 16 7.178 16 16v288zM112 232c30.928 0 56-25.072 56-56s-25.072-56-56-56-56 25.072-56 56 25.072 56 56 56zm0-80c13.234 0 24 10.766 24 24s-10.766 24-24 24-24-10.766-24-24 10.766-24 24-24zm207.029 23.029L224 270.059l-31.029-31.029c-9.373-9.373-24.569-9.373-33.941 0l-88 88A23.998 23.998 0 0 0 64 344v28c0 6.627 5.373 12 12 12h360c6.627 0 12-5.373 12-12v-92c0-6.365-2.529-12.47-7.029-16.971l-88-88c-9.373-9.372-24.569-9.372-33.942 0zM416 352H96v-4.686l80-80 48 48 112-112 80 80V352z"/></svg>
</i>
<span class="download-file-text ps-2">Выберите сразу несколько фото</span>
</label>
</xsl:if>
<input class="form-control" type="text" name="{name}" value="{value}" size="{size}" placeholder="{$caption}">
<xsl:choose>
<!-- Поле для ввода пароля -->
<xsl:when test="type = 1">
<xsl:attribute name="type">password</xsl:attribute>
</xsl:when>
<!-- Поле загрузки файла -->
<xsl:when test="type = 2">
<xsl:attribute name="type">file</xsl:attribute>
<xsl:attribute name="multiple">true</xsl:attribute>
<xsl:attribute name="class">photo</xsl:attribute>
<xsl:attribute name="name">files[]</xsl:attribute>
<xsl:attribute name="id">fileUpload</xsl:attribute>
<xsl:attribute name="accept">image/jpg,image/jpeg,image/png,image/webp</xsl:attribute>
<div id="image-holder" class="d-flex flex-wrap"></div>
</xsl:when>
<!-- HTML5: Дата -->
<xsl:when test="type = 10">
<xsl:attribute name="type">date</xsl:attribute>
</xsl:when>
<!-- HTML5: Цвет -->
<xsl:when test="type = 11">
<xsl:attribute name="type">color</xsl:attribute>
</xsl:when>
<!-- HTML5: Месяц -->
<xsl:when test="type = 12">
<xsl:attribute name="type">month</xsl:attribute>
</xsl:when>
<!-- HTML5: Неделя -->
<xsl:when test="type = 13">
<xsl:attribute name="type">week</xsl:attribute>
</xsl:when>
<!-- HTML5: Время -->
<xsl:when test="type = 14">
<xsl:attribute name="type">time</xsl:attribute>
</xsl:when>
<!-- HTML5: Дата-Время -->
<xsl:when test="type = 15">
<xsl:attribute name="type">datetime</xsl:attribute>
</xsl:when>
<!-- HTML5: E-mail -->
<xsl:when test="type = 16">
<xsl:attribute name="type">email</xsl:attribute>
</xsl:when>
<!-- HTML5: Поиск -->
<xsl:when test="type = 17">
<xsl:attribute name="type">search</xsl:attribute>
</xsl:when>
<!-- HTML5: Телефон -->
<xsl:when test="type = 18">
<xsl:attribute name="type">tel</xsl:attribute>
</xsl:when>
<!-- HTML5: URL -->
<xsl:when test="type = 19">
<xsl:attribute name="type">url</xsl:attribute>
</xsl:when>
<!-- Текстовое поле -->
<xsl:otherwise>
<xsl:attribute name="type">text</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="obligatory = 1">
<xsl:attribute name="class">form-control required</xsl:attribute>
</xsl:if>
<!--xsl:if test="@id = 15">
<xsl:attribute name="disabled">disabled</xsl:attribute>
</xsl:if-->
</input>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:if>
<!-- скрытое поле -->
<xsl:if test="type = 7">
<input type="hidden" name="{name}" value="{value}" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
//
//
//
Код DTD для языка "ru"

<!ENTITY labelModalTitle1 "Благодарим вас за заявку!">
<!ENTITY labelModalTitle2 "Заявка получена.">
<!ENTITY labelSuccessText "Мы свяжемся с вами в ближайшее время">
<!ENTITY labelSuccessText2 "Наш менеджер ответит на все ваши вопросы">

<!ENTITY labelERROR "ОШИБКА!">
<!ENTITY labelTextErrorId0 "Вы неправильно ввели код подтверждения отправки формы!">
<!ENTITY labelTextErrorId1 "Заполните все обязательные поля!">
<!ENTITY labelTextErrorId2 "С момента отправки последней формы прошло слишком мало времени!">

<!ENTITY labelTextError1 "Это поле пустое">
<!ENTITY labelTextError2 "E-mail должен иметь вид name@domain.com">

<!ENTITY labelText1 "Обязательное поле">

<!ENTITY labelCheckNumber1 "Проверочный код">
<!ENTITY labelCheckNumber2 "показать другой код">

Стили CSS

/******************************/

.ask-question {
    padding:20px;
    background-color: #f0f0f0;
    max-width:450px;
    text-align: center;
}
.ask-question > div:not(.last) {
     margin-bottom:15px;   
}

.btn.btn-transparent {

background: transparent;

border: 1px solid #212529;

color: #212529;

/******************************/

Использование jQuery Validate для проверки форм

Рабочая валидация с проверкой captcha
<script>
      $(function() {
            $(".validate").validate({
            focusInvalid: true,
            errorClass: "input_error",
            onkeyup: false,
            onfocusout: false,
            rules: {
            captcha: {
            required: true,
            remote: '/forms/'
            },
            author: "required",
             text: "required",
             email: {
                   required: true,
                   email: true
                  }
               },
               messages: {
                       author: "Пожалуйста, укажите своё Имя!",
                       text: "Пожалуйста, напишите свой комментарий!",
                       captcha: "Вы не вписали контрольное число!",
                       email: {
                       required: "Введите свой email",
                       email: "E-mail должен быть в формате name@domain.com"
                         }
            }});
        });
   </script>
Проверьте, подключен ли в макете файл jquery.validate.min.js, в системе управления он расположен в hostcmsfiles/jquery/jquery.validate.min.js
Core_Page::instance()
// jQuery
->js('/hostcmsfiles/jquery/jquery.min.js')
...
// Validate
->js('/hostcmsfiles/jquery/jquery.validate.min.js')
...
->showJs();

Указание валидации формы

Валидация конкретной формы или форм с классом validate указывается следующим образом:
<script>
$(".validate").validate();
</script>
Валидация всех форм:

<script>
$("form").validate();
</script>

Способы валидации форм

Использование имена классов как правила

Тем полям, которые нужно проверять, добавляете атрибут class="required"
<form action="." method="post" class="validate">
    <input type="text" name="name" value="" class="required" />
    <input type="submit" value="Submit" />
</form>

С помощью метод addClassRules вы можете расширить и добавить условие валидации:
<form action="." method="post" class="validate">
    <input type="text" name="name" value="" class="name" />
    <input type="text" name="zip" value="" class="zip" />
    <input type="submit" value="Submit" />
</form>
Проверка скриптом
<script >
$.validator.addClassRules("name", {
    required: true,
    minlength: 2
});
</script >
или сразу для нескольких полей:
<script >
$.validator.addClassRules({
name: {
required: true,
minlength: 2
},
zip: {
required: true,
digits: true,
minlength: 5,
maxlength: 5
}});
</script >

Передача опций при инициализации validate()

Методу validate() передаем объекты rules и messages которые состоят из пар ключ/значение. В rules в качестве ключа указываем атрибут name поля, значением указываем правило проверки. В messages в качестве ключа так же идет атрибут name поля, а в качестве значения указываем предупреждающее сообщение.
<script >
$(".validate").validate({
rules: {
name: "required",
email: {
required: true,
email: true
}
},
messages: {
name: "Please specify your name",
email: {
required: "Введите свой email",
email: "E-mail должен быть в формате name@domain.com"
}
}});
</script >
Список правил:
  • required — поле обязательное для заполнения (true или false);
  • remote — указывается файл для проверки поля (например: "check.php");
  • email — проверяет корректность e-mail адреса (true или false);
  • url — проверяет корректность url адреса (true или false);
  • date — проверка корректности даты (true или false);
  • dateISO — проверка корректности даты ISO (true или false);
  • number — проверка на число (true или false);
  • digits — только цифры (true или false);
  • creditcard — корректность номера кредитной карты (true или false);
  • equalTo — равное чему-то (например другому полю equalTo: "#pswd");
  • accept — проверка на правильное расширение (accept: "xls|csv");
  • maxlength — максимальное кол-во символов;
  • minlength — минимальное кол-во символов;
  • rangelength — кол-во символов от скольких и до скольких (rangelength: [2, 5]);
  • range — число должно быть в диапазоне от и до (range: [2, 12]);
  • max — максимальное значение числа;
  • min — минимальное значение числа.

Пример использования в XSL-шаблоне

Указываем форме класс например validate, и далее в XSL-шаблоне формы добавляем код проверки формы (пример взят из формы быстрого заказа в адаптивном шаблоне):
<script >
$(".validate").validate({
rules: {
surname: "required",
name: "required",
email: {
required: true,
email: true
}
},
messages: {
surname: "Введите фамилию!",
name: "Введите имя!",
email: {
required: "Введите e-mail!",
email: "Адрес должен быть вида name@domain.com"
}
},
focusInvalid: true,
errorClass: "input_error"
});
</script>
Проверка валидации и заблокированного E-mail нежелательного Пользователя
<SCRIPT>
            $(document).ready(function() {
            $('.select2').addClass('required');
            $("#form<xsl:value-of select="@id" />").submit(function (event) {
            event.preventDefault();
            var eml = $('input[name=email]').val();
            if(eml =='svinya.svintus@yandex.ru') {
            alert ('Вы не можете отправлять нам письма');
            $(location).attr('href', '/');
            }else{
            
            $(this).validate({
            focusInvalid: true,
            errorClass: "input_error"
            });
            }
            });
            });
        </SCRIPT>

Исправление ошибки кодировки таблиц

В результате проверки системы, Битрикс указал, что есть ошибки в Базе данных, но иногда автоматически он не может всё исправить, и тут приходится исправлять своими ручками.

Нажимая на знак вопроса мы видим подсказку от Битрикса где сообщает, что кодировка текущих наших таблиц, должна совпадать с кодировкой базы данных.

Подробное описание ошибки

Если перейдем по ссылке на "журнал проверки системы", то увидим список тех таблиц, где не совпали кодировки, вот нам они и нужны, на скриншоте пометил стрелочками.

Подробное описание ошибки

SQL запрос: 

ALTER TABLE b_iblock_property_feature CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Где b_iblock_property_feature - это название таблицы, у Вас могут быть другие.

Таким образом я выписал и составил список sql запросов для всех своих таблиц из журнала.

ALTER TABLE b_iblock_property_feature CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_block CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_demo CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_domain CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_file CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_hook_data CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_manifest CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_placement CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_repo CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_site CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_syspage CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_template CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_landing_template_ref CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_main_mail_blacklist CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_main_mail_sender CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_messageservice_message CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_messageservice_rest_app CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_messageservice_rest_app_lang CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_mobileapp_app CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_mobileapp_config CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_numerator CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_numerator_sequence CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rating_voting_reaction CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_ap CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_ap_permission CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_app CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_app_lang CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_app_log CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_event CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_event_offline CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_log CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_placement CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_stat CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_rest_stat_method CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_seo_service_subscription CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_user_profile_history CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_user_profile_record CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_utm_iblock_6_section CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_utm_iblock_8_section CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_uts_iblock_6_section CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE b_uts_iblock_8_section CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

После того как Вы собрали список SQL запросов, заходим в админку сайта битрикс (Настройки - Инструменты - SQL-запрос) или по пути, вставляете после адреса сайта /bitrix/admin/sql.php

Подробное описание ошибки

КОНСТАНТЫ

Отключаем Вебвизор в коде Метрики

1. В Система -> Константы создаём Константу WEBVISOR
2. В код Метрики вставляем строчку
webvisor:<?php if (defined('WEBVISOR')) {echo 'true';}else{ echo 'false';};?>,
При неактивной Константе Webvisor в коде Метрики Вебвизор отключается.

Загрузка бета обновлений

 INSTALL_BETA_UPDATE (true)  -  для загрузки бэта обновлений делаем константу активной и прописываем TRUE

Отключаем чат в админпанели, чтобы уменьшить нагрузку

Открыввем файл /modules/core/config
'chat' => FALSE,
php

Меняем путь к базе данных database.php

Открываем файл /modules/core/config/database.php и заменяем параметры

<?php
 return array (
      'default' => array (
           'driver' => 'mysql',
           'host' => 'securitysf.mysql',
           'username' => 'securitysf_mysql',
           'password' => 'auotczlo',
           'database' => 'securitysf_hs'
      )
 );
php

Модальное окно с формой подписки и сохранением куки на jQuery

Добавляем в макет код показа формы:

<?php
        // add ФОРМА ПОДПИСКА ЗА СКИДКУ
     $oForm = Core_Entity::factory('Form', 49);  // заменить ID формы
      
     $Form_Controller_Show = new Form_Controller_Show($oForm);
      
     if (!is_null(Core_Array::getPost($oForm->button_name)))
     {
     $Form_Controller_Show
     ->values($_POST + $_FILES)
     // 0 - html, 1- plain text
     ->mailType(0)
     ->mailXsl(
     Core_Entity::factory('Xsl')->getByName('ПисьмоКураторуФормыВФорматеHTML')
     )
     ->mailFromFieldName('email')
     ->process();
     }
      
     $Form_Controller_Show
     ->xsl(
     Core_Entity::factory('Xsl')->getByName('ОтобразитьФормуВМодальномОкне')
     )
      ->show();
?>

Подключаем в макете скрипт jquery.cookie.min.js:

<script src="/js/jquery.cookie.min.js"></script>

Проверяем куки:

<script>
$(document).ready(function($) {
if ($.cookie('was') == null) {
// Покажем всплывающее окно
setTimeout(function(){$('#popupModal').modal('show');}, 36000);
}
// Запомним в куках, что посетитель к нам уже заходил
$.cookie('was', 'value', { expires: 2, path: '/' });
});
</script>

JavaScript / jQuery . jquery.cookie.min.js

jQuery.cookie=function(b,j,m){if(typeof j!="undefined"){m=m||{};if(j===null){j="";m.expires=-1}var e="";if(m.expires&&(typeof m.expires=="number"||m.expires.toUTCString)){var f;if(typeof m.expires=="number"){f=new Date();f.setTime(f.getTime()+(m.expires*24*60*60*1000))}else{f=m.expires}e="; expires="+f.toUTCString()}var l=m.path?"; path="+(m.path):"";var g=m.domain?"; domain="+(m.domain):"";var a=m.secure?"; secure":"";document.cookie=[b,"=",encodeURIComponent(j),e,l,g,a].join("")}else{var d=null;if(document.cookie&&document.cookie!=""){var k=document.cookie.split(";");for(var h=0;h<k.length;h++){var c=jQuery.trim(k[h]);if(c.substring(0,b.length+1)==(b+"=")){d=decodeURIComponent(c.substring(b.length+1));break}}}return d}};

Модуль "Заказать звонок" (callbackbukleta)

Заменить код файла /modules/callbackbukleta/callbackbukleta.php на этот

PHP

<?php
defined('HOSTCMS') || exit('HostCMS: access denied.');
class Callbackbukleta
{
public static function show()
{
$show_modal = 0;
if (!empty($_POST['callbackbukleta'])) {
$aCallbackbukleta = Core_Config::instance()->get('callbackbukleta_config');
if (empty($_SESSION['callbackbukleta-timeout']) or ($_SESSION['callbackbukleta-timeout'] + (60 * $aCallbackbukleta['time_wait'])) < time()) {
if (self::_saveContact($_POST) === true) {
$alert = "Ваша заявка принята";
} elseif (self::_saveContact($_POST) == 2) {
$alert = "Не верно введен код с картинки";
$show_modal = 1;
} elseif (self::_saveContact($_POST) == 2.1) {
$alert = "Вы не прошли проверку, попробуйте ещё раз.";
$show_modal = 1;
} elseif (self::_saveContact($_POST) == 3) {
$alert = "Не правильно введен телефонный номер";
$show_modal = 1;
}
} else {
$alert = "Следующее сообщение можно отправить через {$aCallbackbukleta['time_wait']} минут.";
}
require_once CMS_FOLDER . 'modules/callbackbukleta/template/alert.htm';
}
$captcha_id = Core_Captcha::getCaptchaId();
require_once CMS_FOLDER . 'modules/callbackbukleta/template/template.htm';
self::_clearAudio();
}
private static function _saveContact($aPost)
{
if ($aPost = Core_Array::get($aPost, 'callbackbukleta')) {
$aCallbackbukleta = Core_Config::instance()->get('callbackbukleta_config');
if (!empty($aCallbackbukleta['show_captcha']) && $aCallbackbukleta['show_captcha'] == 1) {
if(!empty($aCallbackbukleta['show_recaptcha'])) {
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $aCallbackbukleta['recaptcha_secretkey'] . '&response=' . (array_key_exists('g-recaptcha-response', $_POST) ? $_POST["g-recaptcha-response"] : '') . '&remoteip=' . $_SERVER['REMOTE_ADDR'];
$resp = json_decode(file_get_contents($url), true);
if ($resp['success'] == false) {
return 2.1;
}
}
elseif (!Core_Captcha::valid($aPost['captcha_id'], $aPost['captcha_val'])) {
return 2;
}
}
$phone_mask = preg_replace("/[^0-9]/", '', $aCallbackbukleta['phone_mask']);
$phone = preg_replace("/[^0-9]/", '', $aPost['phone']);
if( mb_strlen($phone_mask) != mb_strlen($phone)) {
return 3;
}
$oCallbackbukleta = Core_Entity::factory('Callbackbukleta');
$oCallbackbukleta->phone = $aPost['phone'];
if (!empty($aPost['name'])) {
$oCallbackbukleta->name = $aPost['name'];
}
if (!empty($aPost['email'])) {
$oCallbackbukleta->email = $aPost['email'];
}
if(!empty($aPost['audiofile'])) {
$oCallbackbukleta->audiofile = $aPost['audiofile'];
}
$oCallbackbukleta->datetime = date('Y-m-d H:i:s', time());
if ($oCallbackbukleta->save()) {
if(!empty($oCallbackbukleta->audiofile)) {
if(!is_dir(CMS_FOLDER.'upload/callbackbukleta')) {
mkdir(CMS_FOLDER.'upload/callbackbukleta');
}
$aAudiofile = explode('/', $oCallbackbukleta->audiofile);
$sAudiofile = array_pop($aAudiofile);
copy(CMS_FOLDER.$oCallbackbukleta->audiofile, CMS_FOLDER.'upload/callbackbukleta/'.$sAudiofile);
$oCallbackbukleta->audiofile = '/upload/callbackbukleta/'.$sAudiofile;
$oCallbackbukleta->save();
}
if (!empty($aCallbackbukleta['email_notification']) && self::_isValidEmail($aCallbackbukleta['email_notification'])) {
if (!empty($oCallbackbukleta->name)) {
$message .= 'ФИО: ' . $oCallbackbukleta->name . "\n";
}
$message .= 'Телефон: ' . $oCallbackbukleta->phone . "\n";
if (!empty($oCallbackbukleta->email)) {
$message .= 'Email: ' . $oCallbackbukleta->email . "\n";
}
if (!empty($oCallbackbukleta->audiofile)) {
$message .= 'Ссылка на аудиосообщение <a href="//'. $_SERVER['SERVER_NAME']. $oCallbackbukleta->audiofile . '">Открыть</a>\n';
}
$message .= 'Время запроса: ' . date('H:i:s d.m.Y', strtotime($oCallbackbukleta->datetime));
Core_Mail::instance()
->to($aCallbackbukleta['email_notification'])
->from(!empty($aCallbackbukleta['from_email'])?$aCallbackbukleta['from_email']:false)
->subject("Заказ обратного звонка " . $_SERVER['SERVER_NAME'])
->message($message)
->contentType('text/plain')
->header('X-HostCMS-Reason', 'Alert')
->header('Precedence', 'bulk')
->send();
if (!empty($aCallbackbukleta['sms_enable']) && $aCallbackbukleta['sms_enable'] == 1) {
self::_sendSms($message);
}
}
$_SESSION['callbackbukleta-timeout'] = time();
return true;
} else {
return false;
}
} else {
return false;
}
}
private static function _sendSms($message)
{
$aCallbackbukleta = Core_Config::instance()->get('callbackbukleta_config');
$sms_login = $aCallbackbukleta['sms_login'];
$sms_password = $aCallbackbukleta['sms_password'];
$sms_phone = $aCallbackbukleta['sms_phone'];
if (!empty($sms_login) && !empty($sms_password) && !empty($sms_phone)) {
require_once 'smsc_api.php';
send_sms($sms_phone, $message);
}
}
private static function _isValidEmail($email)
{
if (!function_exists('filter_var')) {
if (preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $email)) {
return true;
}
else {
return false;
}
}
else {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
}
private static function _clearAudio(){
$aFile = scandir(CMS_FOLDER.'/modules/callbackbukleta/audiofiles');
if(!empty($aFile)) {
foreach ($aFile as $sFile) {
if (strstr($sFile, 'wav')) {
$aFilestr = explode('-', $sFile);
$sTime = $aFilestr[1];
//Удаляем все файлы, которые хранятся больше 20 минут.
if ($sTime < (time() - 60 * 20) ) {
echo CMS_FOLDER . '/modules/callbackbukleta/audiofiles/' . $sFile;
unlink(CMS_FOLDER . '/modules/callbackbukleta/audiofiles/' . $sFile);
}
}
}
}
}
}
php

Настройка memcahe

в секции memcahe указываются данные доступа к серверу:
modules/core/config/cache.php
PHP
'memcache' => array(
'name' => 'Memcache',
'driver' => 'Cache_Memcache',
'server' => '127.0.0.1',
'port' => 11211,
'checksum' => FALSE,
'caches' => $aTypicalCaches,
),
а в 'defaultCache' указываете 'memcache'
PHP
<?php
return array (
'skin' => 'bootstrap',
'dateFormat' => 'd.m.Y',
'dateTimeFormat' => 'd.m.Y H:i:s',
//'reverseDateTimeFormat' => '',
'datePickerFormat' => 'DD.MM.YYYY',
'dateTimePickerFormat' => 'DD.MM.YYYY HH:mm:ss',
'timezone' => 'Europe/Moscow',
'translate' => TRUE,
'chat' => FALSE,
'switchSelectToAutocomplete' => 100,
'autocompleteItems' => 10,
'backendSessionLifetime' => 14400,
'availableExtension' => array ('JPG', 'JPEG', 'GIF', 'PNG', 'WEBP', 'PDF', 'ZIP', 'DOC', 'DOCX', 'XLS', 'XLSX'),
'defaultCache' => 'file',
'session' => array(
'driver' => 'database',
'class' => 'Core_Session_Database'
),...

Конфигурационный файл

Конфигурационный файл размещается в modules/core/config/cache.php и содержит переменную $aTypicalCaches с массивом имен кэшей и их конфигурациями, например:

PHP

$aTypicalCaches = array(
'default' => array('expire' => 3600, 'size' => 262144, 'tags' => FALSE),
'Core_ORM' => array('expire' => 3600, 'size' => 262144, 'tags' => FALSE),
'Core_ORM_ColumnCache' => array('expire' => 3600, 'size' => 262144, 'tags' => FALSE),
'Core_ORM_RelationCache' => array('expire' => 3600, 'size' => 262144, 'tags' => FALSE),
'informationsystem_rss' => array('expire' => 14400, 'size' => 262144),
'informationsystem_show' => array('expire' => 14400, 'size' => 262144, 'compress' => TRUE),
'informationsystem_tags' => array('expire' => 14400, 'size' => 262144, 'compress' => TRUE),
'shop_show' => array('expire' => 14400, 'size' => 262144, 'compress' => TRUE),
'shop_tags' => array('expire' => 14400, 'size' => 262144, 'compress' => TRUE),
'search' => array('expire' => 14400, 'size' => 262144, 'tags' => FALSE),
'structure_breadcrumbs' => array('expire' => 14400, 'size' => 262144),
'structure_show' => array('expire' => 14400, 'size' => 262144, 'compress' => TRUE),
'counter_allSession' => array('expire' => 1800, 'size' => 1024, 'tags' => FALSE),
);
Индексом элемента массива является имя кэша, значением - массив опций, где:
  • expire — время жизни закэшированного элемента, указывается в секундах;
  • size — максимальный размер кэшируемого элемента, указывается в байтах;
  • tags — использовать теггирование кэша, по умолчанию TRUE;
  • compress — сжимать значение перед сохранением в кэш, по умолчанию FALSE.
Далее конфигурационный файл возвращает массив со списком доступных хранилищ кэша, например:
PHP
return array (
'memory' => array(
'name' => 'Memory',
'driver' => 'Core_Cache_Memory',
'caches' => array(
'default' => array()
),
),
'file' => array(
'name' => 'File',
'driver' => 'Cache_File',
'checksum' => FALSE,
'caches' => $aTypicalCaches,
),
'eaccelerator' => array(
'name' => 'eAccelerator',
'driver' => 'Cache_Eaccelerator',
'checksum' => TRUE,
'caches' => $aTypicalCaches,
),
'apc' => array(
'name' => 'APC',
'driver' => 'Cache_APC',
'checksum' => TRUE,
'caches' => $aTypicalCaches,
),
'memcache' => array(
'name' => 'Memcache',
'driver' => 'Cache_Memcache',
'server' => '127.0.0.1',
'port' => 11211,
'checksum' => FALSE,
'caches' => $aTypicalCaches,
),
'xcache' => array(
'name' => 'XCache',
'driver' => 'Cache_XCache',
'checksum' => TRUE,
'caches' => $aTypicalCaches,
),
'static' => array(
'name' => 'Static',
'driver' => 'Cache_Static',
'caches' => array(
'default' => array('expire' => 3600, 'size' => NULL),
),
),);
где индексом является уникальное название кэша, а значением массив опций, например:
  • name — текстовое название вида кэширования;
  • driver — имя драйвера, осуществляющего работу с кэшем. Файлы дополнительных драйверов располагаются в директории modules/cache/;
  • checksum — сохранять контрольную сумму кэшируемого объекта и проверять ее при извлечении элемента из кэша, позволяет исключить извлечение поврежденных данных;
  • caches — массив доступных кэшей, чаще всего подставляется переменная $aTypicalCaches.

Конфигурационный файл

Конфигурационный файл размещается в modules/core/config/config.php и содержит следующие настройки:
  • skin — шаблон центра администрирования, по умолчанию 'bootstrap';
  • dateFormat — формат даты, по умолчанию 'd.m.Y';
  • dateTimeFormat — формат даты-времени, по умолчанию 'd.m.Y H:i:s';
  • timezone — временная зона, по умолчанию 'Europe/Moscow';
  • translate — использовать в пути элемента перевод, по умолчанию TRUE. В случае указания FALSE используется транслитерация;
  • chat — использовать чат между пользователями в центре администрирования, по умолчанию TRUE;
  • switchSelectToAutocomplete — количество элементов, при которых большие списки переключать на автоподстановку, по умолчанию 100. Используется, например, при выборе группы для товаров и информационных элементов;
  • autocompleteItems — количество элементов, предлагаемых в автоподстановке, по умолчанию 10;
  • availableExtension — массив расширений файлов, разрешенных для загрузки в атрибуты элементов центра администрирования. При указании дополнительных элементов не забывайте указывать их в верхнем регистре;
  • defaultCache — вид кэширования по умолчанию;
  • fileIcons — массив соответствий расширений файлов и иконок.

PHP . config.php

<?php
return array (
'skin' => 'bootstrap',
'dateFormat' => 'd.m.Y',
'dateTimeFormat' => 'd.m.Y H:i:s',
//'reverseDateTimeFormat' => '',
'datePickerFormat' => 'DD.MM.YYYY',
'dateTimePickerFormat' => 'DD.MM.YYYY HH:mm:ss',
'timezone' => 'Europe/Moscow',
'translate' => TRUE,
'chat' => FALSE,
'switchSelectToAutocomplete' => 100,
'autocompleteItems' => 10,
'backendSessionLifetime' => 14400,
'availableExtension' => array ('JPG', 'JPEG', 'GIF', 'PNG', 'PDF', 'ZIP', 'DOC'),
'defaultCache' => 'file',
'adminMenu' => array(
0 => array(
'image' => '/admin/images/structure.gif'
),
1 => array(
'image' => '/admin/images/service.gif'
),
2 => array(
'image' => '/admin/images/users.gif'
),
3 => array(
'image' => '/admin/images/system.gif'
)
),
'fileIcons' => array(
'sql' => 'sql.gif',
'txt' => 'txt.gif',
'htaccess' => 'config.gif',
'css' => 'css.gif',
'php' => 'php.gif',
'php3' => 'php.gif',
'jpg' => 'jpg.gif',
'jpeg' => 'jpg.gif',
'gif' => 'gif.gif',
'bmp' => 'bmp.gif',
'png' => 'png.gif',
'ico' => 'image.gif', //
'htm' => 'html.gif',
'html' => 'html.gif',
'xml' => 'xml.gif', //
'xsl' => 'xsl.gif',
'zip' => 'zip.gif',
'gz' => 'zip.gif',
'7z' => 'zip.gif',
'rar' => 'rar.gif',
'pdf' => 'pdf.gif',
'doc' => 'doc.gif',
'docx' => 'doc.gif',
'cdr' => 'vector.gif',
'ai' => 'vector.gif',
'eps' => 'vector.gif',
'rb' => 'rb.gif',
'ppt' => 'ppt.gif',
'pptx' => 'ppt.gif',
'pptm' => 'ppt.gif',
'mdb' => 'mdb.gif',
'h' => 'h.gif',
'fh1' => 'fh1.gif',
'fh2' => 'fh2.gif',
'fh3' => 'fh3.gif',
'fh4' => 'fh4.gif',
'fh5' => 'fh5.gif',
'fh6' => 'fh6.gif',
'fh7' => 'fh7.gif',
'fh8' => 'fh8.gif',
'fh9' => 'fh9.gif',
'fla' => 'flash.gif',
'swf' => 'flash.gif',
'xls' => 'xls.gif',
'cpp' => 'cpp.gif',
'chm' => 'chm.gif'
));

Настройка почты, отправка через SMTP, настройка DKIM

Конфигурационный файл размещается в modules/core/config/mail.php. Стандартно используется опция default, для которой задан драйвер sendmail:

   'default' => array (
       'driver' => 'sendmail',
    ),

вместо sendmail укажите драйвер smtp.

Далее настройте секцию с параметрами драйвера smtp:

'smtp' => array (
       'driver' => 'smtp',
       'username' => 'address@domain.com', // Логин
       'password' => 'password', // Пароль
       'host' => 'ssl://smtp.server.com', // для SSL используйте 'ssl://smtp.server.com', для TLS 'smtp.server.com'
       'port' => 465, // порт 25, для SSL порт 465, для TLS порт 587
       'timeout' => 10,
       'log' => FALSE,
       'options' => array(
            'ssl' => array(
                'verify_peer' => FALSE,
                'verify_peer_name' => FALSE,
                'allow_self_signed' => TRUE
            )
        )
    )
В качестве пароля чаще всего указывается пароль приложений, созданный в интерфейсе почтовой службы, а не пароль от самого ящика!

Мы можем подключиться к портам 465 (SMTP over SSL) или 587 (STARTTLS), если необходим TLS, то установите опцию tls в TRUE и порт в 587:

'smtp' => array (
       'driver' => 'smtp',
       ...
       'port' => 587,
       'tls' => TRUE,
       'timeout' => 10,
       ...
    )

Если адрес электронной почты отличается от username, то используйте дополнительную опцию from с указанием адреса электронной почты:

 'smtp' => array (
       'driver' => 'smtp',
       'username' => 'username', // Логин
       'password' => 'password', // Пароль
       'from' => 'address@domain.com', // Адрес эл. почты
       'host' => 'smtp.server.com', // для SSL используйте ssl://smtp.server.com
       'port' => '25', // Порт, для SSL укажите порт 465
        'options' => array(
            'ssl' => array(
                'verify_peer' => FALSE,
                'verify_peer_name' => FALSE,
                'allow_self_signed' => TRUE
            )
        )
    )

Для отладки включите опцию 'log' в значение TRUE, не забудьте отключить опцию после завершения отладки, так как данные имеют большой размер в логах.

Указание отдельных опций для сайтов

Возможно отдельное задание опций отправки почты для выбранных сайтов, при этом для неуказанных отдельно сайтов будут использоваться общие параметры.

'smtp' => array (
        'driver' => 'smtp',
        // Общие настройки для всех сайтов
        'username' => 'address@domain.com', // Адрес электронной почты
        'port' => '25', // Порт, для SSL укажите порт 465
        'host' => 'smtp.server.com', // для SSL используйте ssl://smtp.server.com
        'password' => 'password', // Пароль
        // Индивидуальные настройки для сайта с ID 17
        17 => array(
            'username' => 'address2@domain2.com', // Адрес электронной почты
            'port' => '25', // Порт, для SSL укажите порт 465
            'host' => 'smtp.server.com', // для SSL используйте ssl://smtp.server.com
            'password' => 'password', // Пароль
        )
    )

Отдельное указание доступно с версии 6.5.9. Секция 'options' добавлена в версии 6.6.8. Поддержка TLS и указание timeout добавлены в версии 6.8.4.

DKIM

Для того, чтобы письма, отправляемые из системы управления, проходили проверку DKIM, необходимо соединиться с сервером по SSH и создать открытый и закрытый ключ. * доступно с версии 7.0.6

Генерация открытого и закрытого ключа

Создаем закрытый ключ, указав вместо domain.com имя домена:

openssl genrsa -out domain.com.privatekey.pem 1024

где «domain.com.privatekey.pem» — файл приватного ключа, «1024» — длина ключа.

Сохранить путь к сгенерированному ключу, файл будет создан в той же директории, откуда выполнялась команда.

Создаем открытый ключ, указав вместо domain.com имя вашего домена:

openssl rsa -in domain.com.private.pem -out domain.com.public.pem -pubout

где «domain.com.public.pem» — файл публичного ключа

Содержимое файла открытого ключа domain.com.public.pem после генерирования ключа будет следующим:

-----BEGIN PUBLIC KEY-----
ваш открытый ключ
-----END PUBLIC KEY-----

Указание открытого ключа в NS-записи домена

Через панель регистратора домена, в настройках домена создайте TXT-запись для поддомена mail._domainkey со следующим содержимым:

v=DKIM1; k=rsa; p=ваш-открытый-ключ

Ключ должен быть указан в одну строку, без BEGIN и END.

Указание ключа в настройках системы управления

В настройках драйвера или в настройках конкретного сайта укажите использование DKIM и путь к ключу, пример указания для сайта с кодом 2:

return array (
	'default' => array (
		'driver' => 'sendmail',
	),
	'sendmail' => array (
		'driver' => 'sendmail',
		2 => array(
			'dkim' => array(
				'private_key' => '/home/user4567/domain.com.private.pem',
				'selector' => 'mail',
			)
		),
	);

Кроме приведенных опций, для DKIM могут быть заданы следующие:

array(
	'hash' => 'sha256', // sha256|sha1
	'passphrase' => '',
	'selector' => 'mail',
	'domain' => NULL,
	'identity' => NULL,
	'body_canonicalization' => 'relaxed',
)

Особенности настройки почтовых серверов

Яндекс.Почта

Для подключения к почтовому ящику Яндекс из стороннего приложения не подходит обычный пароль, который используется для входа в учетную запись на Яндексе, создайте специальный пароль приложения.

Также может понадобиться разрешить доступ к почтовому ящику с помощью почтовых клиентов, для этого в почте выберите шестерёнку, затем Все настройки  Почтовые программы.

В качестве SMTP-сервера укажите ssl://smtp.yandex.ru, порт 465, не указывать TLS.

Mail.ru

Для подключения к почтовому ящику Mail.ru из стороннего приложения не подходит обычный пароль, который используется для входа в учетную запись, создайте специальный пароль приложения. Перейдите в настройки Mail ID → «Безопасность» → «Пароли для внешних приложений», нажмите Добавить, введите название приложения, чтобы не забыть, для какой программы пароль, скопируйте сгенерированный пароль приложения.

Ошибка в модуле Редиректы

Ошибка

 Query error 1048: Column 'informationsystem_group_id' cannot be null. Query: INSERT INTO `redirects` (`deleted`, `site_id`, `old_url`, `type`, `new_url`, `active`, `informationsystem_id`, `informationsystem_item_id`, `informationsystem_group_id`, `shop_id`, `shop_group_id`, `shop_item_id`, `referer`) VALUES (0, '1', '/old_url/123', '1', '', 0, '1', '0', NULL, '1', '0', '0', '')
 35 modules/core/exception.php
 641 modules/core/database/mysql.php
 60 modules/core/querybuilder/statement.php
 1450 modules/core/orm.php
 550 modules/core/entity.php
 532 modules/redirect/model.php
 1520 modules/core/orm.php
 363 modules/redirect/model.php
 578 modules/admin/form/action/controller/type/edit.php
 357 modules/redirect/controller/edit.php
 488 modules/admin/form/action/controller/type/edit.php
 408 modules/redirect/controller/edit.php
 1080 modules/admin/form/controller.php
 196 admin/redirect/index.php
Решения

1. открыть /admin/redirect/index.php
2. найти $groups = $oInformationsystem_Item_Controller_Edit->fillInformationsystemGroup(Core_Array::getGet('informationsystem_id'), 0);
3. заменить на  $groups = array('...') + $oInformationsystem_Item_Controller_Edit->fillInformationsystemGroup(Core_Array::getGet('informationsystem_id'), 0);

4. перекачать архив, заменить файл modules/redirect/controller/edit.php
выполнить запрос
ALTER TABLE `redirects`
    CHANGE COLUMN `informationsystem_id` `informationsystem_id` INT(11) NULL DEFAULT '0' AFTER `site_id`,
    CHANGE COLUMN `informationsystem_item_id` `informationsystem_item_id` INT(11) NULL DEFAULT '0' AFTER `informationsystem_id`,
    CHANGE COLUMN `informationsystem_group_id` `informationsystem_group_id` INT(11) NULL DEFAULT '0' AFTER `informationsystem_item_id`,
    CHANGE COLUMN `shop_id` `shop_id` INT(11) NULL DEFAULT '0' AFTER `referer`,
    CHANGE COLUMN `shop_group_id` `shop_group_id` INT(11) NULL DEFAULT '0' AFTER `shop_id`,
    CHANGE COLUMN `shop_item_id` `shop_item_id` INT(11) NULL DEFAULT '0' AFTER `shop_group_id`;
5.  Перекачайте архив и замените файл modules/redirect/model.php файлом из архива
php

Применение ajax + php

Для того, чтобы понять, нужен ли нам вообще ajax с php, давайте разберемся для чего он может быть полезен. Применение ajax+ php может быть разнообразным, единственное, то что, нельзя конструировать элементы страницы с помощью данной технологии, которые несут в себе релевантность для поисковых систем. Потому что ajax подгружает элементы страницы после ее загрузки при вызове js событий, но как нам известно, поисковые системы не умеют читать javascript кода, поэтому нужно тщательно выбирать где нужно, а где не нужно применять ajax с php.

Где можно применить ajax + php?

  1. Добавление нового комментария
  2. Голосование
  3. Авторизация на сайте
  4. Организация поиска на сайте (автозавершение)
  5. Пошаговая регистрация пользователя на сайте
  6. Подписка на e-mail
  7. Просмотр фотографий

И другие…

Как видите вариантов применения ajax + php масса. То есть, можно применить там, где перезагрузка страницы будет не уместной, где нужно просто обменяться данными с сервером.

Где не стоит применять ajax + php

Мое мнение объективное, может вы думаете иначе, но полагаясь на мой опыт скажу что ajax + php не стоит применять:

  1. Для реализации меню
  2. Реализации вкладок на странице (Например: когда в интернет магазине на странице товара вы видите обзор, информация, комментарии, фото, видео … не нужно делать загрузку данных при переключении данных вкладок.)

И другие негативные примеры, которые могут навредить лучшему ранжированию вашей страницы.

Взаимодействие ajax с php

По сути, технология ajax не может существовать без php скриптов, так как ajax, только отправляет данные на сервер и принимает обратный ответ, при этом не перезагружая страницы. Поэтому правильнее задать вопрос, как связать работу ajax и php. Но об этом мы поговорим в следующем пункте статьи (ajax php пример), а сейчас разберемся с спецификой работы ajax.

Для отправки данных на сервер, нужно создать объект XMLHTTPRequest. С помощью него открыть url (php скрипт), послать на него данные (POST или GET метод), получить ответ, и средствами знаний языка js вывести полученный ответ сервера на монитор (ответом может быть любой фрагмент или элемент страницы сайта).

Для прояснения посмотрите ниже предоставленную схему иллюстрирующую взаимодействие ajax с php.

ajax php взаимодействие

Ajax + php пример

Для примера взаимодействия ajax с php, создадим два файла:

1.       ajax_page.html

2.       get_ajax.php

Сначала рассмотрим пользовательскую сторону приложения, то есть ajax_page.html:


<html>
<head>
<title>Ajax + PHP: пример | sitear.ru</title>
<script Language="JavaScript">
function XmlHttp()
{
var xmlhttp;
try{xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");}
catch(e)
{
 try {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");} 
 catch (E) {xmlhttp = false;}
}
if (!xmlhttp && typeof XMLHttpRequest!='undefined')
{
 xmlhttp = new XMLHttpRequest();
}
  return xmlhttp;
}
 
function ajax(param)
{
                if (window.XMLHttpRequest) req = new XmlHttp();
                method=(!param.method ? "POST" : param.method.toUpperCase());
 
                if(method=="GET")
                {
                               send=null;
                               param.url=param.url+"&ajax=true";
                }
                else
                {
                               send="";
                               for (var i in param.data) send+= i+"="+param.data[i]+"&";
                               send=send+"ajax=true";
                }
 
                req.open(method, param.url, true);
                if(param.statbox)document.getElementById(param.statbox).innerHTML = '<img src="images/wait.gif">';
                req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                req.send(send);
                req.onreadystatechange = function()
                {
                               if (req.readyState == 4 && req.status == 200) //если ответ положительный
                               {
                                               if(param.success)param.success(req.responseText);
                               }
                }
}
</script>
</head>
<body>
<div id="status">
Здесь будем принимать отчеты о работе ajax приложения и ответ сервера.
</div>
                <form action="get_ajax.php">
                <p><b>Поле ввода 1</b></p>
                <p><textarea id="area_1" name="area_1" style="height:50px; width:500px;">Введите свой текст. Например: Я люблю sitear.ru!</textarea></p>
                <p><b>Поле ввода 2</b></p>
                <p><textarea id="area_2" name="area_1" style="height:100px; width:500px;">Произвольный текст... Я тащусь от этой статьи, и хочу подписаться на RSS, что-бы читать такие статьи почаще!!!</textarea></p>
                <input type='button' value='TEST AJAX' onclick='
                               ajax({
                                                               url:"get_ajax.php",
                                                               statbox:"status",
                                                               method:"POST",
                                                               data:
                                                               {
                                                                              first_area:document.getElementById("area_1").value,
                                                                              second_area:document.getElementById("area_2").value
                                                              },
                                                               success:function(data){document.getElementById("status").innerHTML=data;}
                                               })'
                >
                </form>
</body>
</html>

ajax_page.html:

Пример странички ajax_page.html

Разберем javascript сторону данного примера:

XmlHttp() – функция которая создает объект XMLHttpRequest(), она написана максимально компактно и кроссбраузерно.

ajax(param) – наш обработчик при вызове событий (onclick), принимает в массиве paramнеобходимые данные:

url – куда отсылать данные, причем он может быть в таком виде page.php?parameter=value, то есть информация может передаваться по методу GET.

statbox – ид html блока который будет принимать результаты работы ajax + php приложения.

method – метод отправки данных, может быть POST или GET. В нашем примере мы используем POST метод, но в то же время через url можно передавать информацию GET методом.

data – массив передаваемых данных. В нашем примере, данные автоматически берутся из поля 1 и 2, хотя можно просто писать data: {name: "value"}.

success – имя функции или сама функция, которая будет обрабатывать полученные данные (текст).

Вызов функции ajax как вы видите сделан событием onclick=ajax().

Теперь разберем серверную сторону ajax + php приложения, то есть файл get_ajax.php:


<?php 
header("Content-type: text/plain; charset=windows-1251");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
sleep(2);
echo "Ура получилось! Спасибо sitear.ru!<br>";
while(list ($key, $val) = each ($_POST))
{
                $val = iconv("UTF-8","CP1251", $_POST[$key]);
                echo "<b>".$key.":</b> "."<pre>".stripslashes($val)."</pre>";
}
?>

Здесь все гораздо проще. Сначала устанавливаем кодировку выходящих данных, с помощью header. Устанавливаем запрет на кеширование данных. sleep(2) – приостанавливает работу скрипта на 2 секунды, это для того, что бы увидеть анимацию ожидания wait.gif. Выводим полученные данные, при этом читая все элементы массива $_POST и преобразуя их в нужную кодировку (для кириллицы).

Для запуска нашего ajax php приложения загружаем в браузер страничку ajax_page.html

Вот что у меня получилось при нажатии кнопки TEST AJAX :

Ожидание ответа php с сервера

Это ответ, полученный от файла get_ajax.php:

Ответ ajax php приложения

Проверка наличия значения переменной (или константы) в PHP

1. Использование оператора условия if

IF проверяет переменную на равенство NULL или пустую строку и выполнит определенные действия в зависимости от результата проверки.
$variable = "Hello, world!";

if($variable != NULL && $variable != "") {
echo "Variable has a value.";
} else {
echo "Variable is empty.";
}

2. Использование функции defined()

defined — Проверяет, существует ли константа с заданным именем
Пример #1
/* Обратите внимание на кавычки, это важно. Пример проверяет,
 * имя ли константы TEST строка «TEST». */
if (defined('TEST')) {
    echo TEST;
}

Пример #2 Проверка вариантов перечисления (начиная с PHP 8.1.0)

enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}
var_dump(defined('Suit::Hearts')); // bool(true)

3. Использование функции isset()

Функция isset() используется для проверки наличия значения переменной.
$variable = "Hello, world!";

if(isset($variable)) {
echo "Variable has a value.";
} else {
echo "Variable is empty.";
}

4. С помощью функции empty()

Функция empty() проверяет, является ли переменная пустой.
$variable = "Hello, world!";

if(empty($variable)) {
echo "Variable is empty.";
} else {
echo "Variable has a value.";
}

5. Использование оператора трехместного условия

Оператор трехместного условия позволяет нам проверить условие и выбрать одно из двух значений в зависимости от результата проверки.
$variable = "Hello, world!";

$message = (isset($variable) && $variable != "") ? "Variable has a value." : "Variable is empty.";

echo $message;

6. Использование функции is_null()

Функция is_null() проверяет, равна ли переменная значению NULL. Она возвращает true, если переменная равна NULL, и false в противном случае.
$variable = "Hello, world!";

if(is_null($variable)) {
echo "Variable is empty.";
} else {
echo "Variable has a value.";
}
php

Сессии

Сессии позволяют сохранять пользовательские данные между запросами. Хранение сессий в HostCMS осуществляется в базе данных (по умолчанию) или в Redis (с версии HostCMS 6.8.3, используя php-redis версии 2.6.12+).

Доступ к данным сессии

К данным сессии доступ получается через суперглобальный массив $_SESSION.
$value = $_SESSION['foo'];
Если вы не уверены в наличии требуемого значения в сессии, то используйте Core_Array::getSession(), при отсутствии значения метод вернет NULL
$value = Core_Array::getSession('foo');
То же действие, только с возвратом значения по умолчанию 'default' в случае, если индекса foo в сессии нет
$value = Core_Array::getSession('foo', 'default');

Открытие и закрытие сессии

// Открыть сессиюCore_Session::start();

// Записать в сессию
$_SESSION['foo'] = 'bar';

// Закрыть сессиюCore_Session::close();
Открывать сессию необходимо каждый раз перед там, как записать данные в $_SESSION, дополнительно проверять наличие открытой сессии перед вызовом Core_Session::start() не требуется, проверка содержится внутри метода start().
Проверить, запущена ли сессия:
$bStarted = Core_Session::isStarted();
Проверить, запущена ли сессия и активна:
$bAcive = Core_Session::isAcive();
Получить имя сессии:
$session_name = Core_Session::getName();
Запустить сессию только в случае, если она была ранее запущена в предыдущих сеансах:
if (Core_Session::hasSessionId()){
Core_Session::start();
print_r($_SESSION);}

Времени жизни сессии

При старте сессии, время жизни устанавливается равным времени, указанном в session.gc_maxlifetime, изменить время жизни сессии можно до или уже после запуска сессии методом Core_Session::setMaxLifeTime($maxlifetime, $overwrite = FALSE)
// Время жизни сессии в секундах (1 сутки с момента последней активности)Core_Session::setMaxLifeTime(86400);
Время жизни сессии будет установлено в том случае, если оно больше ранее заданного. Чтобы перезаписать время жизни сессии в любом случае, вторым параметром передайте TRUE.

Конфигурационный файл

Указание способа хранения сессий и опций осуществляется в основном конфигурационном файле modules/core/config/config.php в опции session.
При хранении сессии в базе данных задаются 2 параметра:
  • driver — название драйвера;
  • class — имя класса драйвера (необязательный параметр).
'session' => array(
'driver' => 'database',
'class' => 'Core_Session_Database'
),
Для хранения в Redis требуется больше параметров:
  • driver — название драйвера;
  • class — имя класса драйвера (необязательный параметр);
  • server — адрес сервера, по умолчанию 127.0.0.1;
  • port — порт, по умолчанию 6379;
  • auth —авторизационный пароль, если сервер не требует пароль, то не устанавливайте опцию или укажите NULL;
'session' => array(
'driver' => 'phpredis',
'class' => 'Core_Session_Phpredis',
'server' => '127.0.0.1',
'port' => 6379,
'auth' => 'авторизационный пароль'
),

Убрать индексацию модификаций при выгрузке из 1С

Добавить хук в файл bootstrap.php в корне сайта
Core_Event::attach('Shop_Item_Import_Cml_Controller.onAfterImportShopItem', array('HostCMS_Shop_Item_Modification', 'onAfterImportShopItem'));


static public function onAfterImportShopItem($oShop_Item_Import_Cml_Controller, $args)
    {
        list($oShopItem, $oXmlItem) = $args;


if ($oShopItem->modification_id)
{
if ($oShopItem->yandex_market /*|| $yandex_market*/)
{
$oShopItem->yandex_market = 0;
}


if ($oShopItem->indexing /*|| $indexing*/)
{
$oShopItem->indexing = 0;
}


$oShopItem->save();
}
}
Теперь при обмене с 1С, если была создана модификация, то для неё будет сниматься выгрузка в Я.Маркет и индексация.

Хранение и восстановление паролей

Система управления для хранения хэшей паролей использует один из двух стандартных методов хэширования — sha1 (с фиксированной и переменной «солью») и md5 (с фиксированной «солью»).
Изменение «соли» или метода хэширование возможно только до установки системы управления. Потеря «соли» делает неработоспособным все пароли в системе управления.
Указание метода хэширования и «соли» осуществляется в файле modules/core/config/hash.php
<?php
    return array (
    'salt' => '7esinqex',
    'hash' => 'sha1',
   );

Восстановление пароля

  1. Загрузите файл restore_password.php
  2. Разместите файл restore_password.php в корне сайта и вызовите http://адрес_сайта/restore_password.php
  3. Запомните выведенные логин и пароль.
  4. После выполнения файл пытается удалиться самостоятельно, если этого не произошло, удалите файл restore_password.php с сайта вручную.

Не получилось!

При попытке восстановить пароль выдает: "User not found"
Отредактируйте файл, вместо admin укажите Ваш логин.

PHP . restore_password.php

<?php
/**
 * Изменение пароля пользователя admin
 *
 * Порядок использования:
 * 1) Загрузите файл https://www.hostcms.ru/download/install/restore_password.php
 * 2) Разместите файл restore_password.php в корне сайта и вызовите http://адрес_сайта/restore_password.php
 * 3) Запомните выведенные логин и пароль.
 *
 * УДАЛЕНИЕ ФАЙЛА:
 * После выполнения файл пытается удалиться самостоятельно, если этого не произошло,
 * удалите файл restore_password.php с сайта вручную.
 */
require_once('bootstrap.php');
$aMessages = array();
$login = Core_Array::getPost('login', '', 'trim');
if (Core_Array::getPost('apply'))
{
if ($login != '')
{
$oUser = Core_Entity::factory('User')->getByLogin($login);
if ($oUser)
{
$password = Core_Password::get();
$oUser->password = Core_Hash::instance()->hash($password);
if (isset($oUser->active))
{
$oUser->active = 1;
}
$oUser->save();
$aMessages['success'][] = 'New login is: <b>' . htmlspecialchars($login) . '</b>';
$aMessages['success'][] = 'New password is: <b>' . htmlspecialchars($password) . '</b>';
}
else
{
$aMessages['danger'][] = 'User ' . htmlspecialchars($login) . ' not found';
}
}
else
{
$aMessages['danger'][] = 'Users not found!';
}
Core_File::delete(CMS_FOLDER . 'restore_password.php');
}
$aUserOptions = array();
$oUsers = Core_Entity::factory('User');
$oUsers->queryBuilder()
->where('users.active', '=', 1)
->clearOrderBy()
->orderBy('users.login');
isset($oUsers->dismissed) && $oUsers->queryBuilder()
->where('users.dismissed', '=', 0);
$aUsers = $oUsers->findAll(FALSE);
foreach ($aUsers as $oUser)
{
$aUserOptions[] = $oUser->login;
}
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Restore Password</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
.margin-top-10 { margin-top: 10px !important; }
</style>
</head>
<body>
<div class="container">
<form class="form-inline margin-top-10" action="restore_password.php" method="POST">
<div class="row">
<div class="col-xs-2">
<!-- <div class="form-group"> -->
<select name="login" class="form-control">
<?php
foreach ($aUserOptions as $name)
{
$selected = $name == $login
? ' selected="selected"'
: '';
?><option <?php echo $selected?> value="<?php echo $name?>"><?php echo htmlspecialchars($name)?></option><?php
}
?>
</select>
<!-- </div> -->
</div>
<div class="col-xs-10">
<button type="submit" name="apply" value="apply" class="btn btn-info">Сбросить пароль</button>
</div>
</div>
</form>
<?php
foreach ($aMessages as $status => $aMessage)
{
foreach ($aMessage as $sMessage)
{
?><div class="alert alert-<?php echo $status?> margin-top-10" role="alert"><?php echo $sMessage?></div><?php
}
}
?>
</div>
</body>
</html>
php

Экспорт прайс-листа в Excel

Для автоматической генерации прайс-листа в формате Excel необходимо загрузить PHPExcel и распаковать его в корневую директорию.

Обновите код настроек ТДС "Прайс" на следующий:

<?php

$oShop = Core_Entity::factory('Shop', Core_Array::get(Core_Page::instance()->libParams, 'shopId'));

$Shop_Controller_Show = new Shop_Controller_Show($oShop);

$path = Core_Page::instance()->structure->getPath();

$Shop_Controller_Show
	//->pattern(rawurldecode($path) . '({path})(page-{page}/)')
	->pattern(rawurldecode($path) . '({path})({xls})(page-{page}/)')
	->patternExpressions(array(
		'xls' => 'xls\/'
	))
	->addEntity(
		Core::factory('Core_Xml_Entity')
			->name('path')
			->value($path)
	)
	->limit(500)
	->parseUrl();

// Генерация Excel прайса
class HostCMS_Excel extends Core_Servant_Properties
{
	/**
	 * Allowed object properties
	 * @var array
	 */
	protected $_allowedProperties = array(
		'title',
		'filename',
	);

	/**
	 * excelObject
	 * @var object
	 */
	protected $_excelObject = NULL;

	/**
	 * excelSheetObject
	 * @var object
	 */
	protected $_excelSheetObject = NULL;

	/**
	 * excelWriterObject
	 * @var object
	 */
	protected $_excelWriterObject = NULL;

	/**
	 * Shop_Model
	 * @var object
	 */
	protected $_shop = NULL;

	protected $_cell = 2;

	/**
	 * Constructor.
	 */
	public function __construct($objPHPExcel, Shop_Model $oShop)
	{
		parent::__construct();

		$this->_excelObject = $objPHPExcel;

		$this->_shop = $oShop;

		$this->title = 'price';
		$this->filename = 'file';

		// set default font
		$this->_excelObject->getDefaultStyle()->getFont()->setName('Calibri');

		// set default font size
		$this->_excelObject->getDefaultStyle()->getFont()->setSize(10);

		// writer already created the first sheet for us, let's get it
		$this->_excelSheetObject = $this->_excelObject->getActiveSheet();

		// create the writer
		$this->_excelWriterObject = PHPExcel_IOFactory::createWriter($this->_excelObject, "Excel5");

		// autosize the columns
		$this->_excelSheetObject->getColumnDimension('A')->setAutoSize(TRUE);
		$this->_excelSheetObject->getColumnDimension('B')->setAutoSize(TRUE);
		$this->_excelSheetObject->getColumnDimension('C')->setAutoSize(TRUE);
	}

	/**
	 * File output.
	 */
	public function output()
	{
		// rename the sheet
		$this->_excelSheetObject->setTitle($this->title);

		// write header
		$this->header($this->_excelSheetObject);

		$aShop_Groups = $this->fillShopGroup($this->_shop->id, 0);

		foreach ($aShop_Groups as $iShopGroupId => $sShopGroupName)
		{
			$this->_excelSheetObject->getStyle('A' . $this->_cell)->getFont()->setBold(TRUE);

			$this->_excelSheetObject->getCell('A' . $this->_cell)->setValue($sShopGroupName);

			$this->_cell++;

			$this->items(intval($iShopGroupId));
		}

		// Setting the header type
		header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
		header('Content-Disposition: attachment;filename="' . $this->filename . '.xls"');
		header('Cache-Control: max-age=0');

		$this->_excelWriterObject->save('php://output');
	}

	/**
	 * Create header row.
	 */
	public function header($oSheet)
	{
		// let's bold and size the header font and write the header
		// as you can see, we can specify a range of cells, like here: cells from A1 to A4
		$oSheet->getStyle('A1:C1')->getFont()->setBold(TRUE)->setSize(12);

		$oSheet->getCell('A1')->setValue('Наименование');
		$oSheet->getCell('B1')->setValue('Артикул');
		$oSheet->getCell('C1')->setValue('Цена');

		return $oSheet;
	}

	/**
	 * Create items row.
	 */
	public function items($iShopGroupId)
	{
		$offset = 0;
		$limit = 100;

		$sCurrency = $this->_shop->Shop_Currency->name;

		$oShop_Group = $this->_shop->Shop_Groups->getById($iShopGroupId);

		$Shop_Item_Controller = new Shop_Item_Controller();

		if (Core::moduleIsActive('siteuser'))
		{
			$oSiteuser = Core_Entity::factory('Siteuser')->getCurrent();

			if ($oSiteuser)
			{
				$Shop_Item_Controller->siteuser($oSiteuser);
			}
		}

		if (!is_null($oShop_Group->id))
		{
			do {
				$oShop_Items = $oShop_Group->Shop_Items;
				$oShop_Items->queryBuilder()
					->where('shop_items.active', '=', 1)
					->offset($offset)
					->limit($limit);

				$aShop_Items = $oShop_Items->findAll(FALSE);

				foreach ($aShop_Items as $oShop_Item)
				{
					// Shortcut
					$iShortcut = $oShop_Item->shortcut_id;

					if ($iShortcut)
					{
						$oShop_Item = $oShop_Item->Shop_Item;
					}

					$aPrice = $Shop_Item_Controller->getPrices($oShop_Item);

					$price = Shop_Controller::instance()->getCurrencyCoefficientInShopCurrency(
						$oShop_Item->Shop_Currency,
						$oShop_Item->Shop->Shop_Currency) * $aPrice['price_discount'];

					$sPrice = $price . ' ' . $sCurrency;

					$this->_excelSheetObject->getCell('A' . $this->_cell)->setValue($oShop_Item->name);
					$this->_excelSheetObject->getCell('B' . $this->_cell)->setValue($oShop_Item->marking);
					$this->_excelSheetObject->getCell('C' . $this->_cell)->setValue($sPrice);

					$this->_cell++;
				}

				$offset += $limit;
			}
			while (count($aShop_Items));
		}

		return $this;
	}

	/**
	 * Shop groups tree
	 * @var array
	 */
	protected $_aGroupTree = array();

	/**
	 * Build visual representation of group tree
	 * @param int $iShopId shop ID
	 * @param int $iShopGroupParentId parent ID
	 * @param int $aExclude exclude group ID
	 * @param int $iLevel current nesting level
	 * @return array
	 */
	public function fillShopGroup($iShopId, $iShopGroupParentId = 0, $aExclude = array(), $iLevel = 0)
	{
		$iShopId = intval($iShopId);
		$iShopGroupParentId = intval($iShopGroupParentId);
		$iLevel = intval($iLevel);

		if ($iLevel == 0)
		{
			$aTmp = Core_QueryBuilder::select('id', 'parent_id', 'name')
				->from('shop_groups')
				->where('shop_id', '=', $iShopId)
				->where('deleted', '=', 0)
				->where('active', '=', 1)
				->orderBy('sorting')
				->orderBy('name')
				->execute()->asAssoc()->result();

			foreach ($aTmp as $aGroup)
			{
				$this->_aGroupTree[$aGroup['parent_id']][] = $aGroup;
			}
		}

		$aReturn = array();

		if (isset($this->_aGroupTree[$iShopGroupParentId]))
		{
			$countExclude = count($aExclude);
			foreach ($this->_aGroupTree[$iShopGroupParentId] as $childrenGroup)
			{
				if ($countExclude == 0 || !in_array($childrenGroup['id'], $aExclude))
				{
					$aReturn[$childrenGroup['id']] = $childrenGroup['name'];
					$aReturn += $this->fillShopGroup($iShopId, $childrenGroup['id'], $aExclude, $iLevel + 1);
				}
			}
		}

		$iLevel == 0 && $this->_aGroupTree = array();

		return $aReturn;
	}
}
	
if (!empty($Shop_Controller_Show->patternParams['xls']))
{
	require_once(CMS_FOLDER . 'PHPExcel/PHPExcel.php');

	// create new PHPExcel object
	$objPHPExcel = new PHPExcel();

	$HostCMS_Excel = new HostCMS_Excel($objPHPExcel, $oShop);
	$HostCMS_Excel
		->title('Прайс ' . $oShop->name)
		->filename('price')
		->output();
	exit();
}
// /Excel

Core_Page::instance()->object = $Shop_Controller_Show;

Обновите код ТДС "Прайс" на следующий:

<?php

$Shop_Controller_Show = Core_Page::instance()->object;

$Shop_Controller_Show
   ->shopItems()
   ->queryBuilder()
   ->clearOrderBy()
   ->leftJoin('shop_groups', 'shop_groups.id', '=', 'shop_items.shop_group_id')
   ->where('shop_items.active', '=', 1)
   ->open()
   ->where('shop_groups.active', '=', 1)
   ->setOr()
   ->where('shop_groups.active', 'IS', NULL)
   ->where('shop_items.modification_id', '=', 0)
   ->close()
   ->clearOrderBy()
   ->orderBy('shop_items.shop_group_id')
   ->orderBy('shop_items.name');

$Shop_Controller_Show
	->shopGroups()
	->queryBuilder()
	->where('shop_groups.active', '=', 1)
	->clearOrderBy()
	->orderBy('shop_groups.id');

$xslName = Core_Array::get(Core_Page::instance()->libParams, 'xsl');

$Shop_Controller_Show
	->xsl(
		Core_Entity::factory('Xsl')->getByName($xslName)
	)
	->groupsMode('all')
	->itemsProperties(TRUE)
	->group(FALSE)
	->show();

Прайс-лист будет доступен по адресу http://ваш_сайт/shop/price/xls/