Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
77.78% |
56 / 72 |
|
14.29% |
1 / 7 |
CRAP | |
0.00% |
0 / 1 |
| HTMLUserTextField | |
78.87% |
56 / 71 |
|
14.29% |
1 / 7 |
60.44 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| validate | |
93.55% |
29 / 31 |
|
0.00% |
0 / 1 |
22.13 | |||
| isValidIPRange | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
15.57 | |||
| getInputWidget | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| shouldInfuseOOUI | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getOOUIModules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getInputHtml | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\HTMLForm\Field; |
| 4 | |
| 5 | use MediaWiki\MediaWikiServices; |
| 6 | use MediaWiki\Message\Message; |
| 7 | use MediaWiki\User\ExternalUserNames; |
| 8 | use MediaWiki\Widget\UserInputWidget; |
| 9 | use Wikimedia\IPUtils; |
| 10 | |
| 11 | /** |
| 12 | * Implements a text input field for user names. |
| 13 | * Automatically auto-completes if using the OOUI display format. |
| 14 | * |
| 15 | * Optional parameters: |
| 16 | * 'exists' - Whether to validate that the user already exists |
| 17 | * 'external' - Whether an external user (imported actor) is interpreted as "valid" |
| 18 | * 'ipallowed' - Whether an IP address is interpreted as "valid" |
| 19 | * 'usemodwiki-ipallowed' - Whether an IP address in the usemod wiki format (e.g. 300.300.300.xxx) is accepted. The |
| 20 | * 'ipallowed' parameter must be set to true if this parameter is set to true. |
| 21 | * 'iprange' - Whether an IP address range is interpreted as "valid" |
| 22 | * 'iprangelimits' - Specifies the valid IP ranges for IPv4 and IPv6 in an array. |
| 23 | * 'excludenamed' - Whether to exclude named users or not. |
| 24 | * 'excludetemp' - Whether to exclude temporary users or not. |
| 25 | * |
| 26 | * @stable to extend |
| 27 | * @since 1.26 |
| 28 | */ |
| 29 | class HTMLUserTextField extends HTMLTextField { |
| 30 | /** |
| 31 | * @stable to call |
| 32 | * @inheritDoc |
| 33 | */ |
| 34 | public function __construct( $params ) { |
| 35 | $params = wfArrayPlus2d( $params, [ |
| 36 | 'exists' => false, |
| 37 | 'external' => false, |
| 38 | 'ipallowed' => false, |
| 39 | 'usemodwiki-ipallowed' => false, |
| 40 | 'iprange' => false, |
| 41 | 'iprangelimits' => [ |
| 42 | 'IPv4' => 0, |
| 43 | 'IPv6' => 0, |
| 44 | ], |
| 45 | 'excludenamed' => false, |
| 46 | 'excludetemp' => false, |
| 47 | ] |
| 48 | ); |
| 49 | |
| 50 | parent::__construct( $params ); |
| 51 | } |
| 52 | |
| 53 | /** @inheritDoc */ |
| 54 | public function validate( $value, $alldata ) { |
| 55 | // If the value is null, reset it to an empty string which is what is expected by the parent. |
| 56 | $value ??= ''; |
| 57 | |
| 58 | // If the value is empty, there are no additional checks that can be performed. |
| 59 | if ( $value === '' ) { |
| 60 | return parent::validate( $value, $alldata ); |
| 61 | } |
| 62 | |
| 63 | // check if the input is a valid username |
| 64 | $user = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $value ); |
| 65 | if ( $user ) { |
| 66 | // check if the user exists, if requested |
| 67 | if ( $this->mParams['exists'] && !( |
| 68 | $user->isRegistered() && |
| 69 | // Treat hidden users as unregistered if current user can't view them (T309894) |
| 70 | !( $user->isHidden() && !( $this->mParent && $this->mParent->getUser()->isAllowed( 'hideuser' ) ) ) |
| 71 | ) ) { |
| 72 | return $this->msg( 'htmlform-user-not-exists', wfEscapeWikiText( $user->getName() ) ); |
| 73 | } |
| 74 | |
| 75 | // check if the user account type matches the account type filter |
| 76 | $excludeNamed = $this->mParams['excludenamed'] ?? null; |
| 77 | $excludeTemp = $this->mParams['excludetemp'] ?? null; |
| 78 | if ( ( $excludeTemp && $user->isTemp() ) || ( $excludeNamed && $user->isNamed() ) ) { |
| 79 | return $this->msg( 'htmlform-user-not-valid', wfEscapeWikiText( $user->getName() ) ); |
| 80 | } |
| 81 | } else { |
| 82 | // not a valid username |
| 83 | $valid = false; |
| 84 | // check if the input is a valid external user |
| 85 | if ( $this->mParams['external'] && ExternalUserNames::isExternal( $value ) ) { |
| 86 | $valid = true; |
| 87 | } |
| 88 | // check if the input is a valid IP address, optionally also checking for usemod wiki IPs |
| 89 | if ( $this->mParams['ipallowed'] ) { |
| 90 | $b = IPUtils::RE_IP_BYTE; |
| 91 | if ( IPUtils::isValid( $value ) ) { |
| 92 | $valid = true; |
| 93 | } elseif ( $this->mParams['usemodwiki-ipallowed'] && preg_match( "/^$b\.$b\.$b\.xxx$/", $value ) ) { |
| 94 | $valid = true; |
| 95 | } |
| 96 | } |
| 97 | // check if the input is a valid IP range |
| 98 | if ( $this->mParams['iprange'] ) { |
| 99 | $rangeError = $this->isValidIPRange( $value ); |
| 100 | if ( $rangeError === true ) { |
| 101 | $valid = true; |
| 102 | } elseif ( $rangeError !== false ) { |
| 103 | return $rangeError; |
| 104 | } |
| 105 | } |
| 106 | if ( !$valid ) { |
| 107 | return $this->msg( 'htmlform-user-not-valid', wfEscapeWikiText( $value ) ); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | return parent::validate( $value, $alldata ); |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * @param string $value |
| 116 | * @return bool|Message |
| 117 | */ |
| 118 | protected function isValidIPRange( $value ) { |
| 119 | $cidrIPRanges = $this->mParams['iprangelimits']; |
| 120 | |
| 121 | if ( !IPUtils::isValidRange( $value ) ) { |
| 122 | return false; |
| 123 | } |
| 124 | |
| 125 | [ $ip, $range ] = explode( '/', $value, 2 ); |
| 126 | |
| 127 | if ( |
| 128 | ( IPUtils::isIPv4( $ip ) && $cidrIPRanges['IPv4'] == 32 ) || |
| 129 | ( IPUtils::isIPv6( $ip ) && $cidrIPRanges['IPv6'] == 128 ) |
| 130 | ) { |
| 131 | // Range block effectively disabled |
| 132 | return $this->msg( 'ip_range_toolow' ); |
| 133 | } |
| 134 | |
| 135 | if ( |
| 136 | ( IPUtils::isIPv4( $ip ) && $range > 32 ) || |
| 137 | ( IPUtils::isIPv6( $ip ) && $range > 128 ) |
| 138 | ) { |
| 139 | // Dodgy range |
| 140 | return $this->msg( 'ip_range_invalid' ); |
| 141 | } |
| 142 | |
| 143 | if ( IPUtils::isIPv4( $ip ) && $range < $cidrIPRanges['IPv4'] ) { |
| 144 | return $this->msg( 'ip_range_exceeded', $cidrIPRanges['IPv4'] ); |
| 145 | } |
| 146 | |
| 147 | if ( IPUtils::isIPv6( $ip ) && $range < $cidrIPRanges['IPv6'] ) { |
| 148 | return $this->msg( 'ip_range_exceeded', $cidrIPRanges['IPv6'] ); |
| 149 | } |
| 150 | |
| 151 | return true; |
| 152 | } |
| 153 | |
| 154 | /** @inheritDoc */ |
| 155 | protected function getInputWidget( $params ) { |
| 156 | if ( isset( $this->mParams['excludenamed'] ) ) { |
| 157 | $params['excludenamed'] = $this->mParams['excludenamed']; |
| 158 | } |
| 159 | |
| 160 | if ( isset( $this->mParams['excludetemp'] ) ) { |
| 161 | $params['excludetemp'] = $this->mParams['excludetemp']; |
| 162 | } |
| 163 | |
| 164 | return new UserInputWidget( $params ); |
| 165 | } |
| 166 | |
| 167 | /** @inheritDoc */ |
| 168 | protected function shouldInfuseOOUI() { |
| 169 | return true; |
| 170 | } |
| 171 | |
| 172 | /** @inheritDoc */ |
| 173 | protected function getOOUIModules() { |
| 174 | return [ 'mediawiki.widgets.UserInputWidget' ]; |
| 175 | } |
| 176 | |
| 177 | /** @inheritDoc */ |
| 178 | public function getInputHtml( $value ) { |
| 179 | // add the required module and css class for user suggestions in non-OOUI mode |
| 180 | $this->mParent->getOutput()->addModules( 'mediawiki.userSuggest' ); |
| 181 | $this->mClass .= ' mw-autocomplete-user'; |
| 182 | |
| 183 | // return parent html |
| 184 | return parent::getInputHTML( $value ); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /** @deprecated class alias since 1.42 */ |
| 189 | class_alias( HTMLUserTextField::class, 'HTMLUserTextField' ); |