Implement static public Parser::getExternalLinkRel
[lhc/web/wiklou.git] / includes / mobile / DeviceDetection.php
1 <?php
2 /**
3 * Mobile device detection code
4 *
5 * Copyright © 2011 Patrick Reilly
6 * http://www.mediawiki.org/
7 *
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.
12 *
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.
17 *
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
22 *
23 * @file
24 */
25
26 /**
27 * Base for classes describing devices and their capabilities
28 * @since 1.20
29 */
30 interface IDeviceProperties {
31 /**
32 * @return string: 'html' or 'wml'
33 */
34 function format();
35
36 /**
37 * @return bool
38 */
39 function supportsJavaScript();
40
41 /**
42 * @return bool
43 */
44 function supportsJQuery();
45
46 /**
47 * @return bool
48 */
49 function disableZoom();
50 }
51
52 /**
53 * @since 1.20
54 */
55 interface IDeviceDetector {
56 /**
57 * @param $userAgent
58 * @param string $acceptHeader
59 * @return IDeviceProperties
60 */
61 function detectDeviceProperties( $userAgent, $acceptHeader = '' );
62
63 /**
64 * @param $deviceName
65 * @return IDeviceProperties
66 */
67 function getDeviceProperties( $deviceName );
68
69 /**
70 * @param $userAgent string
71 * @param $acceptHeader string
72 * @return string
73 */
74 function detectDeviceName( $userAgent, $acceptHeader = '' );
75 }
76
77 /**
78 * MediaWiki's default IDeviceProperties implementation
79 */
80 final class DeviceProperties implements IDeviceProperties {
81 private $device;
82
83 public function __construct( array $deviceCapabilities ) {
84 $this->device = $deviceCapabilities;
85 }
86
87 /**
88 * @return string
89 */
90 function format() {
91 return $this->device['view_format'];
92 }
93
94 /**
95 * @return bool
96 */
97 function supportsJavaScript() {
98 return $this->device['supports_javascript'];
99 }
100
101 /**
102 * @return bool
103 */
104 function supportsJQuery() {
105 return $this->device['supports_jquery'];
106 }
107
108 /**
109 * @return bool
110 */
111 function disableZoom() {
112 return $this->device['disable_zoom'];
113 }
114 }
115
116 /**
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.
120 * @since 1.20
121 */
122 class DeviceDetection implements IDeviceDetector {
123
124 private static $formats = array (
125 'html' => array (
126 'view_format' => 'html',
127 'css_file_name' => 'default',
128 'supports_javascript' => false,
129 'supports_jquery' => false,
130 'disable_zoom' => true,
131 ),
132 'capable' => array (
133 'view_format' => 'html',
134 'css_file_name' => 'default',
135 'supports_javascript' => true,
136 'supports_jquery' => true,
137 'disable_zoom' => true,
138 ),
139 'webkit' => array (
140 'view_format' => 'html',
141 'css_file_name' => 'webkit',
142 'supports_javascript' => true,
143 'supports_jquery' => true,
144 'disable_zoom' => false,
145 ),
146 'ie' => array (
147 'view_format' => 'html',
148 'css_file_name' => 'default',
149 'supports_javascript' => true,
150 'supports_jquery' => true,
151 'disable_zoom' => false,
152 ),
153 'android' => array (
154 'view_format' => 'html',
155 'css_file_name' => 'android',
156 'supports_javascript' => true,
157 'supports_jquery' => true,
158 'disable_zoom' => false,
159 ),
160 'iphone' => array (
161 'view_format' => 'html',
162 'css_file_name' => 'iphone',
163 'supports_javascript' => true,
164 'supports_jquery' => true,
165 'disable_zoom' => false,
166 ),
167 'iphone2' => array (
168 'view_format' => 'html',
169 'css_file_name' => 'iphone2',
170 'supports_javascript' => true,
171 'supports_jquery' => true,
172 'disable_zoom' => true,
173 ),
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,
180 ),
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,
187 ),
188 'kindle' => array (
189 'view_format' => 'html',
190 'css_file_name' => 'kindle',
191 'supports_javascript' => false,
192 'supports_jquery' => false,
193 'disable_zoom' => true,
194 ),
195 'kindle2' => array (
196 'view_format' => 'html',
197 'css_file_name' => 'kindle',
198 'supports_javascript' => false,
199 'supports_jquery' => false,
200 'disable_zoom' => true,
201 ),
202 'blackberry' => array (
203 'view_format' => 'html',
204 'css_file_name' => 'blackberry',
205 'supports_javascript' => true,
206 'supports_jquery' => false,
207 'disable_zoom' => true,
208 ),
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,
215 ),
216 'netfront' => array (
217 'view_format' => 'html',
218 'css_file_name' => 'simple',
219 'supports_javascript' => false,
220 'supports_jquery' => false,
221 'disable_zoom' => true,
222 ),
223 'wap2' => array (
224 'view_format' => 'html',
225 'css_file_name' => 'simple',
226 'supports_javascript' => false,
227 'supports_jquery' => false,
228 'disable_zoom' => true,
229 ),
230 'psp' => array (
231 'view_format' => 'html',
232 'css_file_name' => 'psp',
233 'supports_javascript' => false,
234 'supports_jquery' => false,
235 'disable_zoom' => true,
236 ),
237 'ps3' => array (
238 'view_format' => 'html',
239 'css_file_name' => 'simple',
240 'supports_javascript' => false,
241 'supports_jquery' => false,
242 'disable_zoom' => true,
243 ),
244 'wii' => array (
245 'view_format' => 'html',
246 'css_file_name' => 'wii',
247 'supports_javascript' => true,
248 'supports_jquery' => true,
249 'disable_zoom' => true,
250 ),
251 'operamini' => array (
252 'view_format' => 'html',
253 'css_file_name' => 'operamini',
254 'supports_javascript' => false,
255 'supports_jquery' => false,
256 'disable_zoom' => true,
257 ),
258 'operamobile' => array (
259 'view_format' => 'html',
260 'css_file_name' => 'operamobile',
261 'supports_javascript' => true,
262 'supports_jquery' => true,
263 'disable_zoom' => true,
264 ),
265 'nokia' => array (
266 'view_format' => 'html',
267 'css_file_name' => 'nokia',
268 'supports_javascript' => true,
269 'supports_jquery' => false,
270 'disable_zoom' => true,
271 ),
272 'wml' => array (
273 'view_format' => 'wml',
274 'css_file_name' => null,
275 'supports_javascript' => false,
276 'supports_jquery' => false,
277 'disable_zoom' => true,
278 ),
279 );
280
281 /**
282 * Returns an instance of detection class, overridable by extensions
283 * @return IDeviceDetector
284 */
285 public static function factory() {
286 global $wgDeviceDetectionClass;
287
288 static $instance = null;
289 if ( !$instance ) {
290 $instance = new $wgDeviceDetectionClass();
291 }
292 return $instance;
293 }
294
295 /**
296 * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
297 * @param $userAgent
298 * @param string $acceptHeader
299 * @return array
300 */
301 public function detectDevice( $userAgent, $acceptHeader = '' ) {
302 $formatName = $this->detectFormatName( $userAgent, $acceptHeader );
303 return $this->getDevice( $formatName );
304 }
305
306 /**
307 * @param $userAgent
308 * @param string $acceptHeader
309 * @return IDeviceProperties
310 */
311 public function detectDeviceProperties( $userAgent, $acceptHeader = '' ) {
312 $deviceName = $this->detectDeviceName( $userAgent, $acceptHeader );
313 return $this->getDeviceProperties( $deviceName );
314 }
315
316 /**
317 * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
318 * @param $formatName
319 * @return array
320 */
321 public function getDevice( $formatName ) {
322 return ( isset( self::$formats[$formatName] ) ) ? self::$formats[$formatName] : array();
323 }
324
325 /**
326 * @param $deviceName
327 * @return IDeviceProperties
328 */
329 public function getDeviceProperties( $deviceName ) {
330 if ( isset( self::$formats[$deviceName] ) ) {
331 return new DeviceProperties( self::$formats[$deviceName] );
332 } else {
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,
339 ) );
340 }
341 }
342
343 /**
344 * @deprecated: Renamed to detectDeviceName()
345 * @param $userAgent string
346 * @param $acceptHeader string
347 * @return string
348 */
349 public function detectFormatName( $userAgent, $acceptHeader = '' ) {
350 return $this->detectDeviceName( $userAgent, $acceptHeader );
351 }
352
353 /**
354 * @param $userAgent string
355 * @param $acceptHeader string
356 * @return string
357 */
358 public function detectDeviceName( $userAgent, $acceptHeader = '' ) {
359 wfProfileIn( __METHOD__ );
360
361 $deviceName = '';
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';
368 }
369 } elseif ( preg_match( '/MSIE 9.0/', $userAgent ) ||
370 preg_match( '/MSIE 8.0/', $userAgent ) ) {
371 $deviceName = 'ie';
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';
381 } else {
382 $deviceName = 'iphone';
383 }
384 } elseif ( preg_match( '/iPhone/', $userAgent ) ) {
385 if ( strpos( $userAgent, 'Opera' ) !== false ) {
386 $deviceName = 'operamini';
387 } else {
388 $deviceName = 'native_iphone';
389 }
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';
395 } else {
396 $deviceName = 'webkit';
397 }
398 } elseif ( preg_match( '/Opera/', $userAgent ) ) {
399 if ( strpos( $userAgent, 'Nintendo Wii' ) !== false ) {
400 $deviceName = 'wii';
401 } elseif ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
402 $deviceName = 'operamini';
403 } else {
404 $deviceName = 'operamobile';
405 }
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 ) ) {
419 $deviceName = 'psp';
420 } elseif ( preg_match( '/PLAYSTATION 3/', $userAgent ) ) {
421 $deviceName = 'ps3';
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';
427 } else {
428 $deviceName = 'blackberry';
429 }
430 }
431
432 if ( $deviceName === '' ) {
433 if ( strpos( $acceptHeader, 'application/vnd.wap.xhtml+xml' ) !== false ) {
434 // Should be wap2
435 $deviceName = 'html';
436 } elseif ( strpos( $acceptHeader, 'vnd.wap.wml' ) !== false ) {
437 $deviceName = 'wml';
438 } else {
439 $deviceName = 'html';
440 }
441 }
442 wfProfileOut( __METHOD__ );
443 return $deviceName;
444 }
445
446 /**
447 * @return array: List of all device-specific stylesheets
448 */
449 public function getCssFiles() {
450 $files = array();
451
452 foreach ( self::$formats as $dev ) {
453 if ( isset( $dev['css_file_name'] ) ) {
454 $files[] = $dev['css_file_name'];
455 }
456 }
457 return array_unique( $files );
458 }
459 }