From 8653accc60d074b40c2f775c5e3c50b5a79f189e Mon Sep 17 00:00:00 2001 From: awjrichards Date: Wed, 9 May 2012 14:26:31 -0700 Subject: [PATCH] Integrating mobile device detection class into MW core Change-Id: Ic7bbeba746ea37a92c1df5dbaa1fb4fd08da130c --- CREDITS | 1 + RELEASE-NOTES-1.20 | 1 + includes/AutoLoader.php | 1 + includes/DeviceDetection.php | 425 ++++++++++++++++++ .../phpunit/includes/DeviceDetectionTest.php | 40 ++ 5 files changed, 468 insertions(+) create mode 100644 includes/DeviceDetection.php create mode 100644 tests/phpunit/includes/DeviceDetectionTest.php diff --git a/CREDITS b/CREDITS index 5b876f18a0..dcd4a43309 100644 --- a/CREDITS +++ b/CREDITS @@ -7,6 +7,7 @@ following names for their contribution to the product. * Alex Z. * Alexandre Emsenhuber * Andrew Garrett +* Arthur Richards * Aryeh Gregor * Antoine Musso * Brian Wolff diff --git a/RELEASE-NOTES-1.20 b/RELEASE-NOTES-1.20 index 78c7c95919..d36f840f6b 100644 --- a/RELEASE-NOTES-1.20 +++ b/RELEASE-NOTES-1.20 @@ -56,6 +56,7 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki. * (bug 5445) Now remove autoblocks when a user is unblocked. * Added $wgLogExceptionBacktrace, on by default, to allow logging of exception backtraces. +* Added device detection for determining device capabilities === Bug fixes in 1.20 === * (bug 30245) Use the correct way to construct a log page title. diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 6710796a19..0c951b3a5c 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -56,6 +56,7 @@ $wgAutoloadLocalClasses = array( 'DeferredUpdates' => 'includes/DeferredUpdates.php', 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php', 'DerivativeRequest' => 'includes/WebRequest.php', + 'DeviceDetection' => 'includes/DeviceDetection.php', 'DiffHistoryBlob' => 'includes/HistoryBlob.php', 'DoubleReplacer' => 'includes/StringUtils.php', diff --git a/includes/DeviceDetection.php b/includes/DeviceDetection.php new file mode 100644 index 0000000000..cc6866777a --- /dev/null +++ b/includes/DeviceDetection.php @@ -0,0 +1,425 @@ + array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'default', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'capable' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'default', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'webkit' => array ( + 'view_format' => 'html', + 'search_bar' => 'webkit', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'webkit', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => false, + 'parser' => 'html', + 'disable_links' => true, + ), + 'ie' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'default', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => false, + 'parser' => 'html', + 'disable_links' => true, + ), + 'android' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'android', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => false, + 'parser' => 'html', + 'disable_links' => true, + ), + 'iphone' => array ( + 'view_format' => 'html', + 'search_bar' => 'webkit', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'iphone', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => false, + 'parser' => 'html', + 'disable_links' => true, + ), + 'iphone2' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'iphone2', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'native_iphone' => array ( + 'view_format' => 'html', + 'search_bar' => false, + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'default', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => false, + 'parser' => 'html', + 'disable_links' => false, + ), + 'palm_pre' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'palm_pre', + 'supports_javascript' => true, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'kindle' => array ( + 'view_format' => 'html', + 'search_bar' => 'kindle', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'kindle', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'kindle2' => array ( + 'view_format' => 'html', + 'search_bar' => 'kindle', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'kindle', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'blackberry' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'blackberry', + 'supports_javascript' => true, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'blackberry-lt5' => array ( + 'view_format' => 'html', + 'search_bar' => 'default', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'blackberry', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'netfront' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'simple', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'wap2' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'simple', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'psp' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'psp', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'ps3' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'simple', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'wii' => array ( + 'view_format' => 'html', + 'search_bar' => 'wii', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'wii', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'operamini' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'operamini', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'operamobile' => array ( + 'view_format' => 'html', + 'search_bar' => 'simple', + 'footmenu' => 'simple', + 'with_layout' => 'application', + 'css_file_name' => 'operamobile', + 'supports_javascript' => true, + 'supports_jquery' => true, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'nokia' => array ( + 'view_format' => 'html', + 'search_bar' => 'webkit', + 'footmenu' => 'default', + 'with_layout' => 'application', + 'css_file_name' => 'nokia', + 'supports_javascript' => true, + 'supports_jquery' => false, + 'disable_zoom' => true, + 'parser' => 'html', + 'disable_links' => true, + ), + 'wml' => array ( + 'view_format' => 'wml', + 'search_bar' => 'wml', + 'supports_javascript' => false, + 'supports_jquery' => false, + 'parser' => 'wml', + ), + ); + return $formats; + } + + /** + * @param $userAgent + * @param string $acceptHeader + * @return array + */ + public function detectDevice( $userAgent, $acceptHeader = '' ) { + $formatName = $this->detectFormatName( $userAgent, $acceptHeader ); + return $this->getDevice( $formatName ); + } + + /** + * @param $formatName + * @return array + */ + public function getDevice( $formatName ) { + $format = $this->getAvailableFormats(); + return ( isset( $format[$formatName] ) ) ? $format[$formatName] : array(); + } + + /** + * @param $userAgent string + * @param $acceptHeader string + * @return string + */ + public function detectFormatName( $userAgent, $acceptHeader = '' ) { + $formatName = ''; + + if ( preg_match( '/Android/', $userAgent ) ) { + $formatName = 'android'; + if ( strpos( $userAgent, 'Opera Mini' ) !== false ) { + $formatName = 'operamini'; + } + } else if ( preg_match( '/MSIE 9.0/', $userAgent ) || + preg_match( '/MSIE 8.0/', $userAgent ) ) { + $formatName = 'ie'; + } else if( preg_match( '/MSIE/', $userAgent ) ) { + $formatName = 'html'; + } else if ( strpos( $userAgent, 'Opera Mobi' ) !== false ) { + $formatName = 'operamobile'; + } elseif ( preg_match( '/iPad.* Safari/', $userAgent ) ) { + $formatName = 'iphone'; + } elseif ( preg_match( '/iPhone.* Safari/', $userAgent ) ) { + if ( strpos( $userAgent, 'iPhone OS 2' ) !== false ) { + $formatName = 'iphone2'; + } else { + $formatName = 'iphone'; + } + } elseif ( preg_match( '/iPhone/', $userAgent ) ) { + if ( strpos( $userAgent, 'Opera' ) !== false ) { + $formatName = 'operamini'; + } else { + $formatName = 'native_iphone'; + } + } elseif ( preg_match( '/WebKit/', $userAgent ) ) { + if ( preg_match( '/Series60/', $userAgent ) ) { + $formatName = 'nokia'; + } elseif ( preg_match( '/webOS/', $userAgent ) ) { + $formatName = 'palm_pre'; + } else { + $formatName = 'webkit'; + } + } elseif ( preg_match( '/Opera/', $userAgent ) ) { + if ( strpos( $userAgent, 'Nintendo Wii' ) !== false ) { + $formatName = 'wii'; + } elseif ( strpos( $userAgent, 'Opera Mini' ) !== false ) { + $formatName = 'operamini'; + } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) { + $formatName = 'iphone'; + } else { + $formatName = 'webkit'; + } + } elseif ( preg_match( '/Kindle\/1.0/', $userAgent ) ) { + $formatName = 'kindle'; + } elseif ( preg_match( '/Kindle\/2.0/', $userAgent ) ) { + $formatName = 'kindle2'; + } elseif ( preg_match( '/Firefox/', $userAgent ) ) { + $formatName = 'capable'; + } elseif ( preg_match( '/NetFront/', $userAgent ) ) { + $formatName = 'netfront'; + } elseif ( preg_match( '/SEMC-Browser/', $userAgent ) ) { + $formatName = 'wap2'; + } elseif ( preg_match( '/Series60/', $userAgent ) ) { + $formatName = 'wap2'; + } elseif ( preg_match( '/PlayStation Portable/', $userAgent ) ) { + $formatName = 'psp'; + } elseif ( preg_match( '/PLAYSTATION 3/', $userAgent ) ) { + $formatName = 'ps3'; + } elseif ( preg_match( '/SAMSUNG/', $userAgent ) ) { + $formatName = 'capable'; + } elseif ( preg_match( '/BlackBerry/', $userAgent ) ) { + if( preg_match( '/BlackBerry[^\/]*\/[1-4]\./', $userAgent ) ) { + $formatName = 'blackberry-lt5'; + } else { + $formatName = 'blackberry'; + } + } + + if ( $formatName === '' ) { + if ( strpos( $acceptHeader, 'application/vnd.wap.xhtml+xml' ) !== false ) { + // Should be wap2 + $formatName = 'html'; + } elseif ( strpos( $acceptHeader, 'vnd.wap.wml' ) !== false ) { + $formatName = 'wml'; + } else { + $formatName = 'html'; + } + } + return $formatName; + } + + /** + * @return array: List of all device-specific stylesheets + */ + public function getCssFiles() { + $devices = $this->getAvailableFormats(); + $files = array(); + foreach ( $devices as $dev ) { + if ( isset( $dev['css_file_name'] ) ) { + $files[] = $dev['css_file_name']; + } + } + return array_unique( $files ); + } +} diff --git a/tests/phpunit/includes/DeviceDetectionTest.php b/tests/phpunit/includes/DeviceDetectionTest.php new file mode 100644 index 0000000000..0e15653206 --- /dev/null +++ b/tests/phpunit/includes/DeviceDetectionTest.php @@ -0,0 +1,40 @@ +assertEquals( $format, $detector->detectFormatName( $userAgent ) ); + } + + public function provideTestFormatName() { + return array( + array( 'android', 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ), + array( 'iphone2', 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), + array( 'iphone', 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), + array( 'nokia', 'Mozilla/5.0 (SymbianOS/9.1; U; [en]; SymbianOS/91 Series60/3.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413' ), + array( 'palm_pre', 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0' ), + array( 'wii', 'Opera/9.00 (Nintendo Wii; U; ; 1309-9; en)' ), + array( 'operamini', 'Opera/9.50 (J2ME/MIDP; Opera Mini/4.0.10031/298; U; en)' ), + array( 'operamobile', 'Opera/9.51 Beta (Microsoft Windows; PPC; Opera Mobi/1718; U; en)' ), + array( 'kindle', 'Mozilla/4.0 (compatible; Linux 2.6.10) NetFront/3.3 Kindle/1.0 (screen 600x800)' ), + array( 'kindle2', 'Mozilla/4.0 (compatible; Linux 2.6.22) NetFront/3.4 Kindle/2.0 (screen 824x1200; rotate)' ), + array( 'capable', 'Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1' ), + array( 'netfront', 'Mozilla/4.08 (Windows; Mobile Content Viewer/1.0) NetFront/3.2' ), + array( 'wap2', 'SonyEricssonK608i/R2L/SN356841000828910 Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + array( 'wap2', 'NokiaN73-2/3.0-630.0.2 Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + array( 'psp', 'Mozilla/4.0 (PSP (PlayStation Portable); 2.00)' ), + array( 'ps3', 'Mozilla/5.0 (PLAYSTATION 3; 1.00)' ), + array( 'ie', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' ), + array( 'ie', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)' ), + array( 'blackberry', 'BlackBerry9300/5.0.0.716 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/133' ), + array( 'blackberry-lt5', 'BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), + ); + } +} -- 2.20.1