bool) to indicate success of each part of batch operations */ public $success = []; /** @var int Counter for batch operations */ public $successCount = 0; /** @var int Counter for batch operations */ public $failCount = 0; /** @var mixed arbitrary extra data about the operation */ public $statusData; /** * Factory function for fatal errors * * @param string|MessageSpecifier $message Message key or object * @param mixed ...$parameters * @return static */ public static function newFatal( $message, ...$parameters ) { $result = new static(); $result->fatal( $message, ...$parameters ); return $result; } /** * Factory function for good results * * @param mixed|null $value * @return static */ public static function newGood( $value = null ) { $result = new static(); $result->value = $value; return $result; } /** * Splits this StatusValue object into two new StatusValue objects, one which contains only * the error messages, and one that contains the warnings, only. The returned array is * defined as: * [ * 0 => object(StatusValue) # the StatusValue with error messages, only * 1 => object(StatusValue) # The StatusValue with warning messages, only * ] * * @return static[] */ public function splitByErrorType() { $errorsOnlyStatusValue = static::newGood(); $warningsOnlyStatusValue = static::newGood(); $warningsOnlyStatusValue->setResult( true, $this->getValue() ); $errorsOnlyStatusValue->setResult( $this->isOK(), $this->getValue() ); foreach ( $this->errors as $item ) { if ( $item['type'] === 'warning' ) { $warningsOnlyStatusValue->errors[] = $item; } else { $errorsOnlyStatusValue->errors[] = $item; } } return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ]; } /** * Returns whether the operation completed and didn't have any error or * warnings * * @return bool */ public function isGood() { return $this->ok && !$this->errors; } /** * Returns whether the operation completed * * @return bool */ public function isOK() { return $this->ok; } /** * @return mixed */ public function getValue() { return $this->value; } /** * Get the list of errors * * Each error is a (message:string or MessageSpecifier,params:array) map * * @deprecated since 1.43 Use `->getMessages()` instead * @return array[] * @phan-return array{type:'warning'|'error', message:string|MessageSpecifier, params:array}[] */ public function getErrors() { return $this->errors; } /** * Change operation status * * @param bool $ok * @return $this */ public function setOK( $ok ) { $this->ok = $ok; return $this; } /** * Change operation result * * @param bool $ok Whether the operation completed * @param mixed|null $value * @return $this */ public function setResult( $ok, $value = null ) { $this->ok = (bool)$ok; $this->value = $value; return $this; } /** * Add a new error to the error array ($this->errors) if that error is not already in the * error array. Each error is passed as an array with the following fields: * * - type: 'error' or 'warning' * - message: a string (message key) or MessageSpecifier * - params: an array of string parameters * * If the new error is of type 'error' and it matches an existing error of type 'warning', * the existing error is upgraded to type 'error'. An error provided as a MessageSpecifier * will successfully match an error provided as the same string message key and array of * parameters as separate array elements. * * @param array $newError * @phan-param array{type:'warning'|'error', message:string|MessageSpecifier, params:array} $newError * @return $this */ private function addError( array $newError ) { [ 'type' => $newType, 'message' => $newKey, 'params' => $newParams ] = $newError; if ( $newKey instanceof MessageSpecifier ) { if ( $newParams ) { // Deprecate code like `Status::newFatal( wfMessage( 'foo' ), 'param' )` // - the parameters have always been ignored, so this is usually a mistake. wfDeprecatedMsg( 'Combining MessageSpecifier and parameters array' . ' was deprecated in MediaWiki 1.43', '1.43' ); } $newParams = $newKey->getParams(); $newKey = $newKey->getKey(); } foreach ( $this->errors as [ 'type' => &$type, 'message' => $key, 'params' => $params ] ) { if ( $key instanceof MessageSpecifier ) { $params = $key->getParams(); $key = $key->getKey(); } // This uses loose equality as we must support equality between MessageParam objects // (e.g. ScalarParam), including when they are created separate and not by-ref equal. if ( $newKey === $key && $newParams == $params ) { if ( $type === 'warning' && $newType === 'error' ) { $type = 'error'; } return $this; } } $this->errors[] = $newError; return $this; } /** * Add a new warning * * @param string|MessageSpecifier $message Message key or object * @param mixed ...$parameters * @return $this */ public function warning( $message, ...$parameters ) { return $this->addError( [ 'type' => 'warning', 'message' => $message, 'params' => $parameters ] ); } /** * Add an error, do not set fatal flag * This can be used for non-fatal errors * * @param string|MessageSpecifier $message Message key or object * @param mixed ...$parameters * @return $this */ public function error( $message, ...$parameters ) { return $this->addError( [ 'type' => 'error', 'message' => $message, 'params' => $parameters ] ); } /** * Add an error and set OK to false, indicating that the operation * as a whole was fatal * * @param string|MessageSpecifier $message Message key or object * @param mixed ...$parameters * @return $this */ public function fatal( $message, ...$parameters ) { $this->ok = false; return $this->error( $message, ...$parameters ); } /** * Merge another status object into this one * * @param StatusValue $other * @param bool $overwriteValue Whether to override the "value" member * @return $this */ public function merge( $other, $overwriteValue = false ) { if ( $this->statusData !== null && $other->statusData !== null ) { throw new RuntimeException( "Status cannot be merged, because they both have \$statusData" ); } else { $this->statusData ??= $other->statusData; } foreach ( $other->errors as $error ) { $this->addError( $error ); } $this->ok = $this->ok && $other->ok; if ( $overwriteValue ) { $this->value = $other->value; } $this->successCount += $other->successCount; $this->failCount += $other->failCount; return $this; } /** * Returns a list of status messages of the given type * * Each entry is a map of: * - message: string message key or MessageSpecifier * - params: array list of parameters * * @deprecated since 1.43 Use `->getMessages( $type )` instead * @param string $type * @return array[] * @phan-return array{type:'warning'|'error', message:string|MessageSpecifier, params:array}[] */ public function getErrorsByType( $type ) { $result = []; foreach ( $this->errors as $error ) { if ( $error['type'] === $type ) { $result[] = $error; } } return $result; } /** * Returns a list of error messages, optionally only those of the given type * * If the `warning()` or `error()` method was called with a MessageSpecifier object, * this method is guaranteed to return the same object. * * @since 1.43 * @param ?string $type If provided, only return messages of the type 'warning' or 'error' * @phan-param null|'warning'|'error' $type * @return MessageSpecifier[] */ public function getMessages( ?string $type = null ): array { Assert::parameter( $type === null || $type === 'warning' || $type === 'error', '$type', "must be null, 'warning', or 'error'" ); $result = []; foreach ( $this->errors as $error ) { if ( $type === null || $error['type'] === $type ) { [ 'message' => $key, 'params' => $params ] = $error; if ( $key instanceof MessageSpecifier ) { $result[] = $key; } else { $result[] = new MessageValue( $key, $params ); } } } return $result; } /** * Returns true if the specified message is present as a warning or error. * Any message using the same key will be found (ignoring the message parameters). * * @param string $message Message key to search for * (this parameter used to allow MessageSpecifier too, deprecated since 1.43) * @return bool */ public function hasMessage( $message ) { if ( $message instanceof MessageSpecifier ) { wfDeprecatedMsg( 'Passing MessageSpecifier to hasMessage()' . ' was deprecated in MediaWiki 1.43', '1.43' ); $message = $message->getKey(); } foreach ( $this->errors as [ 'message' => $key ] ) { if ( ( $key instanceof MessageSpecifier && $key->getKey() === $message ) || $key === $message ) { return true; } } return false; } /** * Returns true if any other message than the specified ones is present as a warning or error. * Any messages using the same keys will be found (ignoring the message parameters). * * @param string ...$messages Message keys to search for * (this parameter used to allow MessageSpecifier too, deprecated since 1.43) * @return bool */ public function hasMessagesExcept( ...$messages ) { $exceptedKeys = []; foreach ( $messages as $message ) { if ( $message instanceof MessageSpecifier ) { wfDeprecatedMsg( 'Passing MessageSpecifier to hasMessagesExcept()' . ' was deprecated in MediaWiki 1.43', '1.43' ); $message = $message->getKey(); } $exceptedKeys[] = $message; } foreach ( $this->errors as [ 'message' => $key ] ) { if ( $key instanceof MessageSpecifier ) { $key = $key->getKey(); } if ( !in_array( $key, $exceptedKeys, true ) ) { return true; } } return false; } /** * If the specified source message exists, replace it with the specified * destination message, but keep the same parameters as in the original error. * * When using a string as the `$source` parameter, any message using the same key will be replaced * (regardless of whether it was stored as string or as MessageSpecifier, and ignoring the * message parameters). * * When using a MessageSpecifier as the `$source` parameter, the message will only be replaced * when the same MessageSpecifier object was stored in the StatusValue (compared with `===`). * Since the only reliable way to obtain one is to use getErrors(), which is deprecated, * passing a MessageSpecifier is deprecated (since 1.43). * * @param string $source Message key to search for * (this parameter used to allow MessageSpecifier too, deprecated since 1.43) * @param MessageSpecifier|string $dest Replacement message key or object * @return bool Return true if the replacement was done, false otherwise. */ public function replaceMessage( $source, $dest ) { $replaced = false; if ( $source instanceof MessageSpecifier ) { wfDeprecatedMsg( 'Passing MessageSpecifier as $source to replaceMessage()' . ' was deprecated in MediaWiki 1.43', '1.43' ); } foreach ( $this->errors as [ 'message' => &$message, 'params' => &$params ] ) { if ( $message === $source || ( $message instanceof MessageSpecifier && $message->getKey() === $source ) ) { $message = $dest; if ( $dest instanceof MessageSpecifier ) { // 'params' will be ignored now, so remove them from the internal array $params = []; } $replaced = true; } } return $replaced; } /** * Returns a string representation of the status for debugging. * This is fairly verbose and may change without notice. * * @return string */ public function __toString() { $status = $this->isOK() ? "OK" : "Error"; if ( count( $this->errors ) ) { $errorcount = "collected " . ( count( $this->errors ) ) . " message(s) on the way"; } else { $errorcount = "no errors detected"; } if ( isset( $this->value ) ) { $valstr = get_debug_type( $this->value ) . " value set"; } else { $valstr = "no value set"; } $out = sprintf( "<%s, %s, %s>", $status, $errorcount, $valstr ); if ( count( $this->errors ) > 0 ) { $hdr = sprintf( "+-%'-8s-+-%'-25s-+-%'-36s-+\n", "", "", "" ); $out .= "\n" . $hdr; foreach ( $this->errors as [ 'type' => $type, 'message' => $key, 'params' => $params ] ) { if ( $key instanceof MessageSpecifier ) { $params = $key->getParams(); $key = $key->getKey(); } $keyChunks = mb_str_split( $key, 25 ); $paramsChunks = mb_str_split( $this->flattenParams( $params, " | " ), 36 ); // array_map(null,...) is like Python's zip() foreach ( array_map( null, [ $type ], $keyChunks, $paramsChunks ) as [ $typeChunk, $keyChunk, $paramsChunk ] ) { $out .= sprintf( "| %-8s | %-25s | %-36s |\n", $typeChunk, $keyChunk, $paramsChunk ); } } $out .= $hdr; } return $out; } /** * @param array $params Message parameters * @param string $joiner * * @return string String representation */ private function flattenParams( array $params, string $joiner = ', ' ): string { $ret = []; foreach ( $params as $p ) { if ( is_array( $p ) ) { $r = '[ ' . self::flattenParams( $p ) . ' ]'; } elseif ( $p instanceof MessageSpecifier ) { $r = '{ ' . $p->getKey() . ': ' . self::flattenParams( $p->getParams() ) . ' }'; } elseif ( $p instanceof MessageParam ) { $r = $p->dump(); } else { $r = (string)$p; } $ret[] = mb_strlen( $r ) > 100 ? mb_substr( $r, 0, 99 ) . "..." : $r; } return implode( $joiner, $ret ); } /** * Returns a list of status messages of the given type (or all if false) * * @internal Only for use by Status. * * @param string|bool $type * @return array[] */ protected function getStatusArray( $type = false ) { $result = []; foreach ( $this->getErrors() as $error ) { if ( !$type || $error['type'] === $type ) { if ( $error['message'] instanceof MessageSpecifier ) { $result[] = [ $error['message']->getKey(), ...$error['message']->getParams() ]; } else { $result[] = [ $error['message'], ...$error['params'] ]; } } } return $result; } }