Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
83.33% |
55 / 66 |
|
62.50% |
10 / 16 |
CRAP | |
0.00% |
0 / 1 |
| Status | |
84.62% |
55 / 65 |
|
62.50% |
10 / 16 |
26.10 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| wrap | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
2.00 | |||
| __get | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
| __set | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
| setMessageLocalizer | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| getFormatter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| splitByErrorType | |
55.56% |
5 / 9 |
|
0.00% |
0 / 1 |
3.79 | |||
| getStatusValue | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getWikiText | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| getMessage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| getPsr3MessageAndContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getHTML | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| getErrorsArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getWarningsArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| __sleep | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| __wakeup | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Generic operation result. |
| 4 | * |
| 5 | * @license GPL-2.0-or-later |
| 6 | * @file |
| 7 | */ |
| 8 | |
| 9 | namespace MediaWiki\Status; |
| 10 | |
| 11 | use MediaWiki\Context\IContextSource; |
| 12 | use MediaWiki\Context\RequestContext; |
| 13 | use MediaWiki\Language\Language; |
| 14 | use MediaWiki\Language\MessageLocalizer; |
| 15 | use MediaWiki\MediaWikiServices; |
| 16 | use MediaWiki\Message\Message; |
| 17 | use MediaWiki\StubObject\StubUserLang; |
| 18 | use RuntimeException; |
| 19 | use StatusValue; |
| 20 | |
| 21 | /** |
| 22 | * Generic operation result class |
| 23 | * Has warning/error list, boolean status and arbitrary value |
| 24 | * |
| 25 | * "Good" means the operation was completed with no warnings or errors. |
| 26 | * |
| 27 | * "OK" means the operation was partially or wholly completed. |
| 28 | * |
| 29 | * An operation which is not OK should have errors so that the user can be |
| 30 | * informed as to what went wrong. Calling the fatal() function sets an error |
| 31 | * message and simultaneously switches off the OK flag. |
| 32 | * |
| 33 | * The recommended pattern for Status objects is to return a Status object |
| 34 | * unconditionally, i.e. both on success and on failure -- so that the |
| 35 | * developer of the calling code is reminded that the function can fail, and |
| 36 | * so that a lack of error-handling will be explicit. |
| 37 | * |
| 38 | * @newable |
| 39 | * @template T Type of the value stored in the status when the operation result is OK. |
| 40 | * May be 'never' to indicate that there's no meaningful value, and that |
| 41 | * this status is only used to keep track of errors and warnings. |
| 42 | * @extends StatusValue<T> |
| 43 | */ |
| 44 | class Status extends StatusValue { |
| 45 | /** @var callable|false */ |
| 46 | public $cleanCallback = false; |
| 47 | |
| 48 | /** @var MessageLocalizer|null */ |
| 49 | protected $messageLocalizer; |
| 50 | |
| 51 | private ?StatusFormatter $formatter = null; |
| 52 | |
| 53 | /** |
| 54 | * @suppress PhanGenericConstructorTypes |
| 55 | */ |
| 56 | public function __construct() { |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Succinct helper method to wrap a StatusValue |
| 61 | * |
| 62 | * This is useful when formatting StatusValue objects: |
| 63 | * @code |
| 64 | * $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() ); |
| 65 | * @endcode |
| 66 | * |
| 67 | * Note that the contents of the status are copied by reference, |
| 68 | * so that any changes to the inner status will also affect the wrapped status and vice versa. |
| 69 | * If this is not needed, {@link StatusValue::cast()} is usually preferable. |
| 70 | * |
| 71 | * @param StatusValue|Status $sv |
| 72 | * @return static |
| 73 | */ |
| 74 | public static function wrap( $sv ) { |
| 75 | if ( $sv instanceof static ) { |
| 76 | return $sv; |
| 77 | } |
| 78 | |
| 79 | $result = new static(); |
| 80 | $result->ok =& $sv->ok; |
| 81 | $result->errors =& $sv->errors; |
| 82 | $result->value =& $sv->value; |
| 83 | $result->successCount =& $sv->successCount; |
| 84 | $result->failCount =& $sv->failCount; |
| 85 | $result->success =& $sv->success; |
| 86 | $result->statusData =& $sv->statusData; |
| 87 | |
| 88 | return $result; |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * Backwards compatibility logic |
| 93 | * |
| 94 | * @param string $name |
| 95 | * @return mixed |
| 96 | * @throws RuntimeException |
| 97 | */ |
| 98 | public function __get( $name ) { |
| 99 | if ( $name === 'ok' ) { |
| 100 | return $this->isOK(); |
| 101 | } |
| 102 | if ( $name === 'errors' ) { |
| 103 | return $this->getErrors(); |
| 104 | } |
| 105 | |
| 106 | throw new RuntimeException( "Cannot get '$name' property." ); |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Change operation result |
| 111 | * Backwards compatibility logic |
| 112 | * |
| 113 | * @param string $name |
| 114 | * @param mixed $value |
| 115 | * @throws RuntimeException |
| 116 | */ |
| 117 | public function __set( $name, $value ) { |
| 118 | if ( $name === 'ok' ) { |
| 119 | $this->setOK( $value ); |
| 120 | } else { |
| 121 | throw new RuntimeException( "Cannot set '$name' property." ); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Makes this Status object use the given localizer instead of the global one. |
| 127 | * If it is an IContextSource or a ResourceLoader Context, it will also be used to |
| 128 | * determine the interface language. |
| 129 | * @note This setting does not survive serialization. That's usually for the best |
| 130 | * (there's no guarantee we'll still have the same localization settings after |
| 131 | * unserialization); it is the caller's responsibility to set the localizer again |
| 132 | * if needed. |
| 133 | * @param MessageLocalizer $messageLocalizer |
| 134 | * |
| 135 | * @deprecated since 1.42, use FormatterFactory::getStatusFormatter instead. |
| 136 | */ |
| 137 | public function setMessageLocalizer( MessageLocalizer $messageLocalizer ) { |
| 138 | // TODO: hard deprecate after switching callers to StatusFormatter |
| 139 | $this->messageLocalizer = $messageLocalizer; |
| 140 | $this->formatter = null; |
| 141 | } |
| 142 | |
| 143 | private function getFormatter(): StatusFormatter { |
| 144 | if ( !$this->formatter ) { |
| 145 | // HACK: only works for IContextSource objects. |
| 146 | $context = $this->messageLocalizer instanceof IContextSource ? |
| 147 | $this->messageLocalizer : |
| 148 | RequestContext::getMain(); |
| 149 | |
| 150 | $formatterFactory = MediaWikiServices::getInstance()->getFormatterFactory(); |
| 151 | $this->formatter = $formatterFactory->getStatusFormatter( $context ); |
| 152 | } |
| 153 | |
| 154 | return $this->formatter; |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * @inheritDoc |
| 159 | */ |
| 160 | public function splitByErrorType() { |
| 161 | [ $errorsOnlyStatus, $warningsOnlyStatus ] = parent::splitByErrorType(); |
| 162 | |
| 163 | if ( $this->messageLocalizer ) { |
| 164 | $errorsOnlyStatus->setMessageLocalizer( $this->messageLocalizer ); |
| 165 | $warningsOnlyStatus->setMessageLocalizer( $this->messageLocalizer ); |
| 166 | } |
| 167 | |
| 168 | if ( $this->formatter ) { |
| 169 | $errorsOnlyStatus->formatter = $this->formatter; |
| 170 | $warningsOnlyStatus->formatter = $this->formatter; |
| 171 | } |
| 172 | |
| 173 | $errorsOnlyStatus->cleanCallback = $warningsOnlyStatus->cleanCallback = $this->cleanCallback; |
| 174 | |
| 175 | return [ $errorsOnlyStatus, $warningsOnlyStatus ]; |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Returns the wrapped StatusValue object |
| 180 | * @return StatusValue<T> |
| 181 | * @since 1.27 |
| 182 | */ |
| 183 | public function getStatusValue() { |
| 184 | return $this; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Get the error list as a wikitext formatted list |
| 189 | * |
| 190 | * All message parameters that were provided as strings will be escaped with wfEscapeWikiText. |
| 191 | * This is mostly a historical accident and often undesirable (T368821). |
| 192 | * - To avoid this behavior when producing the Status, pass MessageSpecifier objects to methods |
| 193 | * such as `$status->fatal()`, instead of separate key and params parameters. |
| 194 | * - To avoid this behavior when consuming the Status, use the `$status->getMessages()` method |
| 195 | * instead, and display each message separately (or combine then with `Message::listParams()`). |
| 196 | * |
| 197 | * @deprecated since 1.42, use StatusFormatter instead. |
| 198 | * |
| 199 | * @param string|false $shortContext A short enclosing context message name, to |
| 200 | * be used when there is a single error |
| 201 | * @param string|false $longContext A long enclosing context message name, for a list |
| 202 | * @param string|Language|StubUserLang|null $lang Language to use for processing messages |
| 203 | * @return string |
| 204 | */ |
| 205 | public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) { |
| 206 | return $this->getFormatter()->getWikiText( $this, [ |
| 207 | 'shortContext' => $shortContext, |
| 208 | 'longContext' => $longContext, |
| 209 | 'lang' => $lang, |
| 210 | 'cleanCallback' => $this->cleanCallback |
| 211 | ] ); |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Get a bullet list of the errors as a Message object. |
| 216 | * |
| 217 | * All message parameters that were provided as strings will be escaped with wfEscapeWikiText. |
| 218 | * This is mostly a historical accident and often undesirable (T368821). |
| 219 | * - To avoid this behavior when producing the Status, pass MessageSpecifier objects to methods |
| 220 | * such as `$status->fatal()`, instead of separate key and params parameters. |
| 221 | * - To avoid this behavior when consuming the Status, use the `$status->getMessages()` method |
| 222 | * instead, and display each message separately (or combine then with `Message::listParams()`). |
| 223 | * |
| 224 | * $shortContext and $longContext can be used to wrap the error list in some text. |
| 225 | * $shortContext will be preferred when there is a single error; $longContext will be |
| 226 | * preferred when there are multiple ones. In either case, $1 will be replaced with |
| 227 | * the list of errors. |
| 228 | * |
| 229 | * $shortContext is assumed to use $1 as an inline parameter: if there is a single item, |
| 230 | * it will not be made into a list; if there are multiple items, newlines will be inserted |
| 231 | * around the list. |
| 232 | * $longContext is assumed to use $1 as a standalone parameter; it will always receive a list. |
| 233 | * |
| 234 | * If both parameters are missing, and there is only one error, no bullet will be added. |
| 235 | * |
| 236 | * @deprecated since 1.42, use StatusFormatter instead. |
| 237 | * |
| 238 | * @param string|string[]|false $shortContext A message name or an array of message names. |
| 239 | * @param string|string[]|false $longContext A message name or an array of message names. |
| 240 | * @param string|Language|StubUserLang|null $lang Language to use for processing messages |
| 241 | * @return Message |
| 242 | */ |
| 243 | public function getMessage( $shortContext = false, $longContext = false, $lang = null ) { |
| 244 | return $this->getFormatter()->getMessage( $this, [ |
| 245 | 'shortContext' => $shortContext, |
| 246 | 'longContext' => $longContext, |
| 247 | 'lang' => $lang, |
| 248 | 'cleanCallback' => $this->cleanCallback |
| 249 | ] ); |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * Try to convert the status to a PSR-3 friendly format. The output will be similar to |
| 254 | * getWikiText( false, false, 'en' ), but message parameters will be extracted into the |
| 255 | * context array with parameter names 'parameter1' etc. when possible. |
| 256 | * A predefined context array may be passed for convenience. |
| 257 | * |
| 258 | * @deprecated since 1.42, use StatusFormatter instead. |
| 259 | * |
| 260 | * @return array A pair of (message, context) suitable for passing to a PSR-3 logger. |
| 261 | * @phan-return array{0:string,1:(int|float|string)[]} |
| 262 | */ |
| 263 | public function getPsr3MessageAndContext( array $context = [] ): array { |
| 264 | return $this->getFormatter()->getPsr3MessageAndContext( $this, $context ); |
| 265 | } |
| 266 | |
| 267 | /** |
| 268 | * Get the error message as HTML. This is done by parsing the wikitext error message |
| 269 | * |
| 270 | * All message parameters that were provided as strings will be escaped with wfEscapeWikiText. |
| 271 | * This is mostly a historical accident and often undesirable (T368821). |
| 272 | * - To avoid this behavior when producing the Status, pass MessageSpecifier objects to methods |
| 273 | * such as `$status->fatal()`, instead of separate key and params parameters. |
| 274 | * - To avoid this behavior when consuming the Status, use the `$status->getMessages()` method |
| 275 | * instead, and display each message separately (or combine then with `Message::listParams()`). |
| 276 | * |
| 277 | * @deprecated since 1.42, use StatusFormatter instead. |
| 278 | * |
| 279 | * @param string|false $shortContext A short enclosing context message name, to |
| 280 | * be used when there is a single error |
| 281 | * @param string|false $longContext A long enclosing context message name, for a list |
| 282 | * @param string|Language|StubUserLang|null $lang Language to use for processing messages |
| 283 | * @return string |
| 284 | */ |
| 285 | public function getHTML( $shortContext = false, $longContext = false, $lang = null ) { |
| 286 | return $this->getFormatter()->getHTML( $this, [ |
| 287 | 'shortContext' => $shortContext, |
| 288 | 'longContext' => $longContext, |
| 289 | 'lang' => $lang, |
| 290 | 'cleanCallback' => $this->cleanCallback |
| 291 | ] ); |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Get the list of errors (but not warnings) |
| 296 | * |
| 297 | * @deprecated since 1.43 Use `->getMessages( 'error' )` instead |
| 298 | * @return array[] A list in which each entry is an array with a message key as its first element. |
| 299 | * The remaining array elements are the message parameters. |
| 300 | * @phan-return non-empty-array[] |
| 301 | */ |
| 302 | public function getErrorsArray() { |
| 303 | return $this->getStatusArray( 'error' ); |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Get the list of warnings (but not errors) |
| 308 | * |
| 309 | * @deprecated since 1.43 Use `->getMessages( 'warning' )` instead |
| 310 | * @return array[] A list in which each entry is an array with a message key as its first element. |
| 311 | * The remaining array elements are the message parameters. |
| 312 | * @phan-return non-empty-array[] |
| 313 | */ |
| 314 | public function getWarningsArray() { |
| 315 | return $this->getStatusArray( 'warning' ); |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Don't save the callback when serializing, because Closures can't be |
| 320 | * serialized and we're going to clear it in __wakeup anyway. |
| 321 | * Don't save the localizer, because it can be pretty much anything. Restoring it is |
| 322 | * the caller's responsibility (otherwise it will just fall back to the global request context). |
| 323 | * Same for the formatter, which is likely to contain a localizer. |
| 324 | * @return array |
| 325 | */ |
| 326 | public function __sleep() { |
| 327 | $keys = array_keys( get_object_vars( $this ) ); |
| 328 | return array_diff( $keys, [ 'cleanCallback', 'messageLocalizer', 'formatter' ] ); |
| 329 | } |
| 330 | |
| 331 | /** |
| 332 | * Sanitize the callback parameter on wakeup, to avoid arbitrary execution. |
| 333 | */ |
| 334 | public function __wakeup() { |
| 335 | $this->cleanCallback = false; |
| 336 | $this->messageLocalizer = null; |
| 337 | $this->formatter = null; |
| 338 | } |
| 339 | |
| 340 | } |
| 341 | |
| 342 | /** @deprecated class alias since 1.41 */ |
| 343 | class_alias( Status::class, 'Status' ); |