1*d5ef99ddSAndreas Gohr<?php 2*d5ef99ddSAndreas Gohr 3*d5ef99ddSAndreas Gohr/** 4*d5ef99ddSAndreas Gohr * Device Detector - The Universal Device Detection library for parsing User Agents 5*d5ef99ddSAndreas Gohr * 6*d5ef99ddSAndreas Gohr * @link https://matomo.org 7*d5ef99ddSAndreas Gohr * 8*d5ef99ddSAndreas Gohr * @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later 9*d5ef99ddSAndreas Gohr */ 10*d5ef99ddSAndreas Gohr 11*d5ef99ddSAndreas Gohrdeclare(strict_types=1); 12*d5ef99ddSAndreas Gohr 13*d5ef99ddSAndreas Gohrnamespace DeviceDetector; 14*d5ef99ddSAndreas Gohr 15*d5ef99ddSAndreas Gohrclass ClientHints 16*d5ef99ddSAndreas Gohr{ 17*d5ef99ddSAndreas Gohr /** 18*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Arch` header field: The underlying architecture's instruction set 19*d5ef99ddSAndreas Gohr * 20*d5ef99ddSAndreas Gohr * @var string 21*d5ef99ddSAndreas Gohr */ 22*d5ef99ddSAndreas Gohr protected $architecture = ''; 23*d5ef99ddSAndreas Gohr 24*d5ef99ddSAndreas Gohr /** 25*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Bitness` header field: The underlying architecture's bitness 26*d5ef99ddSAndreas Gohr * 27*d5ef99ddSAndreas Gohr * @var string 28*d5ef99ddSAndreas Gohr */ 29*d5ef99ddSAndreas Gohr protected $bitness = ''; 30*d5ef99ddSAndreas Gohr 31*d5ef99ddSAndreas Gohr /** 32*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Mobile` header field: whether the user agent should receive a specifically "mobile" UX 33*d5ef99ddSAndreas Gohr * 34*d5ef99ddSAndreas Gohr * @var bool 35*d5ef99ddSAndreas Gohr */ 36*d5ef99ddSAndreas Gohr protected $mobile = false; 37*d5ef99ddSAndreas Gohr 38*d5ef99ddSAndreas Gohr /** 39*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Model` header field: the user agent's underlying device model 40*d5ef99ddSAndreas Gohr * 41*d5ef99ddSAndreas Gohr * @var string 42*d5ef99ddSAndreas Gohr */ 43*d5ef99ddSAndreas Gohr protected $model = ''; 44*d5ef99ddSAndreas Gohr 45*d5ef99ddSAndreas Gohr /** 46*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Platform` header field: the platform's brand 47*d5ef99ddSAndreas Gohr * 48*d5ef99ddSAndreas Gohr * @var string 49*d5ef99ddSAndreas Gohr */ 50*d5ef99ddSAndreas Gohr protected $platform = ''; 51*d5ef99ddSAndreas Gohr 52*d5ef99ddSAndreas Gohr /** 53*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Platform-Version` header field: the platform's major version 54*d5ef99ddSAndreas Gohr * 55*d5ef99ddSAndreas Gohr * @var string 56*d5ef99ddSAndreas Gohr */ 57*d5ef99ddSAndreas Gohr protected $platformVersion = ''; 58*d5ef99ddSAndreas Gohr 59*d5ef99ddSAndreas Gohr /** 60*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Full-Version` header field: the platform's major version 61*d5ef99ddSAndreas Gohr * 62*d5ef99ddSAndreas Gohr * @var string 63*d5ef99ddSAndreas Gohr */ 64*d5ef99ddSAndreas Gohr protected $uaFullVersion = ''; 65*d5ef99ddSAndreas Gohr 66*d5ef99ddSAndreas Gohr /** 67*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Full-Version-List` header field: the full version for each brand in its brand list 68*d5ef99ddSAndreas Gohr * 69*d5ef99ddSAndreas Gohr * @var array 70*d5ef99ddSAndreas Gohr */ 71*d5ef99ddSAndreas Gohr protected $fullVersionList = []; 72*d5ef99ddSAndreas Gohr 73*d5ef99ddSAndreas Gohr /** 74*d5ef99ddSAndreas Gohr * Represents `x-requested-with` header field: Android app id 75*d5ef99ddSAndreas Gohr * @var string 76*d5ef99ddSAndreas Gohr */ 77*d5ef99ddSAndreas Gohr protected $app = ''; 78*d5ef99ddSAndreas Gohr 79*d5ef99ddSAndreas Gohr /** 80*d5ef99ddSAndreas Gohr * Represents `Sec-CH-UA-Form-Factors` header field: form factor device type name 81*d5ef99ddSAndreas Gohr * 82*d5ef99ddSAndreas Gohr * @var array 83*d5ef99ddSAndreas Gohr */ 84*d5ef99ddSAndreas Gohr protected $formFactors = []; 85*d5ef99ddSAndreas Gohr 86*d5ef99ddSAndreas Gohr /** 87*d5ef99ddSAndreas Gohr * Constructor 88*d5ef99ddSAndreas Gohr * 89*d5ef99ddSAndreas Gohr * @param string $model `Sec-CH-UA-Model` header field 90*d5ef99ddSAndreas Gohr * @param string $platform `Sec-CH-UA-Platform` header field 91*d5ef99ddSAndreas Gohr * @param string $platformVersion `Sec-CH-UA-Platform-Version` header field 92*d5ef99ddSAndreas Gohr * @param string $uaFullVersion `Sec-CH-UA-Full-Version` header field 93*d5ef99ddSAndreas Gohr * @param array $fullVersionList `Sec-CH-UA-Full-Version-List` header field 94*d5ef99ddSAndreas Gohr * @param bool $mobile `Sec-CH-UA-Mobile` header field 95*d5ef99ddSAndreas Gohr * @param string $architecture `Sec-CH-UA-Arch` header field 96*d5ef99ddSAndreas Gohr * @param string $bitness `Sec-CH-UA-Bitness` 97*d5ef99ddSAndreas Gohr * @param string $app `HTTP_X-REQUESTED-WITH` 98*d5ef99ddSAndreas Gohr * @param array $formFactors `Sec-CH-UA-Form-Factors` header field 99*d5ef99ddSAndreas Gohr */ 100*d5ef99ddSAndreas Gohr public function __construct(string $model = '', string $platform = '', string $platformVersion = '', string $uaFullVersion = '', array $fullVersionList = [], bool $mobile = false, string $architecture = '', string $bitness = '', string $app = '', array $formFactors = []) // phpcs:ignore Generic.Files.LineLength 101*d5ef99ddSAndreas Gohr { 102*d5ef99ddSAndreas Gohr $this->model = $model; 103*d5ef99ddSAndreas Gohr $this->platform = $platform; 104*d5ef99ddSAndreas Gohr $this->platformVersion = $platformVersion; 105*d5ef99ddSAndreas Gohr $this->uaFullVersion = $uaFullVersion; 106*d5ef99ddSAndreas Gohr $this->fullVersionList = $fullVersionList; 107*d5ef99ddSAndreas Gohr $this->mobile = $mobile; 108*d5ef99ddSAndreas Gohr $this->architecture = $architecture; 109*d5ef99ddSAndreas Gohr $this->bitness = $bitness; 110*d5ef99ddSAndreas Gohr $this->app = $app; 111*d5ef99ddSAndreas Gohr $this->formFactors = $formFactors; 112*d5ef99ddSAndreas Gohr } 113*d5ef99ddSAndreas Gohr 114*d5ef99ddSAndreas Gohr /** 115*d5ef99ddSAndreas Gohr * Magic method to directly allow accessing the protected properties 116*d5ef99ddSAndreas Gohr * 117*d5ef99ddSAndreas Gohr * @param string $variable 118*d5ef99ddSAndreas Gohr * 119*d5ef99ddSAndreas Gohr * @return mixed 120*d5ef99ddSAndreas Gohr * 121*d5ef99ddSAndreas Gohr * @throws \Exception 122*d5ef99ddSAndreas Gohr */ 123*d5ef99ddSAndreas Gohr public function __get(string $variable) 124*d5ef99ddSAndreas Gohr { 125*d5ef99ddSAndreas Gohr if (\property_exists($this, $variable)) { 126*d5ef99ddSAndreas Gohr return $this->$variable; 127*d5ef99ddSAndreas Gohr } 128*d5ef99ddSAndreas Gohr 129*d5ef99ddSAndreas Gohr throw new \Exception('Invalid ClientHint property requested.'); 130*d5ef99ddSAndreas Gohr } 131*d5ef99ddSAndreas Gohr 132*d5ef99ddSAndreas Gohr /** 133*d5ef99ddSAndreas Gohr * Returns if the client hints 134*d5ef99ddSAndreas Gohr * 135*d5ef99ddSAndreas Gohr * @return bool 136*d5ef99ddSAndreas Gohr */ 137*d5ef99ddSAndreas Gohr public function isMobile(): bool 138*d5ef99ddSAndreas Gohr { 139*d5ef99ddSAndreas Gohr return $this->mobile; 140*d5ef99ddSAndreas Gohr } 141*d5ef99ddSAndreas Gohr 142*d5ef99ddSAndreas Gohr /** 143*d5ef99ddSAndreas Gohr * Returns the Architecture 144*d5ef99ddSAndreas Gohr * 145*d5ef99ddSAndreas Gohr * @return string 146*d5ef99ddSAndreas Gohr */ 147*d5ef99ddSAndreas Gohr public function getArchitecture(): string 148*d5ef99ddSAndreas Gohr { 149*d5ef99ddSAndreas Gohr return $this->architecture; 150*d5ef99ddSAndreas Gohr } 151*d5ef99ddSAndreas Gohr 152*d5ef99ddSAndreas Gohr /** 153*d5ef99ddSAndreas Gohr * Returns the Bitness 154*d5ef99ddSAndreas Gohr * 155*d5ef99ddSAndreas Gohr * @return string 156*d5ef99ddSAndreas Gohr */ 157*d5ef99ddSAndreas Gohr public function getBitness(): string 158*d5ef99ddSAndreas Gohr { 159*d5ef99ddSAndreas Gohr return $this->bitness; 160*d5ef99ddSAndreas Gohr } 161*d5ef99ddSAndreas Gohr 162*d5ef99ddSAndreas Gohr /** 163*d5ef99ddSAndreas Gohr * Returns the device model 164*d5ef99ddSAndreas Gohr * 165*d5ef99ddSAndreas Gohr * @return string 166*d5ef99ddSAndreas Gohr */ 167*d5ef99ddSAndreas Gohr public function getModel(): string 168*d5ef99ddSAndreas Gohr { 169*d5ef99ddSAndreas Gohr return $this->model; 170*d5ef99ddSAndreas Gohr } 171*d5ef99ddSAndreas Gohr 172*d5ef99ddSAndreas Gohr /** 173*d5ef99ddSAndreas Gohr * Returns the Operating System 174*d5ef99ddSAndreas Gohr * 175*d5ef99ddSAndreas Gohr * @return string 176*d5ef99ddSAndreas Gohr */ 177*d5ef99ddSAndreas Gohr public function getOperatingSystem(): string 178*d5ef99ddSAndreas Gohr { 179*d5ef99ddSAndreas Gohr return $this->platform; 180*d5ef99ddSAndreas Gohr } 181*d5ef99ddSAndreas Gohr 182*d5ef99ddSAndreas Gohr /** 183*d5ef99ddSAndreas Gohr * Returns the Operating System version 184*d5ef99ddSAndreas Gohr * 185*d5ef99ddSAndreas Gohr * @return string 186*d5ef99ddSAndreas Gohr */ 187*d5ef99ddSAndreas Gohr public function getOperatingSystemVersion(): string 188*d5ef99ddSAndreas Gohr { 189*d5ef99ddSAndreas Gohr return $this->platformVersion; 190*d5ef99ddSAndreas Gohr } 191*d5ef99ddSAndreas Gohr 192*d5ef99ddSAndreas Gohr /** 193*d5ef99ddSAndreas Gohr * Returns the Browser name 194*d5ef99ddSAndreas Gohr * 195*d5ef99ddSAndreas Gohr * @return array<string, string> 196*d5ef99ddSAndreas Gohr */ 197*d5ef99ddSAndreas Gohr public function getBrandList(): array 198*d5ef99ddSAndreas Gohr { 199*d5ef99ddSAndreas Gohr if (\is_array($this->fullVersionList) && \count($this->fullVersionList)) { 200*d5ef99ddSAndreas Gohr $brands = \array_column($this->fullVersionList, 'brand'); 201*d5ef99ddSAndreas Gohr $versions = \array_column($this->fullVersionList, 'version'); 202*d5ef99ddSAndreas Gohr 203*d5ef99ddSAndreas Gohr if (\count($brands) === \count($versions)) { 204*d5ef99ddSAndreas Gohr // @phpstan-ignore-next-line 205*d5ef99ddSAndreas Gohr return \array_combine($brands, $versions); 206*d5ef99ddSAndreas Gohr } 207*d5ef99ddSAndreas Gohr } 208*d5ef99ddSAndreas Gohr 209*d5ef99ddSAndreas Gohr return []; 210*d5ef99ddSAndreas Gohr } 211*d5ef99ddSAndreas Gohr 212*d5ef99ddSAndreas Gohr /** 213*d5ef99ddSAndreas Gohr * Returns the Browser version 214*d5ef99ddSAndreas Gohr * 215*d5ef99ddSAndreas Gohr * @return string 216*d5ef99ddSAndreas Gohr */ 217*d5ef99ddSAndreas Gohr public function getBrandVersion(): string 218*d5ef99ddSAndreas Gohr { 219*d5ef99ddSAndreas Gohr if (!empty($this->uaFullVersion)) { 220*d5ef99ddSAndreas Gohr return $this->uaFullVersion; 221*d5ef99ddSAndreas Gohr } 222*d5ef99ddSAndreas Gohr 223*d5ef99ddSAndreas Gohr return ''; 224*d5ef99ddSAndreas Gohr } 225*d5ef99ddSAndreas Gohr 226*d5ef99ddSAndreas Gohr /** 227*d5ef99ddSAndreas Gohr * Returns the Android app id 228*d5ef99ddSAndreas Gohr * 229*d5ef99ddSAndreas Gohr * @return string 230*d5ef99ddSAndreas Gohr */ 231*d5ef99ddSAndreas Gohr public function getApp(): string 232*d5ef99ddSAndreas Gohr { 233*d5ef99ddSAndreas Gohr return $this->app; 234*d5ef99ddSAndreas Gohr } 235*d5ef99ddSAndreas Gohr 236*d5ef99ddSAndreas Gohr /** 237*d5ef99ddSAndreas Gohr * Returns the formFactor device type name 238*d5ef99ddSAndreas Gohr * 239*d5ef99ddSAndreas Gohr * @return array 240*d5ef99ddSAndreas Gohr */ 241*d5ef99ddSAndreas Gohr public function getFormFactors(): array 242*d5ef99ddSAndreas Gohr { 243*d5ef99ddSAndreas Gohr return $this->formFactors; 244*d5ef99ddSAndreas Gohr } 245*d5ef99ddSAndreas Gohr 246*d5ef99ddSAndreas Gohr /** 247*d5ef99ddSAndreas Gohr * Factory method to easily instantiate this class using an array containing all available (client hint) headers 248*d5ef99ddSAndreas Gohr * 249*d5ef99ddSAndreas Gohr * @param array $headers 250*d5ef99ddSAndreas Gohr * 251*d5ef99ddSAndreas Gohr * @return ClientHints 252*d5ef99ddSAndreas Gohr */ 253*d5ef99ddSAndreas Gohr public static function factory(array $headers): ClientHints 254*d5ef99ddSAndreas Gohr { 255*d5ef99ddSAndreas Gohr $model = $platform = $platformVersion = $uaFullVersion = $architecture = $bitness = ''; 256*d5ef99ddSAndreas Gohr $app = ''; 257*d5ef99ddSAndreas Gohr $mobile = false; 258*d5ef99ddSAndreas Gohr $fullVersionList = []; 259*d5ef99ddSAndreas Gohr $formFactors = []; 260*d5ef99ddSAndreas Gohr 261*d5ef99ddSAndreas Gohr foreach ($headers as $name => $value) { 262*d5ef99ddSAndreas Gohr if (empty($value)) { 263*d5ef99ddSAndreas Gohr continue; 264*d5ef99ddSAndreas Gohr } 265*d5ef99ddSAndreas Gohr 266*d5ef99ddSAndreas Gohr switch (\str_replace('_', '-', \strtolower((string) $name))) { 267*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-arch': 268*d5ef99ddSAndreas Gohr case 'sec-ch-ua-arch': 269*d5ef99ddSAndreas Gohr case 'arch': 270*d5ef99ddSAndreas Gohr case 'architecture': 271*d5ef99ddSAndreas Gohr $architecture = \trim($value, '"'); 272*d5ef99ddSAndreas Gohr 273*d5ef99ddSAndreas Gohr break; 274*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-bitness': 275*d5ef99ddSAndreas Gohr case 'sec-ch-ua-bitness': 276*d5ef99ddSAndreas Gohr case 'bitness': 277*d5ef99ddSAndreas Gohr $bitness = \trim($value, '"'); 278*d5ef99ddSAndreas Gohr 279*d5ef99ddSAndreas Gohr break; 280*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-mobile': 281*d5ef99ddSAndreas Gohr case 'sec-ch-ua-mobile': 282*d5ef99ddSAndreas Gohr case 'mobile': 283*d5ef99ddSAndreas Gohr $mobile = true === $value || '1' === $value || '?1' === $value; 284*d5ef99ddSAndreas Gohr 285*d5ef99ddSAndreas Gohr break; 286*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-model': 287*d5ef99ddSAndreas Gohr case 'sec-ch-ua-model': 288*d5ef99ddSAndreas Gohr case 'model': 289*d5ef99ddSAndreas Gohr $model = \trim($value, '"'); 290*d5ef99ddSAndreas Gohr 291*d5ef99ddSAndreas Gohr break; 292*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-full-version': 293*d5ef99ddSAndreas Gohr case 'sec-ch-ua-full-version': 294*d5ef99ddSAndreas Gohr case 'uafullversion': 295*d5ef99ddSAndreas Gohr $uaFullVersion = \trim($value, '"'); 296*d5ef99ddSAndreas Gohr 297*d5ef99ddSAndreas Gohr break; 298*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-platform': 299*d5ef99ddSAndreas Gohr case 'sec-ch-ua-platform': 300*d5ef99ddSAndreas Gohr case 'platform': 301*d5ef99ddSAndreas Gohr $platform = \trim($value, '"'); 302*d5ef99ddSAndreas Gohr 303*d5ef99ddSAndreas Gohr break; 304*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-platform-version': 305*d5ef99ddSAndreas Gohr case 'sec-ch-ua-platform-version': 306*d5ef99ddSAndreas Gohr case 'platformversion': 307*d5ef99ddSAndreas Gohr $platformVersion = \trim($value, '"'); 308*d5ef99ddSAndreas Gohr 309*d5ef99ddSAndreas Gohr break; 310*d5ef99ddSAndreas Gohr case 'brands': 311*d5ef99ddSAndreas Gohr if (!empty($fullVersionList)) { 312*d5ef99ddSAndreas Gohr break; 313*d5ef99ddSAndreas Gohr } 314*d5ef99ddSAndreas Gohr // use this only if no other header already set the list 315*d5ef99ddSAndreas Gohr case 'fullversionlist': 316*d5ef99ddSAndreas Gohr $fullVersionList = \is_array($value) ? $value : $fullVersionList; 317*d5ef99ddSAndreas Gohr 318*d5ef99ddSAndreas Gohr break; 319*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua': 320*d5ef99ddSAndreas Gohr case 'sec-ch-ua': 321*d5ef99ddSAndreas Gohr if (!empty($fullVersionList)) { 322*d5ef99ddSAndreas Gohr break; 323*d5ef99ddSAndreas Gohr } 324*d5ef99ddSAndreas Gohr // use this only if no other header already set the list 325*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-full-version-list': 326*d5ef99ddSAndreas Gohr case 'sec-ch-ua-full-version-list': 327*d5ef99ddSAndreas Gohr $reg = '/^"([^"]+)"; ?v="([^"]+)"(?:, )?/'; 328*d5ef99ddSAndreas Gohr $list = []; 329*d5ef99ddSAndreas Gohr 330*d5ef99ddSAndreas Gohr while (\preg_match($reg, $value, $matches)) { 331*d5ef99ddSAndreas Gohr $list[] = ['brand' => $matches[1], 'version' => $matches[2]]; 332*d5ef99ddSAndreas Gohr $value = \substr($value, \strlen($matches[0])); 333*d5ef99ddSAndreas Gohr } 334*d5ef99ddSAndreas Gohr 335*d5ef99ddSAndreas Gohr if (\count($list)) { 336*d5ef99ddSAndreas Gohr $fullVersionList = $list; 337*d5ef99ddSAndreas Gohr } 338*d5ef99ddSAndreas Gohr 339*d5ef99ddSAndreas Gohr break; 340*d5ef99ddSAndreas Gohr case 'http-x-requested-with': 341*d5ef99ddSAndreas Gohr case 'x-requested-with': 342*d5ef99ddSAndreas Gohr if ('xmlhttprequest' !== \strtolower($value)) { 343*d5ef99ddSAndreas Gohr $app = $value; 344*d5ef99ddSAndreas Gohr } 345*d5ef99ddSAndreas Gohr 346*d5ef99ddSAndreas Gohr break; 347*d5ef99ddSAndreas Gohr case 'formfactors': 348*d5ef99ddSAndreas Gohr case 'http-sec-ch-ua-form-factors': 349*d5ef99ddSAndreas Gohr case 'sec-ch-ua-form-factors': 350*d5ef99ddSAndreas Gohr if (\is_array($value)) { 351*d5ef99ddSAndreas Gohr $formFactors = \array_map('\strtolower', $value); 352*d5ef99ddSAndreas Gohr } elseif (\preg_match_all('~"([a-z]+)"~i', \strtolower($value), $matches)) { 353*d5ef99ddSAndreas Gohr $formFactors = $matches[1]; 354*d5ef99ddSAndreas Gohr } 355*d5ef99ddSAndreas Gohr 356*d5ef99ddSAndreas Gohr break; 357*d5ef99ddSAndreas Gohr } 358*d5ef99ddSAndreas Gohr } 359*d5ef99ddSAndreas Gohr 360*d5ef99ddSAndreas Gohr return new self( 361*d5ef99ddSAndreas Gohr $model, 362*d5ef99ddSAndreas Gohr $platform, 363*d5ef99ddSAndreas Gohr $platformVersion, 364*d5ef99ddSAndreas Gohr $uaFullVersion, 365*d5ef99ddSAndreas Gohr $fullVersionList, 366*d5ef99ddSAndreas Gohr $mobile, 367*d5ef99ddSAndreas Gohr $architecture, 368*d5ef99ddSAndreas Gohr $bitness, 369*d5ef99ddSAndreas Gohr $app, 370*d5ef99ddSAndreas Gohr $formFactors 371*d5ef99ddSAndreas Gohr ); 372*d5ef99ddSAndreas Gohr } 373*d5ef99ddSAndreas Gohr} 374