3 * Mobile device detection code
5 * Copyright © 2011 Patrick Reilly
6 * http://www.mediawiki.org/
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * http://www.gnu.org/copyleft/gpl.html
27 * Base for classes describing devices and their capabilities
30 interface IDeviceProperties
{
32 * @return string: 'html' or 'wml'
39 function supportsJavaScript();
44 function supportsJQuery();
49 function disableZoom();
55 interface IDeviceDetector
{
58 * @param string $acceptHeader
59 * @return IDeviceProperties
61 function detectDeviceProperties( $userAgent, $acceptHeader = '' );
65 * @return IDeviceProperties
67 function getDeviceProperties( $deviceName );
70 * @param $userAgent string
71 * @param $acceptHeader string
74 function detectDeviceName( $userAgent, $acceptHeader = '' );
78 * MediaWiki's default IDeviceProperties implementation
80 final class DeviceProperties
implements IDeviceProperties
{
83 public function __construct( array $deviceCapabilities ) {
84 $this->device
= $deviceCapabilities;
91 return $this->device
['view_format'];
97 function supportsJavaScript() {
98 return $this->device
['supports_javascript'];
104 function supportsJQuery() {
105 return $this->device
['supports_jquery'];
111 function disableZoom() {
112 return $this->device
['disable_zoom'];
117 * Provides abstraction for a device.
118 * A device can select which format a request should receive and
119 * may be extended to provide access to particular device functionality.
122 class DeviceDetection
implements IDeviceDetector
{
124 private static $formats = array (
126 'view_format' => 'html',
127 'css_file_name' => 'default',
128 'supports_javascript' => false,
129 'supports_jquery' => false,
130 'disable_zoom' => true,
133 'view_format' => 'html',
134 'css_file_name' => 'default',
135 'supports_javascript' => true,
136 'supports_jquery' => true,
137 'disable_zoom' => true,
140 'view_format' => 'html',
141 'css_file_name' => 'webkit',
142 'supports_javascript' => true,
143 'supports_jquery' => true,
144 'disable_zoom' => false,
147 'view_format' => 'html',
148 'css_file_name' => 'default',
149 'supports_javascript' => true,
150 'supports_jquery' => true,
151 'disable_zoom' => false,
154 'view_format' => 'html',
155 'css_file_name' => 'android',
156 'supports_javascript' => true,
157 'supports_jquery' => true,
158 'disable_zoom' => false,
161 'view_format' => 'html',
162 'css_file_name' => 'iphone',
163 'supports_javascript' => true,
164 'supports_jquery' => true,
165 'disable_zoom' => false,
168 'view_format' => 'html',
169 'css_file_name' => 'iphone2',
170 'supports_javascript' => true,
171 'supports_jquery' => true,
172 'disable_zoom' => true,
174 'native_iphone' => array (
175 'view_format' => 'html',
176 'css_file_name' => 'default',
177 'supports_javascript' => true,
178 'supports_jquery' => true,
179 'disable_zoom' => false,
181 'palm_pre' => array (
182 'view_format' => 'html',
183 'css_file_name' => 'palm_pre',
184 'supports_javascript' => true,
185 'supports_jquery' => false,
186 'disable_zoom' => true,
189 'view_format' => 'html',
190 'css_file_name' => 'kindle',
191 'supports_javascript' => false,
192 'supports_jquery' => false,
193 'disable_zoom' => true,
196 'view_format' => 'html',
197 'css_file_name' => 'kindle',
198 'supports_javascript' => false,
199 'supports_jquery' => false,
200 'disable_zoom' => true,
202 'blackberry' => array (
203 'view_format' => 'html',
204 'css_file_name' => 'blackberry',
205 'supports_javascript' => true,
206 'supports_jquery' => false,
207 'disable_zoom' => true,
209 'blackberry-lt5' => array (
210 'view_format' => 'html',
211 'css_file_name' => 'blackberry',
212 'supports_javascript' => false,
213 'supports_jquery' => false,
214 'disable_zoom' => true,
216 'netfront' => array (
217 'view_format' => 'html',
218 'css_file_name' => 'simple',
219 'supports_javascript' => false,
220 'supports_jquery' => false,
221 'disable_zoom' => true,
224 'view_format' => 'html',
225 'css_file_name' => 'simple',
226 'supports_javascript' => false,
227 'supports_jquery' => false,
228 'disable_zoom' => true,
231 'view_format' => 'html',
232 'css_file_name' => 'psp',
233 'supports_javascript' => false,
234 'supports_jquery' => false,
235 'disable_zoom' => true,
238 'view_format' => 'html',
239 'css_file_name' => 'simple',
240 'supports_javascript' => false,
241 'supports_jquery' => false,
242 'disable_zoom' => true,
245 'view_format' => 'html',
246 'css_file_name' => 'wii',
247 'supports_javascript' => true,
248 'supports_jquery' => true,
249 'disable_zoom' => true,
251 'operamini' => array (
252 'view_format' => 'html',
253 'css_file_name' => 'operamini',
254 'supports_javascript' => false,
255 'supports_jquery' => false,
256 'disable_zoom' => true,
258 'operamobile' => array (
259 'view_format' => 'html',
260 'css_file_name' => 'operamobile',
261 'supports_javascript' => true,
262 'supports_jquery' => true,
263 'disable_zoom' => true,
266 'view_format' => 'html',
267 'css_file_name' => 'nokia',
268 'supports_javascript' => true,
269 'supports_jquery' => false,
270 'disable_zoom' => true,
273 'view_format' => 'wml',
274 'css_file_name' => null,
275 'supports_javascript' => false,
276 'supports_jquery' => false,
277 'disable_zoom' => true,
282 * Returns an instance of detection class, overridable by extensions
283 * @return IDeviceDetector
285 public static function factory() {
286 global $wgDeviceDetectionClass;
288 static $instance = null;
290 $instance = new $wgDeviceDetectionClass();
296 * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
298 * @param string $acceptHeader
301 public function detectDevice( $userAgent, $acceptHeader = '' ) {
302 $formatName = $this->detectFormatName( $userAgent, $acceptHeader );
303 return $this->getDevice( $formatName );
308 * @param string $acceptHeader
309 * @return IDeviceProperties
311 public function detectDeviceProperties( $userAgent, $acceptHeader = '' ) {
312 $deviceName = $this->detectDeviceName( $userAgent, $acceptHeader );
313 return $this->getDeviceProperties( $deviceName );
317 * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
321 public function getDevice( $formatName ) {
322 return ( isset( self
::$formats[$formatName] ) ) ? self
::$formats[$formatName] : array();
327 * @return IDeviceProperties
329 public function getDeviceProperties( $deviceName ) {
330 if ( isset( self
::$formats[$deviceName] ) ) {
331 return new DeviceProperties( self
::$formats[$deviceName] );
333 return new DeviceProperties( array(
334 'view_format' => 'html',
335 'css_file_name' => 'default',
336 'supports_javascript' => true,
337 'supports_jquery' => true,
338 'disable_zoom' => true,
344 * @deprecated: Renamed to detectDeviceName()
345 * @param $userAgent string
346 * @param $acceptHeader string
349 public function detectFormatName( $userAgent, $acceptHeader = '' ) {
350 return $this->detectDeviceName( $userAgent, $acceptHeader );
354 * @param $userAgent string
355 * @param $acceptHeader string
358 public function detectDeviceName( $userAgent, $acceptHeader = '' ) {
359 wfProfileIn( __METHOD__
);
362 if ( preg_match( '/Android/', $userAgent ) ) {
363 $deviceName = 'android';
364 if ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
365 $deviceName = 'operamini';
366 } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
367 $deviceName = 'operamobile';
369 } elseif ( preg_match( '/MSIE 9.0/', $userAgent ) ||
370 preg_match( '/MSIE 8.0/', $userAgent ) ) {
372 } elseif( preg_match( '/MSIE/', $userAgent ) ) {
373 $deviceName = 'html';
374 } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
375 $deviceName = 'operamobile';
376 } elseif ( preg_match( '/iPad.* Safari/', $userAgent ) ) {
377 $deviceName = 'iphone';
378 } elseif ( preg_match( '/iPhone.* Safari/', $userAgent ) ) {
379 if ( strpos( $userAgent, 'iPhone OS 2' ) !== false ) {
380 $deviceName = 'iphone2';
382 $deviceName = 'iphone';
384 } elseif ( preg_match( '/iPhone/', $userAgent ) ) {
385 if ( strpos( $userAgent, 'Opera' ) !== false ) {
386 $deviceName = 'operamini';
388 $deviceName = 'native_iphone';
390 } elseif ( preg_match( '/WebKit/', $userAgent ) ) {
391 if ( preg_match( '/Series60/', $userAgent ) ) {
392 $deviceName = 'nokia';
393 } elseif ( preg_match( '/webOS/', $userAgent ) ) {
394 $deviceName = 'palm_pre';
396 $deviceName = 'webkit';
398 } elseif ( preg_match( '/Opera/', $userAgent ) ) {
399 if ( strpos( $userAgent, 'Nintendo Wii' ) !== false ) {
401 } elseif ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
402 $deviceName = 'operamini';
404 $deviceName = 'operamobile';
406 } elseif ( preg_match( '/Kindle\/1.0/', $userAgent ) ) {
407 $deviceName = 'kindle';
408 } elseif ( preg_match( '/Kindle\/2.0/', $userAgent ) ) {
409 $deviceName = 'kindle2';
410 } elseif ( preg_match( '/Firefox/', $userAgent ) ) {
411 $deviceName = 'capable';
412 } elseif ( preg_match( '/NetFront/', $userAgent ) ) {
413 $deviceName = 'netfront';
414 } elseif ( preg_match( '/SEMC-Browser/', $userAgent ) ) {
415 $deviceName = 'wap2';
416 } elseif ( preg_match( '/Series60/', $userAgent ) ) {
417 $deviceName = 'wap2';
418 } elseif ( preg_match( '/PlayStation Portable/', $userAgent ) ) {
420 } elseif ( preg_match( '/PLAYSTATION 3/', $userAgent ) ) {
422 } elseif ( preg_match( '/SAMSUNG/', $userAgent ) ) {
423 $deviceName = 'capable';
424 } elseif ( preg_match( '/BlackBerry/', $userAgent ) ) {
425 if( preg_match( '/BlackBerry[^\/]*\/[1-4]\./', $userAgent ) ) {
426 $deviceName = 'blackberry-lt5';
428 $deviceName = 'blackberry';
432 if ( $deviceName === '' ) {
433 if ( strpos( $acceptHeader, 'application/vnd.wap.xhtml+xml' ) !== false ) {
435 $deviceName = 'html';
436 } elseif ( strpos( $acceptHeader, 'vnd.wap.wml' ) !== false ) {
439 $deviceName = 'html';
442 wfProfileOut( __METHOD__
);
447 * @return array: List of all device-specific stylesheets
449 public function getCssFiles() {
452 foreach ( self
::$formats as $dev ) {
453 if ( isset( $dev['css_file_name'] ) ) {
454 $files[] = $dev['css_file_name'];
457 return array_unique( $files );