Skip to content

Commit

Permalink
Merge pull request #66 from andreaselia/fix-l11
Browse files Browse the repository at this point in the history
Revert "Revert "Copy over Agent functionality to allow Laravel 11 Sup…
  • Loading branch information
andreaselia authored Jun 6, 2024
2 parents bef7133 + d5c45e6 commit beb7d3e
Show file tree
Hide file tree
Showing 7 changed files with 683 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/vendor
/.phpunit.cache
composer.lock
.phpunit.result.cache
.phpunit.cache/test-results
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"illuminate/http": "^8.0|^9.0|^10.0|^11.0",
"illuminate/view": "^8.0|^9.0|^10.0|^11.0",
"jenssegers/agent": "^2.6",
"doctrine/dbal": "^3.0"
"jaybizzle/crawler-detect": "^1.2",
"doctrine/dbal": "^3.0",
"mobiledetect/mobiledetectlib": "^2.7.6"
},
"autoload": {
"psr-4": {
Expand Down
382 changes: 382 additions & 0 deletions src/Agent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
<?php

namespace AndreasElia\Analytics;

use BadMethodCallException;
use Jaybizzle\CrawlerDetect\CrawlerDetect;
use Mobile_Detect;

class Agent extends Mobile_Detect
{
/** @var array */
protected static $desktopDevices = [
'Macintosh' => 'Macintosh',
];

/** @var array */
protected static $additionalOperatingSystems = [
'Windows' => 'Windows',
'Windows NT' => 'Windows NT',
'OS X' => 'Mac OS X',
'Debian' => 'Debian',
'Ubuntu' => 'Ubuntu',
'Macintosh' => 'PPC',
'OpenBSD' => 'OpenBSD',
'Linux' => 'Linux',
'ChromeOS' => 'CrOS',
];

/** @var array */
protected static $additionalBrowsers = [
'Opera Mini' => 'Opera Mini',
'Opera' => 'Opera|OPR',
'Edge' => 'Edge|Edg',
'Coc Coc' => 'coc_coc_browser',
'UCBrowser' => 'UCBrowser',
'Vivaldi' => 'Vivaldi',
'Chrome' => 'Chrome',
'Firefox' => 'Firefox',
'Safari' => 'Safari',
'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+',
'Netscape' => 'Netscape',
'Mozilla' => 'Mozilla',
'WeChat' => 'MicroMessenger',
];

/** @var array */
protected static $additionalProperties = [
// Operating systems
'Windows' => 'Windows NT [VER]',
'Windows NT' => 'Windows NT [VER]',
'OS X' => 'OS X [VER]',
'BlackBerryOS' => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
'AndroidOS' => 'Android [VER]',
'ChromeOS' => 'CrOS x86_64 [VER]',

// Browsers
'Opera Mini' => 'Opera Mini/[VER]',
'Opera' => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]', 'Opera [VER]'],
'Netscape' => 'Netscape/[VER]',
'Mozilla' => 'rv:[VER]',
'IE' => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'rv:[VER]'],
'Edge' => ['Edge/[VER]', 'Edg/[VER]'],
'Vivaldi' => 'Vivaldi/[VER]',
'Coc Coc' => 'coc_coc_browser/[VER]',
];

/** @var CrawlerDetect */
protected static $crawlerDetect;

/** @return array */
public static function getDetectionRulesExtended()
{
static $rules;

if (! $rules) {
$rules = static::mergeRules(
static::$desktopDevices, // NEW
static::$phoneDevices,
static::$tabletDevices,
static::$operatingSystems,
static::$additionalOperatingSystems, // NEW
static::$browsers,
static::$additionalBrowsers, // NEW
static::$utilities
);
}

return $rules;
}

public function getRules()
{
if ($this->detectionType === static::DETECTION_TYPE_EXTENDED) {
return static::getDetectionRulesExtended();
}

return static::getMobileDetectionRules();
}

/** @return CrawlerDetect */
public function getCrawlerDetect()
{
if (static::$crawlerDetect === null) {
static::$crawlerDetect = new CrawlerDetect();
}

return static::$crawlerDetect;
}

public static function getBrowsers()
{
return static::mergeRules(
static::$additionalBrowsers,
static::$browsers
);
}

public static function getOperatingSystems()
{
return static::mergeRules(
static::$operatingSystems,
static::$additionalOperatingSystems
);
}

public static function getPlatforms()
{
return static::mergeRules(
static::$operatingSystems,
static::$additionalOperatingSystems
);
}

public static function getDesktopDevices()
{
return static::$desktopDevices;
}

public static function getProperties()
{
return static::mergeRules(
static::$additionalProperties,
static::$properties
);
}

/**
* @param string $acceptLanguage
* @return array
*/
public function languages($acceptLanguage = null)
{
if ($acceptLanguage === null) {
$acceptLanguage = $this->getHttpHeader('HTTP_ACCEPT_LANGUAGE');
}

if (! $acceptLanguage) {
return [];
}

$languages = [];

// Parse accept language string.
foreach (explode(',', $acceptLanguage) as $piece) {
$parts = explode(';', $piece);
$language = strtolower($parts[0]);
$priority = empty($parts[1]) ? 1. : floatval(str_replace('q=', '', $parts[1]));

$languages[$language] = $priority;
}

// Sort languages by priority.
arsort($languages);

return array_keys($languages);
}

/**
* @param array $rules
* @param string|null $userAgent
* @return string|bool
*/
protected function findDetectionRulesAgainstUA(array $rules, $userAgent = null)
{
// Loop given rules
foreach ($rules as $key => $regex) {
if (empty($regex)) {
continue;
}

// Check match
if ($this->match($regex, $userAgent)) {
return $key ?: reset($this->matchesArray);
}
}

return false;
}

/**
* @param string|null $userAgent
* @return string|bool
*/
public function browser($userAgent = null)
{
return $this->findDetectionRulesAgainstUA(static::getBrowsers(), $userAgent);
}

/**
* @param string|null $userAgent
* @return string|bool
*/
public function platform($userAgent = null)
{
return $this->findDetectionRulesAgainstUA(static::getPlatforms(), $userAgent);
}

/**
* @param string|null $userAgent
* @return string|bool
*/
public function device($userAgent = null)
{
$rules = static::mergeRules(
static::getDesktopDevices(),
static::getPhoneDevices(),
static::getTabletDevices(),
static::getUtilities()
);

return $this->findDetectionRulesAgainstUA($rules, $userAgent);
}

/**
* @param string|null $userAgent deprecated
* @param array $httpHeaders deprecated
* @return bool
*/
public function isDesktop($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_DESKTOP_VIEWER', $cfHeaders)) {
return $cfHeaders['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'] === 'true';
}
}

return ! $this->isMobile($userAgent, $httpHeaders) && ! $this->isTablet($userAgent, $httpHeaders) && ! $this->isRobot($userAgent);
}

/**
* @param string|null $userAgent deprecated
* @param array $httpHeaders deprecated
* @return bool
*/
public function isPhone($userAgent = null, $httpHeaders = null)
{
return $this->isMobile($userAgent, $httpHeaders) && ! $this->isTablet($userAgent, $httpHeaders);
}

/**
* @param string|null $userAgent
* @return string|bool
*/
public function robot($userAgent = null)
{
if ($this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent)) {
return ucfirst($this->getCrawlerDetect()->getMatches());
}

return false;
}

/**
* @param string|null $userAgent
* @return bool
*/
public function isRobot($userAgent = null)
{
return $this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent);
}

/**
* @param null $userAgent
* @param null $httpHeaders
* @return string
*/
public function deviceType($userAgent = null, $httpHeaders = null)
{
if ($this->isDesktop($userAgent, $httpHeaders)) {
return 'desktop';
} elseif ($this->isPhone($userAgent, $httpHeaders)) {
return 'phone';
} elseif ($this->isTablet($userAgent, $httpHeaders)) {
return 'tablet';
} elseif ($this->isRobot($userAgent)) {
return 'robot';
}

return 'other';
}

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) {
if (is_array($propertyMatchString)) {
$propertyMatchString = implode('|', $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;
}

/**
* @param array $all
* @return array
*/
protected static function mergeRules(...$all)
{
$merged = [];

foreach ($all as $rules) {
foreach ($rules as $key => $value) {
if (empty($merged[$key])) {
$merged[$key] = $value;
} elseif (is_array($merged[$key])) {
$merged[$key][] = $value;
} else {
$merged[$key] .= '|'.$value;
}
}
}

return $merged;
}

/** @inheritdoc */
public function __call($name, $arguments)
{
// Make sure the name starts with 'is', otherwise
if (strpos($name, 'is') !== 0) {
throw new BadMethodCallException("No such method exists: $name");
}

$this->setDetectionType(self::DETECTION_TYPE_EXTENDED);

$key = substr($name, 2);

return $this->matchUAAgainstKey($key);
}
}
Loading

0 comments on commit beb7d3e

Please sign in to comment.