国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 line
11KB

  1. <?php
  2. if (!defined('DEDEINC')) exit('dedebiz');
  3. require_once(DEDEINC."/libraries/crawlerdetect.class.php");
  4. require_once(DEDEINC."/libraries/mobiledetect.class.php");
  5. // copyright https://github.com/jenssegers/agent
  6. use BadMethodCallException;
  7. class Agent extends Mobile_Detect
  8. {
  9. /**
  10. * List of desktop devices.
  11. * @var array
  12. */
  13. protected static $desktopDevices = [
  14. 'Macintosh' => 'Macintosh',
  15. ];
  16. /**
  17. * List of additional operating systems.
  18. * @var array
  19. */
  20. protected static $additionalOperatingSystems = [
  21. 'Windows' => 'Windows',
  22. 'Windows NT' => 'Windows NT',
  23. 'OS X' => 'Mac OS X',
  24. 'Debian' => 'Debian',
  25. 'Ubuntu' => 'Ubuntu',
  26. 'Macintosh' => 'PPC',
  27. 'OpenBSD' => 'OpenBSD',
  28. 'Linux' => 'Linux',
  29. 'ChromeOS' => 'CrOS',
  30. ];
  31. /**
  32. * List of additional browsers.
  33. * @var array
  34. */
  35. protected static $additionalBrowsers = [
  36. 'Opera Mini' => 'Opera Mini',
  37. 'Opera' => 'Opera|OPR',
  38. 'Edge' => 'Edge|Edg',
  39. 'Coc Coc' => 'coc_coc_browser',
  40. 'UCBrowser' => 'UCBrowser',
  41. 'Vivaldi' => 'Vivaldi',
  42. 'Chrome' => 'Chrome',
  43. 'Firefox' => 'Firefox',
  44. 'Safari' => 'Safari',
  45. 'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+',
  46. 'Netscape' => 'Netscape',
  47. 'Mozilla' => 'Mozilla',
  48. 'WeChat' => 'MicroMessenger',
  49. ];
  50. /**
  51. * List of additional properties.
  52. * @var array
  53. */
  54. protected static $additionalProperties = [
  55. // Operating systems
  56. 'Windows' => 'Windows NT [VER]',
  57. 'Windows NT' => 'Windows NT [VER]',
  58. 'OS X' => 'OS X [VER]',
  59. 'BlackBerryOS' => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
  60. 'AndroidOS' => 'Android [VER]',
  61. 'ChromeOS' => 'CrOS x86_64 [VER]',
  62. // Browsers
  63. 'Opera Mini' => 'Opera Mini/[VER]',
  64. 'Opera' => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]', 'Opera [VER]'],
  65. 'Netscape' => 'Netscape/[VER]',
  66. 'Mozilla' => 'rv:[VER]',
  67. 'IE' => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'rv:[VER]'],
  68. 'Edge' => ['Edge/[VER]', 'Edg/[VER]'],
  69. 'Vivaldi' => 'Vivaldi/[VER]',
  70. 'Coc Coc' => 'coc_coc_browser/[VER]',
  71. ];
  72. /**
  73. * @var CrawlerDetect
  74. */
  75. protected static $crawlerDetect;
  76. /**
  77. * Get all detection rules. These rules include the additional
  78. * platforms and browsers and utilities.
  79. * @return array
  80. */
  81. public static function getDetectionRulesExtended()
  82. {
  83. static $rules;
  84. if (!$rules) {
  85. $rules = static::mergeRules(
  86. static::$desktopDevices, // NEW
  87. static::$phoneDevices,
  88. static::$tabletDevices,
  89. static::$operatingSystems,
  90. static::$additionalOperatingSystems, // NEW
  91. static::$browsers,
  92. static::$additionalBrowsers, // NEW
  93. static::$utilities
  94. );
  95. }
  96. return $rules;
  97. }
  98. public function getRules()
  99. {
  100. if ($this->detectionType === static::DETECTION_TYPE_EXTENDED) {
  101. return static::getDetectionRulesExtended();
  102. }
  103. return static::getMobileDetectionRules();
  104. }
  105. /**
  106. * @return CrawlerDetect
  107. */
  108. public function getCrawlerDetect()
  109. {
  110. if (static::$crawlerDetect === null) {
  111. static::$crawlerDetect = new CrawlerDetect();
  112. }
  113. return static::$crawlerDetect;
  114. }
  115. public static function getBrowsers()
  116. {
  117. return static::mergeRules(
  118. static::$additionalBrowsers,
  119. static::$browsers
  120. );
  121. }
  122. public static function getOperatingSystems()
  123. {
  124. return static::mergeRules(
  125. static::$operatingSystems,
  126. static::$additionalOperatingSystems
  127. );
  128. }
  129. public static function getPlatforms()
  130. {
  131. return static::mergeRules(
  132. static::$operatingSystems,
  133. static::$additionalOperatingSystems
  134. );
  135. }
  136. public static function getDesktopDevices()
  137. {
  138. return static::$desktopDevices;
  139. }
  140. public static function getProperties()
  141. {
  142. return static::mergeRules(
  143. static::$additionalProperties,
  144. static::$properties
  145. );
  146. }
  147. /**
  148. * Get accept languages.
  149. * @param string $acceptLanguage
  150. * @return array
  151. */
  152. public function languages($acceptLanguage = null)
  153. {
  154. if ($acceptLanguage === null) {
  155. $acceptLanguage = $this->getHttpHeader('HTTP_ACCEPT_LANGUAGE');
  156. }
  157. if (!$acceptLanguage) {
  158. return [];
  159. }
  160. $languages = [];
  161. // Parse accept language string.
  162. foreach (explode(',', $acceptLanguage) as $piece) {
  163. $parts = explode(';', $piece);
  164. $language = strtolower($parts[0]);
  165. $priority = empty($parts[1]) ? 1. : floatval(str_replace('q=', '', $parts[1]));
  166. $languages[$language] = $priority;
  167. }
  168. // Sort languages by priority.
  169. arsort($languages);
  170. return array_keys($languages);
  171. }
  172. /**
  173. * Match a detection rule and return the matched key.
  174. * @param array $rules
  175. * @param string|null $userAgent
  176. * @return string|bool
  177. */
  178. protected function findDetectionRulesAgainstUA(array $rules, $userAgent = null)
  179. {
  180. // Loop given rules
  181. foreach ($rules as $key => $regex) {
  182. if (empty($regex)) {
  183. continue;
  184. }
  185. // Check match
  186. if ($this->match($regex, $userAgent)) {
  187. $val = (array)$this->matchesArray;
  188. return $key ?: reset($val);
  189. }
  190. }
  191. return false;
  192. }
  193. /**
  194. * Get the browser name.
  195. * @param string|null $userAgent
  196. * @return string|bool
  197. */
  198. public function browser($userAgent = null)
  199. {
  200. return $this->findDetectionRulesAgainstUA(static::getBrowsers(), $userAgent);
  201. }
  202. /**
  203. * Get the platform name.
  204. * @param string|null $userAgent
  205. * @return string|bool
  206. */
  207. public function platform($userAgent = null)
  208. {
  209. return $this->findDetectionRulesAgainstUA(static::getPlatforms(), $userAgent);
  210. }
  211. /**
  212. * Get the device name.
  213. * @param string|null $userAgent
  214. * @return string|bool
  215. */
  216. public function device($userAgent = null)
  217. {
  218. $rules = static::mergeRules(
  219. static::getDesktopDevices(),
  220. static::getPhoneDevices(),
  221. static::getTabletDevices(),
  222. static::getUtilities()
  223. );
  224. return $this->findDetectionRulesAgainstUA($rules, $userAgent);
  225. }
  226. /**
  227. * Check if the device is a desktop computer.
  228. * @param string|null $userAgent deprecated
  229. * @param array $httpHeaders deprecated
  230. * @return bool
  231. */
  232. public function isDesktop($userAgent = null, $httpHeaders = null)
  233. {
  234. // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
  235. if ($this->getUserAgent() === 'Amazon CloudFront') {
  236. $cfHeaders = $this->getCfHeaders();
  237. if(array_key_exists('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER', $cfHeaders)) {
  238. return $cfHeaders['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER'] === 'true';
  239. }
  240. }
  241. return !$this->isMobile($userAgent, $httpHeaders) && !$this->isTablet($userAgent, $httpHeaders) && !$this->isRobot($userAgent);
  242. }
  243. /**
  244. * Check if the device is a mobile phone.
  245. * @param string|null $userAgent deprecated
  246. * @param array $httpHeaders deprecated
  247. * @return bool
  248. */
  249. public function isPhone($userAgent = null, $httpHeaders = null)
  250. {
  251. return $this->isMobile($userAgent, $httpHeaders) && !$this->isTablet($userAgent, $httpHeaders);
  252. }
  253. /**
  254. * Get the robot name.
  255. * @param string|null $userAgent
  256. * @return string|bool
  257. */
  258. public function robot($userAgent = null)
  259. {
  260. if ($this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent)) {
  261. return ucfirst($this->getCrawlerDetect()->getMatches());
  262. }
  263. return false;
  264. }
  265. /**
  266. * Check if device is a robot.
  267. * @param string|null $userAgent
  268. * @return bool
  269. */
  270. public function isRobot($userAgent = null)
  271. {
  272. return $this->getCrawlerDetect()->isCrawler($userAgent ?: $this->userAgent);
  273. }
  274. /**
  275. * Get the device type
  276. * @param null $userAgent
  277. * @param null $httpHeaders
  278. * @return string
  279. */
  280. public function deviceType($userAgent = null, $httpHeaders = null)
  281. {
  282. if ($this->isDesktop($userAgent, $httpHeaders)) {
  283. return "desktop";
  284. } elseif ($this->isPhone($userAgent, $httpHeaders)) {
  285. return "phone";
  286. } elseif ($this->isTablet($userAgent, $httpHeaders)) {
  287. return "tablet";
  288. } elseif ($this->isRobot($userAgent)) {
  289. return "robot";
  290. }
  291. return "other";
  292. }
  293. public function version($propertyName, $type = self::VERSION_TYPE_STRING)
  294. {
  295. if (empty($propertyName)) {
  296. return false;
  297. }
  298. // set the $type to the default if we don't recognize the type
  299. if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) {
  300. $type = self::VERSION_TYPE_STRING;
  301. }
  302. $properties = self::getProperties();
  303. // Check if the property exists in the properties array.
  304. if (true === isset($properties[$propertyName])) {
  305. // Prepare the pattern to be matched.
  306. // Make sure we always deal with an array (string is converted).
  307. $properties[$propertyName] = (array) $properties[$propertyName];
  308. foreach ($properties[$propertyName] as $propertyMatchString) {
  309. if (is_array($propertyMatchString)) {
  310. $propertyMatchString = implode("|", $propertyMatchString);
  311. }
  312. $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString);
  313. // Identify and extract the version.
  314. preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);
  315. if (false === empty($match[1])) {
  316. $version = ($type === self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);
  317. return $version;
  318. }
  319. }
  320. }
  321. return false;
  322. }
  323. /**
  324. * Merge multiple rules into one array.
  325. * @param array $all
  326. * @return array
  327. */
  328. protected static function mergeRules(...$all)
  329. {
  330. $merged = [];
  331. foreach ($all as $rules) {
  332. foreach ($rules as $key => $value) {
  333. if (empty($merged[$key])) {
  334. $merged[$key] = $value;
  335. } elseif (is_array($merged[$key])) {
  336. $merged[$key][] = $value;
  337. } else {
  338. $merged[$key] .= '|'.$value;
  339. }
  340. }
  341. }
  342. return $merged;
  343. }
  344. /**
  345. * @inheritdoc
  346. */
  347. public function __call($name, $arguments)
  348. {
  349. // Make sure the name starts with 'is', otherwise
  350. if (strpos($name, 'is') !== 0) {
  351. throw new BadMethodCallException("No such method exists: $name");
  352. }
  353. $this->setDetectionType(self::DETECTION_TYPE_EXTENDED);
  354. $key = substr($name, 2);
  355. return $this->matchUAAgainstKey($key);
  356. }
  357. }