r49456 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r49455‎ | r49456 | r49457 >
Date:08:07, 14 April 2009
Author:werdna
Status:deferred
Tags:
Comment:
* Implementation of timezone offset for new preferences form, which is now functionally complete (except for quickbar settings).
* Addition of a new type of field, "HTMLSelectOrOtherField", usable for block reasons, and (in this case) timezones.
* Documentation for the GetPreferences hook.
* Cleanup of preferences javascript, including better variable naming, adding event handlers dynamically instead of with html attributes, and internal documentation for some complicated functions.
Modified paths:
  • /branches/preferences-work/phase3/docs/hooks.txt (modified) (history)
  • /branches/preferences-work/phase3/includes/DefaultSettings.php (modified) (history)
  • /branches/preferences-work/phase3/includes/HTMLForm.php (modified) (history)
  • /branches/preferences-work/phase3/includes/Preferences.php (modified) (history)
  • /branches/preferences-work/phase3/skins/common/htmlform.js (added) (history)
  • /branches/preferences-work/phase3/skins/common/prefs.js (modified) (history)

Diff [purge]

Index: branches/preferences-work/phase3/skins/common/prefs.js
@@ -90,10 +90,19 @@
9191 }
9292 }
9393
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');
96104 if (tzb) {
97105 tzb.style.display = 'inline';
 106+ addHandler( tzb, 'click', guessTimezone );
98107 }
99108 updateTimezoneSelection(false);
100109 }
@@ -113,52 +122,73 @@
114123 }
115124
116125 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.
118132 updateTimezoneSelection(true);
119133 }
120134
121135 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;
126140 var minDiff = 0;
 141+
 142+ // Compatibility code.
 143+ if (!selector.value) selector.value = selector.options[selector.selectedIndex].value;
127144
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(':');
132152 if (diffArr.length == 1) {
 153+ // Specification is of the form [-]XX
133154 minDiff = parseInt(diffArr[0], 10) * 60;
134155 } else {
 156+ // Specification is of the form [-]XX:XX
135157 minDiff = Math.abs(parseInt(diffArr[0], 10))*60 + parseInt(diffArr[1], 10);
136158 if (parseInt(diffArr[0], 10) < 0) minDiff = -minDiff;
137159 }
138160 } 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('|');
141163 minDiff = parseInt(diffArr[1], 10);
142164 }
 165+
 166+ // Gracefully handle non-numbers.
143167 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.
145173 while (localTime < 0) localTime += 1440;
146174 while (localTime >= 1440) localTime -= 1440;
147175
 176+ // Split to hour and minute
148177 var hour = String(Math.floor(localTime/60));
149178 if (hour.length<2) hour = '0'+hour;
150179 var min = String(localTime%60);
151180 if (min.length<2) min = '0'+min;
152 - changeText(wpLocalTime, hour+':'+min);
 181+ changeText(localtimeHolder, hour+':'+min);
153182
154 - if (wpTimeZone.selectedIndex != 1) {
 183+ // If the user selected from the drop-down, fill the offset field.
 184+ if (selector.value != 'other') {
155185 hour = String(Math.abs(Math.floor(minDiff/60)));
156186 if (hour.length<2) hour = '0'+hour;
157187 if (minDiff < 0) hour = '-'+hour;
158188 min = String(minDiff%60);
159189 if (min.length<2) min = '0'+min;
160 - wpHourDiff.value = hour+':'+min;
 190+ textbox.value = hour+':'+min;
161191 }
162192 }
163193
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 @@
711711 $url: string value as output (out parameter, can modify)
712712 $query: query options passed to Title::getLocalURL()
713713
 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+
714718 'getUserPermissionsErrors': Add a permissions error when permissions errors are
715719 checked for. Use instead of userCan for most cases. Return false if the
716720 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 @@
33
44 class HTMLForm {
55
 6+ static $jsAdded = false;
 7+
68 /* The descriptor is an array of arrays.
79 i.e. array(
810 'fieldname' => array( 'section' => 'section/subsection',
@@ -61,6 +63,14 @@
6264
6365 $this->mShowReset = true;
6466 }
 67+
 68+ static function addJS() {
 69+ if (self::$jsAdded) return;
 70+
 71+ global $wgOut, $wgStylePath;
 72+
 73+ $wgOut->addScriptFile( "$wgStylePath/common/htmlform.js" );
 74+ }
6575
6676 static function loadInputFromParameters( $descriptor ) {
6777 if ( isset( $descriptor['class'] ) ) {
@@ -81,6 +91,8 @@
8292 function show() {
8393 $html = '';
8494
 95+ self::addJS();
 96+
8597 // Load data from the request.
8698 $this->loadData();
8799
@@ -379,13 +391,16 @@
380392
381393 $html = '';
382394
383 - $html .= Xml::tags( 'td', array( 'style' => 'text-align: right; vertical-align: top;' ),
 395+ $html .= Xml::tags( 'td', array( 'class' => 'mw-label' ),
384396 Xml::tags( 'label', array( 'for' => $this->mID ), $this->getLabel() )
385397 );
386398 $html .= Xml::tags( 'td', array( 'class' => 'mw-input' ),
387399 $this->getInputHTML( $value ) ."\n$errors" );
 400+
 401+ $fieldType = get_class($this);
388402
389 - $html = Xml::tags( 'tr', null, $html ) . "\n";
 403+ $html = Xml::tags( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ),
 404+ $html ) . "\n";
390405
391406 return $html;
392407 }
@@ -509,6 +524,52 @@
510525 }
511526 }
512527
 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+
513574 class HTMLMultiSelectField extends HTMLFormField {
514575 function validate( $value, $alldata ) {
515576 $p = parent::validate( $value, $alldata );
Index: branches/preferences-work/phase3/includes/DefaultSettings.php
@@ -1471,7 +1471,7 @@
14721472 * to ensure that client-side caches don't keep obsolete copies of global
14731473 * styles.
14741474 */
1475 -$wgStyleVersion = '207';
 1475+$wgStyleVersion = '208';
14761476
14771477
14781478 # Server-side caching:
Index: branches/preferences-work/phase3/includes/Preferences.php
@@ -2,6 +2,10 @@
33
44 class Preferences {
55 static $defaultPreferences = null;
 6+ static $saveFilters =
 7+ array(
 8+ 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
 9+ );
610
711 static function getPreferences( $user ) {
812 if (self::$defaultPreferences)
@@ -308,8 +312,66 @@
309313 );
310314 }
311315
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 ) );
313321
 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' => '&nbsp;',
 373+ 'default' => $button,
 374+ );
 375+
314376 ## Editing #####################################
315377 $defaultPreferences['cols'] =
316378 array(
@@ -854,9 +916,93 @@
855917 return $htmlForm;
856918 }
857919
 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+
858997 static function tryFormSubmit( $formData ) {
859998 global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
860999
 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+
8611007 // Stuff that shouldn't be saved as a preference.
8621008 $saveBlacklist = array(
8631009 'realname',

Status & tagging log