Skip to content

Commit ce08a1a

Browse files
committed
Adds UA and UAFullVersionList objects.
1 parent 6739581 commit ce08a1a

File tree

3 files changed

+292
-0
lines changed

3 files changed

+292
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace DevCoding\Object\Internet\Browser;
4+
5+
use DevCoding\Object\System\Version\VersionImmutable;
6+
7+
/**
8+
* Object representing a Client UA Brand.
9+
*
10+
* @see https://wicg.github.io/ua-client-hints/#user-agent-brand
11+
*
12+
* @author AMJones <am@jonesiscoding.com>
13+
* @license https://github.com/deviscoding/objection/blob/main/LICENSE
14+
*
15+
* @package DevCoding\Object\Internet\Browser
16+
*/
17+
class UserAgentBrand
18+
{
19+
/** @var string */
20+
protected $name;
21+
/** @var VersionImmutable */
22+
protected $version;
23+
24+
/**
25+
* @param string $name
26+
* @param VersionImmutable|string $version
27+
*/
28+
public function __construct(string $name, $version)
29+
{
30+
$this->name = $name;
31+
$this->version = $version instanceof VersionImmutable ? $version : new VersionImmutable((string) $version);
32+
}
33+
34+
/**
35+
* @return string
36+
*/
37+
public function __toString()
38+
{
39+
return $this->getName();
40+
}
41+
42+
/**
43+
* @return string
44+
*/
45+
public function getName(): string
46+
{
47+
return $this->name;
48+
}
49+
50+
/**
51+
* @return VersionImmutable
52+
*/
53+
public function getVersion(): VersionImmutable
54+
{
55+
return $this->version;
56+
}
57+
58+
/**
59+
* @param string $name
60+
*
61+
* @return bool
62+
*/
63+
public function isName($name)
64+
{
65+
return strtolower($name) == strtolower($this->getName());
66+
}
67+
}

src/Object/Internet/Header/UA.php

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
namespace DevCoding\Object\Internet\Header;
4+
5+
use DevCoding\Object\Internet\Browser\UserAgentBrand;
6+
use DevCoding\Object\System\Version\VersionImmutable;
7+
8+
/**
9+
* Object representing a Sec-CH-UA header.
10+
*
11+
* @see https://wicg.github.io/ua-client-hints/#iana-ua
12+
*
13+
* @author AMJones <am@jonesiscoding.com>
14+
* @license https://github.com/deviscoding/objection/blob/main/LICENSE
15+
*
16+
* @package DevCoding\Object\Internet\Header
17+
*/
18+
class UA extends AbstractHeader
19+
{
20+
/** @var string[] Array of known brand strings */
21+
const KNOWN = ['Chrome', 'Chromium', 'Edge', 'Edg', 'Firefox', 'Safari', 'Opera'];
22+
/** @var string The key for this header */
23+
const KEY = 'Sec-CH-UA';
24+
25+
/** @var UserAgentBrand[] An array of brand objects, parsed from this header's value */
26+
protected $brands;
27+
28+
/**
29+
* @return string
30+
*/
31+
public function getString()
32+
{
33+
return $this->value;
34+
}
35+
36+
/**
37+
* Evaluates whether the given string is present in the User Agent Brand list.
38+
*
39+
* @param string $string
40+
*
41+
* @return bool
42+
*/
43+
public function isBrandName($string)
44+
{
45+
foreach ($this->getBrands() as $brand)
46+
{
47+
if ($brand->isName($string))
48+
{
49+
return true;
50+
}
51+
}
52+
53+
return false;
54+
}
55+
56+
/**
57+
* Returns an array of UserAgentBrand objects, present within this header's value.
58+
*
59+
* @return UserAgentBrand[]|null
60+
*/
61+
public function getBrands()
62+
{
63+
if (!isset($this->brands))
64+
{
65+
if ($m = $this->getBrandMatches($this->value))
66+
{
67+
$this->brands = [];
68+
foreach ($m as $set)
69+
{
70+
$this->brands[] = new UserAgentBrand(trim($set['brand']), $set['version']);
71+
}
72+
}
73+
}
74+
75+
return $this->brands;
76+
}
77+
78+
/**
79+
* Returns the dominant major version number, parsed from this header's value.
80+
*
81+
* @return int|null
82+
*/
83+
public function getVersion()
84+
{
85+
return ($v = $this->getCommonVersion()) ? $v->getMajor() : null;
86+
}
87+
88+
/**
89+
* Parses the given string, returning an array of array that contain a 'brand' and 'version' string.
90+
*
91+
* @param string $string
92+
*
93+
* @return array[]|null
94+
*/
95+
protected function getBrandMatches($string)
96+
{
97+
$m = [];
98+
if (preg_match_all('/(?<brand>[^"]+)\";\s?v="(?<version>[^"]+)",?/', $string, $m, PREG_SET_ORDER))
99+
{
100+
return $m;
101+
}
102+
103+
return null;
104+
}
105+
106+
/**
107+
* Returns the most common version contained with the UserAgentBrand objects parsed from this header's value. If
108+
* no version within the UA repeats, returns the first value.
109+
*
110+
* @return VersionImmutable|null
111+
*/
112+
protected function getCommonVersion()
113+
{
114+
foreach ($this->getBrands() as $brand)
115+
{
116+
$versions[] = (string) $brand->getVersion();
117+
}
118+
119+
if (!empty($versions))
120+
{
121+
// Prefer the most common value
122+
if ($v = $this->getCommonValue($versions))
123+
{
124+
return new VersionImmutable($v);
125+
}
126+
127+
// If no common value, default to the first value from a known Client UA Brand
128+
foreach ($this->getBrands() as $brand)
129+
{
130+
if (in_array($brand->getName(), static::KNOWN))
131+
{
132+
return $brand->getVersion();
133+
}
134+
}
135+
}
136+
137+
return null;
138+
}
139+
140+
/**
141+
* Returns the most popular value from the given array.
142+
*
143+
* @param array $arr
144+
*
145+
* @return int|string|null
146+
*/
147+
private function getCommonValue($arr)
148+
{
149+
// Get count of each version number represented
150+
$counts = array_count_values($arr);
151+
// Sort the counted values
152+
arsort($counts);
153+
// Only valid if it's more than 1
154+
if (reset($counts) > 1)
155+
{
156+
$keys = array_slice(array_keys($counts), 0, 1, true);
157+
158+
return array_shift($keys);
159+
}
160+
161+
return null;
162+
}
163+
164+
/**
165+
* @return string[]
166+
*/
167+
protected function getKeys()
168+
{
169+
return [static::KEY];
170+
}
171+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace DevCoding\Object\Internet\Header;
4+
5+
use DevCoding\Object\Internet\Browser\UserAgentBrand;
6+
use DevCoding\Object\System\Version\VersionImmutable;
7+
8+
/**
9+
* Object representing a Sec-CH-UA-Full-Version-List header.
10+
*
11+
* @see https://wicg.github.io/ua-client-hints/#iana-full-version-list
12+
*
13+
* @author AMJones <am@jonesiscoding.com>
14+
* @license https://github.com/deviscoding/objection/blob/main/LICENSE
15+
*
16+
* @package DevCoding\Object\Internet\Header
17+
*/
18+
class UAFullVersionList extends UA
19+
{
20+
const KEY = 'Sec-CH-UA-Full-Version-List';
21+
22+
/**
23+
* Returns an object representing the most common version contained within the header.
24+
*
25+
* @return VersionImmutable|null
26+
*/
27+
public function getVersion()
28+
{
29+
return $this->getCommonVersion();
30+
}
31+
32+
/**
33+
* Returns an array of UserAgentBrand objects, as parsed from the header's value.
34+
*
35+
* @return array|UserAgentBrand[]
36+
*/
37+
public function getBrands()
38+
{
39+
if (!isset($this->brands))
40+
{
41+
if ($m = $this->getBrandMatches($this->value))
42+
{
43+
$this->brands = [];
44+
foreach ($m as $set)
45+
{
46+
$version = new VersionImmutable($set['version']);
47+
$this->brands[] = new UserAgentBrand(trim($set['brand']), (string) $version);
48+
}
49+
}
50+
}
51+
52+
return $this->brands;
53+
}
54+
}

0 commit comments

Comments
 (0)