~ubuntu-branches/ubuntu/quantal/mediawiki/quantal

« back to all changes in this revision

Viewing changes to .pc/fix_invalid_xhtml.patch/includes/HTMLForm.php

  • Committer: Package Import Robot
  • Author(s): Thorsten Glaser, Thorsten Glaser, Jonathan Wiltshire
  • Date: 2012-09-20 13:40:12 UTC
  • mfrom: (16.1.20 sid)
  • Revision ID: package-import@ubuntu.com-20120920134012-fbv87yohdi5b3svt
Tags: 1:1.19.2-1
[ Thorsten Glaser ]
* New upstream: security fixes for CVE-2012-4377, CVE-2012-4378,
  CVE-2012-4379, CVE-2012-4380, CVE-2012-4381, CVE-2012-4382
  (Closes: #686330)
* Prevent <table></table> without any <tr /> inside, globally
* Fix more cases of not checking $wgHtml5
* MW’s ID (XML) sanitiser is there for a reason, use it!
* Prevent <ul></ul> without any <li /> inside in MonoBook
* Fix invalid XHTML caused by code not honouring $wgHtml5
* Quell some PHP warnings from sloppy code
* Do the wfSuppressWarnings patch used with FusionForge right
* Add myself to Uploaders and quieten lintian a bit
* Do not replace patched jquery-tablesorter with unpatched one;
  unbreaks sortable tables (Closes: #687519)
* Update versioned Breaks against fusionforge and mw-extensions

[ Jonathan Wiltshire ]
* Add Recommends on mediawiki-extensions-base and php-wikidiff2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Object handling generic submission, CSRF protection, layout and
 
4
 * other logic for UI forms. in a reusable manner.
 
5
 *
 
6
 * In order to generate the form, the HTMLForm object takes an array
 
7
 * structure detailing the form fields available. Each element of the
 
8
 * array is a basic property-list, including the type of field, the
 
9
 * label it is to be given in the form, callbacks for validation and
 
10
 * 'filtering', and other pertinent information.
 
11
 *
 
12
 * Field types are implemented as subclasses of the generic HTMLFormField
 
13
 * object, and typically implement at least getInputHTML, which generates
 
14
 * the HTML for the input field to be placed in the table.
 
15
 *
 
16
 * The constructor input is an associative array of $fieldname => $info,
 
17
 * where $info is an Associative Array with any of the following:
 
18
 *
 
19
 *      'class'               -- the subclass of HTMLFormField that will be used
 
20
 *                               to create the object.  *NOT* the CSS class!
 
21
 *      'type'                -- roughly translates into the <select> type attribute.
 
22
 *                               if 'class' is not specified, this is used as a map
 
23
 *                               through HTMLForm::$typeMappings to get the class name.
 
24
 *      'default'             -- default value when the form is displayed
 
25
 *      'id'                  -- HTML id attribute
 
26
 *      'cssclass'            -- CSS class
 
27
 *      'options'             -- varies according to the specific object.
 
28
 *      'label-message'       -- message key for a message to use as the label.
 
29
 *                               can be an array of msg key and then parameters to
 
30
 *                               the message.
 
31
 *      'label'               -- alternatively, a raw text message. Overridden by
 
32
 *                               label-message
 
33
 *      'help-message'        -- message key for a message to use as a help text.
 
34
 *                               can be an array of msg key and then parameters to
 
35
 *                               the message.
 
36
 *                               Overwrites 'help-messages'.
 
37
 *      'help-messages'       -- array of message key. As above, each item can
 
38
 *                               be an array of msg key and then parameters.
 
39
 *                               Overwrites 'help-message'.
 
40
 *      'required'            -- passed through to the object, indicating that it
 
41
 *                               is a required field.
 
42
 *      'size'                -- the length of text fields
 
43
 *      'filter-callback      -- a function name to give you the chance to
 
44
 *                               massage the inputted value before it's processed.
 
45
 *                               @see HTMLForm::filter()
 
46
 *      'validation-callback' -- a function name to give you the chance
 
47
 *                               to impose extra validation on the field input.
 
48
 *                               @see HTMLForm::validate()
 
49
 *      'name'                -- By default, the 'name' attribute of the input field
 
50
 *                               is "wp{$fieldname}".  If you want a different name
 
51
 *                               (eg one without the "wp" prefix), specify it here and
 
52
 *                               it will be used without modification.
 
53
 *
 
54
 * TODO: Document 'section' / 'subsection' stuff
 
55
 */
 
56
class HTMLForm extends ContextSource {
 
57
 
 
58
        // A mapping of 'type' inputs onto standard HTMLFormField subclasses
 
59
        static $typeMappings = array(
 
60
                'text' => 'HTMLTextField',
 
61
                'textarea' => 'HTMLTextAreaField',
 
62
                'select' => 'HTMLSelectField',
 
63
                'radio' => 'HTMLRadioField',
 
64
                'multiselect' => 'HTMLMultiSelectField',
 
65
                'check' => 'HTMLCheckField',
 
66
                'toggle' => 'HTMLCheckField',
 
67
                'int' => 'HTMLIntField',
 
68
                'float' => 'HTMLFloatField',
 
69
                'info' => 'HTMLInfoField',
 
70
                'selectorother' => 'HTMLSelectOrOtherField',
 
71
                'selectandother' => 'HTMLSelectAndOtherField',
 
72
                'submit' => 'HTMLSubmitField',
 
73
                'hidden' => 'HTMLHiddenField',
 
74
                'edittools' => 'HTMLEditTools',
 
75
 
 
76
                // HTMLTextField will output the correct type="" attribute automagically.
 
77
                // There are about four zillion other HTML5 input types, like url, but
 
78
                // we don't use those at the moment, so no point in adding all of them.
 
79
                'email' => 'HTMLTextField',
 
80
                'password' => 'HTMLTextField',
 
81
        );
 
82
 
 
83
        protected $mMessagePrefix;
 
84
 
 
85
        /** @var HTMLFormField[] */
 
86
        protected $mFlatFields;
 
87
 
 
88
        protected $mFieldTree;
 
89
        protected $mShowReset = false;
 
90
        public $mFieldData;
 
91
 
 
92
        protected $mSubmitCallback;
 
93
        protected $mValidationErrorMessage;
 
94
 
 
95
        protected $mPre = '';
 
96
        protected $mHeader = '';
 
97
        protected $mFooter = '';
 
98
        protected $mSectionHeaders = array();
 
99
        protected $mSectionFooters = array();
 
100
        protected $mPost = '';
 
101
        protected $mId;
 
102
 
 
103
        protected $mSubmitID;
 
104
        protected $mSubmitName;
 
105
        protected $mSubmitText;
 
106
        protected $mSubmitTooltip;
 
107
 
 
108
        protected $mTitle;
 
109
        protected $mMethod = 'post';
 
110
 
 
111
        /**
 
112
         * Form action URL. false means we will use the URL to set Title
 
113
         * @since 1.19
 
114
         * @var false|string
 
115
         */
 
116
        protected $mAction = false;
 
117
 
 
118
        protected $mUseMultipart = false;
 
119
        protected $mHiddenFields = array();
 
120
        protected $mButtons = array();
 
121
 
 
122
        protected $mWrapperLegend = false;
 
123
        
 
124
        /**
 
125
         * If true, sections that contain both fields and subsections will
 
126
         * render their subsections before their fields.
 
127
         * 
 
128
         * Subclasses may set this to false to render subsections after fields
 
129
         * instead.
 
130
         */
 
131
        protected $mSubSectionBeforeFields = true;
 
132
 
 
133
        /**
 
134
         * Build a new HTMLForm from an array of field attributes
 
135
         * @param $descriptor Array of Field constructs, as described above
 
136
         * @param $context IContextSource available since 1.18, will become compulsory in 1.18.
 
137
         *     Obviates the need to call $form->setTitle()
 
138
         * @param $messagePrefix String a prefix to go in front of default messages
 
139
         */
 
140
        public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
 
141
                if( $context instanceof IContextSource ){
 
142
                        $this->setContext( $context );
 
143
                        $this->mTitle = false; // We don't need them to set a title
 
144
                        $this->mMessagePrefix = $messagePrefix;
 
145
                } else {
 
146
                        // B/C since 1.18
 
147
                        if( is_string( $context ) && $messagePrefix === '' ){
 
148
                                // it's actually $messagePrefix
 
149
                                $this->mMessagePrefix = $context;
 
150
                        }
 
151
                }
 
152
 
 
153
                // Expand out into a tree.
 
154
                $loadedDescriptor = array();
 
155
                $this->mFlatFields = array();
 
156
 
 
157
                foreach ( $descriptor as $fieldname => $info ) {
 
158
                        $section = isset( $info['section'] )
 
159
                                ? $info['section']
 
160
                                : '';
 
161
 
 
162
                        if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
 
163
                                $this->mUseMultipart = true;
 
164
                        }
 
165
 
 
166
                        $field = self::loadInputFromParameters( $fieldname, $info );
 
167
                        $field->mParent = $this;
 
168
 
 
169
                        $setSection =& $loadedDescriptor;
 
170
                        if ( $section ) {
 
171
                                $sectionParts = explode( '/', $section );
 
172
 
 
173
                                while ( count( $sectionParts ) ) {
 
174
                                        $newName = array_shift( $sectionParts );
 
175
 
 
176
                                        if ( !isset( $setSection[$newName] ) ) {
 
177
                                                $setSection[$newName] = array();
 
178
                                        }
 
179
 
 
180
                                        $setSection =& $setSection[$newName];
 
181
                                }
 
182
                        }
 
183
 
 
184
                        $setSection[$fieldname] = $field;
 
185
                        $this->mFlatFields[$fieldname] = $field;
 
186
                }
 
187
 
 
188
                $this->mFieldTree = $loadedDescriptor;
 
189
        }
 
190
 
 
191
        /**
 
192
         * Add the HTMLForm-specific JavaScript, if it hasn't been
 
193
         * done already.
 
194
         * @deprecated since 1.18 load modules with ResourceLoader instead
 
195
         */
 
196
        static function addJS() { wfDeprecated( __METHOD__, '1.18' ); }
 
197
 
 
198
        /**
 
199
         * Initialise a new Object for the field
 
200
         * @param $fieldname string
 
201
         * @param $descriptor string input Descriptor, as described above
 
202
         * @return HTMLFormField subclass
 
203
         */
 
204
        static function loadInputFromParameters( $fieldname, $descriptor ) {
 
205
                if ( isset( $descriptor['class'] ) ) {
 
206
                        $class = $descriptor['class'];
 
207
                } elseif ( isset( $descriptor['type'] ) ) {
 
208
                        $class = self::$typeMappings[$descriptor['type']];
 
209
                        $descriptor['class'] = $class;
 
210
                } else {
 
211
                        $class = null;
 
212
                }
 
213
 
 
214
                if ( !$class ) {
 
215
                        throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
 
216
                }
 
217
 
 
218
                $descriptor['fieldname'] = $fieldname;
 
219
 
 
220
                $obj = new $class( $descriptor );
 
221
 
 
222
                return $obj;
 
223
        }
 
224
 
 
225
        /**
 
226
         * Prepare form for submission
 
227
         */
 
228
        function prepareForm() {
 
229
                # Check if we have the info we need
 
230
                if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
 
231
                        throw new MWException( "You must call setTitle() on an HTMLForm" );
 
232
                }
 
233
 
 
234
                # Load data from the request.
 
235
                $this->loadData();
 
236
        }
 
237
 
 
238
        /**
 
239
         * Try submitting, with edit token check first
 
240
         * @return Status|boolean
 
241
         */
 
242
        function tryAuthorizedSubmit() {
 
243
                $result = false;
 
244
 
 
245
                $submit = false;
 
246
                if ( $this->getMethod() != 'post' ) {
 
247
                        $submit = true; // no session check needed
 
248
                } elseif ( $this->getRequest()->wasPosted() ) {
 
249
                        $editToken = $this->getRequest()->getVal( 'wpEditToken' );
 
250
                        if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
 
251
                                // Session tokens for logged-out users have no security value.
 
252
                                // However, if the user gave one, check it in order to give a nice 
 
253
                                // "session expired" error instead of "permission denied" or such.
 
254
                                $submit = $this->getUser()->matchEditToken( $editToken );
 
255
                        } else {
 
256
                                $submit = true;
 
257
                        }
 
258
                }
 
259
 
 
260
                if ( $submit ) {
 
261
                        $result = $this->trySubmit();
 
262
                }
 
263
 
 
264
                return $result;
 
265
        }
 
266
 
 
267
        /**
 
268
         * The here's-one-I-made-earlier option: do the submission if
 
269
         * posted, or display the form with or without funky valiation
 
270
         * errors
 
271
         * @return Bool or Status whether submission was successful.
 
272
         */
 
273
        function show() {
 
274
                $this->prepareForm();
 
275
 
 
276
                $result = $this->tryAuthorizedSubmit();
 
277
                if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){
 
278
                        return $result;
 
279
                }
 
280
 
 
281
                $this->displayForm( $result );
 
282
                return false;
 
283
        }
 
284
 
 
285
        /**
 
286
         * Validate all the fields, and call the submision callback
 
287
         * function if everything is kosher.
 
288
         * @return Mixed Bool true == Successful submission, Bool false
 
289
         *       == No submission attempted, anything else == Error to
 
290
         *       display.
 
291
         */
 
292
        function trySubmit() {
 
293
                # Check for validation
 
294
                foreach ( $this->mFlatFields as $fieldname => $field ) {
 
295
                        if ( !empty( $field->mParams['nodata'] ) ) {
 
296
                                continue;
 
297
                        }
 
298
                        if ( $field->validate(
 
299
                                        $this->mFieldData[$fieldname],
 
300
                                        $this->mFieldData )
 
301
                                !== true
 
302
                        ) {
 
303
                                return isset( $this->mValidationErrorMessage )
 
304
                                        ? $this->mValidationErrorMessage
 
305
                                        : array( 'htmlform-invalid-input' );
 
306
                        }
 
307
                }
 
308
 
 
309
                $callback = $this->mSubmitCallback;
 
310
 
 
311
                $data = $this->filterDataForSubmit( $this->mFieldData );
 
312
 
 
313
                $res = call_user_func( $callback, $data, $this );
 
314
 
 
315
                return $res;
 
316
        }
 
317
 
 
318
        /**
 
319
         * Set a callback to a function to do something with the form
 
320
         * once it's been successfully validated.
 
321
         * @param $cb String function name.  The function will be passed
 
322
         *       the output from HTMLForm::filterDataForSubmit, and must
 
323
         *       return Bool true on success, Bool false if no submission
 
324
         *       was attempted, or String HTML output to display on error.
 
325
         */
 
326
        function setSubmitCallback( $cb ) {
 
327
                $this->mSubmitCallback = $cb;
 
328
        }
 
329
 
 
330
        /**
 
331
         * Set a message to display on a validation error.
 
332
         * @param $msg Mixed String or Array of valid inputs to wfMsgExt()
 
333
         *       (so each entry can be either a String or Array)
 
334
         */
 
335
        function setValidationErrorMessage( $msg ) {
 
336
                $this->mValidationErrorMessage = $msg;
 
337
        }
 
338
 
 
339
        /**
 
340
         * Set the introductory message, overwriting any existing message.
 
341
         * @param $msg String complete text of message to display
 
342
         */
 
343
        function setIntro( $msg ) {
 
344
                $this->setPreText( $msg );
 
345
        }
 
346
 
 
347
        /**
 
348
         * Set the introductory message, overwriting any existing message.
 
349
         * @since 1.19
 
350
         * @param $msg String complete text of message to display
 
351
         */
 
352
        function setPreText( $msg ) { $this->mPre = $msg; }
 
353
 
 
354
        /**
 
355
         * Add introductory text.
 
356
         * @param $msg String complete text of message to display
 
357
         */
 
358
        function addPreText( $msg ) { $this->mPre .= $msg; }
 
359
 
 
360
        /**
 
361
         * Add header text, inside the form.
 
362
         * @param $msg String complete text of message to display
 
363
         * @param $section string The section to add the header to
 
364
         */
 
365
        function addHeaderText( $msg, $section = null ) {
 
366
                if ( is_null( $section ) ) {
 
367
                        $this->mHeader .= $msg;
 
368
                } else {
 
369
                        if ( !isset( $this->mSectionHeaders[$section] ) ) {
 
370
                                $this->mSectionHeaders[$section] = '';
 
371
                        }
 
372
                        $this->mSectionHeaders[$section] .= $msg;
 
373
                }
 
374
        }
 
375
 
 
376
        /**
 
377
         * Set header text, inside the form.
 
378
         * @since 1.19
 
379
         * @param $msg String complete text of message to display
 
380
         * @param $section The section to add the header to
 
381
         */
 
382
        function setHeaderText( $msg, $section = null ) {
 
383
                if ( is_null( $section ) ) {
 
384
                        $this->mHeader = $msg;
 
385
                } else {
 
386
                        $this->mSectionHeaders[$section] = $msg;
 
387
                }
 
388
        }
 
389
 
 
390
        /**
 
391
         * Add footer text, inside the form.
 
392
         * @param $msg String complete text of message to display
 
393
         * @param $section string The section to add the footer text to
 
394
         */
 
395
        function addFooterText( $msg, $section = null ) {
 
396
                if ( is_null( $section ) ) {
 
397
                        $this->mFooter .= $msg;
 
398
                } else {
 
399
                        if ( !isset( $this->mSectionFooters[$section] ) ) {
 
400
                                $this->mSectionFooters[$section] = '';
 
401
                        }
 
402
                        $this->mSectionFooters[$section] .= $msg;
 
403
                }
 
404
        }
 
405
 
 
406
        /**
 
407
         * Set footer text, inside the form.
 
408
         * @since 1.19
 
409
         * @param $msg String complete text of message to display
 
410
         * @param $section string The section to add the footer text to
 
411
         */
 
412
        function setFooterText( $msg, $section = null ) {
 
413
                if ( is_null( $section ) ) {
 
414
                        $this->mFooter = $msg;
 
415
                } else {
 
416
                        $this->mSectionFooters[$section] = $msg;
 
417
                }
 
418
        }
 
419
 
 
420
        /**
 
421
         * Add text to the end of the display.
 
422
         * @param $msg String complete text of message to display
 
423
         */
 
424
        function addPostText( $msg ) { $this->mPost .= $msg; }
 
425
 
 
426
        /**
 
427
         * Set text at the end of the display.
 
428
         * @param $msg String complete text of message to display
 
429
         */
 
430
        function setPostText( $msg ) { $this->mPost = $msg; }
 
431
 
 
432
        /**
 
433
         * Add a hidden field to the output
 
434
         * @param $name String field name.  This will be used exactly as entered
 
435
         * @param $value String field value
 
436
         * @param $attribs Array
 
437
         */
 
438
        public function addHiddenField( $name, $value, $attribs = array() ) {
 
439
                $attribs += array( 'name' => $name );
 
440
                $this->mHiddenFields[] = array( $value, $attribs );
 
441
        }
 
442
 
 
443
        public function addButton( $name, $value, $id = null, $attribs = null ) {
 
444
                $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
 
445
        }
 
446
 
 
447
        /**
 
448
         * Display the form (sending to $wgOut), with an appropriate error
 
449
         * message or stack of messages, and any validation errors, etc.
 
450
         * @param $submitResult Mixed output from HTMLForm::trySubmit()
 
451
         */
 
452
        function displayForm( $submitResult ) {
 
453
                $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
 
454
        }
 
455
 
 
456
        /**
 
457
         * Returns the raw HTML generated by the form
 
458
         * @param $submitResult Mixed output from HTMLForm::trySubmit()
 
459
         * @return string
 
460
         */
 
461
        function getHTML( $submitResult ) {
 
462
                # For good measure (it is the default)
 
463
                $this->getOutput()->preventClickjacking();
 
464
                $this->getOutput()->addModules( 'mediawiki.htmlform' );
 
465
 
 
466
                $html = ''
 
467
                        . $this->getErrors( $submitResult )
 
468
                        . $this->mHeader
 
469
                        . $this->getBody()
 
470
                        . $this->getHiddenFields()
 
471
                        . $this->getButtons()
 
472
                        . $this->mFooter
 
473
                ;
 
474
 
 
475
                $html = $this->wrapForm( $html );
 
476
 
 
477
                return '' . $this->mPre . $html . $this->mPost;
 
478
        }
 
479
 
 
480
        /**
 
481
         * Wrap the form innards in an actual <form> element
 
482
         * @param $html String HTML contents to wrap.
 
483
         * @return String wrapped HTML.
 
484
         */
 
485
        function wrapForm( $html ) {
 
486
 
 
487
                # Include a <fieldset> wrapper for style, if requested.
 
488
                if ( $this->mWrapperLegend !== false ) {
 
489
                        $html = Xml::fieldset( $this->mWrapperLegend, $html );
 
490
                }
 
491
                # Use multipart/form-data
 
492
                $encType = $this->mUseMultipart
 
493
                        ? 'multipart/form-data'
 
494
                        : 'application/x-www-form-urlencoded';
 
495
                # Attributes
 
496
                $attribs = array(
 
497
                        'action'  => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
 
498
                        'method'  => $this->mMethod,
 
499
                        'class'   => 'visualClear',
 
500
                        'enctype' => $encType,
 
501
                );
 
502
                if ( !empty( $this->mId ) ) {
 
503
                        $attribs['id'] = $this->mId;
 
504
                }
 
505
 
 
506
                return Html::rawElement( 'form', $attribs, $html );
 
507
        }
 
508
 
 
509
        /**
 
510
         * Get the hidden fields that should go inside the form.
 
511
         * @return String HTML.
 
512
         */
 
513
        function getHiddenFields() {
 
514
                global $wgUsePathInfo;
 
515
 
 
516
                $html = '';
 
517
                if( $this->getMethod() == 'post' ){
 
518
                        $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
 
519
                        $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
 
520
                }
 
521
 
 
522
                if ( !$wgUsePathInfo && $this->getMethod() == 'get' ) {
 
523
                        $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
 
524
                }
 
525
 
 
526
                foreach ( $this->mHiddenFields as $data ) {
 
527
                        list( $value, $attribs ) = $data;
 
528
                        $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
 
529
                }
 
530
 
 
531
                return $html;
 
532
        }
 
533
 
 
534
        /**
 
535
         * Get the submit and (potentially) reset buttons.
 
536
         * @return String HTML.
 
537
         */
 
538
        function getButtons() {
 
539
                $html = '';
 
540
                $attribs = array();
 
541
 
 
542
                if ( isset( $this->mSubmitID ) ) {
 
543
                        $attribs['id'] = $this->mSubmitID;
 
544
                }
 
545
 
 
546
                if ( isset( $this->mSubmitName ) ) {
 
547
                        $attribs['name'] = $this->mSubmitName;
 
548
                }
 
549
 
 
550
                if ( isset( $this->mSubmitTooltip ) ) {
 
551
                        $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
 
552
                }
 
553
 
 
554
                $attribs['class'] = 'mw-htmlform-submit';
 
555
 
 
556
                $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
 
557
 
 
558
                if ( $this->mShowReset ) {
 
559
                        $html .= Html::element(
 
560
                                'input',
 
561
                                array(
 
562
                                        'type' => 'reset',
 
563
                                        'value' => wfMsg( 'htmlform-reset' )
 
564
                                )
 
565
                        ) . "\n";
 
566
                }
 
567
 
 
568
                foreach ( $this->mButtons as $button ) {
 
569
                        $attrs = array(
 
570
                                'type'  => 'submit',
 
571
                                'name'  => $button['name'],
 
572
                                'value' => $button['value']
 
573
                        );
 
574
 
 
575
                        if ( $button['attribs'] ) {
 
576
                                $attrs += $button['attribs'];
 
577
                        }
 
578
 
 
579
                        if ( isset( $button['id'] ) ) {
 
580
                                $attrs['id'] = $button['id'];
 
581
                        }
 
582
 
 
583
                        $html .= Html::element( 'input', $attrs );
 
584
                }
 
585
 
 
586
                return $html;
 
587
        }
 
588
 
 
589
        /**
 
590
         * Get the whole body of the form.
 
591
         * @return String
 
592
         */
 
593
        function getBody() {
 
594
                return $this->displaySection( $this->mFieldTree );
 
595
        }
 
596
 
 
597
        /**
 
598
         * Format and display an error message stack.
 
599
         * @param $errors String|Array|Status
 
600
         * @return String
 
601
         */
 
602
        function getErrors( $errors ) {
 
603
                if ( $errors instanceof Status ) {
 
604
                        if ( $errors->isOK() ) {
 
605
                                $errorstr = '';
 
606
                        } else {
 
607
                                $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
 
608
                        }
 
609
                } elseif ( is_array( $errors ) ) {
 
610
                        $errorstr = $this->formatErrors( $errors );
 
611
                } else {
 
612
                        $errorstr = $errors;
 
613
                }
 
614
 
 
615
                return $errorstr
 
616
                        ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
 
617
                        : '';
 
618
        }
 
619
 
 
620
        /**
 
621
         * Format a stack of error messages into a single HTML string
 
622
         * @param $errors Array of message keys/values
 
623
         * @return String HTML, a <ul> list of errors
 
624
         */
 
625
        public static function formatErrors( $errors ) {
 
626
                $errorstr = '';
 
627
 
 
628
                foreach ( $errors as $error ) {
 
629
                        if ( is_array( $error ) ) {
 
630
                                $msg = array_shift( $error );
 
631
                        } else {
 
632
                                $msg = $error;
 
633
                                $error = array();
 
634
                        }
 
635
 
 
636
                        $errorstr .= Html::rawElement(
 
637
                                'li',
 
638
                                array(),
 
639
                                wfMsgExt( $msg, array( 'parseinline' ), $error )
 
640
                        );
 
641
                }
 
642
 
 
643
                $errorstr = Html::rawElement( 'ul', array(), $errorstr );
 
644
 
 
645
                return $errorstr;
 
646
        }
 
647
 
 
648
        /**
 
649
         * Set the text for the submit button
 
650
         * @param $t String plaintext.
 
651
         */
 
652
        function setSubmitText( $t ) {
 
653
                $this->mSubmitText = $t;
 
654
        }
 
655
 
 
656
        /**
 
657
         * Set the text for the submit button to a message
 
658
         * @since 1.19
 
659
         * @param $msg String message key
 
660
         */
 
661
        public function setSubmitTextMsg( $msg ) {
 
662
                return $this->setSubmitText( $this->msg( $msg )->escaped() );
 
663
        }
 
664
 
 
665
        /**
 
666
         * Get the text for the submit button, either customised or a default.
 
667
         * @return unknown_type
 
668
         */
 
669
        function getSubmitText() {
 
670
                return $this->mSubmitText
 
671
                        ? $this->mSubmitText
 
672
                        : wfMsg( 'htmlform-submit' );
 
673
        }
 
674
 
 
675
        public function setSubmitName( $name ) {
 
676
                $this->mSubmitName = $name;
 
677
        }
 
678
 
 
679
        public function setSubmitTooltip( $name ) {
 
680
                $this->mSubmitTooltip = $name;
 
681
        }
 
682
 
 
683
        /**
 
684
         * Set the id for the submit button.
 
685
         * @param $t String.
 
686
         * @todo FIXME: Integrity of $t is *not* validated
 
687
         */
 
688
        function setSubmitID( $t ) {
 
689
                $this->mSubmitID = $t;
 
690
        }
 
691
 
 
692
        public function setId( $id ) {
 
693
                $this->mId = $id;
 
694
        }
 
695
        /**
 
696
         * Prompt the whole form to be wrapped in a <fieldset>, with
 
697
         * this text as its <legend> element.
 
698
         * @param $legend String HTML to go inside the <legend> element.
 
699
         *       Will be escaped
 
700
         */
 
701
        public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; }
 
702
 
 
703
        /**
 
704
         * Prompt the whole form to be wrapped in a <fieldset>, with
 
705
         * this message as its <legend> element.
 
706
         * @since 1.19
 
707
         * @param $msg String message key
 
708
         */
 
709
        public function setWrapperLegendMsg( $msg ) {
 
710
                return $this->setWrapperLegend( $this->msg( $msg )->escaped() );
 
711
        }
 
712
 
 
713
        /**
 
714
         * Set the prefix for various default messages
 
715
         * TODO: currently only used for the <fieldset> legend on forms
 
716
         * with multiple sections; should be used elsewhre?
 
717
         * @param $p String
 
718
         */
 
719
        function setMessagePrefix( $p ) {
 
720
                $this->mMessagePrefix = $p;
 
721
        }
 
722
 
 
723
        /**
 
724
         * Set the title for form submission
 
725
         * @param $t Title of page the form is on/should be posted to
 
726
         */
 
727
        function setTitle( $t ) {
 
728
                $this->mTitle = $t;
 
729
        }
 
730
 
 
731
        /**
 
732
         * Get the title
 
733
         * @return Title
 
734
         */
 
735
        function getTitle() {
 
736
                return $this->mTitle === false
 
737
                        ? $this->getContext()->getTitle()
 
738
                        : $this->mTitle;
 
739
        }
 
740
 
 
741
        /**
 
742
         * Set the method used to submit the form
 
743
         * @param $method String
 
744
         */
 
745
        public function setMethod( $method='post' ){
 
746
                $this->mMethod = $method;
 
747
        }
 
748
 
 
749
        public function getMethod(){
 
750
                return $this->mMethod;
 
751
        }
 
752
 
 
753
        /**
 
754
         * TODO: Document
 
755
         * @param $fields array[]|HTMLFormField[] array of fields (either arrays or objects)
 
756
         * @param $sectionName string ID attribute of the <table> tag for this section, ignored if empty
 
757
         * @param $fieldsetIDPrefix string ID prefix for the <fieldset> tag of each subsection, ignored if empty
 
758
         * @return String
 
759
         */
 
760
        function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
 
761
                $tableHtml = '';
 
762
                $subsectionHtml = '';
 
763
                $hasLeftColumn = false;
 
764
 
 
765
                foreach ( $fields as $key => $value ) {
 
766
                        if ( is_object( $value ) ) {
 
767
                                $v = empty( $value->mParams['nodata'] )
 
768
                                        ? $this->mFieldData[$key]
 
769
                                        : $value->getDefault();
 
770
                                $tableHtml .= $value->getTableRow( $v );
 
771
 
 
772
                                if ( $value->getLabel() != '&#160;' ) {
 
773
                                        $hasLeftColumn = true;
 
774
                                }
 
775
                        } elseif ( is_array( $value ) ) {
 
776
                                $section = $this->displaySection( $value, $key );
 
777
                                $legend = $this->getLegend( $key );
 
778
                                if ( isset( $this->mSectionHeaders[$key] ) ) {
 
779
                                        $section = $this->mSectionHeaders[$key] . $section;
 
780
                                }
 
781
                                if ( isset( $this->mSectionFooters[$key] ) ) {
 
782
                                        $section .= $this->mSectionFooters[$key];
 
783
                                }
 
784
                                $attributes = array();
 
785
                                if ( $fieldsetIDPrefix ) {
 
786
                                        $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
 
787
                                }
 
788
                                $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
 
789
                        }
 
790
                }
 
791
 
 
792
                $classes = array();
 
793
 
 
794
                if ( !$hasLeftColumn ) { // Avoid strange spacing when no labels exist
 
795
                        $classes[] = 'mw-htmlform-nolabel';
 
796
                }
 
797
 
 
798
                $attribs = array(
 
799
                        'class' => implode( ' ', $classes ),
 
800
                );
 
801
 
 
802
                if ( $sectionName ) {
 
803
                        $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
 
804
                }
 
805
 
 
806
                $tableHtml = Html::rawElement( 'table', $attribs,
 
807
                        Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n";
 
808
 
 
809
                if ( $this->mSubSectionBeforeFields ) {
 
810
                        return $subsectionHtml . "\n" . $tableHtml;
 
811
                } else {
 
812
                        return $tableHtml . "\n" . $subsectionHtml;
 
813
                }
 
814
        }
 
815
 
 
816
        /**
 
817
         * Construct the form fields from the Descriptor array
 
818
         */
 
819
        function loadData() {
 
820
                $fieldData = array();
 
821
 
 
822
                foreach ( $this->mFlatFields as $fieldname => $field ) {
 
823
                        if ( !empty( $field->mParams['nodata'] ) ) {
 
824
                                continue;
 
825
                        } elseif ( !empty( $field->mParams['disabled'] ) ) {
 
826
                                $fieldData[$fieldname] = $field->getDefault();
 
827
                        } else {
 
828
                                $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
 
829
                        }
 
830
                }
 
831
 
 
832
                # Filter data.
 
833
                foreach ( $fieldData as $name => &$value ) {
 
834
                        $field = $this->mFlatFields[$name];
 
835
                        $value = $field->filter( $value, $this->mFlatFields );
 
836
                }
 
837
 
 
838
                $this->mFieldData = $fieldData;
 
839
        }
 
840
 
 
841
        /**
 
842
         * Stop a reset button being shown for this form
 
843
         * @param $suppressReset Bool set to false to re-enable the
 
844
         *       button again
 
845
         */
 
846
        function suppressReset( $suppressReset = true ) {
 
847
                $this->mShowReset = !$suppressReset;
 
848
        }
 
849
 
 
850
        /**
 
851
         * Overload this if you want to apply special filtration routines
 
852
         * to the form as a whole, after it's submitted but before it's
 
853
         * processed.
 
854
         * @param $data
 
855
         * @return unknown_type
 
856
         */
 
857
        function filterDataForSubmit( $data ) {
 
858
                return $data;
 
859
        }
 
860
 
 
861
        /**
 
862
         * Get a string to go in the <legend> of a section fieldset.  Override this if you
 
863
         * want something more complicated
 
864
         * @param $key String
 
865
         * @return String
 
866
         */
 
867
        public function getLegend( $key ) {
 
868
                return wfMsg( "{$this->mMessagePrefix}-$key" );
 
869
        }
 
870
 
 
871
        /**
 
872
         * Set the value for the action attribute of the form.
 
873
         * When set to false (which is the default state), the set title is used.
 
874
         *
 
875
         * @since 1.19
 
876
         *
 
877
         * @param string|false $action
 
878
         */
 
879
        public function setAction( $action ) {
 
880
                $this->mAction = $action;
 
881
        }
 
882
 
 
883
}
 
884
 
 
885
/**
 
886
 * The parent class to generate form fields.  Any field type should
 
887
 * be a subclass of this.
 
888
 */
 
889
abstract class HTMLFormField {
 
890
 
 
891
        protected $mValidationCallback;
 
892
        protected $mFilterCallback;
 
893
        protected $mName;
 
894
        public $mParams;
 
895
        protected $mLabel;      # String label.  Set on construction
 
896
        protected $mID;
 
897
        protected $mClass = '';
 
898
        protected $mDefault;
 
899
 
 
900
        /**
 
901
         * @var HTMLForm
 
902
         */
 
903
        public $mParent;
 
904
 
 
905
        /**
 
906
         * This function must be implemented to return the HTML to generate
 
907
         * the input object itself.  It should not implement the surrounding
 
908
         * table cells/rows, or labels/help messages.
 
909
         * @param $value String the value to set the input to; eg a default
 
910
         *       text for a text input.
 
911
         * @return String valid HTML.
 
912
         */
 
913
        abstract function getInputHTML( $value );
 
914
 
 
915
        /**
 
916
         * Override this function to add specific validation checks on the
 
917
         * field input.  Don't forget to call parent::validate() to ensure
 
918
         * that the user-defined callback mValidationCallback is still run
 
919
         * @param $value String the value the field was submitted with
 
920
         * @param $alldata Array the data collected from the form
 
921
         * @return Mixed Bool true on success, or String error to display.
 
922
         */
 
923
        function validate( $value, $alldata ) {
 
924
                if ( isset( $this->mParams['required'] ) && $value === '' ) {
 
925
                        return wfMsgExt( 'htmlform-required', 'parseinline' );
 
926
                }
 
927
 
 
928
                if ( isset( $this->mValidationCallback ) ) {
 
929
                        return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
 
930
                }
 
931
 
 
932
                return true;
 
933
        }
 
934
 
 
935
        function filter( $value, $alldata ) {
 
936
                if ( isset( $this->mFilterCallback ) ) {
 
937
                        $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
 
938
                }
 
939
 
 
940
                return $value;
 
941
        }
 
942
 
 
943
        /**
 
944
         * Should this field have a label, or is there no input element with the
 
945
         * appropriate id for the label to point to?
 
946
         *
 
947
         * @return bool True to output a label, false to suppress
 
948
         */
 
949
        protected function needsLabel() {
 
950
                return true;
 
951
        }
 
952
 
 
953
        /**
 
954
         * Get the value that this input has been set to from a posted form,
 
955
         * or the input's default value if it has not been set.
 
956
         * @param $request WebRequest
 
957
         * @return String the value
 
958
         */
 
959
        function loadDataFromRequest( $request ) {
 
960
                if ( $request->getCheck( $this->mName ) ) {
 
961
                        return $request->getText( $this->mName );
 
962
                } else {
 
963
                        return $this->getDefault();
 
964
                }
 
965
        }
 
966
 
 
967
        /**
 
968
         * Initialise the object
 
969
         * @param $params array Associative Array. See HTMLForm doc for syntax.
 
970
         */
 
971
        function __construct( $params ) {
 
972
                $this->mParams = $params;
 
973
 
 
974
                # Generate the label from a message, if possible
 
975
                if ( isset( $params['label-message'] ) ) {
 
976
                        $msgInfo = $params['label-message'];
 
977
 
 
978
                        if ( is_array( $msgInfo ) ) {
 
979
                                $msg = array_shift( $msgInfo );
 
980
                        } else {
 
981
                                $msg = $msgInfo;
 
982
                                $msgInfo = array();
 
983
                        }
 
984
 
 
985
                        $this->mLabel = wfMsgExt( $msg, 'parseinline', $msgInfo );
 
986
                } elseif ( isset( $params['label'] ) ) {
 
987
                        $this->mLabel = $params['label'];
 
988
                }
 
989
 
 
990
                $this->mName = "wp{$params['fieldname']}";
 
991
                if ( isset( $params['name'] ) ) {
 
992
                        $this->mName = $params['name'];
 
993
                }
 
994
 
 
995
                $validName = Sanitizer::escapeId( $this->mName );
 
996
                if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
 
997
                        throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
 
998
                }
 
999
 
 
1000
                $this->mID = "mw-input-{$this->mName}";
 
1001
 
 
1002
                if ( isset( $params['default'] ) ) {
 
1003
                        $this->mDefault = $params['default'];
 
1004
                }
 
1005
 
 
1006
                if ( isset( $params['id'] ) ) {
 
1007
                        $id = $params['id'];
 
1008
                        $validId = Sanitizer::escapeId( $id );
 
1009
 
 
1010
                        if ( $id != $validId ) {
 
1011
                                throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
 
1012
                        }
 
1013
 
 
1014
                        $this->mID = $id;
 
1015
                }
 
1016
 
 
1017
                if ( isset( $params['cssclass'] ) ) {
 
1018
                        $this->mClass = $params['cssclass'];
 
1019
                }
 
1020
 
 
1021
                if ( isset( $params['validation-callback'] ) ) {
 
1022
                        $this->mValidationCallback = $params['validation-callback'];
 
1023
                }
 
1024
 
 
1025
                if ( isset( $params['filter-callback'] ) ) {
 
1026
                        $this->mFilterCallback = $params['filter-callback'];
 
1027
                }
 
1028
 
 
1029
                if ( isset( $params['flatlist'] ) ){
 
1030
                        $this->mClass .= ' mw-htmlform-flatlist';
 
1031
                }
 
1032
        }
 
1033
 
 
1034
        /**
 
1035
         * Get the complete table row for the input, including help text,
 
1036
         * labels, and whatever.
 
1037
         * @param $value String the value to set the input to.
 
1038
         * @return String complete HTML table row.
 
1039
         */
 
1040
        function getTableRow( $value ) {
 
1041
                # Check for invalid data.
 
1042
 
 
1043
                $errors = $this->validate( $value, $this->mParent->mFieldData );
 
1044
 
 
1045
                $cellAttributes = array();
 
1046
                $verticalLabel = false;
 
1047
 
 
1048
                if ( !empty($this->mParams['vertical-label']) ) {
 
1049
                        $cellAttributes['colspan'] = 2;
 
1050
                        $verticalLabel = true;
 
1051
                }
 
1052
 
 
1053
                if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
 
1054
                        $errors = '';
 
1055
                        $errorClass = '';
 
1056
                } else {
 
1057
                        $errors = self::formatErrors( $errors );
 
1058
                        $errorClass = 'mw-htmlform-invalid-input';
 
1059
                }
 
1060
 
 
1061
                $label = $this->getLabelHtml( $cellAttributes );
 
1062
                $field = Html::rawElement(
 
1063
                        'td',
 
1064
                        array( 'class' => 'mw-input' ) + $cellAttributes,
 
1065
                        $this->getInputHTML( $value ) . "\n$errors"
 
1066
                );
 
1067
 
 
1068
                $fieldType = get_class( $this );
 
1069
 
 
1070
                if ( $verticalLabel ) {
 
1071
                        $html = Html::rawElement( 'tr',
 
1072
                                array( 'class' => 'mw-htmlform-vertical-label' ), $label );
 
1073
                        $html .= Html::rawElement( 'tr',
 
1074
                                array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
 
1075
                                $field );
 
1076
                } else {
 
1077
                        $html = Html::rawElement( 'tr',
 
1078
                                array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
 
1079
                                $label . $field );
 
1080
                }
 
1081
 
 
1082
                $helptext = null;
 
1083
 
 
1084
                if ( isset( $this->mParams['help-message'] ) ) {
 
1085
                        $msg = wfMessage( $this->mParams['help-message'] );
 
1086
                        if ( $msg->exists() ) {
 
1087
                                $helptext = $msg->parse();
 
1088
                        }
 
1089
                } elseif ( isset( $this->mParams['help-messages'] ) ) {
 
1090
                        # help-message can be passed a message key (string) or an array containing
 
1091
                        # a message key and additional parameters. This makes it impossible to pass
 
1092
                        # an array of message key
 
1093
                        foreach( $this->mParams['help-messages'] as $name ) {
 
1094
                                $msg = wfMessage( $name );
 
1095
                                if( $msg->exists() ) {
 
1096
                                        $helptext .= $msg->parse(); // append message
 
1097
                                }
 
1098
                        }
 
1099
                } elseif ( isset( $this->mParams['help'] ) ) {
 
1100
                        $helptext = $this->mParams['help'];
 
1101
                }
 
1102
 
 
1103
                if ( !is_null( $helptext ) ) {
 
1104
                        $row = Html::rawElement( 'td', array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
 
1105
                                $helptext );
 
1106
                        $row = Html::rawElement( 'tr', array(), $row );
 
1107
                        $html .= "$row\n";
 
1108
                }
 
1109
 
 
1110
                return $html;
 
1111
        }
 
1112
 
 
1113
        function getLabel() {
 
1114
                return $this->mLabel;
 
1115
        }
 
1116
        function getLabelHtml( $cellAttributes = array() ) {
 
1117
                # Don't output a for= attribute for labels with no associated input.
 
1118
                # Kind of hacky here, possibly we don't want these to be <label>s at all.
 
1119
                $for = array();
 
1120
 
 
1121
                if ( $this->needsLabel() ) {
 
1122
                        $for['for'] = $this->mID;
 
1123
                }
 
1124
 
 
1125
                return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
 
1126
                        Html::rawElement( 'label', $for, $this->getLabel() )
 
1127
                );
 
1128
        }
 
1129
 
 
1130
        function getDefault() {
 
1131
                if ( isset( $this->mDefault ) ) {
 
1132
                        return $this->mDefault;
 
1133
                } else {
 
1134
                        return null;
 
1135
                }
 
1136
        }
 
1137
 
 
1138
        /**
 
1139
         * Returns the attributes required for the tooltip and accesskey.
 
1140
         *
 
1141
         * @return array Attributes
 
1142
         */
 
1143
        public function getTooltipAndAccessKey() {
 
1144
                if ( empty( $this->mParams['tooltip'] ) ) {
 
1145
                        return array();
 
1146
                }
 
1147
                return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
 
1148
        }
 
1149
 
 
1150
        /**
 
1151
         * flatten an array of options to a single array, for instance,
 
1152
         * a set of <options> inside <optgroups>.
 
1153
         * @param $options array Associative Array with values either Strings
 
1154
         *       or Arrays
 
1155
         * @return Array flattened input
 
1156
         */
 
1157
        public static function flattenOptions( $options ) {
 
1158
                $flatOpts = array();
 
1159
 
 
1160
                foreach ( $options as $value ) {
 
1161
                        if ( is_array( $value ) ) {
 
1162
                                $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
 
1163
                        } else {
 
1164
                                $flatOpts[] = $value;
 
1165
                        }
 
1166
                }
 
1167
 
 
1168
                return $flatOpts;
 
1169
        }
 
1170
 
 
1171
        /**
 
1172
         * Formats one or more errors as accepted by field validation-callback.
 
1173
         * @param $errors String|Message|Array of strings or Message instances
 
1174
         * @return String html
 
1175
         * @since 1.18
 
1176
         */
 
1177
        protected static function formatErrors( $errors ) {
 
1178
                if ( is_array( $errors ) && count( $errors ) === 1 ) {
 
1179
                        $errors = array_shift( $errors );
 
1180
                }
 
1181
 
 
1182
                if ( is_array( $errors ) ) {
 
1183
                        $lines = array();
 
1184
                        foreach ( $errors as $error ) {
 
1185
                                if ( $error instanceof Message ) {
 
1186
                                        $lines[] = Html::rawElement( 'li', array(), $error->parse() );
 
1187
                                } else {
 
1188
                                        $lines[] = Html::rawElement( 'li', array(), $error );
 
1189
                                }
 
1190
                        }
 
1191
                        return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
 
1192
                } else {
 
1193
                        if ( $errors instanceof Message ) {
 
1194
                                $errors = $errors->parse();
 
1195
                        }
 
1196
                        return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
 
1197
                }
 
1198
        }
 
1199
}
 
1200
 
 
1201
class HTMLTextField extends HTMLFormField {
 
1202
        function getSize() {
 
1203
                return isset( $this->mParams['size'] )
 
1204
                        ? $this->mParams['size']
 
1205
                        : 45;
 
1206
        }
 
1207
 
 
1208
        function getInputHTML( $value ) {
 
1209
                $attribs = array(
 
1210
                        'id' => $this->mID,
 
1211
                        'name' => $this->mName,
 
1212
                        'size' => $this->getSize(),
 
1213
                        'value' => $value,
 
1214
                ) + $this->getTooltipAndAccessKey();
 
1215
 
 
1216
                if ( $this->mClass !== '' ) {
 
1217
                        $attribs['class'] = $this->mClass;
 
1218
                }
 
1219
                
 
1220
                if ( isset( $this->mParams['maxlength'] ) ) {
 
1221
                        $attribs['maxlength'] = $this->mParams['maxlength'];
 
1222
                }
 
1223
 
 
1224
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1225
                        $attribs['disabled'] = 'disabled';
 
1226
                }
 
1227
 
 
1228
                # TODO: Enforce pattern, step, required, readonly on the server side as
 
1229
                # well
 
1230
                foreach ( array( 'min', 'max', 'pattern', 'title', 'step',
 
1231
                'placeholder' ) as $param ) {
 
1232
                        if ( isset( $this->mParams[$param] ) ) {
 
1233
                                $attribs[$param] = $this->mParams[$param];
 
1234
                        }
 
1235
                }
 
1236
 
 
1237
                foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
 
1238
                        if ( isset( $this->mParams[$param] ) ) {
 
1239
                                $attribs[$param] = '';
 
1240
                        }
 
1241
                }
 
1242
 
 
1243
                # Implement tiny differences between some field variants
 
1244
                # here, rather than creating a new class for each one which
 
1245
                # is essentially just a clone of this one.
 
1246
                if ( isset( $this->mParams['type'] ) ) {
 
1247
                        switch ( $this->mParams['type'] ) {
 
1248
                                case 'email':
 
1249
                                        $attribs['type'] = 'email';
 
1250
                                        break;
 
1251
                                case 'int':
 
1252
                                        $attribs['type'] = 'number';
 
1253
                                        break;
 
1254
                                case 'float':
 
1255
                                        $attribs['type'] = 'number';
 
1256
                                        $attribs['step'] = 'any';
 
1257
                                        break;
 
1258
                                # Pass through
 
1259
                                case 'password':
 
1260
                                case 'file':
 
1261
                                        $attribs['type'] = $this->mParams['type'];
 
1262
                                        break;
 
1263
                        }
 
1264
                }
 
1265
 
 
1266
                return Html::element( 'input', $attribs );
 
1267
        }
 
1268
}
 
1269
class HTMLTextAreaField extends HTMLFormField {
 
1270
        function getCols() {
 
1271
                return isset( $this->mParams['cols'] )
 
1272
                        ? $this->mParams['cols']
 
1273
                        : 80;
 
1274
        }
 
1275
 
 
1276
        function getRows() {
 
1277
                return isset( $this->mParams['rows'] )
 
1278
                        ? $this->mParams['rows']
 
1279
                        : 25;
 
1280
        }
 
1281
 
 
1282
        function getInputHTML( $value ) {
 
1283
                $attribs = array(
 
1284
                        'id' => $this->mID,
 
1285
                        'name' => $this->mName,
 
1286
                        'cols' => $this->getCols(),
 
1287
                        'rows' => $this->getRows(),
 
1288
                ) + $this->getTooltipAndAccessKey();
 
1289
 
 
1290
                if ( $this->mClass !== '' ) {
 
1291
                        $attribs['class'] = $this->mClass;
 
1292
                }
 
1293
                
 
1294
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1295
                        $attribs['disabled'] = 'disabled';
 
1296
                }
 
1297
 
 
1298
                if ( !empty( $this->mParams['readonly'] ) ) {
 
1299
                        $attribs['readonly'] = 'readonly';
 
1300
                }
 
1301
 
 
1302
                foreach ( array( 'required', 'autofocus' ) as $param ) {
 
1303
                        if ( isset( $this->mParams[$param] ) ) {
 
1304
                                $attribs[$param] = '';
 
1305
                        }
 
1306
                }
 
1307
 
 
1308
                return Html::element( 'textarea', $attribs, $value );
 
1309
        }
 
1310
}
 
1311
 
 
1312
/**
 
1313
 * A field that will contain a numeric value
 
1314
 */
 
1315
class HTMLFloatField extends HTMLTextField {
 
1316
        function getSize() {
 
1317
                return isset( $this->mParams['size'] )
 
1318
                        ? $this->mParams['size']
 
1319
                        : 20;
 
1320
        }
 
1321
 
 
1322
        function validate( $value, $alldata ) {
 
1323
                $p = parent::validate( $value, $alldata );
 
1324
 
 
1325
                if ( $p !== true ) {
 
1326
                        return $p;
 
1327
                }
 
1328
 
 
1329
                $value = trim( $value );
 
1330
 
 
1331
                # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
 
1332
                # with the addition that a leading '+' sign is ok.
 
1333
                if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
 
1334
                        return wfMsgExt( 'htmlform-float-invalid', 'parse' );
 
1335
                }
 
1336
 
 
1337
                # The "int" part of these message names is rather confusing.
 
1338
                # They make equal sense for all numbers.
 
1339
                if ( isset( $this->mParams['min'] ) ) {
 
1340
                        $min = $this->mParams['min'];
 
1341
 
 
1342
                        if ( $min > $value ) {
 
1343
                                return wfMsgExt( 'htmlform-int-toolow', 'parse', array( $min ) );
 
1344
                        }
 
1345
                }
 
1346
 
 
1347
                if ( isset( $this->mParams['max'] ) ) {
 
1348
                        $max = $this->mParams['max'];
 
1349
 
 
1350
                        if ( $max < $value ) {
 
1351
                                return wfMsgExt( 'htmlform-int-toohigh', 'parse', array( $max ) );
 
1352
                        }
 
1353
                }
 
1354
 
 
1355
                return true;
 
1356
        }
 
1357
}
 
1358
 
 
1359
/**
 
1360
 * A field that must contain a number
 
1361
 */
 
1362
class HTMLIntField extends HTMLFloatField {
 
1363
        function validate( $value, $alldata ) {
 
1364
                $p = parent::validate( $value, $alldata );
 
1365
 
 
1366
                if ( $p !== true ) {
 
1367
                        return $p;
 
1368
                }
 
1369
 
 
1370
                # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
 
1371
                # with the addition that a leading '+' sign is ok. Note that leading zeros
 
1372
                # are fine, and will be left in the input, which is useful for things like
 
1373
                # phone numbers when you know that they are integers (the HTML5 type=tel
 
1374
                # input does not require its value to be numeric).  If you want a tidier
 
1375
                # value to, eg, save in the DB, clean it up with intval().
 
1376
                if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
 
1377
                ) {
 
1378
                        return wfMsgExt( 'htmlform-int-invalid', 'parse' );
 
1379
                }
 
1380
 
 
1381
                return true;
 
1382
        }
 
1383
}
 
1384
 
 
1385
/**
 
1386
 * A checkbox field
 
1387
 */
 
1388
class HTMLCheckField extends HTMLFormField {
 
1389
        function getInputHTML( $value ) {
 
1390
                if ( !empty( $this->mParams['invert'] ) ) {
 
1391
                        $value = !$value;
 
1392
                }
 
1393
 
 
1394
                $attr = $this->getTooltipAndAccessKey();
 
1395
                $attr['id'] = $this->mID;
 
1396
 
 
1397
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1398
                        $attr['disabled'] = 'disabled';
 
1399
                }
 
1400
                
 
1401
                if ( $this->mClass !== '' ) {
 
1402
                        $attr['class'] = $this->mClass;
 
1403
                }
 
1404
 
 
1405
                return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
 
1406
                        Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
 
1407
        }
 
1408
 
 
1409
        /**
 
1410
         * For a checkbox, the label goes on the right hand side, and is
 
1411
         * added in getInputHTML(), rather than HTMLFormField::getRow()
 
1412
         * @return String
 
1413
         */
 
1414
        function getLabel() {
 
1415
                return '&#160;';
 
1416
        }
 
1417
 
 
1418
        /**
 
1419
         * @param  $request WebRequest
 
1420
         * @return String
 
1421
         */
 
1422
        function loadDataFromRequest( $request ) {
 
1423
                $invert = false;
 
1424
                if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
 
1425
                        $invert = true;
 
1426
                }
 
1427
 
 
1428
                // GetCheck won't work like we want for checks.
 
1429
                // Fetch the value in either one of the two following case:
 
1430
                // - we have a valid token (form got posted or GET forged by the user)
 
1431
                // - checkbox name has a value (false or true), ie is not null
 
1432
                if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName )!== null ) {
 
1433
                        // XOR has the following truth table, which is what we want
 
1434
                        // INVERT VALUE | OUTPUT
 
1435
                        // true   true  | false
 
1436
                        // false  true  | true
 
1437
                        // false  false | false
 
1438
                        // true   false | true
 
1439
                        return $request->getBool( $this->mName ) xor $invert;
 
1440
                } else {
 
1441
                        return $this->getDefault();
 
1442
                }
 
1443
        }
 
1444
}
 
1445
 
 
1446
/**
 
1447
 * A select dropdown field.  Basically a wrapper for Xmlselect class
 
1448
 */
 
1449
class HTMLSelectField extends HTMLFormField {
 
1450
        function validate( $value, $alldata ) {
 
1451
                $p = parent::validate( $value, $alldata );
 
1452
 
 
1453
                if ( $p !== true ) {
 
1454
                        return $p;
 
1455
                }
 
1456
 
 
1457
                $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
 
1458
 
 
1459
                if ( in_array( $value, $validOptions ) )
 
1460
                        return true;
 
1461
                else
 
1462
                        return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
 
1463
        }
 
1464
 
 
1465
        function getInputHTML( $value ) {
 
1466
                $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
 
1467
 
 
1468
                # If one of the options' 'name' is int(0), it is automatically selected.
 
1469
                # because PHP sucks and thinks int(0) == 'some string'.
 
1470
                # Working around this by forcing all of them to strings.
 
1471
                foreach( $this->mParams['options'] as &$opt ){
 
1472
                        if( is_int( $opt ) ){
 
1473
                                $opt = strval( $opt );
 
1474
                        }
 
1475
                }
 
1476
                unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
 
1477
 
 
1478
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1479
                        $select->setAttribute( 'disabled', 'disabled' );
 
1480
                }
 
1481
                
 
1482
                if ( $this->mClass !== '' ) {
 
1483
                        $select->setAttribute( 'class', $this->mClass );
 
1484
                }
 
1485
 
 
1486
                $select->addOptions( $this->mParams['options'] );
 
1487
 
 
1488
                return $select->getHTML();
 
1489
        }
 
1490
}
 
1491
 
 
1492
/**
 
1493
 * Select dropdown field, with an additional "other" textbox.
 
1494
 */
 
1495
class HTMLSelectOrOtherField extends HTMLTextField {
 
1496
        static $jsAdded = false;
 
1497
 
 
1498
        function __construct( $params ) {
 
1499
                if ( !in_array( 'other', $params['options'], true ) ) {
 
1500
                        $msg = isset( $params['other'] ) ? $params['other'] : wfMsg( 'htmlform-selectorother-other' );
 
1501
                        $params['options'][$msg] = 'other';
 
1502
                }
 
1503
 
 
1504
                parent::__construct( $params );
 
1505
        }
 
1506
 
 
1507
        static function forceToStringRecursive( $array ) {
 
1508
                if ( is_array( $array ) ) {
 
1509
                        return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
 
1510
                } else {
 
1511
                        return strval( $array );
 
1512
                }
 
1513
        }
 
1514
 
 
1515
        function getInputHTML( $value ) {
 
1516
                $valInSelect = false;
 
1517
 
 
1518
                if ( $value !== false ) {
 
1519
                        $valInSelect = in_array(
 
1520
                                $value,
 
1521
                                HTMLFormField::flattenOptions( $this->mParams['options'] )
 
1522
                        );
 
1523
                }
 
1524
 
 
1525
                $selected = $valInSelect ? $value : 'other';
 
1526
 
 
1527
                $opts = self::forceToStringRecursive( $this->mParams['options'] );
 
1528
 
 
1529
                $select = new XmlSelect( $this->mName, $this->mID, $selected );
 
1530
                $select->addOptions( $opts );
 
1531
 
 
1532
                $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
 
1533
 
 
1534
                $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
 
1535
 
 
1536
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1537
                        $select->setAttribute( 'disabled', 'disabled' );
 
1538
                        $tbAttribs['disabled'] = 'disabled';
 
1539
                }
 
1540
 
 
1541
                $select = $select->getHTML();
 
1542
 
 
1543
                if ( isset( $this->mParams['maxlength'] ) ) {
 
1544
                        $tbAttribs['maxlength'] = $this->mParams['maxlength'];
 
1545
                }
 
1546
                
 
1547
                if ( $this->mClass !== '' ) {
 
1548
                        $tbAttribs['class'] = $this->mClass;
 
1549
                }
 
1550
 
 
1551
                $textbox = Html::input(
 
1552
                        $this->mName . '-other',
 
1553
                        $valInSelect ? '' : $value,
 
1554
                        'text',
 
1555
                        $tbAttribs
 
1556
                );
 
1557
 
 
1558
                return "$select<br />\n$textbox";
 
1559
        }
 
1560
 
 
1561
        /**
 
1562
         * @param  $request WebRequest
 
1563
         * @return String
 
1564
         */
 
1565
        function loadDataFromRequest( $request ) {
 
1566
                if ( $request->getCheck( $this->mName ) ) {
 
1567
                        $val = $request->getText( $this->mName );
 
1568
 
 
1569
                        if ( $val == 'other' ) {
 
1570
                                $val = $request->getText( $this->mName . '-other' );
 
1571
                        }
 
1572
 
 
1573
                        return $val;
 
1574
                } else {
 
1575
                        return $this->getDefault();
 
1576
                }
 
1577
        }
 
1578
}
 
1579
 
 
1580
/**
 
1581
 * Multi-select field
 
1582
 */
 
1583
class HTMLMultiSelectField extends HTMLFormField {
 
1584
 
 
1585
        function validate( $value, $alldata ) {
 
1586
                $p = parent::validate( $value, $alldata );
 
1587
 
 
1588
                if ( $p !== true ) {
 
1589
                        return $p;
 
1590
                }
 
1591
 
 
1592
                if ( !is_array( $value ) ) {
 
1593
                        return false;
 
1594
                }
 
1595
 
 
1596
                # If all options are valid, array_intersect of the valid options
 
1597
                # and the provided options will return the provided options.
 
1598
                $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
 
1599
 
 
1600
                $validValues = array_intersect( $value, $validOptions );
 
1601
                if ( count( $validValues ) == count( $value ) ) {
 
1602
                        return true;
 
1603
                } else {
 
1604
                        return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
 
1605
                }
 
1606
        }
 
1607
 
 
1608
        function getInputHTML( $value ) {
 
1609
                $html = $this->formatOptions( $this->mParams['options'], $value );
 
1610
 
 
1611
                return $html;
 
1612
        }
 
1613
 
 
1614
        function formatOptions( $options, $value ) {
 
1615
                $html = '';
 
1616
 
 
1617
                $attribs = array();
 
1618
 
 
1619
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1620
                        $attribs['disabled'] = 'disabled';
 
1621
                }
 
1622
 
 
1623
                foreach ( $options as $label => $info ) {
 
1624
                        if ( is_array( $info ) ) {
 
1625
                                $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
 
1626
                                $html .= $this->formatOptions( $info, $value );
 
1627
                        } else {
 
1628
                                $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
 
1629
 
 
1630
                                $checkbox = Xml::check(
 
1631
                                        $this->mName . '[]',
 
1632
                                        in_array( $info, $value, true ),
 
1633
                                        $attribs + $thisAttribs );
 
1634
                                $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
 
1635
 
 
1636
                                $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
 
1637
                        }
 
1638
                }
 
1639
 
 
1640
                return $html;
 
1641
        }
 
1642
 
 
1643
        /**
 
1644
         * @param  $request WebRequest
 
1645
         * @return String
 
1646
         */
 
1647
        function loadDataFromRequest( $request ) {
 
1648
                if ( $this->mParent->getMethod() == 'post' ) {
 
1649
                        if( $request->wasPosted() ){
 
1650
                                # Checkboxes are just not added to the request arrays if they're not checked,
 
1651
                                # so it's perfectly possible for there not to be an entry at all
 
1652
                                return $request->getArray( $this->mName, array() );
 
1653
                        } else {
 
1654
                                # That's ok, the user has not yet submitted the form, so show the defaults
 
1655
                                return $this->getDefault();
 
1656
                        }
 
1657
                } else {
 
1658
                        # This is the impossible case: if we look at $_GET and see no data for our
 
1659
                        # field, is it because the user has not yet submitted the form, or that they
 
1660
                        # have submitted it with all the options unchecked? We will have to assume the
 
1661
                        # latter, which basically means that you can't specify 'positive' defaults
 
1662
                        # for GET forms.
 
1663
                        # @todo FIXME...
 
1664
                        return $request->getArray( $this->mName, array() );
 
1665
                }
 
1666
        }
 
1667
 
 
1668
        function getDefault() {
 
1669
                if ( isset( $this->mDefault ) ) {
 
1670
                        return $this->mDefault;
 
1671
                } else {
 
1672
                        return array();
 
1673
                }
 
1674
        }
 
1675
 
 
1676
        protected function needsLabel() {
 
1677
                return false;
 
1678
        }
 
1679
}
 
1680
 
 
1681
/**
 
1682
 * Double field with a dropdown list constructed from a system message in the format
 
1683
 *     * Optgroup header
 
1684
 *     ** <option value>
 
1685
 *     * New Optgroup header
 
1686
 * Plus a text field underneath for an additional reason.  The 'value' of the field is
 
1687
 * ""<select>: <extra reason>"", or "<extra reason>" if nothing has been selected in the
 
1688
 * select dropdown.
 
1689
 * @todo FIXME: If made 'required', only the text field should be compulsory.
 
1690
 */
 
1691
class HTMLSelectAndOtherField extends HTMLSelectField {
 
1692
 
 
1693
        function __construct( $params ) {
 
1694
                if ( array_key_exists( 'other', $params ) ) {
 
1695
                } elseif( array_key_exists( 'other-message', $params ) ){
 
1696
                        $params['other'] = wfMessage( $params['other-message'] )->plain();
 
1697
                } else {
 
1698
                        $params['other'] = null;
 
1699
                }
 
1700
 
 
1701
                if ( array_key_exists( 'options', $params ) ) {
 
1702
                        # Options array already specified
 
1703
                } elseif( array_key_exists( 'options-message', $params ) ){
 
1704
                        # Generate options array from a system message
 
1705
                        $params['options'] = self::parseMessage(
 
1706
                                wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
 
1707
                                $params['other']
 
1708
                        );
 
1709
                } else {
 
1710
                        # Sulk
 
1711
                        throw new MWException( 'HTMLSelectAndOtherField called without any options' );
 
1712
                }
 
1713
                $this->mFlatOptions = self::flattenOptions( $params['options'] );
 
1714
 
 
1715
                parent::__construct( $params );
 
1716
        }
 
1717
 
 
1718
        /**
 
1719
         * Build a drop-down box from a textual list.
 
1720
         * @param $string String message text
 
1721
         * @param $otherName String name of "other reason" option
 
1722
         * @return Array
 
1723
         * TODO: this is copied from Xml::listDropDown(), deprecate/avoid duplication?
 
1724
         */
 
1725
        public static function parseMessage( $string, $otherName=null ) {
 
1726
                if( $otherName === null ){
 
1727
                        $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
 
1728
                }
 
1729
 
 
1730
                $optgroup = false;
 
1731
                $options = array( $otherName => 'other' );
 
1732
 
 
1733
                foreach ( explode( "\n", $string ) as $option ) {
 
1734
                        $value = trim( $option );
 
1735
                        if ( $value == '' ) {
 
1736
                                continue;
 
1737
                        } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
 
1738
                                # A new group is starting...
 
1739
                                $value = trim( substr( $value, 1 ) );
 
1740
                                $optgroup = $value;
 
1741
                        } elseif ( substr( $value, 0, 2) == '**' ) {
 
1742
                                # groupmember
 
1743
                                $opt = trim( substr( $value, 2 ) );
 
1744
                                if( $optgroup === false ){
 
1745
                                        $options[$opt] = $opt;
 
1746
                                } else {
 
1747
                                        $options[$optgroup][$opt] = $opt;
 
1748
                                }
 
1749
                        } else {
 
1750
                                # groupless reason list
 
1751
                                $optgroup = false;
 
1752
                                $options[$option] = $option;
 
1753
                        }
 
1754
                }
 
1755
 
 
1756
                return $options;
 
1757
        }
 
1758
 
 
1759
        function getInputHTML( $value ) {
 
1760
                $select = parent::getInputHTML( $value[1] );
 
1761
 
 
1762
                $textAttribs = array(
 
1763
                        'id' => $this->mID . '-other',
 
1764
                        'size' => $this->getSize(),
 
1765
                );
 
1766
                
 
1767
                if ( $this->mClass !== '' ) {
 
1768
                        $textAttribs['class'] = $this->mClass;
 
1769
                }
 
1770
 
 
1771
                foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
 
1772
                        if ( isset( $this->mParams[$param] ) ) {
 
1773
                                $textAttribs[$param] = '';
 
1774
                        }
 
1775
                }
 
1776
 
 
1777
                $textbox = Html::input(
 
1778
                        $this->mName . '-other',
 
1779
                        $value[2],
 
1780
                        'text',
 
1781
                        $textAttribs
 
1782
                );
 
1783
 
 
1784
                return "$select<br />\n$textbox";
 
1785
        }
 
1786
 
 
1787
        /**
 
1788
         * @param  $request WebRequest
 
1789
         * @return Array( <overall message>, <select value>, <text field value> )
 
1790
         */
 
1791
        function loadDataFromRequest( $request ) {
 
1792
                if ( $request->getCheck( $this->mName ) ) {
 
1793
 
 
1794
                        $list = $request->getText( $this->mName );
 
1795
                        $text = $request->getText( $this->mName . '-other' );
 
1796
 
 
1797
                        if ( $list == 'other' ) {
 
1798
                                $final = $text;
 
1799
                        } elseif( !in_array( $list, $this->mFlatOptions ) ){
 
1800
                                # User has spoofed the select form to give an option which wasn't
 
1801
                                # in the original offer.  Sulk...
 
1802
                                $final = $text;
 
1803
                        } elseif( $text == '' ) {
 
1804
                                $final = $list;
 
1805
                        } else {
 
1806
                                $final = $list . wfMsgForContent( 'colon-separator' ) . $text;
 
1807
                        }
 
1808
 
 
1809
                } else {
 
1810
                        $final = $this->getDefault();
 
1811
 
 
1812
                        $list = 'other';
 
1813
                        $text = $final;
 
1814
                        foreach ( $this->mFlatOptions as $option ) {
 
1815
                                $match = $option . wfMsgForContent( 'colon-separator' );
 
1816
                                if( strpos( $text, $match ) === 0 ) {
 
1817
                                        $list = $option;
 
1818
                                        $text = substr( $text, strlen( $match ) );
 
1819
                                        break;
 
1820
                                }
 
1821
                        }
 
1822
                }
 
1823
                return array( $final, $list, $text );
 
1824
        }
 
1825
 
 
1826
        function getSize() {
 
1827
                return isset( $this->mParams['size'] )
 
1828
                        ? $this->mParams['size']
 
1829
                        : 45;
 
1830
        }
 
1831
 
 
1832
        function validate( $value, $alldata ) {
 
1833
                # HTMLSelectField forces $value to be one of the options in the select
 
1834
                # field, which is not useful here.  But we do want the validation further up
 
1835
                # the chain
 
1836
                $p = parent::validate( $value[1], $alldata );
 
1837
 
 
1838
                if ( $p !== true ) {
 
1839
                        return $p;
 
1840
                }
 
1841
 
 
1842
                if( isset( $this->mParams['required'] ) && $value[1] === '' ){
 
1843
                        return wfMsgExt( 'htmlform-required', 'parseinline' );
 
1844
                }
 
1845
 
 
1846
                return true;
 
1847
        }
 
1848
}
 
1849
 
 
1850
/**
 
1851
 * Radio checkbox fields.
 
1852
 */
 
1853
class HTMLRadioField extends HTMLFormField {
 
1854
 
 
1855
 
 
1856
        function validate( $value, $alldata ) {
 
1857
                $p = parent::validate( $value, $alldata );
 
1858
 
 
1859
                if ( $p !== true ) {
 
1860
                        return $p;
 
1861
                }
 
1862
 
 
1863
                if ( !is_string( $value ) && !is_int( $value ) ) {
 
1864
                        return false;
 
1865
                }
 
1866
 
 
1867
                $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
 
1868
 
 
1869
                if ( in_array( $value, $validOptions ) ) {
 
1870
                        return true;
 
1871
                } else {
 
1872
                        return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
 
1873
                }
 
1874
        }
 
1875
 
 
1876
        /**
 
1877
         * This returns a block of all the radio options, in one cell.
 
1878
         * @see includes/HTMLFormField#getInputHTML()
 
1879
         * @param $value String
 
1880
         * @return String
 
1881
         */
 
1882
        function getInputHTML( $value ) {
 
1883
                $html = $this->formatOptions( $this->mParams['options'], $value );
 
1884
 
 
1885
                return $html;
 
1886
        }
 
1887
 
 
1888
        function formatOptions( $options, $value ) {
 
1889
                $html = '';
 
1890
 
 
1891
                $attribs = array();
 
1892
                if ( !empty( $this->mParams['disabled'] ) ) {
 
1893
                        $attribs['disabled'] = 'disabled';
 
1894
                }
 
1895
 
 
1896
                # TODO: should this produce an unordered list perhaps?
 
1897
                foreach ( $options as $label => $info ) {
 
1898
                        if ( is_array( $info ) ) {
 
1899
                                $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
 
1900
                                $html .= $this->formatOptions( $info, $value );
 
1901
                        } else {
 
1902
                                $id = Sanitizer::escapeId( $this->mID . "-$info" );
 
1903
                                $radio = Xml::radio(
 
1904
                                        $this->mName,
 
1905
                                        $info,
 
1906
                                        $info == $value,
 
1907
                                        $attribs + array( 'id' => $id )
 
1908
                                );
 
1909
                                $radio .= '&#160;' .
 
1910
                                                Html::rawElement( 'label', array( 'for' => $id ), $label );
 
1911
 
 
1912
                                $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
 
1913
                        }
 
1914
                }
 
1915
 
 
1916
                return $html;
 
1917
        }
 
1918
 
 
1919
        protected function needsLabel() {
 
1920
                return false;
 
1921
        }
 
1922
}
 
1923
 
 
1924
/**
 
1925
 * An information field (text blob), not a proper input.
 
1926
 */
 
1927
class HTMLInfoField extends HTMLFormField {
 
1928
        function __construct( $info ) {
 
1929
                $info['nodata'] = true;
 
1930
 
 
1931
                parent::__construct( $info );
 
1932
        }
 
1933
 
 
1934
        function getInputHTML( $value ) {
 
1935
                return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
 
1936
        }
 
1937
 
 
1938
        function getTableRow( $value ) {
 
1939
                if ( !empty( $this->mParams['rawrow'] ) ) {
 
1940
                        return $value;
 
1941
                }
 
1942
 
 
1943
                return parent::getTableRow( $value );
 
1944
        }
 
1945
 
 
1946
        protected function needsLabel() {
 
1947
                return false;
 
1948
        }
 
1949
}
 
1950
 
 
1951
class HTMLHiddenField extends HTMLFormField {
 
1952
        public function __construct( $params ) {
 
1953
                parent::__construct( $params );
 
1954
 
 
1955
                # Per HTML5 spec, hidden fields cannot be 'required'
 
1956
                # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
 
1957
                unset( $this->mParams['required'] );
 
1958
        }
 
1959
 
 
1960
        public function getTableRow( $value ) {
 
1961
                $params = array();
 
1962
                if ( $this->mID ) {
 
1963
                        $params['id'] = $this->mID;
 
1964
                }
 
1965
 
 
1966
                $this->mParent->addHiddenField(
 
1967
                        $this->mName,
 
1968
                        $this->mDefault,
 
1969
                        $params
 
1970
                );
 
1971
 
 
1972
                return '';
 
1973
        }
 
1974
 
 
1975
        public function getInputHTML( $value ) { return ''; }
 
1976
}
 
1977
 
 
1978
/**
 
1979
 * Add a submit button inline in the form (as opposed to
 
1980
 * HTMLForm::addButton(), which will add it at the end).
 
1981
 */
 
1982
class HTMLSubmitField extends HTMLFormField {
 
1983
 
 
1984
        function __construct( $info ) {
 
1985
                $info['nodata'] = true;
 
1986
                parent::__construct( $info );
 
1987
        }
 
1988
 
 
1989
        function getInputHTML( $value ) {
 
1990
                return Xml::submitButton(
 
1991
                        $value,
 
1992
                        array(
 
1993
                                'class' => 'mw-htmlform-submit ' . $this->mClass,
 
1994
                                'name' => $this->mName,
 
1995
                                'id' => $this->mID,
 
1996
                        )
 
1997
                );
 
1998
        }
 
1999
 
 
2000
        protected function needsLabel() {
 
2001
                return false;
 
2002
        }
 
2003
 
 
2004
        /**
 
2005
         * Button cannot be invalid
 
2006
         * @param $value String
 
2007
         * @param $alldata Array
 
2008
         * @return Bool
 
2009
         */
 
2010
        public function validate( $value, $alldata ){
 
2011
                return true;
 
2012
        }
 
2013
}
 
2014
 
 
2015
class HTMLEditTools extends HTMLFormField {
 
2016
        public function getInputHTML( $value ) {
 
2017
                return '';
 
2018
        }
 
2019
 
 
2020
        public function getTableRow( $value ) {
 
2021
                if ( empty( $this->mParams['message'] ) ) {
 
2022
                        $msg = wfMessage( 'edittools' );
 
2023
                } else {
 
2024
                        $msg = wfMessage( $this->mParams['message'] );
 
2025
                        if ( $msg->isDisabled() ) {
 
2026
                                $msg = wfMessage( 'edittools' );
 
2027
                        }
 
2028
                }
 
2029
                $msg->inContentLanguage();
 
2030
 
 
2031
 
 
2032
                return '<tr><td></td><td class="mw-input">'
 
2033
                        . '<div class="mw-editTools">'
 
2034
                        . $msg->parseAsBlock()
 
2035
                        . "</div></td></tr>\n";
 
2036
        }
 
2037
}