Index: branches/preferences-work/phase3/skins/common/prefs.js |
— | — | @@ -90,10 +90,19 @@ |
91 | 91 | } |
92 | 92 | } |
93 | 93 | |
94 | | -function unhidetzbutton() { |
95 | | - var tzb = document.getElementById('guesstimezonebutton'); |
| 94 | +function timezoneSetup() { |
| 95 | + var tzSelect = document.getElementById( 'mw-input-timecorrection' ); |
| 96 | + var tzTextbox = document.getElementById( 'mw-input-timecorrection-other' ); |
| 97 | + |
| 98 | + if (tzSelect && tzTextbox) { |
| 99 | + addHandler( tzSelect, 'change', function(e) { updateTimezoneSelection(false); } ); |
| 100 | + addHandler( tzTextbox, 'blur', function(e) { updateTimezoneSelection(true); } ); |
| 101 | + } |
| 102 | + |
| 103 | + var tzb = document.getElementById('mw-prefs-guesstimezone'); |
96 | 104 | if (tzb) { |
97 | 105 | tzb.style.display = 'inline'; |
| 106 | + addHandler( tzb, 'click', guessTimezone ); |
98 | 107 | } |
99 | 108 | updateTimezoneSelection(false); |
100 | 109 | } |
— | — | @@ -113,52 +122,73 @@ |
114 | 123 | } |
115 | 124 | |
116 | 125 | function guessTimezone(box) { |
117 | | - document.getElementsByName("wpHourDiff")[0].value = fetchTimezone(); |
| 126 | + var textbox = document.getElementById("mw-input-timecorrection-other"); |
| 127 | + var selector = document.getElementById( 'mw-input-timecorrection' ); |
| 128 | + |
| 129 | + selector.value = 'other'; |
| 130 | + textbox.value = fetchTimezone(); |
| 131 | + textbox.disabled = false; // The changed handler doesn't trip, obviously. |
118 | 132 | updateTimezoneSelection(true); |
119 | 133 | } |
120 | 134 | |
121 | 135 | function updateTimezoneSelection(force_offset) { |
122 | | - var wpTimeZone = document.getElementsByName("wpTimeZone")[0]; |
123 | | - var wpHourDiff = document.getElementsByName("wpHourDiff")[0]; |
124 | | - var wpLocalTime = document.getElementById("wpLocalTime"); |
125 | | - var wpServerTime = document.getElementsByName("wpServerTime")[0]; |
| 136 | + var selector = document.getElementById("mw-input-timecorrection"); |
| 137 | + var textbox = document.getElementById( 'mw-input-timecorrection-other' ); |
| 138 | + var localtimeHolder = document.getElementById("wpLocalTime"); |
| 139 | + var servertime = document.getElementsByName("wpServerTime")[0].value; |
126 | 140 | var minDiff = 0; |
| 141 | + |
| 142 | + // Compatibility code. |
| 143 | + if (!selector.value) selector.value = selector.options[selector.selectedIndex].value; |
127 | 144 | |
128 | | - if (force_offset) wpTimeZone.selectedIndex = 1; |
129 | | - if (wpTimeZone.selectedIndex == 1) { |
130 | | - wpHourDiff.disabled = false; |
131 | | - var diffArr = wpHourDiff.value.split(':'); |
| 145 | + // Handle force_offset |
| 146 | + if (force_offset) selector.value = 'other'; |
| 147 | + |
| 148 | + // Get min_diff |
| 149 | + if (selector.value == 'other') { |
| 150 | + // Grab data from the textbox, parse it. |
| 151 | + var diffArr = textbox.value.split(':'); |
132 | 152 | if (diffArr.length == 1) { |
| 153 | + // Specification is of the form [-]XX |
133 | 154 | minDiff = parseInt(diffArr[0], 10) * 60; |
134 | 155 | } else { |
| 156 | + // Specification is of the form [-]XX:XX |
135 | 157 | minDiff = Math.abs(parseInt(diffArr[0], 10))*60 + parseInt(diffArr[1], 10); |
136 | 158 | if (parseInt(diffArr[0], 10) < 0) minDiff = -minDiff; |
137 | 159 | } |
138 | 160 | } else { |
139 | | - wpHourDiff.disabled = true; |
140 | | - var diffArr = wpTimeZone.options[wpTimeZone.selectedIndex].value.split('|'); |
| 161 | + // Grab data from the selector value |
| 162 | + var diffArr = selector.value.split('|'); |
141 | 163 | minDiff = parseInt(diffArr[1], 10); |
142 | 164 | } |
| 165 | + |
| 166 | + // Gracefully handle non-numbers. |
143 | 167 | if (isNaN(minDiff)) minDiff = 0; |
144 | | - var localTime = parseInt(wpServerTime.value, 10) + minDiff; |
| 168 | + |
| 169 | + // Determine local time from server time and minutes difference, for display. |
| 170 | + var localTime = parseInt(servertime, 10) + minDiff; |
| 171 | + |
| 172 | + // Bring time within the [0,1440) range. |
145 | 173 | while (localTime < 0) localTime += 1440; |
146 | 174 | while (localTime >= 1440) localTime -= 1440; |
147 | 175 | |
| 176 | + // Split to hour and minute |
148 | 177 | var hour = String(Math.floor(localTime/60)); |
149 | 178 | if (hour.length<2) hour = '0'+hour; |
150 | 179 | var min = String(localTime%60); |
151 | 180 | if (min.length<2) min = '0'+min; |
152 | | - changeText(wpLocalTime, hour+':'+min); |
| 181 | + changeText(localtimeHolder, hour+':'+min); |
153 | 182 | |
154 | | - if (wpTimeZone.selectedIndex != 1) { |
| 183 | + // If the user selected from the drop-down, fill the offset field. |
| 184 | + if (selector.value != 'other') { |
155 | 185 | hour = String(Math.abs(Math.floor(minDiff/60))); |
156 | 186 | if (hour.length<2) hour = '0'+hour; |
157 | 187 | if (minDiff < 0) hour = '-'+hour; |
158 | 188 | min = String(minDiff%60); |
159 | 189 | if (min.length<2) min = '0'+min; |
160 | | - wpHourDiff.value = hour+':'+min; |
| 190 | + textbox.value = hour+':'+min; |
161 | 191 | } |
162 | 192 | } |
163 | 193 | |
164 | | -//hookEvent("load", unhidetzbutton); |
165 | | -hookEvent("load", tabbedprefs); |
| 194 | +addOnloadHook(timezoneSetup); |
| 195 | +addOnloadHook(tabbedprefs); |
Index: branches/preferences-work/phase3/skins/common/htmlform.js |
— | — | @@ -0,0 +1,26 @@ |
| 2 | +// Find select-or-other fields. |
| 3 | +addOnloadHook( function() { |
| 4 | + var fields = getElementsByClassName( document, 'select', 'mw-htmlform-select-or-other' ); |
| 5 | + |
| 6 | + for( var i = 0; i < fields.length; i++ ) { |
| 7 | + var select = fields[i]; |
| 8 | + |
| 9 | + addHandler( select, 'change', htmlforms.selectOrOtherSelectChanged ); |
| 10 | + } |
| 11 | +} ); |
| 12 | + |
| 13 | +var htmlforms = { |
| 14 | + 'selectOrOtherSelectChanged' : function(e) { |
| 15 | + if (!e) e = window.event; |
| 16 | + var select = e.target; |
| 17 | + var id = select.id; |
| 18 | + var textbox = document.getElementById( id+'-other' ); |
| 19 | + |
| 20 | + if (select.value == 'other') { |
| 21 | + textbox.disabled = false; |
| 22 | + } else { |
| 23 | + textbox.disabled = true; |
| 24 | + } |
| 25 | + }, |
| 26 | +} |
| 27 | + |
Index: branches/preferences-work/phase3/docs/hooks.txt |
— | — | @@ -710,6 +710,10 @@ |
711 | 711 | $url: string value as output (out parameter, can modify) |
712 | 712 | $query: query options passed to Title::getLocalURL() |
713 | 713 | |
| 714 | +'GetPreferences': modify user preferences |
| 715 | +$user: User whose preferences are being modified. |
| 716 | +&$preferences: Preferences description array, to be fed to an HTMLForm object |
| 717 | + |
714 | 718 | 'getUserPermissionsErrors': Add a permissions error when permissions errors are |
715 | 719 | checked for. Use instead of userCan for most cases. Return false if the |
716 | 720 | user can't do it, and populate $result with the reason in the form of |
Index: branches/preferences-work/phase3/includes/HTMLForm.php |
— | — | @@ -2,6 +2,8 @@ |
3 | 3 | |
4 | 4 | class HTMLForm { |
5 | 5 | |
| 6 | + static $jsAdded = false; |
| 7 | + |
6 | 8 | /* The descriptor is an array of arrays. |
7 | 9 | i.e. array( |
8 | 10 | 'fieldname' => array( 'section' => 'section/subsection', |
— | — | @@ -61,6 +63,14 @@ |
62 | 64 | |
63 | 65 | $this->mShowReset = true; |
64 | 66 | } |
| 67 | + |
| 68 | + static function addJS() { |
| 69 | + if (self::$jsAdded) return; |
| 70 | + |
| 71 | + global $wgOut, $wgStylePath; |
| 72 | + |
| 73 | + $wgOut->addScriptFile( "$wgStylePath/common/htmlform.js" ); |
| 74 | + } |
65 | 75 | |
66 | 76 | static function loadInputFromParameters( $descriptor ) { |
67 | 77 | if ( isset( $descriptor['class'] ) ) { |
— | — | @@ -81,6 +91,8 @@ |
82 | 92 | function show() { |
83 | 93 | $html = ''; |
84 | 94 | |
| 95 | + self::addJS(); |
| 96 | + |
85 | 97 | // Load data from the request. |
86 | 98 | $this->loadData(); |
87 | 99 | |
— | — | @@ -379,13 +391,16 @@ |
380 | 392 | |
381 | 393 | $html = ''; |
382 | 394 | |
383 | | - $html .= Xml::tags( 'td', array( 'style' => 'text-align: right; vertical-align: top;' ), |
| 395 | + $html .= Xml::tags( 'td', array( 'class' => 'mw-label' ), |
384 | 396 | Xml::tags( 'label', array( 'for' => $this->mID ), $this->getLabel() ) |
385 | 397 | ); |
386 | 398 | $html .= Xml::tags( 'td', array( 'class' => 'mw-input' ), |
387 | 399 | $this->getInputHTML( $value ) ."\n$errors" ); |
| 400 | + |
| 401 | + $fieldType = get_class($this); |
388 | 402 | |
389 | | - $html = Xml::tags( 'tr', null, $html ) . "\n"; |
| 403 | + $html = Xml::tags( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ), |
| 404 | + $html ) . "\n"; |
390 | 405 | |
391 | 406 | return $html; |
392 | 407 | } |
— | — | @@ -509,6 +524,52 @@ |
510 | 525 | } |
511 | 526 | } |
512 | 527 | |
| 528 | +class HTMLSelectOrOtherField extends HTMLTextField { |
| 529 | + static $jsAdded = false; |
| 530 | + |
| 531 | + function getInputHTML( $value ) { |
| 532 | + $valInSelect = array_key_exists( $value, $this->mParams['options'] ); |
| 533 | + |
| 534 | + $selected = $valInSelect ? $value : 'other'; |
| 535 | + |
| 536 | + $select = new XmlSelect( $this->mName, $this->mID, $selected ); |
| 537 | + foreach( $this->mParams['options'] as $key => $label ) { |
| 538 | + $select->addOption( $label, $key ); |
| 539 | + } |
| 540 | + |
| 541 | + $select->setAttribute( 'class', 'mw-htmlform-select-or-other' ); |
| 542 | + |
| 543 | + $select = $select->getHTML(); |
| 544 | + |
| 545 | + $tbAttribs = array( 'id' => $this->mID.'-other' ); |
| 546 | + |
| 547 | + if ( isset($this->mParams['maxlength']) ) { |
| 548 | + $tbAttribs['maxlength'] = $this->mParams['maxlength']; |
| 549 | + } |
| 550 | + |
| 551 | + $textbox = Xml::input( $this->mName.'-other', |
| 552 | + $this->getSize(), |
| 553 | + $valInSelect ? '' : $value, |
| 554 | + $tbAttribs ); |
| 555 | + |
| 556 | + return "$select<br/>\n$textbox"; |
| 557 | + } |
| 558 | + |
| 559 | + function loadDataFromRequest( $request ) { |
| 560 | + if ($request->getCheck( $this->mName ) ) { |
| 561 | + $val = $request->getText( $this->mName ); |
| 562 | + |
| 563 | + if ($val == 'other') { |
| 564 | + $val = $request->getText( $this->mName.'-other' ); |
| 565 | + } |
| 566 | + |
| 567 | + return $val; |
| 568 | + } else { |
| 569 | + return $this->getDefault(); |
| 570 | + } |
| 571 | + } |
| 572 | +} |
| 573 | + |
513 | 574 | class HTMLMultiSelectField extends HTMLFormField { |
514 | 575 | function validate( $value, $alldata ) { |
515 | 576 | $p = parent::validate( $value, $alldata ); |
Index: branches/preferences-work/phase3/includes/DefaultSettings.php |
— | — | @@ -1471,7 +1471,7 @@ |
1472 | 1472 | * to ensure that client-side caches don't keep obsolete copies of global |
1473 | 1473 | * styles. |
1474 | 1474 | */ |
1475 | | -$wgStyleVersion = '207'; |
| 1475 | +$wgStyleVersion = '208'; |
1476 | 1476 | |
1477 | 1477 | |
1478 | 1478 | # Server-side caching: |
Index: branches/preferences-work/phase3/includes/Preferences.php |
— | — | @@ -2,6 +2,10 @@ |
3 | 3 | |
4 | 4 | class Preferences { |
5 | 5 | static $defaultPreferences = null; |
| 6 | + static $saveFilters = |
| 7 | + array( |
| 8 | + 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ), |
| 9 | + ); |
6 | 10 | |
7 | 11 | static function getPreferences( $user ) { |
8 | 12 | if (self::$defaultPreferences) |
— | — | @@ -308,8 +312,66 @@ |
309 | 313 | ); |
310 | 314 | } |
311 | 315 | |
312 | | - ## TODO OFFSET |
| 316 | + // Info |
| 317 | + $nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ), |
| 318 | + $wgLang->time( $now = wfTimestampNow(), true ) ); |
| 319 | + $nowserver = $wgLang->time( $now, false ) . |
| 320 | + Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) ); |
313 | 321 | |
| 322 | + $defaultPreferences['nowserver'] = |
| 323 | + array( |
| 324 | + 'type' => 'info', |
| 325 | + 'raw' => 1, |
| 326 | + 'label-message' => 'servertime', |
| 327 | + 'default' => $nowserver, |
| 328 | + 'section' => 'datetime', |
| 329 | + ); |
| 330 | + |
| 331 | + $defaultPreferences['nowlocal'] = |
| 332 | + array( |
| 333 | + 'type' => 'info', |
| 334 | + 'raw' => 1, |
| 335 | + 'label-message' => 'localtime', |
| 336 | + 'default' => $nowlocal, |
| 337 | + 'section' => 'datetime', |
| 338 | + ); |
| 339 | + |
| 340 | + // Grab existing pref. |
| 341 | + $tzOffset = $user->getOption( 'timecorrection' ); |
| 342 | + $tz = explode( '|', $tzOffset, 2 ); |
| 343 | + |
| 344 | + $tzSetting = $tzOffset; |
| 345 | + if (count($tz) > 1 && $tz[0] == 'Offset') { |
| 346 | + $minDiff = $tz[1]; |
| 347 | + $tzSetting = sprintf( '%+03d:%02d', floor($minDiff/60), abs($minDiff)%60 );; |
| 348 | + } |
| 349 | + |
| 350 | + $defaultPreferences['timecorrection'] = |
| 351 | + array( |
| 352 | + 'class' => 'HTMLSelectOrOtherField', |
| 353 | + 'label-message' => 'timezonelegend', |
| 354 | + 'options' => self::getTimezoneOptions(), |
| 355 | + 'default' => $tzSetting, |
| 356 | + 'section' => 'datetime', |
| 357 | + ); |
| 358 | + |
| 359 | + // Add auto-select button |
| 360 | + |
| 361 | + $button = Xml::element( 'input', |
| 362 | + array( 'type' => 'button', |
| 363 | + 'id' => 'mw-prefs-guesstimezone', |
| 364 | + 'style' => 'display: none;', // Graceful degradation. |
| 365 | + 'value' => wfMsg( 'guesstimezone' ), |
| 366 | + ) ); |
| 367 | + |
| 368 | + $defaultPreferences['guesstimezone'] = |
| 369 | + array( |
| 370 | + 'type' => 'info', |
| 371 | + 'raw' => true, |
| 372 | + 'label' => ' ', |
| 373 | + 'default' => $button, |
| 374 | + ); |
| 375 | + |
314 | 376 | ## Editing ##################################### |
315 | 377 | $defaultPreferences['cols'] = |
316 | 378 | array( |
— | — | @@ -854,9 +916,93 @@ |
855 | 917 | return $htmlForm; |
856 | 918 | } |
857 | 919 | |
| 920 | + static function getTimezoneOptions() { |
| 921 | + $opt = array(); |
| 922 | + |
| 923 | + global $wgLocalTZoffset; |
| 924 | + |
| 925 | + $opt["System|$wgLocalTZoffset"] = wfMsg( 'timezoneuseserverdefault' ); |
| 926 | + $opt['other'] = wfMsg( 'timezoneuseoffset' ); |
| 927 | + |
| 928 | + if ( function_exists( 'timezone_identifiers_list' ) ) { |
| 929 | + # Read timezone list |
| 930 | + $tzs = timezone_identifiers_list(); |
| 931 | + sort( $tzs ); |
| 932 | + |
| 933 | + $tzRegions = array(); |
| 934 | + $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' ); |
| 935 | + $tzRegions['America'] = wfMsg( 'timezoneregion-america' ); |
| 936 | + $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' ); |
| 937 | + $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' ); |
| 938 | + $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' ); |
| 939 | + $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' ); |
| 940 | + $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' ); |
| 941 | + $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' ); |
| 942 | + $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' ); |
| 943 | + $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' ); |
| 944 | + asort( $tzRegions ); |
| 945 | + |
| 946 | + $now = date_create( 'now' ); |
| 947 | + |
| 948 | + foreach ( $tzs as $tz ) { |
| 949 | + $z = explode( '/', $tz, 2 ); |
| 950 | + |
| 951 | + # timezone_identifiers_list() returns a number of |
| 952 | + # backwards-compatibility entries. This filters them out of the |
| 953 | + # list presented to the user. |
| 954 | + if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) ) |
| 955 | + continue; |
| 956 | + |
| 957 | + # Localize region |
| 958 | + $z[0] = $tzRegions[$z[0]]; |
| 959 | + |
| 960 | + $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 ); |
| 961 | + |
| 962 | + $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] ); |
| 963 | + $value = "ZoneInfo|$minDiff|$tz"; |
| 964 | + |
| 965 | + $opt[$value] = $display; |
| 966 | + } |
| 967 | + } |
| 968 | + return $opt; |
| 969 | + } |
| 970 | + |
| 971 | + static function filterTimezoneInput( $tz, $alldata ) { |
| 972 | + $data = explode( '|', $tz, 3 ); |
| 973 | + switch ( $data[0] ) { |
| 974 | + case 'ZoneInfo': |
| 975 | + case 'System': |
| 976 | + return $tz; |
| 977 | + default: |
| 978 | + $data = explode( ':', $tz, 2 ); |
| 979 | + $minDiff = 0; |
| 980 | + if( count( $data ) == 2 ) { |
| 981 | + $data[0] = intval( $data[0] ); |
| 982 | + $data[1] = intval( $data[1] ); |
| 983 | + $minDiff = abs( $data[0] ) * 60 + $data[1]; |
| 984 | + if ( $data[0] < 0 ) $minDiff = -$minDiff; |
| 985 | + } else { |
| 986 | + $minDiff = intval( $data[0] ) * 60; |
| 987 | + } |
| 988 | + |
| 989 | + # Max is +14:00 and min is -12:00, see: |
| 990 | + # http://en.wikipedia.org/wiki/Timezone |
| 991 | + $minDiff = min( $minDiff, 840 ); # 14:00 |
| 992 | + $minDiff = max( $minDiff, -720 ); # -12:00 |
| 993 | + return 'Offset|'.$minDiff; |
| 994 | + } |
| 995 | + } |
| 996 | + |
858 | 997 | static function tryFormSubmit( $formData ) { |
859 | 998 | global $wgUser, $wgEmailAuthentication, $wgEnableEmail; |
860 | 999 | |
| 1000 | + // Filter input |
| 1001 | + foreach( $formData as $name => &$value ) { |
| 1002 | + if ( isset(self::$saveFilters[$name]) ) { |
| 1003 | + $value = call_user_func( self::$saveFilters[$name], $value, $formData ); |
| 1004 | + } |
| 1005 | + } |
| 1006 | + |
861 | 1007 | // Stuff that shouldn't be saved as a preference. |
862 | 1008 | $saveBlacklist = array( |
863 | 1009 | 'realname', |