国内流行的内容管理系统(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.

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