wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * HTML form generation and submission handling. |
||
| 5 | * |
||
| 6 | * This program is free software; you can redistribute it and/or modify |
||
| 7 | * it under the terms of the GNU General Public License as published by |
||
| 8 | * the Free Software Foundation; either version 2 of the License, or |
||
| 9 | * (at your option) any later version. |
||
| 10 | * |
||
| 11 | * This program is distributed in the hope that it will be useful, |
||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 14 | * GNU General Public License for more details. |
||
| 15 | * |
||
| 16 | * You should have received a copy of the GNU General Public License along |
||
| 17 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 19 | * http://www.gnu.org/copyleft/gpl.html |
||
| 20 | * |
||
| 21 | * @file |
||
| 22 | */ |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Object handling generic submission, CSRF protection, layout and |
||
| 26 | * other logic for UI forms. in a reusable manner. |
||
| 27 | * |
||
| 28 | * In order to generate the form, the HTMLForm object takes an array |
||
| 29 | * structure detailing the form fields available. Each element of the |
||
| 30 | * array is a basic property-list, including the type of field, the |
||
| 31 | * label it is to be given in the form, callbacks for validation and |
||
| 32 | * 'filtering', and other pertinent information. |
||
| 33 | * |
||
| 34 | * Field types are implemented as subclasses of the generic HTMLFormField |
||
| 35 | * object, and typically implement at least getInputHTML, which generates |
||
| 36 | * the HTML for the input field to be placed in the table. |
||
| 37 | * |
||
| 38 | * You can find extensive documentation on the www.mediawiki.org wiki: |
||
| 39 | * - https://www.mediawiki.org/wiki/HTMLForm |
||
| 40 | * - https://www.mediawiki.org/wiki/HTMLForm/tutorial |
||
| 41 | * |
||
| 42 | * The constructor input is an associative array of $fieldname => $info, |
||
| 43 | * where $info is an Associative Array with any of the following: |
||
| 44 | * |
||
| 45 | * 'class' -- the subclass of HTMLFormField that will be used |
||
| 46 | * to create the object. *NOT* the CSS class! |
||
| 47 | * 'type' -- roughly translates into the <select> type attribute. |
||
| 48 | * if 'class' is not specified, this is used as a map |
||
| 49 | * through HTMLForm::$typeMappings to get the class name. |
||
| 50 | * 'default' -- default value when the form is displayed |
||
| 51 | * 'id' -- HTML id attribute |
||
| 52 | * 'cssclass' -- CSS class |
||
| 53 | * 'csshelpclass' -- CSS class used to style help text |
||
| 54 | * 'dir' -- Direction of the element. |
||
| 55 | * 'options' -- associative array mapping labels to values. |
||
| 56 | * Some field types support multi-level arrays. |
||
| 57 | * 'options-messages' -- associative array mapping message keys to values. |
||
| 58 | * Some field types support multi-level arrays. |
||
| 59 | * 'options-message' -- message key or object to be parsed to extract the list of |
||
| 60 | * options (like 'ipbreason-dropdown'). |
||
| 61 | * 'label-message' -- message key or object for a message to use as the label. |
||
| 62 | * can be an array of msg key and then parameters to |
||
| 63 | * the message. |
||
| 64 | * 'label' -- alternatively, a raw text message. Overridden by |
||
| 65 | * label-message |
||
| 66 | * 'help' -- message text for a message to use as a help text. |
||
| 67 | * 'help-message' -- message key or object for a message to use as a help text. |
||
| 68 | * can be an array of msg key and then parameters to |
||
| 69 | * the message. |
||
| 70 | * Overwrites 'help-messages' and 'help'. |
||
| 71 | * 'help-messages' -- array of message keys/objects. As above, each item can |
||
| 72 | * be an array of msg key and then parameters. |
||
| 73 | * Overwrites 'help'. |
||
| 74 | * 'notice' -- message text for a message to use as a notice in the field. |
||
| 75 | * Currently used by OOUI form fields only. |
||
| 76 | * 'notice-messages' -- array of message keys/objects to use for notice. |
||
| 77 | * Overrides 'notice'. |
||
| 78 | * 'notice-message' -- message key or object to use as a notice. |
||
| 79 | * 'required' -- passed through to the object, indicating that it |
||
| 80 | * is a required field. |
||
| 81 | * 'size' -- the length of text fields |
||
| 82 | * 'filter-callback' -- a function name to give you the chance to |
||
| 83 | * massage the inputted value before it's processed. |
||
| 84 | * @see HTMLFormField::filter() |
||
| 85 | * 'validation-callback' -- a function name to give you the chance |
||
| 86 | * to impose extra validation on the field input. |
||
| 87 | * @see HTMLFormField::validate() |
||
| 88 | * 'name' -- By default, the 'name' attribute of the input field |
||
| 89 | * is "wp{$fieldname}". If you want a different name |
||
| 90 | * (eg one without the "wp" prefix), specify it here and |
||
| 91 | * it will be used without modification. |
||
| 92 | * 'hide-if' -- expression given as an array stating when the field |
||
| 93 | * should be hidden. The first array value has to be the |
||
| 94 | * expression's logic operator. Supported expressions: |
||
| 95 | * 'NOT' |
||
| 96 | * [ 'NOT', array $expression ] |
||
| 97 | * To hide a field if a given expression is not true. |
||
| 98 | * '===' |
||
| 99 | * [ '===', string $fieldName, string $value ] |
||
| 100 | * To hide a field if another field identified by |
||
| 101 | * $field has the value $value. |
||
| 102 | * '!==' |
||
| 103 | * [ '!==', string $fieldName, string $value ] |
||
| 104 | * Same as [ 'NOT', [ '===', $fieldName, $value ] |
||
| 105 | * 'OR', 'AND', 'NOR', 'NAND' |
||
| 106 | * [ 'XXX', array $expression1, ..., array $expressionN ] |
||
| 107 | * To hide a field if one or more (OR), all (AND), |
||
| 108 | * neither (NOR) or not all (NAND) given expressions |
||
| 109 | * are evaluated as true. |
||
| 110 | * The expressions will be given to a JavaScript frontend |
||
| 111 | * module which will continually update the field's |
||
| 112 | * visibility. |
||
| 113 | * |
||
| 114 | * Since 1.20, you can chain mutators to ease the form generation: |
||
| 115 | * @par Example: |
||
| 116 | * @code |
||
| 117 | * $form = new HTMLForm( $someFields ); |
||
| 118 | * $form->setMethod( 'get' ) |
||
| 119 | * ->setWrapperLegendMsg( 'message-key' ) |
||
| 120 | * ->prepareForm() |
||
| 121 | * ->displayForm( '' ); |
||
| 122 | * @endcode |
||
| 123 | * Note that you will have prepareForm and displayForm at the end. Other |
||
| 124 | * methods call done after that would simply not be part of the form :( |
||
| 125 | * |
||
| 126 | * @todo Document 'section' / 'subsection' stuff |
||
| 127 | */ |
||
| 128 | class HTMLForm extends ContextSource { |
||
| 129 | // A mapping of 'type' inputs onto standard HTMLFormField subclasses |
||
| 130 | public static $typeMappings = [ |
||
| 131 | 'api' => 'HTMLApiField', |
||
| 132 | 'text' => 'HTMLTextField', |
||
| 133 | 'textwithbutton' => 'HTMLTextFieldWithButton', |
||
| 134 | 'textarea' => 'HTMLTextAreaField', |
||
| 135 | 'select' => 'HTMLSelectField', |
||
| 136 | 'combobox' => 'HTMLComboboxField', |
||
| 137 | 'radio' => 'HTMLRadioField', |
||
| 138 | 'multiselect' => 'HTMLMultiSelectField', |
||
| 139 | 'limitselect' => 'HTMLSelectLimitField', |
||
| 140 | 'check' => 'HTMLCheckField', |
||
| 141 | 'toggle' => 'HTMLCheckField', |
||
| 142 | 'int' => 'HTMLIntField', |
||
| 143 | 'float' => 'HTMLFloatField', |
||
| 144 | 'info' => 'HTMLInfoField', |
||
| 145 | 'selectorother' => 'HTMLSelectOrOtherField', |
||
| 146 | 'selectandother' => 'HTMLSelectAndOtherField', |
||
| 147 | 'namespaceselect' => 'HTMLSelectNamespace', |
||
| 148 | 'namespaceselectwithbutton' => 'HTMLSelectNamespaceWithButton', |
||
| 149 | 'tagfilter' => 'HTMLTagFilter', |
||
| 150 | 'sizefilter' => 'HTMLSizeFilterField', |
||
| 151 | 'submit' => 'HTMLSubmitField', |
||
| 152 | 'hidden' => 'HTMLHiddenField', |
||
| 153 | 'edittools' => 'HTMLEditTools', |
||
| 154 | 'checkmatrix' => 'HTMLCheckMatrix', |
||
| 155 | 'cloner' => 'HTMLFormFieldCloner', |
||
| 156 | 'autocompleteselect' => 'HTMLAutoCompleteSelectField', |
||
| 157 | 'date' => 'HTMLDateTimeField', |
||
| 158 | 'time' => 'HTMLDateTimeField', |
||
| 159 | 'datetime' => 'HTMLDateTimeField', |
||
| 160 | // HTMLTextField will output the correct type="" attribute automagically. |
||
| 161 | // There are about four zillion other HTML5 input types, like range, but |
||
| 162 | // we don't use those at the moment, so no point in adding all of them. |
||
| 163 | 'email' => 'HTMLTextField', |
||
| 164 | 'password' => 'HTMLTextField', |
||
| 165 | 'url' => 'HTMLTextField', |
||
| 166 | 'title' => 'HTMLTitleTextField', |
||
| 167 | 'user' => 'HTMLUserTextField', |
||
| 168 | ]; |
||
| 169 | |||
| 170 | public $mFieldData; |
||
| 171 | |||
| 172 | protected $mMessagePrefix; |
||
| 173 | |||
| 174 | /** @var HTMLFormField[] */ |
||
| 175 | protected $mFlatFields; |
||
| 176 | |||
| 177 | protected $mFieldTree; |
||
| 178 | protected $mShowReset = false; |
||
| 179 | protected $mShowSubmit = true; |
||
| 180 | protected $mSubmitFlags = [ 'primary', 'progressive' ]; |
||
| 181 | protected $mShowCancel = false; |
||
| 182 | protected $mCancelTarget; |
||
| 183 | |||
| 184 | protected $mSubmitCallback; |
||
| 185 | protected $mValidationErrorMessage; |
||
| 186 | |||
| 187 | protected $mPre = ''; |
||
| 188 | protected $mHeader = ''; |
||
| 189 | protected $mFooter = ''; |
||
| 190 | protected $mSectionHeaders = []; |
||
| 191 | protected $mSectionFooters = []; |
||
| 192 | protected $mPost = ''; |
||
| 193 | protected $mId; |
||
| 194 | protected $mName; |
||
| 195 | protected $mTableId = ''; |
||
| 196 | |||
| 197 | protected $mSubmitID; |
||
| 198 | protected $mSubmitName; |
||
| 199 | protected $mSubmitText; |
||
| 200 | protected $mSubmitTooltip; |
||
| 201 | |||
| 202 | protected $mFormIdentifier; |
||
| 203 | protected $mTitle; |
||
| 204 | protected $mMethod = 'post'; |
||
| 205 | protected $mWasSubmitted = false; |
||
| 206 | |||
| 207 | /** |
||
| 208 | * Form action URL. false means we will use the URL to set Title |
||
| 209 | * @since 1.19 |
||
| 210 | * @var bool|string |
||
| 211 | */ |
||
| 212 | protected $mAction = false; |
||
| 213 | |||
| 214 | /** |
||
| 215 | * Form attribute autocomplete. false does not set the attribute |
||
| 216 | * @since 1.27 |
||
| 217 | * @var bool|string |
||
| 218 | */ |
||
| 219 | protected $mAutocomplete = false; |
||
| 220 | |||
| 221 | protected $mUseMultipart = false; |
||
| 222 | protected $mHiddenFields = []; |
||
| 223 | protected $mButtons = []; |
||
| 224 | |||
| 225 | protected $mWrapperLegend = false; |
||
| 226 | |||
| 227 | /** |
||
| 228 | * Salt for the edit token. |
||
| 229 | * @var string|array |
||
| 230 | */ |
||
| 231 | protected $mTokenSalt = ''; |
||
| 232 | |||
| 233 | /** |
||
| 234 | * If true, sections that contain both fields and subsections will |
||
| 235 | * render their subsections before their fields. |
||
| 236 | * |
||
| 237 | * Subclasses may set this to false to render subsections after fields |
||
| 238 | * instead. |
||
| 239 | */ |
||
| 240 | protected $mSubSectionBeforeFields = true; |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Format in which to display form. For viable options, |
||
| 244 | * @see $availableDisplayFormats |
||
| 245 | * @var string |
||
| 246 | */ |
||
| 247 | protected $displayFormat = 'table'; |
||
| 248 | |||
| 249 | /** |
||
| 250 | * Available formats in which to display the form |
||
| 251 | * @var array |
||
| 252 | */ |
||
| 253 | protected $availableDisplayFormats = [ |
||
| 254 | 'table', |
||
| 255 | 'div', |
||
| 256 | 'raw', |
||
| 257 | 'inline', |
||
| 258 | ]; |
||
| 259 | |||
| 260 | /** |
||
| 261 | * Available formats in which to display the form |
||
| 262 | * @var array |
||
| 263 | */ |
||
| 264 | protected $availableSubclassDisplayFormats = [ |
||
| 265 | 'vform', |
||
| 266 | 'ooui', |
||
| 267 | ]; |
||
| 268 | |||
| 269 | /** |
||
| 270 | * Construct a HTMLForm object for given display type. May return a HTMLForm subclass. |
||
| 271 | * |
||
| 272 | * @param string $displayFormat |
||
| 273 | * @param mixed $arguments... Additional arguments to pass to the constructor. |
||
|
0 ignored issues
–
show
|
|||
| 274 | * @return HTMLForm |
||
| 275 | */ |
||
| 276 | public static function factory( $displayFormat/*, $arguments...*/ ) { |
||
| 277 | $arguments = func_get_args(); |
||
| 278 | array_shift( $arguments ); |
||
| 279 | |||
| 280 | switch ( $displayFormat ) { |
||
| 281 | case 'vform': |
||
| 282 | return ObjectFactory::constructClassInstance( VFormHTMLForm::class, $arguments ); |
||
| 283 | case 'ooui': |
||
| 284 | return ObjectFactory::constructClassInstance( OOUIHTMLForm::class, $arguments ); |
||
| 285 | default: |
||
| 286 | /** @var HTMLForm $form */ |
||
| 287 | $form = ObjectFactory::constructClassInstance( HTMLForm::class, $arguments ); |
||
| 288 | $form->setDisplayFormat( $displayFormat ); |
||
| 289 | return $form; |
||
| 290 | } |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * Build a new HTMLForm from an array of field attributes |
||
| 295 | * |
||
| 296 | * @param array $descriptor Array of Field constructs, as described above |
||
| 297 | * @param IContextSource $context Available since 1.18, will become compulsory in 1.18. |
||
| 298 | * Obviates the need to call $form->setTitle() |
||
| 299 | * @param string $messagePrefix A prefix to go in front of default messages |
||
| 300 | */ |
||
| 301 | public function __construct( $descriptor, /*IContextSource*/ $context = null, |
||
| 302 | $messagePrefix = '' |
||
| 303 | ) { |
||
| 304 | if ( $context instanceof IContextSource ) { |
||
| 305 | $this->setContext( $context ); |
||
| 306 | $this->mTitle = false; // We don't need them to set a title |
||
| 307 | $this->mMessagePrefix = $messagePrefix; |
||
| 308 | } elseif ( $context === null && $messagePrefix !== '' ) { |
||
| 309 | $this->mMessagePrefix = $messagePrefix; |
||
| 310 | } elseif ( is_string( $context ) && $messagePrefix === '' ) { |
||
| 311 | // B/C since 1.18 |
||
| 312 | // it's actually $messagePrefix |
||
| 313 | $this->mMessagePrefix = $context; |
||
| 314 | } |
||
| 315 | |||
| 316 | // Evil hack for mobile :( |
||
| 317 | if ( |
||
| 318 | !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) |
||
| 319 | && $this->displayFormat === 'table' |
||
| 320 | ) { |
||
| 321 | $this->displayFormat = 'div'; |
||
| 322 | } |
||
| 323 | |||
| 324 | // Expand out into a tree. |
||
| 325 | $loadedDescriptor = []; |
||
| 326 | $this->mFlatFields = []; |
||
| 327 | |||
| 328 | foreach ( $descriptor as $fieldname => $info ) { |
||
| 329 | $section = isset( $info['section'] ) |
||
| 330 | ? $info['section'] |
||
| 331 | : ''; |
||
| 332 | |||
| 333 | if ( isset( $info['type'] ) && $info['type'] === 'file' ) { |
||
| 334 | $this->mUseMultipart = true; |
||
| 335 | } |
||
| 336 | |||
| 337 | $field = static::loadInputFromParameters( $fieldname, $info, $this ); |
||
| 338 | |||
| 339 | $setSection =& $loadedDescriptor; |
||
| 340 | if ( $section ) { |
||
| 341 | $sectionParts = explode( '/', $section ); |
||
| 342 | |||
| 343 | while ( count( $sectionParts ) ) { |
||
| 344 | $newName = array_shift( $sectionParts ); |
||
| 345 | |||
| 346 | if ( !isset( $setSection[$newName] ) ) { |
||
| 347 | $setSection[$newName] = []; |
||
| 348 | } |
||
| 349 | |||
| 350 | $setSection =& $setSection[$newName]; |
||
| 351 | } |
||
| 352 | } |
||
| 353 | |||
| 354 | $setSection[$fieldname] = $field; |
||
| 355 | $this->mFlatFields[$fieldname] = $field; |
||
| 356 | } |
||
| 357 | |||
| 358 | $this->mFieldTree = $loadedDescriptor; |
||
| 359 | } |
||
| 360 | |||
| 361 | /** |
||
| 362 | * @param string $fieldname |
||
| 363 | * @return bool |
||
| 364 | */ |
||
| 365 | public function hasField( $fieldname ) { |
||
| 366 | return isset( $this->mFlatFields[$fieldname] ); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * @param string $fieldname |
||
| 371 | * @return HTMLFormField |
||
| 372 | * @throws DomainException on invalid field name |
||
| 373 | */ |
||
| 374 | public function getField( $fieldname ) { |
||
| 375 | if ( !$this->hasField( $fieldname ) ) { |
||
| 376 | throw new DomainException( __METHOD__ . ': no field named ' . $fieldname ); |
||
| 377 | } |
||
| 378 | return $this->mFlatFields[$fieldname]; |
||
| 379 | } |
||
| 380 | |||
| 381 | /** |
||
| 382 | * Set format in which to display the form |
||
| 383 | * |
||
| 384 | * @param string $format The name of the format to use, must be one of |
||
| 385 | * $this->availableDisplayFormats |
||
| 386 | * |
||
| 387 | * @throws MWException |
||
| 388 | * @since 1.20 |
||
| 389 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 390 | */ |
||
| 391 | public function setDisplayFormat( $format ) { |
||
| 392 | if ( |
||
| 393 | in_array( $format, $this->availableSubclassDisplayFormats, true ) || |
||
| 394 | in_array( $this->displayFormat, $this->availableSubclassDisplayFormats, true ) |
||
| 395 | ) { |
||
| 396 | throw new MWException( 'Cannot change display format after creation, ' . |
||
| 397 | 'use HTMLForm::factory() instead' ); |
||
| 398 | } |
||
| 399 | |||
| 400 | if ( !in_array( $format, $this->availableDisplayFormats, true ) ) { |
||
| 401 | throw new MWException( 'Display format must be one of ' . |
||
| 402 | print_r( $this->availableDisplayFormats, true ) ); |
||
| 403 | } |
||
| 404 | |||
| 405 | // Evil hack for mobile :( |
||
| 406 | if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) { |
||
| 407 | $format = 'div'; |
||
| 408 | } |
||
| 409 | |||
| 410 | $this->displayFormat = $format; |
||
| 411 | |||
| 412 | return $this; |
||
| 413 | } |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Getter for displayFormat |
||
| 417 | * @since 1.20 |
||
| 418 | * @return string |
||
| 419 | */ |
||
| 420 | public function getDisplayFormat() { |
||
| 421 | return $this->displayFormat; |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Test if displayFormat is 'vform' |
||
| 426 | * @since 1.22 |
||
| 427 | * @deprecated since 1.25 |
||
| 428 | * @return bool |
||
| 429 | */ |
||
| 430 | public function isVForm() { |
||
| 431 | wfDeprecated( __METHOD__, '1.25' ); |
||
| 432 | return false; |
||
| 433 | } |
||
| 434 | |||
| 435 | /** |
||
| 436 | * Get the HTMLFormField subclass for this descriptor. |
||
| 437 | * |
||
| 438 | * The descriptor can be passed either 'class' which is the name of |
||
| 439 | * a HTMLFormField subclass, or a shorter 'type' which is an alias. |
||
| 440 | * This makes sure the 'class' is always set, and also is returned by |
||
| 441 | * this function for ease. |
||
| 442 | * |
||
| 443 | * @since 1.23 |
||
| 444 | * |
||
| 445 | * @param string $fieldname Name of the field |
||
| 446 | * @param array $descriptor Input Descriptor, as described above |
||
| 447 | * |
||
| 448 | * @throws MWException |
||
| 449 | * @return string Name of a HTMLFormField subclass |
||
| 450 | */ |
||
| 451 | public static function getClassFromDescriptor( $fieldname, &$descriptor ) { |
||
| 452 | if ( isset( $descriptor['class'] ) ) { |
||
| 453 | $class = $descriptor['class']; |
||
| 454 | } elseif ( isset( $descriptor['type'] ) ) { |
||
| 455 | $class = static::$typeMappings[$descriptor['type']]; |
||
| 456 | $descriptor['class'] = $class; |
||
| 457 | } else { |
||
| 458 | $class = null; |
||
| 459 | } |
||
| 460 | |||
| 461 | if ( !$class ) { |
||
| 462 | throw new MWException( "Descriptor with no class for $fieldname: " |
||
| 463 | . print_r( $descriptor, true ) ); |
||
| 464 | } |
||
| 465 | |||
| 466 | return $class; |
||
| 467 | } |
||
| 468 | |||
| 469 | /** |
||
| 470 | * Initialise a new Object for the field |
||
| 471 | * |
||
| 472 | * @param string $fieldname Name of the field |
||
| 473 | * @param array $descriptor Input Descriptor, as described above |
||
| 474 | * @param HTMLForm|null $parent Parent instance of HTMLForm |
||
| 475 | * |
||
| 476 | * @throws MWException |
||
| 477 | * @return HTMLFormField Instance of a subclass of HTMLFormField |
||
| 478 | */ |
||
| 479 | public static function loadInputFromParameters( $fieldname, $descriptor, |
||
| 480 | HTMLForm $parent = null |
||
| 481 | ) { |
||
| 482 | $class = static::getClassFromDescriptor( $fieldname, $descriptor ); |
||
| 483 | |||
| 484 | $descriptor['fieldname'] = $fieldname; |
||
| 485 | if ( $parent ) { |
||
| 486 | $descriptor['parent'] = $parent; |
||
| 487 | } |
||
| 488 | |||
| 489 | # @todo This will throw a fatal error whenever someone try to use |
||
| 490 | # 'class' to feed a CSS class instead of 'cssclass'. Would be |
||
| 491 | # great to avoid the fatal error and show a nice error. |
||
| 492 | return new $class( $descriptor ); |
||
| 493 | } |
||
| 494 | |||
| 495 | /** |
||
| 496 | * Prepare form for submission. |
||
| 497 | * |
||
| 498 | * @attention When doing method chaining, that should be the very last |
||
| 499 | * method call before displayForm(). |
||
| 500 | * |
||
| 501 | * @throws MWException |
||
| 502 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 503 | */ |
||
| 504 | public function prepareForm() { |
||
| 505 | # Check if we have the info we need |
||
| 506 | if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) { |
||
| 507 | throw new MWException( 'You must call setTitle() on an HTMLForm' ); |
||
| 508 | } |
||
| 509 | |||
| 510 | # Load data from the request. |
||
| 511 | if ( |
||
| 512 | $this->mFormIdentifier === null || |
||
| 513 | $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier |
||
| 514 | ) { |
||
| 515 | $this->loadData(); |
||
| 516 | } else { |
||
| 517 | $this->mFieldData = []; |
||
| 518 | } |
||
| 519 | |||
| 520 | return $this; |
||
| 521 | } |
||
| 522 | |||
| 523 | /** |
||
| 524 | * Try submitting, with edit token check first |
||
| 525 | * @return Status|bool |
||
| 526 | */ |
||
| 527 | public function tryAuthorizedSubmit() { |
||
| 528 | $result = false; |
||
| 529 | |||
| 530 | $identOkay = false; |
||
|
0 ignored issues
–
show
$identOkay is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the Loading history...
|
|||
| 531 | if ( $this->mFormIdentifier === null ) { |
||
| 532 | $identOkay = true; |
||
| 533 | } else { |
||
| 534 | $identOkay = $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier; |
||
| 535 | } |
||
| 536 | |||
| 537 | $tokenOkay = false; |
||
| 538 | if ( $this->getMethod() !== 'post' ) { |
||
| 539 | $tokenOkay = true; // no session check needed |
||
| 540 | } elseif ( $this->getRequest()->wasPosted() ) { |
||
| 541 | $editToken = $this->getRequest()->getVal( 'wpEditToken' ); |
||
| 542 | if ( $this->getUser()->isLoggedIn() || $editToken !== null ) { |
||
| 543 | // Session tokens for logged-out users have no security value. |
||
| 544 | // However, if the user gave one, check it in order to give a nice |
||
| 545 | // "session expired" error instead of "permission denied" or such. |
||
| 546 | $tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt ); |
||
|
0 ignored issues
–
show
It seems like
$this->mTokenSalt can also be of type array; however, User::matchEditToken() does only seem to accept string, maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. Loading history...
|
|||
| 547 | } else { |
||
| 548 | $tokenOkay = true; |
||
| 549 | } |
||
| 550 | } |
||
| 551 | |||
| 552 | if ( $tokenOkay && $identOkay ) { |
||
| 553 | $this->mWasSubmitted = true; |
||
| 554 | $result = $this->trySubmit(); |
||
| 555 | } |
||
| 556 | |||
| 557 | return $result; |
||
| 558 | } |
||
| 559 | |||
| 560 | /** |
||
| 561 | * The here's-one-I-made-earlier option: do the submission if |
||
| 562 | * posted, or display the form with or without funky validation |
||
| 563 | * errors |
||
| 564 | * @return bool|Status Whether submission was successful. |
||
| 565 | */ |
||
| 566 | public function show() { |
||
| 567 | $this->prepareForm(); |
||
| 568 | |||
| 569 | $result = $this->tryAuthorizedSubmit(); |
||
| 570 | if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { |
||
| 571 | return $result; |
||
| 572 | } |
||
| 573 | |||
| 574 | $this->displayForm( $result ); |
||
| 575 | |||
| 576 | return false; |
||
| 577 | } |
||
| 578 | |||
| 579 | /** |
||
| 580 | * Same as self::show with the difference, that the form will be |
||
| 581 | * added to the output, no matter, if the validation was good or not. |
||
| 582 | * @return bool|Status Whether submission was successful. |
||
| 583 | */ |
||
| 584 | public function showAlways() { |
||
| 585 | $this->prepareForm(); |
||
| 586 | |||
| 587 | $result = $this->tryAuthorizedSubmit(); |
||
| 588 | |||
| 589 | $this->displayForm( $result ); |
||
| 590 | |||
| 591 | return $result; |
||
| 592 | } |
||
| 593 | |||
| 594 | /** |
||
| 595 | * Validate all the fields, and call the submission callback |
||
| 596 | * function if everything is kosher. |
||
| 597 | * @throws MWException |
||
| 598 | * @return bool|string|array|Status |
||
| 599 | * - Bool true or a good Status object indicates success, |
||
| 600 | * - Bool false indicates no submission was attempted, |
||
| 601 | * - Anything else indicates failure. The value may be a fatal Status |
||
| 602 | * object, an HTML string, or an array of arrays (message keys and |
||
| 603 | * params) or strings (message keys) |
||
| 604 | */ |
||
| 605 | public function trySubmit() { |
||
| 606 | $valid = true; |
||
| 607 | $hoistedErrors = []; |
||
| 608 | $hoistedErrors[] = isset( $this->mValidationErrorMessage ) |
||
| 609 | ? $this->mValidationErrorMessage |
||
| 610 | : [ 'htmlform-invalid-input' ]; |
||
| 611 | |||
| 612 | $this->mWasSubmitted = true; |
||
| 613 | |||
| 614 | # Check for cancelled submission |
||
| 615 | foreach ( $this->mFlatFields as $fieldname => $field ) { |
||
| 616 | if ( !array_key_exists( $fieldname, $this->mFieldData ) ) { |
||
| 617 | continue; |
||
| 618 | } |
||
| 619 | if ( $field->cancelSubmit( $this->mFieldData[$fieldname], $this->mFieldData ) ) { |
||
| 620 | $this->mWasSubmitted = false; |
||
| 621 | return false; |
||
| 622 | } |
||
| 623 | } |
||
| 624 | |||
| 625 | # Check for validation |
||
| 626 | foreach ( $this->mFlatFields as $fieldname => $field ) { |
||
| 627 | if ( !array_key_exists( $fieldname, $this->mFieldData ) ) { |
||
| 628 | continue; |
||
| 629 | } |
||
| 630 | if ( $field->isHidden( $this->mFieldData ) ) { |
||
| 631 | continue; |
||
| 632 | } |
||
| 633 | $res = $field->validate( $this->mFieldData[$fieldname], $this->mFieldData ); |
||
| 634 | if ( $res !== true ) { |
||
| 635 | $valid = false; |
||
| 636 | if ( $res !== false && !$field->canDisplayErrors() ) { |
||
| 637 | $hoistedErrors[] = [ 'rawmessage', $res ]; |
||
| 638 | } |
||
| 639 | } |
||
| 640 | } |
||
| 641 | |||
| 642 | if ( !$valid ) { |
||
| 643 | if ( count( $hoistedErrors ) === 1 ) { |
||
| 644 | $hoistedErrors = $hoistedErrors[0]; |
||
| 645 | } |
||
| 646 | return $hoistedErrors; |
||
| 647 | } |
||
| 648 | |||
| 649 | $callback = $this->mSubmitCallback; |
||
| 650 | if ( !is_callable( $callback ) ) { |
||
| 651 | throw new MWException( 'HTMLForm: no submit callback provided. Use ' . |
||
| 652 | 'setSubmitCallback() to set one.' ); |
||
| 653 | } |
||
| 654 | |||
| 655 | $data = $this->filterDataForSubmit( $this->mFieldData ); |
||
| 656 | |||
| 657 | $res = call_user_func( $callback, $data, $this ); |
||
| 658 | if ( $res === false ) { |
||
| 659 | $this->mWasSubmitted = false; |
||
| 660 | } |
||
| 661 | |||
| 662 | return $res; |
||
| 663 | } |
||
| 664 | |||
| 665 | /** |
||
| 666 | * Test whether the form was considered to have been submitted or not, i.e. |
||
| 667 | * whether the last call to tryAuthorizedSubmit or trySubmit returned |
||
| 668 | * non-false. |
||
| 669 | * |
||
| 670 | * This will return false until HTMLForm::tryAuthorizedSubmit or |
||
| 671 | * HTMLForm::trySubmit is called. |
||
| 672 | * |
||
| 673 | * @since 1.23 |
||
| 674 | * @return bool |
||
| 675 | */ |
||
| 676 | public function wasSubmitted() { |
||
| 677 | return $this->mWasSubmitted; |
||
| 678 | } |
||
| 679 | |||
| 680 | /** |
||
| 681 | * Set a callback to a function to do something with the form |
||
| 682 | * once it's been successfully validated. |
||
| 683 | * |
||
| 684 | * @param callable $cb The function will be passed the output from |
||
| 685 | * HTMLForm::filterDataForSubmit and this HTMLForm object, and must |
||
| 686 | * return as documented for HTMLForm::trySubmit |
||
| 687 | * |
||
| 688 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 689 | */ |
||
| 690 | public function setSubmitCallback( $cb ) { |
||
| 691 | $this->mSubmitCallback = $cb; |
||
| 692 | |||
| 693 | return $this; |
||
| 694 | } |
||
| 695 | |||
| 696 | /** |
||
| 697 | * Set a message to display on a validation error. |
||
| 698 | * |
||
| 699 | * @param string|array $msg String or Array of valid inputs to wfMessage() |
||
| 700 | * (so each entry can be either a String or Array) |
||
| 701 | * |
||
| 702 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 703 | */ |
||
| 704 | public function setValidationErrorMessage( $msg ) { |
||
| 705 | $this->mValidationErrorMessage = $msg; |
||
| 706 | |||
| 707 | return $this; |
||
| 708 | } |
||
| 709 | |||
| 710 | /** |
||
| 711 | * Set the introductory message, overwriting any existing message. |
||
| 712 | * |
||
| 713 | * @param string $msg Complete text of message to display |
||
| 714 | * |
||
| 715 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 716 | */ |
||
| 717 | public function setIntro( $msg ) { |
||
| 718 | $this->setPreText( $msg ); |
||
| 719 | |||
| 720 | return $this; |
||
| 721 | } |
||
| 722 | |||
| 723 | /** |
||
| 724 | * Set the introductory message HTML, overwriting any existing message. |
||
| 725 | * @since 1.19 |
||
| 726 | * |
||
| 727 | * @param string $msg Complete HTML of message to display |
||
| 728 | * |
||
| 729 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 730 | */ |
||
| 731 | public function setPreText( $msg ) { |
||
| 732 | $this->mPre = $msg; |
||
| 733 | |||
| 734 | return $this; |
||
| 735 | } |
||
| 736 | |||
| 737 | /** |
||
| 738 | * Add HTML to introductory message. |
||
| 739 | * |
||
| 740 | * @param string $msg Complete HTML of message to display |
||
| 741 | * |
||
| 742 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 743 | */ |
||
| 744 | public function addPreText( $msg ) { |
||
| 745 | $this->mPre .= $msg; |
||
| 746 | |||
| 747 | return $this; |
||
| 748 | } |
||
| 749 | |||
| 750 | /** |
||
| 751 | * Add HTML to the header, inside the form. |
||
| 752 | * |
||
| 753 | * @param string $msg Additional HTML to display in header |
||
| 754 | * @param string|null $section The section to add the header to |
||
| 755 | * |
||
| 756 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 757 | */ |
||
| 758 | View Code Duplication | public function addHeaderText( $msg, $section = null ) { |
|
| 759 | if ( $section === null ) { |
||
| 760 | $this->mHeader .= $msg; |
||
| 761 | } else { |
||
| 762 | if ( !isset( $this->mSectionHeaders[$section] ) ) { |
||
| 763 | $this->mSectionHeaders[$section] = ''; |
||
| 764 | } |
||
| 765 | $this->mSectionHeaders[$section] .= $msg; |
||
| 766 | } |
||
| 767 | |||
| 768 | return $this; |
||
| 769 | } |
||
| 770 | |||
| 771 | /** |
||
| 772 | * Set header text, inside the form. |
||
| 773 | * @since 1.19 |
||
| 774 | * |
||
| 775 | * @param string $msg Complete HTML of header to display |
||
| 776 | * @param string|null $section The section to add the header to |
||
| 777 | * |
||
| 778 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 779 | */ |
||
| 780 | public function setHeaderText( $msg, $section = null ) { |
||
| 781 | if ( $section === null ) { |
||
| 782 | $this->mHeader = $msg; |
||
| 783 | } else { |
||
| 784 | $this->mSectionHeaders[$section] = $msg; |
||
| 785 | } |
||
| 786 | |||
| 787 | return $this; |
||
| 788 | } |
||
| 789 | |||
| 790 | /** |
||
| 791 | * Get header text. |
||
| 792 | * |
||
| 793 | * @param string|null $section The section to get the header text for |
||
| 794 | * @since 1.26 |
||
| 795 | * @return string HTML |
||
| 796 | */ |
||
| 797 | public function getHeaderText( $section = null ) { |
||
| 798 | if ( $section === null ) { |
||
| 799 | return $this->mHeader; |
||
| 800 | } else { |
||
| 801 | return isset( $this->mSectionHeaders[$section] ) ? $this->mSectionHeaders[$section] : ''; |
||
| 802 | } |
||
| 803 | } |
||
| 804 | |||
| 805 | /** |
||
| 806 | * Add footer text, inside the form. |
||
| 807 | * |
||
| 808 | * @param string $msg Complete text of message to display |
||
| 809 | * @param string|null $section The section to add the footer text to |
||
| 810 | * |
||
| 811 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 812 | */ |
||
| 813 | View Code Duplication | public function addFooterText( $msg, $section = null ) { |
|
| 814 | if ( $section === null ) { |
||
| 815 | $this->mFooter .= $msg; |
||
| 816 | } else { |
||
| 817 | if ( !isset( $this->mSectionFooters[$section] ) ) { |
||
| 818 | $this->mSectionFooters[$section] = ''; |
||
| 819 | } |
||
| 820 | $this->mSectionFooters[$section] .= $msg; |
||
| 821 | } |
||
| 822 | |||
| 823 | return $this; |
||
| 824 | } |
||
| 825 | |||
| 826 | /** |
||
| 827 | * Set footer text, inside the form. |
||
| 828 | * @since 1.19 |
||
| 829 | * |
||
| 830 | * @param string $msg Complete text of message to display |
||
| 831 | * @param string|null $section The section to add the footer text to |
||
| 832 | * |
||
| 833 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 834 | */ |
||
| 835 | public function setFooterText( $msg, $section = null ) { |
||
| 836 | if ( $section === null ) { |
||
| 837 | $this->mFooter = $msg; |
||
| 838 | } else { |
||
| 839 | $this->mSectionFooters[$section] = $msg; |
||
| 840 | } |
||
| 841 | |||
| 842 | return $this; |
||
| 843 | } |
||
| 844 | |||
| 845 | /** |
||
| 846 | * Get footer text. |
||
| 847 | * |
||
| 848 | * @param string|null $section The section to get the footer text for |
||
| 849 | * @since 1.26 |
||
| 850 | * @return string |
||
| 851 | */ |
||
| 852 | public function getFooterText( $section = null ) { |
||
| 853 | if ( $section === null ) { |
||
| 854 | return $this->mFooter; |
||
| 855 | } else { |
||
| 856 | return isset( $this->mSectionFooters[$section] ) ? $this->mSectionFooters[$section] : ''; |
||
| 857 | } |
||
| 858 | } |
||
| 859 | |||
| 860 | /** |
||
| 861 | * Add text to the end of the display. |
||
| 862 | * |
||
| 863 | * @param string $msg Complete text of message to display |
||
| 864 | * |
||
| 865 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 866 | */ |
||
| 867 | public function addPostText( $msg ) { |
||
| 868 | $this->mPost .= $msg; |
||
| 869 | |||
| 870 | return $this; |
||
| 871 | } |
||
| 872 | |||
| 873 | /** |
||
| 874 | * Set text at the end of the display. |
||
| 875 | * |
||
| 876 | * @param string $msg Complete text of message to display |
||
| 877 | * |
||
| 878 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 879 | */ |
||
| 880 | public function setPostText( $msg ) { |
||
| 881 | $this->mPost = $msg; |
||
| 882 | |||
| 883 | return $this; |
||
| 884 | } |
||
| 885 | |||
| 886 | /** |
||
| 887 | * Add a hidden field to the output |
||
| 888 | * |
||
| 889 | * @param string $name Field name. This will be used exactly as entered |
||
| 890 | * @param string $value Field value |
||
| 891 | * @param array $attribs |
||
| 892 | * |
||
| 893 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 894 | */ |
||
| 895 | public function addHiddenField( $name, $value, array $attribs = [] ) { |
||
| 896 | $attribs += [ 'name' => $name ]; |
||
| 897 | $this->mHiddenFields[] = [ $value, $attribs ]; |
||
| 898 | |||
| 899 | return $this; |
||
| 900 | } |
||
| 901 | |||
| 902 | /** |
||
| 903 | * Add an array of hidden fields to the output |
||
| 904 | * |
||
| 905 | * @since 1.22 |
||
| 906 | * |
||
| 907 | * @param array $fields Associative array of fields to add; |
||
| 908 | * mapping names to their values |
||
| 909 | * |
||
| 910 | * @return HTMLForm $this for chaining calls |
||
| 911 | */ |
||
| 912 | public function addHiddenFields( array $fields ) { |
||
| 913 | foreach ( $fields as $name => $value ) { |
||
| 914 | $this->mHiddenFields[] = [ $value, [ 'name' => $name ] ]; |
||
| 915 | } |
||
| 916 | |||
| 917 | return $this; |
||
| 918 | } |
||
| 919 | |||
| 920 | /** |
||
| 921 | * Add a button to the form |
||
| 922 | * |
||
| 923 | * @since 1.27 takes an array as shown. Earlier versions accepted |
||
| 924 | * 'name', 'value', 'id', and 'attribs' as separate parameters in that |
||
| 925 | * order. |
||
| 926 | * @note Custom labels ('label', 'label-message', 'label-raw') are not |
||
| 927 | * supported for IE6 and IE7 due to bugs in those browsers. If detected, |
||
| 928 | * they will be served buttons using 'value' as the button label. |
||
| 929 | * @param array $data Data to define the button: |
||
| 930 | * - name: (string) Button name. |
||
| 931 | * - value: (string) Button value. |
||
| 932 | * - label-message: (string, optional) Button label message key to use |
||
| 933 | * instead of 'value'. Overrides 'label' and 'label-raw'. |
||
| 934 | * - label: (string, optional) Button label text to use instead of |
||
| 935 | * 'value'. Overrides 'label-raw'. |
||
| 936 | * - label-raw: (string, optional) Button label HTML to use instead of |
||
| 937 | * 'value'. |
||
| 938 | * - id: (string, optional) DOM id for the button. |
||
| 939 | * - attribs: (array, optional) Additional HTML attributes. |
||
| 940 | * - flags: (string|string[], optional) OOUI flags. |
||
| 941 | * - framed: (boolean=true, optional) OOUI framed attribute. |
||
| 942 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 943 | */ |
||
| 944 | public function addButton( $data ) { |
||
| 945 | if ( !is_array( $data ) ) { |
||
| 946 | $args = func_get_args(); |
||
| 947 | if ( count( $args ) < 2 || count( $args ) > 4 ) { |
||
| 948 | throw new InvalidArgumentException( |
||
| 949 | 'Incorrect number of arguments for deprecated calling style' |
||
| 950 | ); |
||
| 951 | } |
||
| 952 | $data = [ |
||
| 953 | 'name' => $args[0], |
||
| 954 | 'value' => $args[1], |
||
| 955 | 'id' => isset( $args[2] ) ? $args[2] : null, |
||
| 956 | 'attribs' => isset( $args[3] ) ? $args[3] : null, |
||
| 957 | ]; |
||
| 958 | } else { |
||
| 959 | if ( !isset( $data['name'] ) ) { |
||
| 960 | throw new InvalidArgumentException( 'A name is required' ); |
||
| 961 | } |
||
| 962 | if ( !isset( $data['value'] ) ) { |
||
| 963 | throw new InvalidArgumentException( 'A value is required' ); |
||
| 964 | } |
||
| 965 | } |
||
| 966 | $this->mButtons[] = $data + [ |
||
| 967 | 'id' => null, |
||
| 968 | 'attribs' => null, |
||
| 969 | 'flags' => null, |
||
| 970 | 'framed' => true, |
||
| 971 | ]; |
||
| 972 | |||
| 973 | return $this; |
||
| 974 | } |
||
| 975 | |||
| 976 | /** |
||
| 977 | * Set the salt for the edit token. |
||
| 978 | * |
||
| 979 | * Only useful when the method is "post". |
||
| 980 | * |
||
| 981 | * @since 1.24 |
||
| 982 | * @param string|array $salt Salt to use |
||
| 983 | * @return HTMLForm $this For chaining calls |
||
| 984 | */ |
||
| 985 | public function setTokenSalt( $salt ) { |
||
| 986 | $this->mTokenSalt = $salt; |
||
| 987 | |||
| 988 | return $this; |
||
| 989 | } |
||
| 990 | |||
| 991 | /** |
||
| 992 | * Display the form (sending to the context's OutputPage object), with an |
||
| 993 | * appropriate error message or stack of messages, and any validation errors, etc. |
||
| 994 | * |
||
| 995 | * @attention You should call prepareForm() before calling this function. |
||
| 996 | * Moreover, when doing method chaining this should be the very last method |
||
| 997 | * call just after prepareForm(). |
||
| 998 | * |
||
| 999 | * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit() |
||
| 1000 | * |
||
| 1001 | * @return void Nothing, should be last call |
||
| 1002 | */ |
||
| 1003 | public function displayForm( $submitResult ) { |
||
| 1004 | $this->getOutput()->addHTML( $this->getHTML( $submitResult ) ); |
||
| 1005 | } |
||
| 1006 | |||
| 1007 | /** |
||
| 1008 | * Returns the raw HTML generated by the form |
||
| 1009 | * |
||
| 1010 | * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit() |
||
| 1011 | * |
||
| 1012 | * @return string HTML |
||
| 1013 | */ |
||
| 1014 | public function getHTML( $submitResult ) { |
||
| 1015 | # For good measure (it is the default) |
||
| 1016 | $this->getOutput()->preventClickjacking(); |
||
| 1017 | $this->getOutput()->addModules( 'mediawiki.htmlform' ); |
||
| 1018 | $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' ); |
||
| 1019 | |||
| 1020 | $html = '' |
||
| 1021 | . $this->getErrorsOrWarnings( $submitResult, 'error' ) |
||
| 1022 | . $this->getErrorsOrWarnings( $submitResult, 'warning' ) |
||
| 1023 | . $this->getHeaderText() |
||
| 1024 | . $this->getBody() |
||
| 1025 | . $this->getHiddenFields() |
||
| 1026 | . $this->getButtons() |
||
| 1027 | . $this->getFooterText(); |
||
| 1028 | |||
| 1029 | $html = $this->wrapForm( $html ); |
||
| 1030 | |||
| 1031 | return '' . $this->mPre . $html . $this->mPost; |
||
| 1032 | } |
||
| 1033 | |||
| 1034 | /** |
||
| 1035 | * Get HTML attributes for the `<form>` tag. |
||
| 1036 | * @return array |
||
| 1037 | */ |
||
| 1038 | protected function getFormAttributes() { |
||
| 1039 | # Use multipart/form-data |
||
| 1040 | $encType = $this->mUseMultipart |
||
| 1041 | ? 'multipart/form-data' |
||
| 1042 | : 'application/x-www-form-urlencoded'; |
||
| 1043 | # Attributes |
||
| 1044 | $attribs = [ |
||
| 1045 | 'action' => $this->getAction(), |
||
| 1046 | 'method' => $this->getMethod(), |
||
| 1047 | 'enctype' => $encType, |
||
| 1048 | ]; |
||
| 1049 | if ( $this->mId ) { |
||
| 1050 | $attribs['id'] = $this->mId; |
||
| 1051 | } |
||
| 1052 | if ( $this->mAutocomplete ) { |
||
| 1053 | $attribs['autocomplete'] = $this->mAutocomplete; |
||
| 1054 | } |
||
| 1055 | if ( $this->mName ) { |
||
| 1056 | $attribs['name'] = $this->mName; |
||
| 1057 | } |
||
| 1058 | return $attribs; |
||
| 1059 | } |
||
| 1060 | |||
| 1061 | /** |
||
| 1062 | * Wrap the form innards in an actual "<form>" element |
||
| 1063 | * |
||
| 1064 | * @param string $html HTML contents to wrap. |
||
| 1065 | * |
||
| 1066 | * @return string Wrapped HTML. |
||
| 1067 | */ |
||
| 1068 | public function wrapForm( $html ) { |
||
| 1069 | # Include a <fieldset> wrapper for style, if requested. |
||
| 1070 | if ( $this->mWrapperLegend !== false ) { |
||
| 1071 | $legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false; |
||
| 1072 | $html = Xml::fieldset( $legend, $html ); |
||
| 1073 | } |
||
| 1074 | |||
| 1075 | return Html::rawElement( |
||
| 1076 | 'form', |
||
| 1077 | $this->getFormAttributes() + [ 'class' => 'visualClear' ], |
||
| 1078 | $html |
||
| 1079 | ); |
||
| 1080 | } |
||
| 1081 | |||
| 1082 | /** |
||
| 1083 | * Get the hidden fields that should go inside the form. |
||
| 1084 | * @return string HTML. |
||
| 1085 | */ |
||
| 1086 | public function getHiddenFields() { |
||
| 1087 | $html = ''; |
||
| 1088 | if ( $this->mFormIdentifier !== null ) { |
||
| 1089 | $html .= Html::hidden( |
||
| 1090 | 'wpFormIdentifier', |
||
| 1091 | $this->mFormIdentifier |
||
| 1092 | ) . "\n"; |
||
| 1093 | } |
||
| 1094 | if ( $this->getMethod() === 'post' ) { |
||
| 1095 | $html .= Html::hidden( |
||
| 1096 | 'wpEditToken', |
||
| 1097 | $this->getUser()->getEditToken( $this->mTokenSalt ), |
||
| 1098 | [ 'id' => 'wpEditToken' ] |
||
| 1099 | ) . "\n"; |
||
| 1100 | $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; |
||
| 1101 | } |
||
| 1102 | |||
| 1103 | $articlePath = $this->getConfig()->get( 'ArticlePath' ); |
||
| 1104 | if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) { |
||
| 1105 | $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; |
||
| 1106 | } |
||
| 1107 | |||
| 1108 | foreach ( $this->mHiddenFields as $data ) { |
||
| 1109 | list( $value, $attribs ) = $data; |
||
| 1110 | $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n"; |
||
| 1111 | } |
||
| 1112 | |||
| 1113 | return $html; |
||
| 1114 | } |
||
| 1115 | |||
| 1116 | /** |
||
| 1117 | * Get the submit and (potentially) reset buttons. |
||
| 1118 | * @return string HTML. |
||
| 1119 | */ |
||
| 1120 | public function getButtons() { |
||
| 1121 | $buttons = ''; |
||
| 1122 | $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ); |
||
| 1123 | |||
| 1124 | if ( $this->mShowSubmit ) { |
||
| 1125 | $attribs = []; |
||
| 1126 | |||
| 1127 | if ( isset( $this->mSubmitID ) ) { |
||
| 1128 | $attribs['id'] = $this->mSubmitID; |
||
| 1129 | } |
||
| 1130 | |||
| 1131 | if ( isset( $this->mSubmitName ) ) { |
||
| 1132 | $attribs['name'] = $this->mSubmitName; |
||
| 1133 | } |
||
| 1134 | |||
| 1135 | if ( isset( $this->mSubmitTooltip ) ) { |
||
| 1136 | $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip ); |
||
| 1137 | } |
||
| 1138 | |||
| 1139 | $attribs['class'] = [ 'mw-htmlform-submit' ]; |
||
| 1140 | |||
| 1141 | if ( $useMediaWikiUIEverywhere ) { |
||
| 1142 | foreach ( $this->mSubmitFlags as $flag ) { |
||
| 1143 | $attribs['class'][] = 'mw-ui-' . $flag; |
||
| 1144 | } |
||
| 1145 | $attribs['class'][] = 'mw-ui-button'; |
||
| 1146 | } |
||
| 1147 | |||
| 1148 | $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; |
||
| 1149 | } |
||
| 1150 | |||
| 1151 | View Code Duplication | if ( $this->mShowReset ) { |
|
| 1152 | $buttons .= Html::element( |
||
| 1153 | 'input', |
||
| 1154 | [ |
||
| 1155 | 'type' => 'reset', |
||
| 1156 | 'value' => $this->msg( 'htmlform-reset' )->text(), |
||
| 1157 | 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null, |
||
| 1158 | ] |
||
| 1159 | ) . "\n"; |
||
| 1160 | } |
||
| 1161 | |||
| 1162 | View Code Duplication | if ( $this->mShowCancel ) { |
|
| 1163 | $target = $this->mCancelTarget ?: Title::newMainPage(); |
||
| 1164 | if ( $target instanceof Title ) { |
||
| 1165 | $target = $target->getLocalURL(); |
||
| 1166 | } |
||
| 1167 | $buttons .= Html::element( |
||
| 1168 | 'a', |
||
| 1169 | [ |
||
| 1170 | 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null, |
||
| 1171 | 'href' => $target, |
||
| 1172 | ], |
||
| 1173 | $this->msg( 'cancel' )->text() |
||
| 1174 | ) . "\n"; |
||
| 1175 | } |
||
| 1176 | |||
| 1177 | // IE<8 has bugs with <button>, so we'll need to avoid them. |
||
| 1178 | $isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) ); |
||
| 1179 | |||
| 1180 | foreach ( $this->mButtons as $button ) { |
||
| 1181 | $attrs = [ |
||
| 1182 | 'type' => 'submit', |
||
| 1183 | 'name' => $button['name'], |
||
| 1184 | 'value' => $button['value'] |
||
| 1185 | ]; |
||
| 1186 | |||
| 1187 | if ( isset( $button['label-message'] ) ) { |
||
| 1188 | $label = $this->getMessage( $button['label-message'] )->parse(); |
||
| 1189 | } elseif ( isset( $button['label'] ) ) { |
||
| 1190 | $label = htmlspecialchars( $button['label'] ); |
||
| 1191 | } elseif ( isset( $button['label-raw'] ) ) { |
||
| 1192 | $label = $button['label-raw']; |
||
| 1193 | } else { |
||
| 1194 | $label = htmlspecialchars( $button['value'] ); |
||
| 1195 | } |
||
| 1196 | |||
| 1197 | if ( $button['attribs'] ) { |
||
| 1198 | $attrs += $button['attribs']; |
||
| 1199 | } |
||
| 1200 | |||
| 1201 | if ( isset( $button['id'] ) ) { |
||
| 1202 | $attrs['id'] = $button['id']; |
||
| 1203 | } |
||
| 1204 | |||
| 1205 | if ( $useMediaWikiUIEverywhere ) { |
||
| 1206 | $attrs['class'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : []; |
||
| 1207 | $attrs['class'][] = 'mw-ui-button'; |
||
| 1208 | } |
||
| 1209 | |||
| 1210 | if ( $isBadIE ) { |
||
| 1211 | $buttons .= Html::element( 'input', $attrs ) . "\n"; |
||
| 1212 | } else { |
||
| 1213 | $buttons .= Html::rawElement( 'button', $attrs, $label ) . "\n"; |
||
| 1214 | } |
||
| 1215 | } |
||
| 1216 | |||
| 1217 | if ( !$buttons ) { |
||
| 1218 | return ''; |
||
| 1219 | } |
||
| 1220 | |||
| 1221 | return Html::rawElement( 'span', |
||
| 1222 | [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n"; |
||
| 1223 | } |
||
| 1224 | |||
| 1225 | /** |
||
| 1226 | * Get the whole body of the form. |
||
| 1227 | * @return string |
||
| 1228 | */ |
||
| 1229 | public function getBody() { |
||
| 1230 | return $this->displaySection( $this->mFieldTree, $this->mTableId ); |
||
| 1231 | } |
||
| 1232 | |||
| 1233 | /** |
||
| 1234 | * Format and display an error message stack. |
||
| 1235 | * |
||
| 1236 | * @param string|array|Status $errors |
||
| 1237 | * |
||
| 1238 | * @deprecated since 1.28, use getErrorsOrWarnings() instead |
||
| 1239 | * |
||
| 1240 | * @return string |
||
| 1241 | */ |
||
| 1242 | public function getErrors( $errors ) { |
||
| 1243 | wfDeprecated( __METHOD__ ); |
||
| 1244 | return $this->getErrorsOrWarnings( $errors, 'error' ); |
||
| 1245 | } |
||
| 1246 | |||
| 1247 | /** |
||
| 1248 | * Returns a formatted list of errors or warnings from the given elements. |
||
| 1249 | * |
||
| 1250 | * @param string|array|Status $elements The set of errors/warnings to process. |
||
| 1251 | * @param string $elementsType Should warnings or errors be returned. This is meant |
||
| 1252 | * for Status objects, all other valid types are always considered as errors. |
||
| 1253 | * @return string |
||
| 1254 | */ |
||
| 1255 | public function getErrorsOrWarnings( $elements, $elementsType ) { |
||
| 1256 | View Code Duplication | if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) { |
|
| 1257 | throw new DomainException( $elementsType . ' is not a valid type.' ); |
||
| 1258 | } |
||
| 1259 | $elementstr = false; |
||
| 1260 | if ( $elements instanceof Status ) { |
||
| 1261 | list( $errorStatus, $warningStatus ) = $elements->splitByErrorType(); |
||
| 1262 | $status = $elementsType === 'error' ? $errorStatus : $warningStatus; |
||
| 1263 | if ( $status->isGood() ) { |
||
| 1264 | $elementstr = ''; |
||
| 1265 | } else { |
||
| 1266 | $elementstr = $this->getOutput()->parse( |
||
| 1267 | $status->getWikiText() |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
StatusValue as the method getWikiText() does only exist in the following sub-classes of StatusValue: FileRepoStatus, Status. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 1268 | ); |
||
| 1269 | } |
||
| 1270 | } elseif ( is_array( $elements ) && $elementsType === 'error' ) { |
||
| 1271 | $elementstr = $this->formatErrors( $elements ); |
||
| 1272 | } elseif ( $elementsType === 'error' ) { |
||
| 1273 | $elementstr = $elements; |
||
| 1274 | } |
||
| 1275 | |||
| 1276 | return $elementstr |
||
| 1277 | ? Html::rawElement( 'div', [ 'class' => $elementsType ], $elementstr ) |
||
|
0 ignored issues
–
show
It seems like
$elementstr can also be of type array; however, Html::rawElement() does only seem to accept string, maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. Loading history...
|
|||
| 1278 | : ''; |
||
| 1279 | } |
||
| 1280 | |||
| 1281 | /** |
||
| 1282 | * Format a stack of error messages into a single HTML string |
||
| 1283 | * |
||
| 1284 | * @param array $errors Array of message keys/values |
||
| 1285 | * |
||
| 1286 | * @return string HTML, a "<ul>" list of errors |
||
| 1287 | */ |
||
| 1288 | public function formatErrors( $errors ) { |
||
| 1289 | $errorstr = ''; |
||
| 1290 | |||
| 1291 | foreach ( $errors as $error ) { |
||
| 1292 | $errorstr .= Html::rawElement( |
||
| 1293 | 'li', |
||
| 1294 | [], |
||
| 1295 | $this->getMessage( $error )->parse() |
||
| 1296 | ); |
||
| 1297 | } |
||
| 1298 | |||
| 1299 | $errorstr = Html::rawElement( 'ul', [], $errorstr ); |
||
| 1300 | |||
| 1301 | return $errorstr; |
||
| 1302 | } |
||
| 1303 | |||
| 1304 | /** |
||
| 1305 | * Set the text for the submit button |
||
| 1306 | * |
||
| 1307 | * @param string $t Plaintext |
||
| 1308 | * |
||
| 1309 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1310 | */ |
||
| 1311 | public function setSubmitText( $t ) { |
||
| 1312 | $this->mSubmitText = $t; |
||
| 1313 | |||
| 1314 | return $this; |
||
| 1315 | } |
||
| 1316 | |||
| 1317 | /** |
||
| 1318 | * Identify that the submit button in the form has a destructive action |
||
| 1319 | * @since 1.24 |
||
| 1320 | * |
||
| 1321 | * @return HTMLForm $this for chaining calls (since 1.28) |
||
| 1322 | */ |
||
| 1323 | public function setSubmitDestructive() { |
||
| 1324 | $this->mSubmitFlags = [ 'destructive', 'primary' ]; |
||
| 1325 | |||
| 1326 | return $this; |
||
| 1327 | } |
||
| 1328 | |||
| 1329 | /** |
||
| 1330 | * Identify that the submit button in the form has a progressive action |
||
| 1331 | * @since 1.25 |
||
| 1332 | * |
||
| 1333 | * @return HTMLForm $this for chaining calls (since 1.28) |
||
| 1334 | */ |
||
| 1335 | public function setSubmitProgressive() { |
||
| 1336 | $this->mSubmitFlags = [ 'progressive', 'primary' ]; |
||
| 1337 | |||
| 1338 | return $this; |
||
| 1339 | } |
||
| 1340 | |||
| 1341 | /** |
||
| 1342 | * Set the text for the submit button to a message |
||
| 1343 | * @since 1.19 |
||
| 1344 | * |
||
| 1345 | * @param string|Message $msg Message key or Message object |
||
| 1346 | * |
||
| 1347 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1348 | */ |
||
| 1349 | public function setSubmitTextMsg( $msg ) { |
||
| 1350 | if ( !$msg instanceof Message ) { |
||
| 1351 | $msg = $this->msg( $msg ); |
||
| 1352 | } |
||
| 1353 | $this->setSubmitText( $msg->text() ); |
||
| 1354 | |||
| 1355 | return $this; |
||
| 1356 | } |
||
| 1357 | |||
| 1358 | /** |
||
| 1359 | * Get the text for the submit button, either customised or a default. |
||
| 1360 | * @return string |
||
| 1361 | */ |
||
| 1362 | public function getSubmitText() { |
||
| 1363 | return $this->mSubmitText ?: $this->msg( 'htmlform-submit' )->text(); |
||
| 1364 | } |
||
| 1365 | |||
| 1366 | /** |
||
| 1367 | * @param string $name Submit button name |
||
| 1368 | * |
||
| 1369 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1370 | */ |
||
| 1371 | public function setSubmitName( $name ) { |
||
| 1372 | $this->mSubmitName = $name; |
||
| 1373 | |||
| 1374 | return $this; |
||
| 1375 | } |
||
| 1376 | |||
| 1377 | /** |
||
| 1378 | * @param string $name Tooltip for the submit button |
||
| 1379 | * |
||
| 1380 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1381 | */ |
||
| 1382 | public function setSubmitTooltip( $name ) { |
||
| 1383 | $this->mSubmitTooltip = $name; |
||
| 1384 | |||
| 1385 | return $this; |
||
| 1386 | } |
||
| 1387 | |||
| 1388 | /** |
||
| 1389 | * Set the id for the submit button. |
||
| 1390 | * |
||
| 1391 | * @param string $t |
||
| 1392 | * |
||
| 1393 | * @todo FIXME: Integrity of $t is *not* validated |
||
| 1394 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1395 | */ |
||
| 1396 | public function setSubmitID( $t ) { |
||
| 1397 | $this->mSubmitID = $t; |
||
| 1398 | |||
| 1399 | return $this; |
||
| 1400 | } |
||
| 1401 | |||
| 1402 | /** |
||
| 1403 | * Set an internal identifier for this form. It will be submitted as a hidden form field, allowing |
||
| 1404 | * HTMLForm to determine whether the form was submitted (or merely viewed). Setting this serves |
||
| 1405 | * two purposes: |
||
| 1406 | * |
||
| 1407 | * - If you use two or more forms on one page, it allows HTMLForm to identify which of the forms |
||
| 1408 | * was submitted, and not attempt to validate the other ones. |
||
| 1409 | * - If you use checkbox or multiselect fields inside a form using the GET method, it allows |
||
| 1410 | * HTMLForm to distinguish between the initial page view and a form submission with all |
||
| 1411 | * checkboxes or select options unchecked. |
||
| 1412 | * |
||
| 1413 | * @since 1.28 |
||
| 1414 | * @param string $ident |
||
| 1415 | * @return $this |
||
| 1416 | */ |
||
| 1417 | public function setFormIdentifier( $ident ) { |
||
| 1418 | $this->mFormIdentifier = $ident; |
||
| 1419 | |||
| 1420 | return $this; |
||
| 1421 | } |
||
| 1422 | |||
| 1423 | /** |
||
| 1424 | * Stop a default submit button being shown for this form. This implies that an |
||
| 1425 | * alternate submit method must be provided manually. |
||
| 1426 | * |
||
| 1427 | * @since 1.22 |
||
| 1428 | * |
||
| 1429 | * @param bool $suppressSubmit Set to false to re-enable the button again |
||
| 1430 | * |
||
| 1431 | * @return HTMLForm $this for chaining calls |
||
| 1432 | */ |
||
| 1433 | public function suppressDefaultSubmit( $suppressSubmit = true ) { |
||
| 1434 | $this->mShowSubmit = !$suppressSubmit; |
||
| 1435 | |||
| 1436 | return $this; |
||
| 1437 | } |
||
| 1438 | |||
| 1439 | /** |
||
| 1440 | * Show a cancel button (or prevent it). The button is not shown by default. |
||
| 1441 | * @param bool $show |
||
| 1442 | * @return HTMLForm $this for chaining calls |
||
| 1443 | * @since 1.27 |
||
| 1444 | */ |
||
| 1445 | public function showCancel( $show = true ) { |
||
| 1446 | $this->mShowCancel = $show; |
||
| 1447 | return $this; |
||
| 1448 | } |
||
| 1449 | |||
| 1450 | /** |
||
| 1451 | * Sets the target where the user is redirected to after clicking cancel. |
||
| 1452 | * @param Title|string $target Target as a Title object or an URL |
||
| 1453 | * @return HTMLForm $this for chaining calls |
||
| 1454 | * @since 1.27 |
||
| 1455 | */ |
||
| 1456 | public function setCancelTarget( $target ) { |
||
| 1457 | $this->mCancelTarget = $target; |
||
| 1458 | return $this; |
||
| 1459 | } |
||
| 1460 | |||
| 1461 | /** |
||
| 1462 | * Set the id of the \<table\> or outermost \<div\> element. |
||
| 1463 | * |
||
| 1464 | * @since 1.22 |
||
| 1465 | * |
||
| 1466 | * @param string $id New value of the id attribute, or "" to remove |
||
| 1467 | * |
||
| 1468 | * @return HTMLForm $this for chaining calls |
||
| 1469 | */ |
||
| 1470 | public function setTableId( $id ) { |
||
| 1471 | $this->mTableId = $id; |
||
| 1472 | |||
| 1473 | return $this; |
||
| 1474 | } |
||
| 1475 | |||
| 1476 | /** |
||
| 1477 | * @param string $id DOM id for the form |
||
| 1478 | * |
||
| 1479 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1480 | */ |
||
| 1481 | public function setId( $id ) { |
||
| 1482 | $this->mId = $id; |
||
| 1483 | |||
| 1484 | return $this; |
||
| 1485 | } |
||
| 1486 | |||
| 1487 | /** |
||
| 1488 | * @param string $name 'name' attribute for the form |
||
| 1489 | * @return HTMLForm $this for chaining calls |
||
| 1490 | */ |
||
| 1491 | public function setName( $name ) { |
||
| 1492 | $this->mName = $name; |
||
| 1493 | |||
| 1494 | return $this; |
||
| 1495 | } |
||
| 1496 | |||
| 1497 | /** |
||
| 1498 | * Prompt the whole form to be wrapped in a "<fieldset>", with |
||
| 1499 | * this text as its "<legend>" element. |
||
| 1500 | * |
||
| 1501 | * @param string|bool $legend If false, no wrapper or legend will be displayed. |
||
| 1502 | * If true, a wrapper will be displayed, but no legend. |
||
| 1503 | * If a string, a wrapper will be displayed with that string as a legend. |
||
| 1504 | * The string will be escaped before being output (this doesn't support HTML). |
||
| 1505 | * |
||
| 1506 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1507 | */ |
||
| 1508 | public function setWrapperLegend( $legend ) { |
||
| 1509 | $this->mWrapperLegend = $legend; |
||
|
0 ignored issues
–
show
It seems like
$legend can also be of type string. However, the property $mWrapperLegend is declared as type boolean. Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
| 1510 | |||
| 1511 | return $this; |
||
| 1512 | } |
||
| 1513 | |||
| 1514 | /** |
||
| 1515 | * Prompt the whole form to be wrapped in a "<fieldset>", with |
||
| 1516 | * this message as its "<legend>" element. |
||
| 1517 | * @since 1.19 |
||
| 1518 | * |
||
| 1519 | * @param string|Message $msg Message key or Message object |
||
| 1520 | * |
||
| 1521 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1522 | */ |
||
| 1523 | public function setWrapperLegendMsg( $msg ) { |
||
| 1524 | if ( !$msg instanceof Message ) { |
||
| 1525 | $msg = $this->msg( $msg ); |
||
| 1526 | } |
||
| 1527 | $this->setWrapperLegend( $msg->text() ); |
||
| 1528 | |||
| 1529 | return $this; |
||
| 1530 | } |
||
| 1531 | |||
| 1532 | /** |
||
| 1533 | * Set the prefix for various default messages |
||
| 1534 | * @todo Currently only used for the "<fieldset>" legend on forms |
||
| 1535 | * with multiple sections; should be used elsewhere? |
||
| 1536 | * |
||
| 1537 | * @param string $p |
||
| 1538 | * |
||
| 1539 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1540 | */ |
||
| 1541 | public function setMessagePrefix( $p ) { |
||
| 1542 | $this->mMessagePrefix = $p; |
||
| 1543 | |||
| 1544 | return $this; |
||
| 1545 | } |
||
| 1546 | |||
| 1547 | /** |
||
| 1548 | * Set the title for form submission |
||
| 1549 | * |
||
| 1550 | * @param Title $t Title of page the form is on/should be posted to |
||
| 1551 | * |
||
| 1552 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1553 | */ |
||
| 1554 | public function setTitle( $t ) { |
||
| 1555 | $this->mTitle = $t; |
||
| 1556 | |||
| 1557 | return $this; |
||
| 1558 | } |
||
| 1559 | |||
| 1560 | /** |
||
| 1561 | * Get the title |
||
| 1562 | * @return Title |
||
| 1563 | */ |
||
| 1564 | public function getTitle() { |
||
| 1565 | return $this->mTitle === false |
||
| 1566 | ? $this->getContext()->getTitle() |
||
| 1567 | : $this->mTitle; |
||
| 1568 | } |
||
| 1569 | |||
| 1570 | /** |
||
| 1571 | * Set the method used to submit the form |
||
| 1572 | * |
||
| 1573 | * @param string $method |
||
| 1574 | * |
||
| 1575 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1576 | */ |
||
| 1577 | public function setMethod( $method = 'post' ) { |
||
| 1578 | $this->mMethod = strtolower( $method ); |
||
| 1579 | |||
| 1580 | return $this; |
||
| 1581 | } |
||
| 1582 | |||
| 1583 | /** |
||
| 1584 | * @return string Always lowercase |
||
| 1585 | */ |
||
| 1586 | public function getMethod() { |
||
| 1587 | return $this->mMethod; |
||
| 1588 | } |
||
| 1589 | |||
| 1590 | /** |
||
| 1591 | * Wraps the given $section into an user-visible fieldset. |
||
| 1592 | * |
||
| 1593 | * @param string $legend Legend text for the fieldset |
||
| 1594 | * @param string $section The section content in plain Html |
||
| 1595 | * @param array $attributes Additional attributes for the fieldset |
||
| 1596 | * @return string The fieldset's Html |
||
| 1597 | */ |
||
| 1598 | protected function wrapFieldSetSection( $legend, $section, $attributes ) { |
||
| 1599 | return Xml::fieldset( $legend, $section, $attributes ) . "\n"; |
||
| 1600 | } |
||
| 1601 | |||
| 1602 | /** |
||
| 1603 | * @todo Document |
||
| 1604 | * |
||
| 1605 | * @param array[]|HTMLFormField[] $fields Array of fields (either arrays or |
||
| 1606 | * objects). |
||
| 1607 | * @param string $sectionName ID attribute of the "<table>" tag for this |
||
| 1608 | * section, ignored if empty. |
||
| 1609 | * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of |
||
| 1610 | * each subsection, ignored if empty. |
||
| 1611 | * @param bool &$hasUserVisibleFields Whether the section had user-visible fields. |
||
| 1612 | * @throws LogicException When called on uninitialized field data, e.g. When |
||
| 1613 | * HTMLForm::displayForm was called without calling HTMLForm::prepareForm |
||
| 1614 | * first. |
||
| 1615 | * |
||
| 1616 | * @return string |
||
| 1617 | */ |
||
| 1618 | public function displaySection( $fields, |
||
| 1619 | $sectionName = '', |
||
| 1620 | $fieldsetIDPrefix = '', |
||
| 1621 | &$hasUserVisibleFields = false |
||
| 1622 | ) { |
||
| 1623 | if ( $this->mFieldData === null ) { |
||
| 1624 | throw new LogicException( 'HTMLForm::displaySection() called on uninitialized field data. ' |
||
| 1625 | . 'You probably called displayForm() without calling prepareForm() first.' ); |
||
| 1626 | } |
||
| 1627 | |||
| 1628 | $displayFormat = $this->getDisplayFormat(); |
||
| 1629 | |||
| 1630 | $html = []; |
||
| 1631 | $subsectionHtml = ''; |
||
| 1632 | $hasLabel = false; |
||
| 1633 | |||
| 1634 | // Conveniently, PHP method names are case-insensitive. |
||
| 1635 | // For grep: this can call getDiv, getRaw, getInline, getVForm, getOOUI |
||
| 1636 | $getFieldHtmlMethod = $displayFormat === 'table' ? 'getTableRow' : ( 'get' . $displayFormat ); |
||
| 1637 | |||
| 1638 | foreach ( $fields as $key => $value ) { |
||
| 1639 | if ( $value instanceof HTMLFormField ) { |
||
| 1640 | $v = array_key_exists( $key, $this->mFieldData ) |
||
| 1641 | ? $this->mFieldData[$key] |
||
| 1642 | : $value->getDefault(); |
||
| 1643 | |||
| 1644 | $retval = $value->$getFieldHtmlMethod( $v ); |
||
| 1645 | |||
| 1646 | // check, if the form field should be added to |
||
| 1647 | // the output. |
||
| 1648 | if ( $value->hasVisibleOutput() ) { |
||
| 1649 | $html[] = $retval; |
||
| 1650 | |||
| 1651 | $labelValue = trim( $value->getLabel() ); |
||
| 1652 | if ( $labelValue !== ' ' && $labelValue !== '' ) { |
||
| 1653 | $hasLabel = true; |
||
| 1654 | } |
||
| 1655 | |||
| 1656 | $hasUserVisibleFields = true; |
||
| 1657 | } |
||
| 1658 | } elseif ( is_array( $value ) ) { |
||
| 1659 | $subsectionHasVisibleFields = false; |
||
| 1660 | $section = |
||
| 1661 | $this->displaySection( $value, |
||
| 1662 | "mw-htmlform-$key", |
||
| 1663 | "$fieldsetIDPrefix$key-", |
||
| 1664 | $subsectionHasVisibleFields ); |
||
| 1665 | $legend = null; |
||
|
0 ignored issues
–
show
$legend is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the Loading history...
|
|||
| 1666 | |||
| 1667 | if ( $subsectionHasVisibleFields === true ) { |
||
| 1668 | // Display the section with various niceties. |
||
| 1669 | $hasUserVisibleFields = true; |
||
| 1670 | |||
| 1671 | $legend = $this->getLegend( $key ); |
||
| 1672 | |||
| 1673 | $section = $this->getHeaderText( $key ) . |
||
| 1674 | $section . |
||
| 1675 | $this->getFooterText( $key ); |
||
| 1676 | |||
| 1677 | $attributes = []; |
||
| 1678 | if ( $fieldsetIDPrefix ) { |
||
| 1679 | $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" ); |
||
| 1680 | } |
||
| 1681 | $subsectionHtml .= $this->wrapFieldSetSection( $legend, $section, $attributes ); |
||
| 1682 | } else { |
||
| 1683 | // Just return the inputs, nothing fancy. |
||
| 1684 | $subsectionHtml .= $section; |
||
| 1685 | } |
||
| 1686 | } |
||
| 1687 | } |
||
| 1688 | |||
| 1689 | $html = $this->formatSection( $html, $sectionName, $hasLabel ); |
||
| 1690 | |||
| 1691 | if ( $subsectionHtml ) { |
||
| 1692 | if ( $this->mSubSectionBeforeFields ) { |
||
| 1693 | return $subsectionHtml . "\n" . $html; |
||
| 1694 | } else { |
||
| 1695 | return $html . "\n" . $subsectionHtml; |
||
| 1696 | } |
||
| 1697 | } else { |
||
| 1698 | return $html; |
||
| 1699 | } |
||
| 1700 | } |
||
| 1701 | |||
| 1702 | /** |
||
| 1703 | * Put a form section together from the individual fields' HTML, merging it and wrapping. |
||
| 1704 | * @param array $fieldsHtml |
||
| 1705 | * @param string $sectionName |
||
| 1706 | * @param bool $anyFieldHasLabel |
||
| 1707 | * @return string HTML |
||
| 1708 | */ |
||
| 1709 | protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) { |
||
| 1710 | $displayFormat = $this->getDisplayFormat(); |
||
| 1711 | $html = implode( '', $fieldsHtml ); |
||
| 1712 | |||
| 1713 | if ( $displayFormat === 'raw' ) { |
||
| 1714 | return $html; |
||
| 1715 | } |
||
| 1716 | |||
| 1717 | $classes = []; |
||
| 1718 | |||
| 1719 | if ( !$anyFieldHasLabel ) { // Avoid strange spacing when no labels exist |
||
| 1720 | $classes[] = 'mw-htmlform-nolabel'; |
||
| 1721 | } |
||
| 1722 | |||
| 1723 | $attribs = [ |
||
| 1724 | 'class' => implode( ' ', $classes ), |
||
| 1725 | ]; |
||
| 1726 | |||
| 1727 | if ( $sectionName ) { |
||
| 1728 | $attribs['id'] = Sanitizer::escapeId( $sectionName ); |
||
| 1729 | } |
||
| 1730 | |||
| 1731 | if ( $displayFormat === 'table' ) { |
||
| 1732 | return Html::rawElement( 'table', |
||
| 1733 | $attribs, |
||
| 1734 | Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n"; |
||
| 1735 | } elseif ( $displayFormat === 'inline' ) { |
||
| 1736 | return Html::rawElement( 'span', $attribs, "\n$html\n" ); |
||
| 1737 | } else { |
||
| 1738 | return Html::rawElement( 'div', $attribs, "\n$html\n" ); |
||
| 1739 | } |
||
| 1740 | } |
||
| 1741 | |||
| 1742 | /** |
||
| 1743 | * Construct the form fields from the Descriptor array |
||
| 1744 | */ |
||
| 1745 | public function loadData() { |
||
| 1746 | $fieldData = []; |
||
| 1747 | |||
| 1748 | View Code Duplication | foreach ( $this->mFlatFields as $fieldname => $field ) { |
|
| 1749 | $request = $this->getRequest(); |
||
| 1750 | if ( $field->skipLoadData( $request ) ) { |
||
| 1751 | continue; |
||
| 1752 | } elseif ( !empty( $field->mParams['disabled'] ) ) { |
||
| 1753 | $fieldData[$fieldname] = $field->getDefault(); |
||
| 1754 | } else { |
||
| 1755 | $fieldData[$fieldname] = $field->loadDataFromRequest( $request ); |
||
| 1756 | } |
||
| 1757 | } |
||
| 1758 | |||
| 1759 | # Filter data. |
||
| 1760 | foreach ( $fieldData as $name => &$value ) { |
||
| 1761 | $field = $this->mFlatFields[$name]; |
||
| 1762 | $value = $field->filter( $value, $this->mFlatFields ); |
||
| 1763 | } |
||
| 1764 | |||
| 1765 | $this->mFieldData = $fieldData; |
||
| 1766 | } |
||
| 1767 | |||
| 1768 | /** |
||
| 1769 | * Stop a reset button being shown for this form |
||
| 1770 | * |
||
| 1771 | * @param bool $suppressReset Set to false to re-enable the button again |
||
| 1772 | * |
||
| 1773 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1774 | */ |
||
| 1775 | public function suppressReset( $suppressReset = true ) { |
||
| 1776 | $this->mShowReset = !$suppressReset; |
||
| 1777 | |||
| 1778 | return $this; |
||
| 1779 | } |
||
| 1780 | |||
| 1781 | /** |
||
| 1782 | * Overload this if you want to apply special filtration routines |
||
| 1783 | * to the form as a whole, after it's submitted but before it's |
||
| 1784 | * processed. |
||
| 1785 | * |
||
| 1786 | * @param array $data |
||
| 1787 | * |
||
| 1788 | * @return array |
||
| 1789 | */ |
||
| 1790 | public function filterDataForSubmit( $data ) { |
||
| 1791 | return $data; |
||
| 1792 | } |
||
| 1793 | |||
| 1794 | /** |
||
| 1795 | * Get a string to go in the "<legend>" of a section fieldset. |
||
| 1796 | * Override this if you want something more complicated. |
||
| 1797 | * |
||
| 1798 | * @param string $key |
||
| 1799 | * |
||
| 1800 | * @return string |
||
| 1801 | */ |
||
| 1802 | public function getLegend( $key ) { |
||
| 1803 | return $this->msg( "{$this->mMessagePrefix}-$key" )->text(); |
||
| 1804 | } |
||
| 1805 | |||
| 1806 | /** |
||
| 1807 | * Set the value for the action attribute of the form. |
||
| 1808 | * When set to false (which is the default state), the set title is used. |
||
| 1809 | * |
||
| 1810 | * @since 1.19 |
||
| 1811 | * |
||
| 1812 | * @param string|bool $action |
||
| 1813 | * |
||
| 1814 | * @return HTMLForm $this for chaining calls (since 1.20) |
||
| 1815 | */ |
||
| 1816 | public function setAction( $action ) { |
||
| 1817 | $this->mAction = $action; |
||
| 1818 | |||
| 1819 | return $this; |
||
| 1820 | } |
||
| 1821 | |||
| 1822 | /** |
||
| 1823 | * Get the value for the action attribute of the form. |
||
| 1824 | * |
||
| 1825 | * @since 1.22 |
||
| 1826 | * |
||
| 1827 | * @return string |
||
| 1828 | */ |
||
| 1829 | public function getAction() { |
||
| 1830 | // If an action is alredy provided, return it |
||
| 1831 | if ( $this->mAction !== false ) { |
||
| 1832 | return $this->mAction; |
||
| 1833 | } |
||
| 1834 | |||
| 1835 | $articlePath = $this->getConfig()->get( 'ArticlePath' ); |
||
| 1836 | // Check whether we are in GET mode and the ArticlePath contains a "?" |
||
| 1837 | // meaning that getLocalURL() would return something like "index.php?title=...". |
||
| 1838 | // As browser remove the query string before submitting GET forms, |
||
| 1839 | // it means that the title would be lost. In such case use wfScript() instead |
||
| 1840 | // and put title in an hidden field (see getHiddenFields()). |
||
| 1841 | if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) { |
||
| 1842 | return wfScript(); |
||
| 1843 | } |
||
| 1844 | |||
| 1845 | return $this->getTitle()->getLocalURL(); |
||
| 1846 | } |
||
| 1847 | |||
| 1848 | /** |
||
| 1849 | * Set the value for the autocomplete attribute of the form. |
||
| 1850 | * When set to false (which is the default state), the attribute get not set. |
||
| 1851 | * |
||
| 1852 | * @since 1.27 |
||
| 1853 | * |
||
| 1854 | * @param string|bool $autocomplete |
||
| 1855 | * |
||
| 1856 | * @return HTMLForm $this for chaining calls |
||
| 1857 | */ |
||
| 1858 | public function setAutocomplete( $autocomplete ) { |
||
| 1859 | $this->mAutocomplete = $autocomplete; |
||
| 1860 | |||
| 1861 | return $this; |
||
| 1862 | } |
||
| 1863 | |||
| 1864 | /** |
||
| 1865 | * Turns a *-message parameter (which could be a MessageSpecifier, or a message name, or a |
||
| 1866 | * name + parameters array) into a Message. |
||
| 1867 | * @param mixed $value |
||
| 1868 | * @return Message |
||
| 1869 | */ |
||
| 1870 | protected function getMessage( $value ) { |
||
| 1871 | return Message::newFromSpecifier( $value )->setContext( $this ); |
||
| 1872 | } |
||
| 1873 | } |
||
| 1874 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italyis not defined by the methodfinale(...).The most likely cause is that the parameter was removed, but the annotation was not.