Index: trunk/phase3/includes/ExternalUser.php |
— | — | @@ -282,7 +282,25 @@ |
283 | 283 | $dbw->replace( 'external_user', |
284 | 284 | array( 'eu_wiki_id', 'eu_external_id' ), |
285 | 285 | array( 'eu_wiki_id' => $id, |
286 | | - 'eu_external_id' => $this->getId() ), |
287 | | - __METHOD__ ); |
| 286 | + 'eu_external_id' => $this->getId() ), |
| 287 | + __METHOD__ ); |
288 | 288 | } |
289 | | -} |
| 289 | + |
| 290 | + /** |
| 291 | + * Check whether this external user id is already linked with |
| 292 | + * a local user. |
| 293 | + * @return Mixed User if the account is linked, Null otherwise. |
| 294 | + */ |
| 295 | + public final function getLocalUser(){ |
| 296 | + $dbr = wfGetDb( DB_SLAVE ); |
| 297 | + $row = $dbr->selectRow( |
| 298 | + 'external_user', |
| 299 | + '*', |
| 300 | + array( 'eu_external_id' => $this->getId() ) |
| 301 | + ); |
| 302 | + return $row |
| 303 | + ? User::newFromId( $row->eu_wiki_id ) |
| 304 | + : null; |
| 305 | + } |
| 306 | + |
| 307 | +} |
\ No newline at end of file |
Index: trunk/phase3/includes/HTMLForm.php |
— | — | @@ -1,16 +1,54 @@ |
2 | 2 | <?php |
3 | 3 | |
| 4 | +/** |
| 5 | + * Object handling generic submission, CSRF protection, layout and |
| 6 | + * other logic for UI forms. in a reusable manner. |
| 7 | + * |
| 8 | + * In order to generate the form, the HTMLForm object takes an array |
| 9 | + * structure detailing the form fields available. Each element of the |
| 10 | + * array is a basic property-list, including the type of field, the |
| 11 | + * label it is to be given in the form, callbacks for validation and |
| 12 | + * 'filtering', and other pertinent information. |
| 13 | + * |
| 14 | + * Field types are implemented as subclasses of the generic HTMLFormField |
| 15 | + * object, and typically implement at least getInputHTML, which generates |
| 16 | + * the HTML for the input field to be placed in the table. |
| 17 | + * |
| 18 | + * The constructor input is an associative array of $fieldname => $info, |
| 19 | + * where $info is an Associative Array with any of the following: |
| 20 | + * |
| 21 | + * 'class' -- the subclass of HTMLFormField that will be used |
| 22 | + * to create the object. *NOT* the CSS class! |
| 23 | + * 'type' -- roughly translates into the <select> type attribute. |
| 24 | + * if 'class' is not specified, this is used as a map |
| 25 | + * through HTMLForm::$typeMappings to get the class name. |
| 26 | + * 'default' -- default value when the form is displayed |
| 27 | + * 'id' -- HTML id attribute |
| 28 | + * 'options' -- varies according to the specific object. |
| 29 | + * 'label-message' -- message key for a message to use as the label. |
| 30 | + * can be an array of msg key and then parameters to |
| 31 | + * the message. |
| 32 | + * 'label' -- alternatively, a raw text message. Overridden by |
| 33 | + * label-message |
| 34 | + * 'help-message' -- message key for a message to use as a help text. |
| 35 | + * can be an array of msg key and then parameters to |
| 36 | + * the message. |
| 37 | + * 'required' -- passed through to the object, indicating that it |
| 38 | + * is a required field. |
| 39 | + * 'size' -- the length of text fields |
| 40 | + * 'filter-callback -- a function name to give you the chance to |
| 41 | + * massage the inputted value before it's processed. |
| 42 | + * @see HTMLForm::filter() |
| 43 | + * 'validation-callback' -- a function name to give you the chance |
| 44 | + * to impose extra validation on the field input. |
| 45 | + * @see HTMLForm::validate() |
| 46 | + * |
| 47 | + * TODO: Document 'section' / 'subsection' stuff |
| 48 | + */ |
4 | 49 | class HTMLForm { |
5 | 50 | static $jsAdded = false; |
6 | 51 | |
7 | | - /* The descriptor is an array of arrays. |
8 | | - i.e. array( |
9 | | - 'fieldname' => array( 'section' => 'section/subsection', |
10 | | - properties... ), |
11 | | - ... |
12 | | - ) |
13 | | - */ |
14 | | - |
| 52 | + # A mapping of 'type' inputs onto standard HTMLFormField subclasses |
15 | 53 | static $typeMappings = array( |
16 | 54 | 'text' => 'HTMLTextField', |
17 | 55 | 'select' => 'HTMLSelectField', |
— | — | @@ -22,14 +60,44 @@ |
23 | 61 | 'float' => 'HTMLFloatField', |
24 | 62 | 'info' => 'HTMLInfoField', |
25 | 63 | 'selectorother' => 'HTMLSelectOrOtherField', |
| 64 | + 'submit' => 'HTMLSubmitField', |
| 65 | + 'hidden' => 'HTMLHiddenField', |
| 66 | + |
26 | 67 | # HTMLTextField will output the correct type="" attribute automagically. |
27 | 68 | # There are about four zillion other HTML 5 input types, like url, but |
28 | 69 | # we don't use those at the moment, so no point in adding all of them. |
29 | 70 | 'email' => 'HTMLTextField', |
30 | 71 | 'password' => 'HTMLTextField', |
31 | 72 | ); |
| 73 | + |
| 74 | + protected $mMessagePrefix; |
| 75 | + protected $mFlatFields; |
| 76 | + protected $mFieldTree; |
| 77 | + protected $mShowReset = false; |
| 78 | + public $mFieldData; |
| 79 | + |
| 80 | + protected $mSubmitCallback; |
| 81 | + protected $mValidationErrorMessage; |
| 82 | + |
| 83 | + protected $mPre = ''; |
| 84 | + protected $mHeader = ''; |
| 85 | + protected $mPost = ''; |
| 86 | + |
| 87 | + protected $mSubmitID; |
| 88 | + protected $mSubmitText; |
| 89 | + protected $mTitle; |
| 90 | + |
| 91 | + protected $mHiddenFields = array(); |
| 92 | + protected $mButtons = array(); |
| 93 | + |
| 94 | + protected $mWrapperLegend = false; |
32 | 95 | |
33 | | - function __construct( $descriptor, $messagePrefix ) { |
| 96 | + /** |
| 97 | + * Build a new HTMLForm from an array of field attributes |
| 98 | + * @param $descriptor Array of Field constructs, as described above |
| 99 | + * @param $messagePrefix String a prefix to go in front of default messages |
| 100 | + */ |
| 101 | + public function __construct( $descriptor, $messagePrefix='' ) { |
34 | 102 | $this->mMessagePrefix = $messagePrefix; |
35 | 103 | |
36 | 104 | // Expand out into a tree. |
— | — | @@ -43,7 +111,7 @@ |
44 | 112 | |
45 | 113 | $info['name'] = $fieldname; |
46 | 114 | |
47 | | - $field = $this->loadInputFromParameters( $info ); |
| 115 | + $field = self::loadInputFromParameters( $info ); |
48 | 116 | $field->mParent = $this; |
49 | 117 | |
50 | 118 | $setSection =& $loadedDescriptor; |
— | — | @@ -66,10 +134,12 @@ |
67 | 135 | } |
68 | 136 | |
69 | 137 | $this->mFieldTree = $loadedDescriptor; |
70 | | - |
71 | | - $this->mShowReset = true; |
72 | 138 | } |
73 | 139 | |
| 140 | + /** |
| 141 | + * Add the HTMLForm-specific JavaScript, if it hasn't been |
| 142 | + * done already. |
| 143 | + */ |
74 | 144 | static function addJS() { |
75 | 145 | if( self::$jsAdded ) return; |
76 | 146 | |
— | — | @@ -78,6 +148,11 @@ |
79 | 149 | $wgOut->addScriptClass( 'htmlform' ); |
80 | 150 | } |
81 | 151 | |
| 152 | + /** |
| 153 | + * Initialise a new Object for the field |
| 154 | + * @param $descriptor input Descriptor, as described above |
| 155 | + * @return HTMLFormField subclass |
| 156 | + */ |
82 | 157 | static function loadInputFromParameters( $descriptor ) { |
83 | 158 | if ( isset( $descriptor['class'] ) ) { |
84 | 159 | $class = $descriptor['class']; |
— | — | @@ -95,15 +170,21 @@ |
96 | 171 | return $obj; |
97 | 172 | } |
98 | 173 | |
| 174 | + /** |
| 175 | + * The here's-one-I-made-earlier option: do the submission if |
| 176 | + * posted, or display the form with or without funky valiation |
| 177 | + * errors |
| 178 | + * @return Bool whether submission was successful. |
| 179 | + */ |
99 | 180 | function show() { |
100 | 181 | $html = ''; |
101 | 182 | |
102 | 183 | self::addJS(); |
103 | 184 | |
104 | | - // Load data from the request. |
| 185 | + # Load data from the request. |
105 | 186 | $this->loadData(); |
106 | 187 | |
107 | | - // Try a submission |
| 188 | + # Try a submission |
108 | 189 | global $wgUser, $wgRequest; |
109 | 190 | $editToken = $wgRequest->getVal( 'wpEditToken' ); |
110 | 191 | |
— | — | @@ -114,23 +195,31 @@ |
115 | 196 | if( $result === true ) |
116 | 197 | return $result; |
117 | 198 | |
118 | | - // Display form. |
| 199 | + # Display form. |
119 | 200 | $this->displayForm( $result ); |
| 201 | + return false; |
120 | 202 | } |
121 | 203 | |
122 | | - /** Return values: |
123 | | - * TRUE == Successful submission |
124 | | - * FALSE == No submission attempted |
125 | | - * Anything else == Error to display. |
126 | | - */ |
| 204 | + /** |
| 205 | + * Validate all the fields, and call the submision callback |
| 206 | + * function if everything is kosher. |
| 207 | + * @return Mixed Bool true == Successful submission, Bool false |
| 208 | + * == No submission attempted, anything else == Error to |
| 209 | + * display. |
| 210 | + */ |
127 | 211 | function trySubmit() { |
128 | | - // Check for validation |
| 212 | + # Check for validation |
129 | 213 | foreach( $this->mFlatFields as $fieldname => $field ) { |
130 | | - if ( !empty( $field->mParams['nodata'] ) ) continue; |
131 | | - if ( $field->validate( $this->mFieldData[$fieldname], |
132 | | - $this->mFieldData ) !== true ) { |
133 | | - return isset( $this->mValidationErrorMessage ) ? |
134 | | - $this->mValidationErrorMessage : array( 'htmlform-invalid-input' ); |
| 214 | + if ( !empty( $field->mParams['nodata'] ) ) |
| 215 | + continue; |
| 216 | + if ( $field->validate( |
| 217 | + $this->mFieldData[$fieldname], |
| 218 | + $this->mFieldData ) |
| 219 | + !== true ) |
| 220 | + { |
| 221 | + return isset( $this->mValidationErrorMessage ) |
| 222 | + ? $this->mValidationErrorMessage |
| 223 | + : array( 'htmlform-invalid-input' ); |
135 | 224 | } |
136 | 225 | } |
137 | 226 | |
— | — | @@ -143,18 +232,69 @@ |
144 | 233 | return $res; |
145 | 234 | } |
146 | 235 | |
| 236 | + /** |
| 237 | + * Set a callback to a function to do something with the form |
| 238 | + * once it's been successfully validated. |
| 239 | + * @param $cb String function name. The function will be passed |
| 240 | + * the output from HTMLForm::filterDataForSubmit, and must |
| 241 | + * return Bool true on success, Bool false if no submission |
| 242 | + * was attempted, or String HTML output to display on error. |
| 243 | + */ |
147 | 244 | function setSubmitCallback( $cb ) { |
148 | 245 | $this->mSubmitCallback = $cb; |
149 | 246 | } |
150 | 247 | |
| 248 | + /** |
| 249 | + * Set a message to display on a validation error. |
| 250 | + * @param $msg Mixed String or Array of valid inputs to wfMsgExt() |
| 251 | + * (so each entry can be either a String or Array) |
| 252 | + */ |
151 | 253 | function setValidationErrorMessage( $msg ) { |
152 | 254 | $this->mValidationErrorMessage = $msg; |
153 | 255 | } |
| 256 | + |
| 257 | + /** |
| 258 | + * Set the introductory message, overwriting any existing message. |
| 259 | + * @param $msg String complete text of message to display |
| 260 | + */ |
| 261 | + function setIntro( $msg ) { $this->mPre = $msg; } |
154 | 262 | |
155 | | - function setIntro( $msg ) { |
156 | | - $this->mIntro = $msg; |
| 263 | + /** |
| 264 | + * Add introductory text. |
| 265 | + * @param $msg String complete text of message to display |
| 266 | + */ |
| 267 | + function addPreText( $msg ) { $this->mPre .= $msg; } |
| 268 | + |
| 269 | + /** |
| 270 | + * Add header text, inside the form. |
| 271 | + * @param $msg String complete text of message to display |
| 272 | + */ |
| 273 | + function addHeaderText( $msg ) { $this->mHeader .= $msg; } |
| 274 | + |
| 275 | + /** |
| 276 | + * Add text to the end of the display. |
| 277 | + * @param $msg String complete text of message to display |
| 278 | + */ |
| 279 | + function addPostText( $msg ) { $this->mPost .= $msg; } |
| 280 | + |
| 281 | + /** |
| 282 | + * Add a hidden field to the output |
| 283 | + * @param $name String field name |
| 284 | + * @param $value String field value |
| 285 | + */ |
| 286 | + public function addHiddenField( $name, $value ){ |
| 287 | + $this->mHiddenFields[ $name ] = $value; |
157 | 288 | } |
| 289 | + |
| 290 | + public function addButton( $name, $value, $id=null ){ |
| 291 | + $this->mButtons[] = compact( 'name', 'value', 'id' ); |
| 292 | + } |
158 | 293 | |
| 294 | + /** |
| 295 | + * Display the form (sending to wgOut), with an appropriate error |
| 296 | + * message or stack of messages, and any validation errors, etc. |
| 297 | + * @param $submitResult Mixed output from HTMLForm::trySubmit() |
| 298 | + */ |
159 | 299 | function displayForm( $submitResult ) { |
160 | 300 | global $wgOut; |
161 | 301 | |
— | — | @@ -162,44 +302,67 @@ |
163 | 303 | $this->displayErrors( $submitResult ); |
164 | 304 | } |
165 | 305 | |
166 | | - if ( isset( $this->mIntro ) ) { |
167 | | - $wgOut->addHTML( $this->mIntro ); |
168 | | - } |
| 306 | + $html = '' |
| 307 | + . $this->mHeader |
| 308 | + . $this->getBody() |
| 309 | + . $this->getHiddenFields() |
| 310 | + . $this->getButtons() |
| 311 | + ; |
169 | 312 | |
170 | | - $html = $this->getBody(); |
171 | | - |
172 | | - // Hidden fields |
173 | | - $html .= $this->getHiddenFields(); |
174 | | - |
175 | | - // Buttons |
176 | | - $html .= $this->getButtons(); |
177 | | - |
178 | 313 | $html = $this->wrapForm( $html ); |
179 | 314 | |
180 | | - $wgOut->addHTML( $html ); |
| 315 | + $wgOut->addHTML( '' |
| 316 | + . $this->mPre |
| 317 | + . $html |
| 318 | + . $this->mPost |
| 319 | + ); |
181 | 320 | } |
182 | 321 | |
| 322 | + /** |
| 323 | + * Wrap the form innards in an actual <form> element |
| 324 | + * @param $html String HTML contents to wrap. |
| 325 | + * @return String wrapped HTML. |
| 326 | + */ |
183 | 327 | function wrapForm( $html ) { |
| 328 | + |
| 329 | + # Include a <fieldset> wrapper for style, if requested. |
| 330 | + if( $this->mWrapperLegend !== false ){ |
| 331 | + $html = Xml::fieldset( $this->mWrapperLegend, $html ); |
| 332 | + } |
| 333 | + |
184 | 334 | return Html::rawElement( |
185 | 335 | 'form', |
186 | 336 | array( |
187 | 337 | 'action' => $this->getTitle()->getFullURL(), |
188 | 338 | 'method' => 'post', |
| 339 | + 'class' => 'visualClear', |
189 | 340 | ), |
190 | 341 | $html |
191 | 342 | ); |
192 | 343 | } |
193 | 344 | |
| 345 | + /** |
| 346 | + * Get the hidden fields that should go inside the form. |
| 347 | + * @return String HTML. |
| 348 | + */ |
194 | 349 | function getHiddenFields() { |
195 | 350 | global $wgUser; |
196 | 351 | $html = ''; |
197 | 352 | |
198 | 353 | $html .= Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n"; |
199 | 354 | $html .= Html::hidden( 'title', $this->getTitle() ) . "\n"; |
| 355 | + |
| 356 | + foreach( $this->mHiddenFields as $name => $value ){ |
| 357 | + $html .= Html::hidden( $name, $value ) . "\n"; |
| 358 | + } |
200 | 359 | |
201 | 360 | return $html; |
202 | 361 | } |
203 | 362 | |
| 363 | + /** |
| 364 | + * Get the submit and (potentially) reset buttons. |
| 365 | + * @return String HTML. |
| 366 | + */ |
204 | 367 | function getButtons() { |
205 | 368 | $html = ''; |
206 | 369 | |
— | — | @@ -222,13 +385,31 @@ |
223 | 386 | ) . "\n"; |
224 | 387 | } |
225 | 388 | |
| 389 | + foreach( $this->mButtons as $button ){ |
| 390 | + $attrs = array( |
| 391 | + 'type' => 'submit', |
| 392 | + 'name' => $button['name'], |
| 393 | + 'value' => $button['value'] |
| 394 | + ); |
| 395 | + if( isset( $button['id'] ) ) |
| 396 | + $attrs['id'] = $button['id']; |
| 397 | + $html .= Html::element( 'input', $attrs ); |
| 398 | + } |
| 399 | + |
226 | 400 | return $html; |
227 | 401 | } |
228 | 402 | |
| 403 | + /** |
| 404 | + * Get the whole body of the form. |
| 405 | + */ |
229 | 406 | function getBody() { |
230 | 407 | return $this->displaySection( $this->mFieldTree ); |
231 | 408 | } |
232 | 409 | |
| 410 | + /** |
| 411 | + * Format and display an error message stack. |
| 412 | + * @param $errors Mixed String or Array of message keys |
| 413 | + */ |
233 | 414 | function displayErrors( $errors ) { |
234 | 415 | if ( is_array( $errors ) ) { |
235 | 416 | $errorstr = $this->formatErrors( $errors ); |
— | — | @@ -242,6 +423,11 @@ |
243 | 424 | $wgOut->addHTML( $errorstr ); |
244 | 425 | } |
245 | 426 | |
| 427 | + /** |
| 428 | + * Format a stack of error messages into a single HTML string |
| 429 | + * @param $errors Array of message keys/values |
| 430 | + * @return String HTML, a <ul> list of errors |
| 431 | + */ |
246 | 432 | static function formatErrors( $errors ) { |
247 | 433 | $errorstr = ''; |
248 | 434 | foreach ( $errors as $error ) { |
— | — | @@ -263,30 +449,70 @@ |
264 | 450 | return $errorstr; |
265 | 451 | } |
266 | 452 | |
| 453 | + /** |
| 454 | + * Set the text for the submit button |
| 455 | + * @param $t String plaintext. |
| 456 | + */ |
267 | 457 | function setSubmitText( $t ) { |
268 | 458 | $this->mSubmitText = $t; |
269 | 459 | } |
270 | 460 | |
| 461 | + /** |
| 462 | + * Get the text for the submit button, either customised or a default. |
| 463 | + * @return unknown_type |
| 464 | + */ |
271 | 465 | function getSubmitText() { |
272 | | - return isset( $this->mSubmitText ) ? $this->mSubmitText : wfMsg( 'htmlform-submit' ); |
| 466 | + return $this->mSubmitText |
| 467 | + ? $this->mSubmitText |
| 468 | + : wfMsg( 'htmlform-submit' ); |
273 | 469 | } |
274 | 470 | |
| 471 | + /** |
| 472 | + * Set the id for the submit button. |
| 473 | + * @param $t String. FIXME: Integrity is *not* validated |
| 474 | + */ |
275 | 475 | function setSubmitID( $t ) { |
276 | 476 | $this->mSubmitID = $t; |
277 | 477 | } |
| 478 | + |
| 479 | + /** |
| 480 | + * Prompt the whole form to be wrapped in a <fieldset>, with |
| 481 | + * this text as its <legend> element. |
| 482 | + * @param $legend String HTML to go inside the <legend> element. |
| 483 | + * Will be escaped |
| 484 | + */ |
| 485 | + public function setWrapperLegend( $legend ){ $this->mWrapperLegend = $legend; } |
278 | 486 | |
| 487 | + /** |
| 488 | + * Set the prefix for various default messages |
| 489 | + * TODO: currently only used for the <fieldset> legend on forms |
| 490 | + * with multiple sections; should be used elsewhre? |
| 491 | + * @param $p String |
| 492 | + */ |
279 | 493 | function setMessagePrefix( $p ) { |
280 | 494 | $this->mMessagePrefix = $p; |
281 | 495 | } |
282 | 496 | |
| 497 | + /** |
| 498 | + * Set the title for form submission |
| 499 | + * @param $t Title of page the form is on/should be posted to |
| 500 | + */ |
283 | 501 | function setTitle( $t ) { |
284 | 502 | $this->mTitle = $t; |
285 | 503 | } |
286 | 504 | |
| 505 | + /** |
| 506 | + * Get the title |
| 507 | + * @return Title |
| 508 | + */ |
287 | 509 | function getTitle() { |
288 | 510 | return $this->mTitle; |
289 | 511 | } |
290 | 512 | |
| 513 | + /** |
| 514 | + * TODO: Document |
| 515 | + * @param $fields |
| 516 | + */ |
291 | 517 | function displaySection( $fields ) { |
292 | 518 | $tableHtml = ''; |
293 | 519 | $subsectionHtml = ''; |
— | — | @@ -295,8 +521,8 @@ |
296 | 522 | foreach( $fields as $key => $value ) { |
297 | 523 | if ( is_object( $value ) ) { |
298 | 524 | $v = empty( $value->mParams['nodata'] ) |
299 | | - ? $this->mFieldData[$key] |
300 | | - : $value->getDefault(); |
| 525 | + ? $this->mFieldData[$key] |
| 526 | + : $value->getDefault(); |
301 | 527 | $tableHtml .= $value->getTableRow( $v ); |
302 | 528 | |
303 | 529 | if( $value->getLabel() != ' ' ) |
— | — | @@ -319,6 +545,9 @@ |
320 | 546 | return $subsectionHtml . "\n" . $tableHtml; |
321 | 547 | } |
322 | 548 | |
| 549 | + /** |
| 550 | + * Construct the form fields from the Descriptor array |
| 551 | + */ |
323 | 552 | function loadData() { |
324 | 553 | global $wgRequest; |
325 | 554 | |
— | — | @@ -333,7 +562,7 @@ |
334 | 563 | } |
335 | 564 | } |
336 | 565 | |
337 | | - // Filter data. |
| 566 | + # Filter data. |
338 | 567 | foreach( $fieldData as $name => &$value ) { |
339 | 568 | $field = $this->mFlatFields[$name]; |
340 | 569 | $value = $field->filter( $value, $this->mFlatFields ); |
— | — | @@ -342,33 +571,60 @@ |
343 | 572 | $this->mFieldData = $fieldData; |
344 | 573 | } |
345 | 574 | |
346 | | - function importData( $fieldData ) { |
347 | | - // Filter data. |
348 | | - foreach( $fieldData as $name => &$value ) { |
349 | | - $field = $this->mFlatFields[$name]; |
350 | | - $value = $field->filter( $value, $this->mFlatFields ); |
351 | | - } |
352 | | - |
353 | | - foreach( $this->mFlatFields as $fieldname => $field ) { |
354 | | - if ( !isset( $fieldData[$fieldname] ) ) |
355 | | - $fieldData[$fieldname] = $field->getDefault(); |
356 | | - } |
357 | | - |
358 | | - $this->mFieldData = $fieldData; |
359 | | - } |
360 | | - |
| 575 | + /** |
| 576 | + * Stop a reset button being shown for this form |
| 577 | + * @param $suppressReset Bool set to false to re-enable the |
| 578 | + * button again |
| 579 | + */ |
361 | 580 | function suppressReset( $suppressReset = true ) { |
362 | 581 | $this->mShowReset = !$suppressReset; |
363 | 582 | } |
364 | 583 | |
| 584 | + /** |
| 585 | + * Overload this if you want to apply special filtration routines |
| 586 | + * to the form as a whole, after it's submitted but before it's |
| 587 | + * processed. |
| 588 | + * @param $data |
| 589 | + * @return unknown_type |
| 590 | + */ |
365 | 591 | function filterDataForSubmit( $data ) { |
366 | 592 | return $data; |
367 | 593 | } |
368 | 594 | } |
369 | 595 | |
| 596 | +/** |
| 597 | + * The parent class to generate form fields. Any field type should |
| 598 | + * be a subclass of this. |
| 599 | + */ |
370 | 600 | abstract class HTMLFormField { |
| 601 | + |
| 602 | + protected $mValidationCallback; |
| 603 | + protected $mFilterCallback; |
| 604 | + protected $mName; |
| 605 | + public $mParams; |
| 606 | + protected $mLabel; # String label. Set on construction |
| 607 | + protected $mID; |
| 608 | + protected $mDefault; |
| 609 | + public $mParent; |
| 610 | + |
| 611 | + /** |
| 612 | + * This function must be implemented to return the HTML to generate |
| 613 | + * the input object itself. It should not implement the surrounding |
| 614 | + * table cells/rows, or labels/help messages. |
| 615 | + * @param $value String the value to set the input to; eg a default |
| 616 | + * text for a text input. |
| 617 | + * @return String valid HTML. |
| 618 | + */ |
371 | 619 | abstract function getInputHTML( $value ); |
372 | 620 | |
| 621 | + /** |
| 622 | + * Override this function to add specific validation checks on the |
| 623 | + * field input. Don't forget to call parent::validate() to ensure |
| 624 | + * that the user-defined callback mValidationCallback is still run |
| 625 | + * @param $value String the value the field was submitted with |
| 626 | + * @param $alldata $all the data collected from the form |
| 627 | + * @return Mixed Bool true on success, or String error to display. |
| 628 | + */ |
373 | 629 | function validate( $value, $alldata ) { |
374 | 630 | if ( isset( $this->mValidationCallback ) ) { |
375 | 631 | return call_user_func( $this->mValidationCallback, $value, $alldata ); |
— | — | @@ -395,6 +651,12 @@ |
396 | 652 | return true; |
397 | 653 | } |
398 | 654 | |
| 655 | + /** |
| 656 | + * Get the value that this input has been set to from a posted form, |
| 657 | + * or the input's default value if it has not been set. |
| 658 | + * @param $request WebRequest |
| 659 | + * @return String the value |
| 660 | + */ |
399 | 661 | function loadDataFromRequest( $request ) { |
400 | 662 | if( $request->getCheck( $this->mName ) ) { |
401 | 663 | return $request->getText( $this->mName ); |
— | — | @@ -403,9 +665,14 @@ |
404 | 666 | } |
405 | 667 | } |
406 | 668 | |
| 669 | + /** |
| 670 | + * Initialise the object |
| 671 | + * @param $params Associative Array. See HTMLForm doc for syntax. |
| 672 | + */ |
407 | 673 | function __construct( $params ) { |
408 | 674 | $this->mParams = $params; |
409 | 675 | |
| 676 | + # Generate the label from a message, if possible |
410 | 677 | if( isset( $params['label-message'] ) ) { |
411 | 678 | $msgInfo = $params['label-message']; |
412 | 679 | |
— | — | @@ -453,8 +720,14 @@ |
454 | 721 | } |
455 | 722 | } |
456 | 723 | |
| 724 | + /** |
| 725 | + * Get the complete table row for the input, including help text, |
| 726 | + * labels, and whatever. |
| 727 | + * @param $value String the value to set the input to. |
| 728 | + * @return String complete HTML table row. |
| 729 | + */ |
457 | 730 | function getTableRow( $value ) { |
458 | | - // Check for invalid data. |
| 731 | + # Check for invalid data. |
459 | 732 | global $wgRequest; |
460 | 733 | |
461 | 734 | $errors = $this->validate( $value, $this->mParent->mFieldData ); |
— | — | @@ -517,7 +790,14 @@ |
518 | 791 | } |
519 | 792 | } |
520 | 793 | |
521 | | - static function flattenOptions( $options ) { |
| 794 | + /** |
| 795 | + * flatten an array of options to a single array, for instance, |
| 796 | + * a set of <options> inside <optgroups>. |
| 797 | + * @param $options Associative Array with values either Strings |
| 798 | + * or Arrays |
| 799 | + * @return Array flattened input |
| 800 | + */ |
| 801 | + public static function flattenOptions( $options ) { |
522 | 802 | $flatOpts = array(); |
523 | 803 | |
524 | 804 | foreach( $options as $key => $value ) { |
— | — | @@ -533,11 +813,11 @@ |
534 | 814 | } |
535 | 815 | |
536 | 816 | class HTMLTextField extends HTMLFormField { |
537 | | - # Override in derived classes to use other Xml::... functions |
538 | | - protected $mFunction = 'input'; |
539 | 817 | |
540 | 818 | function getSize() { |
541 | | - return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45; |
| 819 | + return isset( $this->mParams['size'] ) |
| 820 | + ? $this->mParams['size'] |
| 821 | + : 45; |
542 | 822 | } |
543 | 823 | |
544 | 824 | function getInputHTML( $value ) { |
— | — | @@ -572,14 +852,11 @@ |
573 | 853 | $attribs[$param] = ''; |
574 | 854 | } |
575 | 855 | } |
576 | | - } |
577 | | - |
578 | | - # Implement tiny differences between some field variants |
579 | | - # here, rather than creating a new class for each one which |
580 | | - # is essentially just a clone of this one. |
581 | | - if ( isset( $this->mParams['type'] ) ) { |
582 | | - # Options that apply only to HTML5 |
583 | | - if( $wgHtml5 ){ |
| 856 | + |
| 857 | + # Implement tiny differences between some field variants |
| 858 | + # here, rather than creating a new class for each one which |
| 859 | + # is essentially just a clone of this one. |
| 860 | + if ( isset( $this->mParams['type'] ) ) { |
584 | 861 | switch ( $this->mParams['type'] ) { |
585 | 862 | case 'email': |
586 | 863 | $attribs['type'] = 'email'; |
— | — | @@ -591,23 +868,26 @@ |
592 | 869 | $attribs['type'] = 'number'; |
593 | 870 | $attribs['step'] = 'any'; |
594 | 871 | break; |
| 872 | + case 'password': |
| 873 | + $attribs['type'] = 'password'; |
| 874 | + break; |
595 | 875 | } |
596 | 876 | } |
597 | | - # Options that apply to HTML4 as well |
598 | | - switch( $this->mParams['type'] ){ |
599 | | - case 'password': |
600 | | - $attribs['type'] = 'password'; |
601 | | - break; |
602 | | - } |
603 | 877 | } |
604 | 878 | |
605 | 879 | return Html::element( 'input', $attribs ); |
606 | 880 | } |
607 | 881 | } |
608 | 882 | |
| 883 | +/** |
| 884 | + * A field that will contain a numeric value |
| 885 | + */ |
609 | 886 | class HTMLFloatField extends HTMLTextField { |
| 887 | + |
610 | 888 | function getSize() { |
611 | | - return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 20; |
| 889 | + return isset( $this->mParams['size'] ) |
| 890 | + ? $this->mParams['size'] |
| 891 | + : 20; |
612 | 892 | } |
613 | 893 | |
614 | 894 | function validate( $value, $alldata ) { |
— | — | @@ -621,8 +901,8 @@ |
622 | 902 | |
623 | 903 | $in_range = true; |
624 | 904 | |
625 | | - # The "int" part of these message names is rather confusing. They make |
626 | | - # equal sense for all numbers. |
| 905 | + # The "int" part of these message names is rather confusing. |
| 906 | + # They make equal sense for all numbers. |
627 | 907 | if ( isset( $this->mParams['min'] ) ) { |
628 | 908 | $min = $this->mParams['min']; |
629 | 909 | if ( $min > $value ) |
— | — | @@ -639,6 +919,9 @@ |
640 | 920 | } |
641 | 921 | } |
642 | 922 | |
| 923 | +/** |
| 924 | + * A field that must contain a number |
| 925 | + */ |
643 | 926 | class HTMLIntField extends HTMLFloatField { |
644 | 927 | function validate( $value, $alldata ) { |
645 | 928 | $p = parent::validate( $value, $alldata ); |
— | — | @@ -653,6 +936,9 @@ |
654 | 937 | } |
655 | 938 | } |
656 | 939 | |
| 940 | +/** |
| 941 | + * A checkbox field |
| 942 | + */ |
657 | 943 | class HTMLCheckField extends HTMLFormField { |
658 | 944 | function getInputHTML( $value ) { |
659 | 945 | if ( !empty( $this->mParams['invert'] ) ) |
— | — | @@ -667,8 +953,12 @@ |
668 | 954 | Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); |
669 | 955 | } |
670 | 956 | |
| 957 | + /** |
| 958 | + * For a checkbox, the label goes on the right hand side, and is |
| 959 | + * added in getInputHTML(), rather than HTMLFormField::getRow() |
| 960 | + */ |
671 | 961 | function getLabel() { |
672 | | - return ' '; // In the right-hand column. |
| 962 | + return ' '; |
673 | 963 | } |
674 | 964 | |
675 | 965 | function loadDataFromRequest( $request ) { |
— | — | @@ -692,6 +982,9 @@ |
693 | 983 | } |
694 | 984 | } |
695 | 985 | |
| 986 | +/** |
| 987 | + * A select dropdown field. Basically a wrapper for Xmlselect class |
| 988 | + */ |
696 | 989 | class HTMLSelectField extends HTMLFormField { |
697 | 990 | |
698 | 991 | function validate( $value, $alldata ) { |
— | — | @@ -708,9 +1001,9 @@ |
709 | 1002 | function getInputHTML( $value ) { |
710 | 1003 | $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) ); |
711 | 1004 | |
712 | | - // If one of the options' 'name' is int(0), it is automatically selected. |
713 | | - // because PHP sucks and things int(0) == 'some string'. |
714 | | - // Working around this by forcing all of them to strings. |
| 1005 | + # If one of the options' 'name' is int(0), it is automatically selected. |
| 1006 | + # because PHP sucks and things int(0) == 'some string'. |
| 1007 | + # Working around this by forcing all of them to strings. |
715 | 1008 | $options = array_map( 'strval', $this->mParams['options'] ); |
716 | 1009 | |
717 | 1010 | if( !empty( $this->mParams['disabled'] ) ) { |
— | — | @@ -723,6 +1016,9 @@ |
724 | 1017 | } |
725 | 1018 | } |
726 | 1019 | |
| 1020 | +/** |
| 1021 | + * Select dropdown field, with an additional "other" textbox. |
| 1022 | + */ |
727 | 1023 | class HTMLSelectOrOtherField extends HTMLTextField { |
728 | 1024 | static $jsAdded = false; |
729 | 1025 | |
— | — | @@ -793,15 +1089,19 @@ |
794 | 1090 | } |
795 | 1091 | } |
796 | 1092 | |
| 1093 | +/** |
| 1094 | + * Multi-select field |
| 1095 | + */ |
797 | 1096 | class HTMLMultiSelectField extends HTMLFormField { |
| 1097 | + |
798 | 1098 | function validate( $value, $alldata ) { |
799 | 1099 | $p = parent::validate( $value, $alldata ); |
800 | 1100 | if( $p !== true ) return $p; |
801 | 1101 | |
802 | 1102 | if( !is_array( $value ) ) return false; |
803 | 1103 | |
804 | | - // If all options are valid, array_intersect of the valid options and the provided |
805 | | - // options will return the provided options. |
| 1104 | + # If all options are valid, array_intersect of the valid options |
| 1105 | + # and the provided options will return the provided options. |
806 | 1106 | $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); |
807 | 1107 | |
808 | 1108 | $validValues = array_intersect( $value, $validOptions ); |
— | — | @@ -844,7 +1144,7 @@ |
845 | 1145 | } |
846 | 1146 | |
847 | 1147 | function loadDataFromRequest( $request ) { |
848 | | - // won't work with getCheck |
| 1148 | + # won't work with getCheck |
849 | 1149 | if( $request->getCheck( 'wpEditToken' ) ) { |
850 | 1150 | $arr = $request->getArray( $this->mName ); |
851 | 1151 | |
— | — | @@ -870,6 +1170,9 @@ |
871 | 1171 | } |
872 | 1172 | } |
873 | 1173 | |
| 1174 | +/** |
| 1175 | + * Radio checkbox fields. |
| 1176 | + */ |
874 | 1177 | class HTMLRadioField extends HTMLFormField { |
875 | 1178 | function validate( $value, $alldata ) { |
876 | 1179 | $p = parent::validate( $value, $alldata ); |
— | — | @@ -886,6 +1189,10 @@ |
887 | 1190 | return wfMsgExt( 'htmlform-select-badoption', 'parseinline' ); |
888 | 1191 | } |
889 | 1192 | |
| 1193 | + /** |
| 1194 | + * This returns a block of all the radio options, in one cell. |
| 1195 | + * @see includes/HTMLFormField#getInputHTML() |
| 1196 | + */ |
890 | 1197 | function getInputHTML( $value ) { |
891 | 1198 | $html = $this->formatOptions( $this->mParams['options'], $value ); |
892 | 1199 | |
— | — | @@ -900,6 +1207,7 @@ |
901 | 1208 | $attribs['disabled'] = 'disabled'; |
902 | 1209 | } |
903 | 1210 | |
| 1211 | + # TODO: should this produce an unordered list perhaps? |
904 | 1212 | foreach( $options as $label => $info ) { |
905 | 1213 | if( is_array( $info ) ) { |
906 | 1214 | $html .= Html::rawElement( 'h1', array(), $label ) . "\n"; |
— | — | @@ -923,6 +1231,9 @@ |
924 | 1232 | } |
925 | 1233 | } |
926 | 1234 | |
| 1235 | +/** |
| 1236 | + * An information field (text blob), not a proper input. |
| 1237 | + */ |
927 | 1238 | class HTMLInfoField extends HTMLFormField { |
928 | 1239 | function __construct( $info ) { |
929 | 1240 | $info['nodata'] = true; |
— | — | @@ -946,3 +1257,29 @@ |
947 | 1258 | return false; |
948 | 1259 | } |
949 | 1260 | } |
| 1261 | + |
| 1262 | +class HTMLHiddenField extends HTMLFormField { |
| 1263 | + |
| 1264 | + public function getTableRow( $value ){ |
| 1265 | + $this->mParent->addHiddenField( |
| 1266 | + $this->mParams['name'], |
| 1267 | + $this->mParams['default'] |
| 1268 | + ); |
| 1269 | + return ''; |
| 1270 | + } |
| 1271 | + |
| 1272 | + public function getInputHTML( $value ){ return ''; } |
| 1273 | +} |
| 1274 | + |
| 1275 | +class HTMLSubmitField extends HTMLFormField { |
| 1276 | + |
| 1277 | + public function getTableRow( $value ){ |
| 1278 | + $this->mParent->addButton( |
| 1279 | + $this->mParams['name'], |
| 1280 | + $this->mParams['default'], |
| 1281 | + isset($this->mParams['id']) ? $this->mParams['id'] : null |
| 1282 | + ); |
| 1283 | + } |
| 1284 | + |
| 1285 | + public function getInputHTML( $value ){ return ''; } |
| 1286 | +} |
\ No newline at end of file |