src/Version/Macos.php
<?php
/**
* This file is part of the browser-detector package.
*
* Copyright (c) 2012-2024, Thomas Mueller <mimmi20@live.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types = 1);
namespace BrowserDetector\Version;
use BrowserDetector\Version\Exception\NotNumericException;
use MacosBuild\Exception\NotFoundException;
use MacosBuild\MacosBuildInterface;
use Psr\Log\LoggerInterface;
use UnexpectedValueException;
use function array_key_exists;
use function mb_strtolower;
use function preg_match;
use function str_contains;
use function str_replace;
final class Macos implements VersionFactoryInterface
{
public const SEARCHES = ['Mac OS X Version', 'Mac OS X v', 'Mac OS X', 'OS X', 'os=mac '];
private const DARWIN_MAP = [
'/darwin\/24\.2/i' => '15.2.0',
'/darwin\/24\.1/i' => '15.1.0',
'/darwin\/24/i' => '15.0.0',
'/darwin\/23\.2/i' => '14.2.0',
'/darwin\/23\.1/i' => '14.1.0',
'/darwin\/23/i' => '14.0.0',
'/darwin\/22\.6/i' => '13.5.0',
'/darwin\/22\.5/i' => '13.4.0',
'/darwin\/22\.4/i' => '13.3.0',
'/darwin\/22\.3/i' => '13.2.0',
'/darwin\/22\.2/i' => '13.1.0',
'/darwin\/22/i' => '13.0.0',
'/darwin\/21\.6/i' => '12.5.0',
'/darwin\/21\.5/i' => '12.4.0',
'/darwin\/21\.4/i' => '12.3.0',
'/darwin\/21\.3/i' => '12.2.0',
'/darwin\/21\.2/i' => '12.1.0',
'/darwin\/21\.1/i' => '12.0.1',
'/darwin\/21/i' => '12.0.0',
'/darwin\/20\.6/i' => '11.5.0',
'/darwin\/20\.5/i' => '11.4.0',
'/darwin\/20\.4/i' => '11.3.0',
'/darwin\/20\.3/i' => '11.2.0',
'/darwin\/20\.2/i' => '11.1.0',
'/darwin\/20/i' => '11.0.0',
'/darwin\/19/i' => '10.15.0',
'/darwin\/18\.[67]/i' => '10.14.6',
'/darwin\/18\.5/i' => '10.14.5',
'/darwin\/18\.4/i' => '10.14.4',
'/darwin\/18\.3/i' => '10.14.3',
'/darwin\/18\.2/i' => '10.14.2',
'/darwin\/18\.1/i' => '10.14.1',
'/darwin\/18/i' => '10.14.0',
'/darwin\/17\.6/i' => '10.13.6',
'/darwin\/17\.5/i' => '10.13.5',
'/darwin\/17\.4/i' => '10.13.4',
'/darwin\/17\.3/i' => '10.13.3',
'/darwin\/17\.2/i' => '10.13.2',
'/darwin\/17\.1/i' => '10.13.1',
'/darwin\/17/i' => '10.13.0',
'/darwin\/16\.6/i' => '10.12.6',
'/darwin\/16\.5/i' => '10.12.5',
'/darwin\/16\.4/i' => '10.12.4',
'/darwin\/16\.3/i' => '10.12.3',
'/darwin\/16\.2/i' => '10.12.2',
'/darwin\/16\.1/i' => '10.12.1',
'/darwin\/16/i' => '10.12.0',
'/darwin\/15\.6/i' => '10.11.6',
'/darwin\/15\.5/i' => '10.11.5',
'/darwin\/15\.4/i' => '10.11.4',
'/darwin\/15\.3/i' => '10.11.3',
'/darwin\/15\.2/i' => '10.11.2',
'/darwin\/15\.1/i' => '10.11.1',
'/darwin\/15/i' => '10.11.0',
'/darwin\/14\.5/i' => '10.10.5',
'/darwin\/14\.4/i' => '10.10.4',
'/darwin\/14\.3/i' => '10.10.3',
'/darwin\/14\.2/i' => '10.10.2',
'/darwin\/14\.1/i' => '10.10.1',
'/darwin\/14/i' => '10.10.0',
'/darwin\/13\.5/i' => '10.9.5',
'/darwin\/13\.4/i' => '10.9.4',
'/darwin\/13\.3/i' => '10.9.3',
'/darwin\/13\.2/i' => '10.9.2',
'/darwin\/13\.1/i' => '10.9.1',
'/darwin\/13/i' => '10.9.0',
'/darwin\/12\.5/i' => '10.8.5',
'/darwin\/12\.4/i' => '10.8.4',
'/darwin\/12\.3/i' => '10.8.3',
'/darwin\/12\.2/i' => '10.8.2',
'/darwin\/12\.1/i' => '10.8.1',
'/darwin\/12/i' => '10.8.0',
'/darwin\/11\.4\.2/i' => '10.7.5',
'/darwin\/11\.4/i' => '10.7.4',
'/darwin\/11\.3/i' => '10.7.3',
'/darwin\/11\.2/i' => '10.7.2',
'/darwin\/11\.1/i' => '10.7.1',
'/darwin\/11/i' => '10.7.0',
'/darwin\/10\.8/i' => '10.6.8',
'/darwin\/10\.7/i' => '10.6.7',
'/darwin\/10\.6/i' => '10.6.6',
'/darwin\/10\.5/i' => '10.6.5',
'/darwin\/10\.4/i' => '10.6.4',
'/darwin\/10\.3/i' => '10.6.3',
'/darwin\/10\.2/i' => '10.6.2',
'/darwin\/10\.1/i' => '10.6.1',
'/darwin\/10/i' => '10.6.0',
'/darwin\/9\.8/i' => '10.5.8',
'/darwin\/9\.7/i' => '10.5.7',
'/darwin\/9\.6/i' => '10.5.6',
'/darwin\/9\.5/i' => '10.5.5',
'/darwin\/9\.4/i' => '10.5.4',
'/darwin\/9\.3/i' => '10.5.3',
'/darwin\/9\.2/i' => '10.5.2',
'/darwin\/9\.1/i' => '10.5.1',
'/darwin\/9/i' => '10.5.0',
'/darwin\/8\.11/i' => '10.4.11',
'/darwin\/8\.10/i' => '10.4.10',
'/darwin\/8\.9/i' => '10.4.9',
'/darwin\/8\.8/i' => '10.4.8',
'/darwin\/8\.7/i' => '10.4.7',
'/darwin\/8\.6/i' => '10.4.6',
'/darwin\/8\.5/i' => '10.4.5',
'/darwin\/8\.4/i' => '10.4.4',
'/darwin\/8\.3/i' => '10.4.3',
'/darwin\/8\.2/i' => '10.4.2',
'/darwin\/8\.1/i' => '10.4.1',
'/darwin\/8/i' => '10.4.0',
'/darwin\/7\.9/i' => '10.3.9',
'/darwin\/7\.8/i' => '10.3.8',
'/darwin\/7\.7/i' => '10.3.7',
'/darwin\/7\.6/i' => '10.3.6',
'/darwin\/7\.5/i' => '10.3.5',
'/darwin\/7\.4/i' => '10.3.4',
'/darwin\/7\.3/i' => '10.3.3',
'/darwin\/7\.2/i' => '10.3.2',
'/darwin\/7\.1/i' => '10.3.1',
'/darwin\/7/i' => '10.3.0',
'/darwin\/6\.8/i' => '10.2.8',
'/darwin\/6\.7/i' => '10.2.7',
'/darwin\/6\.6/i' => '10.2.6',
'/darwin\/6\.5/i' => '10.2.5',
'/darwin\/6\.4/i' => '10.2.4',
'/darwin\/6\.3/i' => '10.2.3',
'/darwin\/6\.2/i' => '10.2.2',
'/darwin\/6\.1/i' => '10.2.1',
'/darwin\/6/i' => '10.2.0',
'/darwin\/5\.5/i' => '10.1.5',
'/darwin\/5\.4/i' => '10.1.4',
'/darwin\/5\.3/i' => '10.1.3',
'/darwin\/5\.2/i' => '10.1.2',
'/darwin\/5\.1/i' => '10.1.1',
'/darwin\/1\.4\.1/i' => '10.1.0',
'/darwin\/1\.3\.1/i' => '10.0.0',
];
/** @throws void */
public function __construct(
private readonly LoggerInterface $logger,
private readonly VersionBuilderInterface $versionBuilder,
private readonly MacosBuildInterface $macosBuild,
) {
// nothing to do
}
/**
* returns the version of the operating system/platform
*
* @throws UnexpectedValueException
*/
public function detectVersion(string $useragent): VersionInterface
{
$doMatch = preg_match(
'/\((?:build )?(?P<build>\d+[A-Z]\d+(?:[a-z])?)\)/i',
$useragent,
$matches,
);
if ($doMatch) {
try {
$buildVersion = $this->macosBuild->getVersion($matches['build']);
} catch (NotFoundException) {
$buildVersion = false;
}
if ($buildVersion !== false) {
try {
return $this->versionBuilder->set($buildVersion);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
}
}
$doMatch = preg_match(
'/coremedia v\d+\.\d+\.\d+\.(?P<build>\d+[A-Z]\d+(?:[a-z])?)/i',
$useragent,
$matches,
);
if ($doMatch) {
try {
$buildVersion = $this->macosBuild->getVersion($matches['build']);
} catch (NotFoundException) {
$buildVersion = false;
}
if ($buildVersion !== false) {
try {
return $this->versionBuilder->set($buildVersion);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
}
}
if (str_contains(mb_strtolower($useragent), 'darwin')) {
foreach (self::DARWIN_MAP as $rule => $version) {
if (!preg_match($rule, $useragent)) {
continue;
}
try {
return $this->versionBuilder->set($version);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
}
}
try {
$detectedVersion = $this->versionBuilder->detectVersion(
str_replace(',', '.', $useragent),
self::SEARCHES,
);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
$versionNumber = $detectedVersion->getVersion(VersionInterface::IGNORE_MINOR);
if ($versionNumber !== null) {
if (
preg_match('/(?P<major>\d{2})(?P<minor>\d{2})(?P<micro>\d)/', $versionNumber, $versions)
) {
try {
return $this->versionBuilder->set(
$versions['major'] . '.' . $versions['minor'] . '.' . $versions['micro'],
);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
}
if (
preg_match('/(?P<major>\d{2})(?P<minor>\d)(?P<micro>\d)?/', $versionNumber, $versions)
) {
$version = $versions['major'] . '.' . $versions['minor'];
if (array_key_exists('micro', $versions)) {
$version .= '.' . $versions['micro'];
}
try {
return $this->versionBuilder->set($version);
} catch (NotNumericException $e) {
$this->logger->info($e);
return new NullVersion();
}
}
}
return $detectedVersion;
}
}