MediaWiki
REL1_24
|
00001 <?php 00002 00007 abstract class HTMLFormField { 00008 public $mParams; 00009 00010 protected $mValidationCallback; 00011 protected $mFilterCallback; 00012 protected $mName; 00013 protected $mLabel; # String label. Set on construction 00014 protected $mID; 00015 protected $mClass = ''; 00016 protected $mHelpClass = false; 00017 protected $mDefault; 00018 protected $mOptions = false; 00019 protected $mOptionsLabelsNotFromMessage = false; 00020 protected $mHideIf = null; 00021 00026 protected $mShowEmptyLabels = true; 00027 00031 public $mParent; 00032 00043 abstract function getInputHTML( $value ); 00044 00055 function msg() { 00056 $args = func_get_args(); 00057 00058 if ( $this->mParent ) { 00059 $callback = array( $this->mParent, 'msg' ); 00060 } else { 00061 $callback = 'wfMessage'; 00062 } 00063 00064 return call_user_func_array( $callback, $args ); 00065 } 00066 00067 00081 protected function getNearestFieldByName( $alldata, $name ) { 00082 $tmp = $this->mName; 00083 $thisKeys = array(); 00084 while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) { 00085 array_unshift( $thisKeys, $m[2] ); 00086 $tmp = $m[1]; 00087 } 00088 if ( substr( $tmp, 0, 2 ) == 'wp' && 00089 !isset( $alldata[$tmp] ) && 00090 isset( $alldata[substr( $tmp, 2 )] ) 00091 ) { 00092 // Adjust for name mangling. 00093 $tmp = substr( $tmp, 2 ); 00094 } 00095 array_unshift( $thisKeys, $tmp ); 00096 00097 $tmp = $name; 00098 $nameKeys = array(); 00099 while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) { 00100 array_unshift( $nameKeys, $m[2] ); 00101 $tmp = $m[1]; 00102 } 00103 array_unshift( $nameKeys, $tmp ); 00104 00105 $testValue = ''; 00106 for ( $i = count( $thisKeys ) - 1; $i >= 0; $i-- ) { 00107 $keys = array_merge( array_slice( $thisKeys, 0, $i ), $nameKeys ); 00108 $data = $alldata; 00109 while ( $keys ) { 00110 $key = array_shift( $keys ); 00111 if ( !is_array( $data ) || !isset( $data[$key] ) ) { 00112 continue 2; 00113 } 00114 $data = $data[$key]; 00115 } 00116 $testValue = (string)$data; 00117 break; 00118 } 00119 00120 return $testValue; 00121 } 00122 00130 protected function isHiddenRecurse( array $alldata, array $params ) { 00131 $origParams = $params; 00132 $op = array_shift( $params ); 00133 00134 try { 00135 switch ( $op ) { 00136 case 'AND': 00137 foreach ( $params as $i => $p ) { 00138 if ( !is_array( $p ) ) { 00139 throw new MWException( 00140 "Expected array, found " . gettype( $p ) . " at index $i" 00141 ); 00142 } 00143 if ( !$this->isHiddenRecurse( $alldata, $p ) ) { 00144 return false; 00145 } 00146 } 00147 return true; 00148 00149 case 'OR': 00150 foreach ( $params as $p ) { 00151 if ( !is_array( $p ) ) { 00152 throw new MWException( 00153 "Expected array, found " . gettype( $p ) . " at index $i" 00154 ); 00155 } 00156 if ( $this->isHiddenRecurse( $alldata, $p ) ) { 00157 return true; 00158 } 00159 } 00160 return false; 00161 00162 case 'NAND': 00163 foreach ( $params as $i => $p ) { 00164 if ( !is_array( $p ) ) { 00165 throw new MWException( 00166 "Expected array, found " . gettype( $p ) . " at index $i" 00167 ); 00168 } 00169 if ( !$this->isHiddenRecurse( $alldata, $p ) ) { 00170 return true; 00171 } 00172 } 00173 return false; 00174 00175 case 'NOR': 00176 foreach ( $params as $p ) { 00177 if ( !is_array( $p ) ) { 00178 throw new MWException( 00179 "Expected array, found " . gettype( $p ) . " at index $i" 00180 ); 00181 } 00182 if ( $this->isHiddenRecurse( $alldata, $p ) ) { 00183 return false; 00184 } 00185 } 00186 return true; 00187 00188 case 'NOT': 00189 if ( count( $params ) !== 1 ) { 00190 throw new MWException( "NOT takes exactly one parameter" ); 00191 } 00192 $p = $params[0]; 00193 if ( !is_array( $p ) ) { 00194 throw new MWException( 00195 "Expected array, found " . gettype( $p ) . " at index 0" 00196 ); 00197 } 00198 return !$this->isHiddenRecurse( $alldata, $p ); 00199 00200 case '===': 00201 case '!==': 00202 if ( count( $params ) !== 2 ) { 00203 throw new MWException( "$op takes exactly two parameters" ); 00204 } 00205 list( $field, $value ) = $params; 00206 if ( !is_string( $field ) || !is_string( $value ) ) { 00207 throw new MWException( "Parameters for $op must be strings" ); 00208 } 00209 $testValue = $this->getNearestFieldByName( $alldata, $field ); 00210 switch ( $op ) { 00211 case '===': 00212 return ( $value === $testValue ); 00213 case '!==': 00214 return ( $value !== $testValue ); 00215 } 00216 00217 default: 00218 throw new MWException( "Unknown operation" ); 00219 } 00220 } catch ( MWException $ex ) { 00221 throw new MWException( 00222 "Invalid hide-if specification for $this->mName: " . 00223 $ex->getMessage() . " in " . var_export( $origParams, true ), 00224 0, $ex 00225 ); 00226 } 00227 } 00228 00237 function isHidden( $alldata ) { 00238 if ( !$this->mHideIf ) { 00239 return false; 00240 } 00241 00242 return $this->isHiddenRecurse( $alldata, $this->mHideIf ); 00243 } 00244 00255 function cancelSubmit( $value, $alldata ) { 00256 return false; 00257 } 00258 00270 function validate( $value, $alldata ) { 00271 if ( $this->isHidden( $alldata ) ) { 00272 return true; 00273 } 00274 00275 if ( isset( $this->mParams['required'] ) 00276 && $this->mParams['required'] !== false 00277 && $value === '' 00278 ) { 00279 return $this->msg( 'htmlform-required' )->parse(); 00280 } 00281 00282 if ( isset( $this->mValidationCallback ) ) { 00283 return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent ); 00284 } 00285 00286 return true; 00287 } 00288 00289 function filter( $value, $alldata ) { 00290 if ( isset( $this->mFilterCallback ) ) { 00291 $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent ); 00292 } 00293 00294 return $value; 00295 } 00296 00303 protected function needsLabel() { 00304 return true; 00305 } 00306 00316 public function setShowEmptyLabel( $show ) { 00317 $this->mShowEmptyLabels = $show; 00318 } 00319 00327 function loadDataFromRequest( $request ) { 00328 if ( $request->getCheck( $this->mName ) ) { 00329 return $request->getText( $this->mName ); 00330 } else { 00331 return $this->getDefault(); 00332 } 00333 } 00334 00343 function __construct( $params ) { 00344 $this->mParams = $params; 00345 00346 # Generate the label from a message, if possible 00347 if ( isset( $params['label-message'] ) ) { 00348 $msgInfo = $params['label-message']; 00349 00350 if ( is_array( $msgInfo ) ) { 00351 $msg = array_shift( $msgInfo ); 00352 } else { 00353 $msg = $msgInfo; 00354 $msgInfo = array(); 00355 } 00356 00357 $this->mLabel = wfMessage( $msg, $msgInfo )->parse(); 00358 } elseif ( isset( $params['label'] ) ) { 00359 if ( $params['label'] === ' ' ) { 00360 // Apparently some things set   directly and in an odd format 00361 $this->mLabel = ' '; 00362 } else { 00363 $this->mLabel = htmlspecialchars( $params['label'] ); 00364 } 00365 } elseif ( isset( $params['label-raw'] ) ) { 00366 $this->mLabel = $params['label-raw']; 00367 } 00368 00369 $this->mName = "wp{$params['fieldname']}"; 00370 if ( isset( $params['name'] ) ) { 00371 $this->mName = $params['name']; 00372 } 00373 00374 $validName = Sanitizer::escapeId( $this->mName ); 00375 $validName = str_replace( array( '.5B', '.5D' ), array( '[', ']' ), $validName ); 00376 if ( $this->mName != $validName && !isset( $params['nodata'] ) ) { 00377 throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ ); 00378 } 00379 00380 $this->mID = "mw-input-{$this->mName}"; 00381 00382 if ( isset( $params['default'] ) ) { 00383 $this->mDefault = $params['default']; 00384 } 00385 00386 if ( isset( $params['id'] ) ) { 00387 $id = $params['id']; 00388 $validId = Sanitizer::escapeId( $id ); 00389 00390 if ( $id != $validId ) { 00391 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ ); 00392 } 00393 00394 $this->mID = $id; 00395 } 00396 00397 if ( isset( $params['cssclass'] ) ) { 00398 $this->mClass = $params['cssclass']; 00399 } 00400 00401 if ( isset( $params['csshelpclass'] ) ) { 00402 $this->mHelpClass = $params['csshelpclass']; 00403 } 00404 00405 if ( isset( $params['validation-callback'] ) ) { 00406 $this->mValidationCallback = $params['validation-callback']; 00407 } 00408 00409 if ( isset( $params['filter-callback'] ) ) { 00410 $this->mFilterCallback = $params['filter-callback']; 00411 } 00412 00413 if ( isset( $params['flatlist'] ) ) { 00414 $this->mClass .= ' mw-htmlform-flatlist'; 00415 } 00416 00417 if ( isset( $params['hidelabel'] ) ) { 00418 $this->mShowEmptyLabels = false; 00419 } 00420 00421 if ( isset( $params['hide-if'] ) ) { 00422 $this->mHideIf = $params['hide-if']; 00423 } 00424 } 00425 00434 function getTableRow( $value ) { 00435 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); 00436 $inputHtml = $this->getInputHTML( $value ); 00437 $fieldType = get_class( $this ); 00438 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() ); 00439 $cellAttributes = array(); 00440 $rowAttributes = array(); 00441 $rowClasses = ''; 00442 00443 if ( !empty( $this->mParams['vertical-label'] ) ) { 00444 $cellAttributes['colspan'] = 2; 00445 $verticalLabel = true; 00446 } else { 00447 $verticalLabel = false; 00448 } 00449 00450 $label = $this->getLabelHtml( $cellAttributes ); 00451 00452 $field = Html::rawElement( 00453 'td', 00454 array( 'class' => 'mw-input' ) + $cellAttributes, 00455 $inputHtml . "\n$errors" 00456 ); 00457 00458 if ( $this->mHideIf ) { 00459 $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf ); 00460 $rowClasses .= ' mw-htmlform-hide-if'; 00461 } 00462 00463 if ( $verticalLabel ) { 00464 $html = Html::rawElement( 'tr', 00465 $rowAttributes + array( 'class' => "mw-htmlform-vertical-label $rowClasses" ), $label ); 00466 $html .= Html::rawElement( 'tr', 00467 $rowAttributes + array( 00468 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses" 00469 ), 00470 $field ); 00471 } else { 00472 $html = 00473 Html::rawElement( 'tr', 00474 $rowAttributes + array( 00475 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses" 00476 ), 00477 $label . $field ); 00478 } 00479 00480 return $html . $helptext; 00481 } 00482 00492 public function getDiv( $value ) { 00493 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); 00494 $inputHtml = $this->getInputHTML( $value ); 00495 $fieldType = get_class( $this ); 00496 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() ); 00497 $cellAttributes = array(); 00498 $label = $this->getLabelHtml( $cellAttributes ); 00499 00500 $outerDivClass = array( 00501 'mw-input', 00502 'mw-htmlform-nolabel' => ( $label === '' ) 00503 ); 00504 00505 $field = Html::rawElement( 00506 'div', 00507 array( 'class' => $outerDivClass ) + $cellAttributes, 00508 $inputHtml . "\n$errors" 00509 ); 00510 $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass ); 00511 if ( $this->mParent->isVForm() ) { 00512 $divCssClasses[] = 'mw-ui-vform-field'; 00513 } 00514 00515 $wrapperAttributes = array( 00516 'class' => $divCssClasses, 00517 ); 00518 if ( $this->mHideIf ) { 00519 $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf ); 00520 $wrapperAttributes['class'][] = ' mw-htmlform-hide-if'; 00521 } 00522 $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field ); 00523 $html .= $helptext; 00524 00525 return $html; 00526 } 00527 00537 public function getRaw( $value ) { 00538 list( $errors, ) = $this->getErrorsAndErrorClass( $value ); 00539 $inputHtml = $this->getInputHTML( $value ); 00540 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() ); 00541 $cellAttributes = array(); 00542 $label = $this->getLabelHtml( $cellAttributes ); 00543 00544 $html = "\n$errors"; 00545 $html .= $label; 00546 $html .= $inputHtml; 00547 $html .= $helptext; 00548 00549 return $html; 00550 } 00551 00559 public function getHelpTextHtmlTable( $helptext ) { 00560 if ( is_null( $helptext ) ) { 00561 return ''; 00562 } 00563 00564 $rowAttributes = array(); 00565 if ( $this->mHideIf ) { 00566 $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf ); 00567 $rowAttributes['class'] = 'mw-htmlform-hide-if'; 00568 } 00569 00570 $tdClasses = array( 'htmlform-tip' ); 00571 if ( $this->mHelpClass !== false ) { 00572 $tdClasses[] = $this->mHelpClass; 00573 } 00574 $row = Html::rawElement( 'td', array( 'colspan' => 2, 'class' => $tdClasses ), $helptext ); 00575 $row = Html::rawElement( 'tr', $rowAttributes, $row ); 00576 00577 return $row; 00578 } 00579 00588 public function getHelpTextHtmlDiv( $helptext ) { 00589 if ( is_null( $helptext ) ) { 00590 return ''; 00591 } 00592 00593 $wrapperAttributes = array( 00594 'class' => 'htmlform-tip', 00595 ); 00596 if ( $this->mHelpClass !== false ) { 00597 $wrapperAttributes['class'] .= " {$this->mHelpClass}"; 00598 } 00599 if ( $this->mHideIf ) { 00600 $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf ); 00601 $wrapperAttributes['class'] .= ' mw-htmlform-hide-if'; 00602 } 00603 $div = Html::rawElement( 'div', $wrapperAttributes, $helptext ); 00604 00605 return $div; 00606 } 00607 00615 public function getHelpTextHtmlRaw( $helptext ) { 00616 return $this->getHelpTextHtmlDiv( $helptext ); 00617 } 00618 00624 public function getHelpText() { 00625 $helptext = null; 00626 00627 if ( isset( $this->mParams['help-message'] ) ) { 00628 $this->mParams['help-messages'] = array( $this->mParams['help-message'] ); 00629 } 00630 00631 if ( isset( $this->mParams['help-messages'] ) ) { 00632 foreach ( $this->mParams['help-messages'] as $name ) { 00633 $helpMessage = (array)$name; 00634 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage ); 00635 00636 if ( $msg->exists() ) { 00637 if ( is_null( $helptext ) ) { 00638 $helptext = ''; 00639 } else { 00640 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space 00641 } 00642 $helptext .= $msg->parse(); // Append message 00643 } 00644 } 00645 } elseif ( isset( $this->mParams['help'] ) ) { 00646 $helptext = $this->mParams['help']; 00647 } 00648 00649 return $helptext; 00650 } 00651 00659 public function getErrorsAndErrorClass( $value ) { 00660 $errors = $this->validate( $value, $this->mParent->mFieldData ); 00661 00662 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) { 00663 $errors = ''; 00664 $errorClass = ''; 00665 } else { 00666 $errors = self::formatErrors( $errors ); 00667 $errorClass = 'mw-htmlform-invalid-input'; 00668 } 00669 00670 return array( $errors, $errorClass ); 00671 } 00672 00673 function getLabel() { 00674 return is_null( $this->mLabel ) ? '' : $this->mLabel; 00675 } 00676 00677 function getLabelHtml( $cellAttributes = array() ) { 00678 # Don't output a for= attribute for labels with no associated input. 00679 # Kind of hacky here, possibly we don't want these to be <label>s at all. 00680 $for = array(); 00681 00682 if ( $this->needsLabel() ) { 00683 $for['for'] = $this->mID; 00684 } 00685 00686 $labelValue = trim( $this->getLabel() ); 00687 $hasLabel = false; 00688 if ( $labelValue !== ' ' && $labelValue !== '' ) { 00689 $hasLabel = true; 00690 } 00691 00692 $displayFormat = $this->mParent->getDisplayFormat(); 00693 $html = ''; 00694 00695 if ( $displayFormat === 'table' ) { 00696 $html = 00697 Html::rawElement( 'td', 00698 array( 'class' => 'mw-label' ) + $cellAttributes, 00699 Html::rawElement( 'label', $for, $labelValue ) ); 00700 } elseif ( $hasLabel || $this->mShowEmptyLabels ) { 00701 if ( $displayFormat === 'div' ) { 00702 $html = 00703 Html::rawElement( 'div', 00704 array( 'class' => 'mw-label' ) + $cellAttributes, 00705 Html::rawElement( 'label', $for, $labelValue ) ); 00706 } else { 00707 $html = Html::rawElement( 'label', $for, $labelValue ); 00708 } 00709 } 00710 00711 return $html; 00712 } 00713 00714 function getDefault() { 00715 if ( isset( $this->mDefault ) ) { 00716 return $this->mDefault; 00717 } else { 00718 return null; 00719 } 00720 } 00721 00727 public function getTooltipAndAccessKey() { 00728 if ( empty( $this->mParams['tooltip'] ) ) { 00729 return array(); 00730 } 00731 00732 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] ); 00733 } 00734 00741 public function getAttributes( array $list ) { 00742 static $boolAttribs = array( 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ); 00743 00744 $ret = array(); 00745 00746 foreach ( $list as $key ) { 00747 if ( in_array( $key, $boolAttribs ) ) { 00748 if ( !empty( $this->mParams[$key] ) ) { 00749 $ret[$key] = ''; 00750 } 00751 } elseif ( isset( $this->mParams[$key] ) ) { 00752 $ret[$key] = $this->mParams[$key]; 00753 } 00754 } 00755 00756 return $ret; 00757 } 00758 00766 private function lookupOptionsKeys( $options ) { 00767 $ret = array(); 00768 foreach ( $options as $key => $value ) { 00769 $key = $this->msg( $key )->plain(); 00770 $ret[$key] = is_array( $value ) 00771 ? $this->lookupOptionsKeys( $value ) 00772 : strval( $value ); 00773 } 00774 return $ret; 00775 } 00776 00784 static function forceToStringRecursive( $array ) { 00785 if ( is_array( $array ) ) { 00786 return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array ); 00787 } else { 00788 return strval( $array ); 00789 } 00790 } 00791 00798 public function getOptions() { 00799 if ( $this->mOptions === false ) { 00800 if ( array_key_exists( 'options-messages', $this->mParams ) ) { 00801 $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'] ); 00802 } elseif ( array_key_exists( 'options', $this->mParams ) ) { 00803 $this->mOptionsLabelsNotFromMessage = true; 00804 $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] ); 00805 } elseif ( array_key_exists( 'options-message', $this->mParams ) ) { 00807 $message = $this->msg( $this->mParams['options-message'] )->inContentLanguage()->plain(); 00808 00809 $optgroup = false; 00810 $this->mOptions = array(); 00811 foreach ( explode( "\n", $message ) as $option ) { 00812 $value = trim( $option ); 00813 if ( $value == '' ) { 00814 continue; 00815 } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) { 00816 # A new group is starting... 00817 $value = trim( substr( $value, 1 ) ); 00818 $optgroup = $value; 00819 } elseif ( substr( $value, 0, 2 ) == '**' ) { 00820 # groupmember 00821 $opt = trim( substr( $value, 2 ) ); 00822 if ( $optgroup === false ) { 00823 $this->mOptions[$opt] = $opt; 00824 } else { 00825 $this->mOptions[$optgroup][$opt] = $opt; 00826 } 00827 } else { 00828 # groupless reason list 00829 $optgroup = false; 00830 $this->mOptions[$option] = $option; 00831 } 00832 } 00833 } else { 00834 $this->mOptions = null; 00835 } 00836 } 00837 00838 return $this->mOptions; 00839 } 00840 00848 public static function flattenOptions( $options ) { 00849 $flatOpts = array(); 00850 00851 foreach ( $options as $value ) { 00852 if ( is_array( $value ) ) { 00853 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) ); 00854 } else { 00855 $flatOpts[] = $value; 00856 } 00857 } 00858 00859 return $flatOpts; 00860 } 00861 00869 protected static function formatErrors( $errors ) { 00870 if ( is_array( $errors ) && count( $errors ) === 1 ) { 00871 $errors = array_shift( $errors ); 00872 } 00873 00874 if ( is_array( $errors ) ) { 00875 $lines = array(); 00876 foreach ( $errors as $error ) { 00877 if ( $error instanceof Message ) { 00878 $lines[] = Html::rawElement( 'li', array(), $error->parse() ); 00879 } else { 00880 $lines[] = Html::rawElement( 'li', array(), $error ); 00881 } 00882 } 00883 00884 return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) ); 00885 } else { 00886 if ( $errors instanceof Message ) { 00887 $errors = $errors->parse(); 00888 } 00889 00890 return Html::rawElement( 'span', array( 'class' => 'error' ), $errors ); 00891 } 00892 } 00893 }