]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/Status.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / Status.php
index f049980f64810ed5d0346f085afd1313e5c737cc..a35af6e8c6be9560da0aa7137e09cff36353ebc9 100644 (file)
@@ -1,4 +1,24 @@
 <?php
+/**
+ * Generic operation result.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
 
 /**
  * Generic operation result class
  * An operation which is not OK should have errors so that the user can be
  * informed as to what went wrong. Calling the fatal() function sets an error
  * message and simultaneously switches off the OK flag.
+ *
+ * The recommended pattern for Status objects is to return a Status object
+ * unconditionally, i.e. both on success and on failure -- so that the
+ * developer of the calling code is reminded that the function can fail, and
+ * so that a lack of error-handling will be explicit.
  */
-class Status {
-       var $ok = true;
-       var $value;
-
-       /** Counters for batch operations */
-       var $successCount = 0, $failCount = 0;
-
-       /*semi-private*/ var $errors = array();
-       /*semi-private*/ var $cleanCallback = false;
+class Status extends StatusValue {
+       /** @var callable */
+       public $cleanCallback = false;
 
        /**
-        * Factory function for fatal errors
+        * Succinct helper method to wrap a StatusValue
         *
-        * @param $message String: message name
-        */
-       static function newFatal( $message /*, parameters...*/ ) {
-               $params = func_get_args();
-               $result = new self;
-               call_user_func_array( array( &$result, 'error' ), $params );
-               $result->ok = false;
-               return $result;
-       }
-
-       /**
-        * Factory function for good results
+        * This is is useful when formatting StatusValue objects:
+        * @code
+        *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
+        * @endcode
         *
-        * @param $value Mixed
+        * @param StatusValue|Status $sv
+        * @return Status
         */
-       static function newGood( $value = null ) {
-               $result = new self;
-               $result->value = $value;
+       public static function wrap( $sv ) {
+               if ( $sv instanceof static ) {
+                       return $sv;
+               }
+
+               $result = new static();
+               $result->ok =& $sv->ok;
+               $result->errors =& $sv->errors;
+               $result->value =& $sv->value;
+               $result->successCount =& $sv->successCount;
+               $result->failCount =& $sv->failCount;
+               $result->success =& $sv->success;
+
                return $result;
        }
 
        /**
-        * Change operation result
+        * Backwards compatibility logic
         *
-        * @param $ok Boolean: whether to operation completed
-        * @param $value Mixed
+        * @param string $name
+        * @return mixed
+        * @throws RuntimeException
         */
-       function setResult( $ok, $value = null ) {
-               $this->ok = $ok;
-               $this->value = $value;
-       }
+       function __get( $name ) {
+               if ( $name === 'ok' ) {
+                       return $this->isOK();
+               } elseif ( $name === 'errors' ) {
+                       return $this->getErrors();
+               }
 
-       /**
-        * Returns whether the operation completed and didn't have any error or
-        * warnings
-        *
-        * @return Boolean
-        */
-       function isGood() {
-               return $this->ok && !$this->errors;
+               throw new RuntimeException( "Cannot get '$name' property." );
        }
 
        /**
-        * Returns whether the operation completed
+        * Change operation result
+        * Backwards compatibility logic
         *
-        * @return Boolean
+        * @param string $name
+        * @param mixed $value
+        * @throws RuntimeException
         */
-       function isOK() {
-               return $this->ok;
+       function __set( $name, $value ) {
+               if ( $name === 'ok' ) {
+                       $this->setOK( $value );
+               } elseif ( !property_exists( $this, $name ) ) {
+                       // Caller is using undeclared ad-hoc properties
+                       $this->$name = $value;
+               } else {
+                       throw new RuntimeException( "Cannot set '$name' property." );
+               }
        }
 
        /**
-        * Add a new warning
+        * Splits this Status object into two new Status objects, one which contains only
+        * the error messages, and one that contains the warnings, only. The returned array is
+        * defined as:
+        * [
+        *     0 => object(Status) # the Status with error messages, only
+        *     1 => object(Status) # The Status with warning messages, only
+        * ]
         *
-        * @param $message String: message name
+        * @return Status[]
         */
-       function warning( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'warning',
-                       'message' => $message,
-                       'params' => $params );
-       }
+       public function splitByErrorType() {
+               list( $errorsOnlyStatus, $warningsOnlyStatus ) = parent::splitByErrorType();
+               $errorsOnlyStatus->cleanCallback =
+                       $warningsOnlyStatus->cleanCallback = $this->cleanCallback;
 
-       /**
-        * Add an error, do not set fatal flag
-        * This can be used for non-fatal errors
-        *
-        * @param $message String: message name
-        */
-       function error( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
+               return [ $errorsOnlyStatus, $warningsOnlyStatus ];
        }
 
        /**
-        * Add an error and set OK to false, indicating that the operation
-        * as a whole was fatal
-        *
-        * @param $message String: message name
+        * Returns the wrapped StatusValue object
+        * @return StatusValue
+        * @since 1.27
         */
-       function fatal( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
-               $this->ok = false;
+       public function getStatusValue() {
+               return $this;
        }
 
        /**
-        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
+        * @param array $params
+        * @return array
         */
-       function __wakeup() {
-               $this->cleanCallback = false;
-       }
-
-       protected function cleanParams( $params ) {
+       protected function cleanParams( array $params ) {
                if ( !$this->cleanCallback ) {
                        return $params;
                }
-               $cleanParams = array();
+               $cleanParams = [];
                foreach ( $params as $i => $param ) {
                        $cleanParams[$i] = call_user_func( $this->cleanCallback, $param );
                }
                return $cleanParams;
        }
 
-       protected function getItemXML( $item ) {
-               $params = $this->cleanParams( $item['params'] );
-               $xml = "<{$item['type']}>\n" .
-                       Xml::element( 'message', null, $item['message'] ) . "\n" .
-                       Xml::element( 'text', null, wfMsgReal( $item['message'], $params ) ) ."\n";
-               foreach ( $params as $param ) {
-                       $xml .= Xml::element( 'param', null, $param );
+       /**
+        * @param string|Language|null $lang Language to use for processing
+        *  messages, or null to default to the user language.
+        * @return Language
+        */
+       protected function languageFromParam( $lang ) {
+               global $wgLang;
+
+               if ( $lang === null ) {
+                       // @todo: Use RequestContext::getMain()->getLanguage() instead
+                       return $wgLang;
+               } elseif ( $lang instanceof Language || $lang instanceof StubUserLang ) {
+                       return $lang;
+               } else {
+                       return Language::factory( $lang );
                }
-               $xml .= "</{$this->type}>\n";
-               return $xml;
        }
 
        /**
-        * Get the error list as XML
+        * Get the error list as a wikitext formatted list
+        *
+        * @param string|bool $shortContext A short enclosing context message name, to
+        *        be used when there is a single error
+        * @param string|bool $longContext A long enclosing context message name, for a list
+        * @param string|Language $lang Language to use for processing messages
+        * @return string
         */
-       function getXML() {
-               $xml = "<errors>\n";
-               foreach ( $this->errors as $error ) {
-                       $xml .= $this->getItemXML( $error );
+       public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
+               $lang = $this->languageFromParam( $lang );
+
+               $rawErrors = $this->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->isOK() ) {
+                               $this->fatal( 'internalerror_info',
+                                       __METHOD__ . " called for a good result, this is incorrect\n" );
+                       } else {
+                               $this->fatal( 'internalerror_info',
+                                       __METHOD__ . ": Invalid result object: no error text but not OK\n" );
+                       }
+                       $rawErrors = $this->getErrors(); // just added a fatal
+               }
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0], $lang )->plain();
+                       if ( $shortContext ) {
+                               $s = wfMessage( $shortContext, $s )->inLanguage( $lang )->plain();
+                       } elseif ( $longContext ) {
+                               $s = wfMessage( $longContext, "* $s\n" )->inLanguage( $lang )->plain();
+                       }
+               } else {
+                       $errors = $this->getErrorMessageArray( $rawErrors, $lang );
+                       foreach ( $errors as &$error ) {
+                               $error = $error->plain();
+                       }
+                       $s = '* ' . implode( "\n* ", $errors ) . "\n";
+                       if ( $longContext ) {
+                               $s = wfMessage( $longContext, $s )->inLanguage( $lang )->plain();
+                       } elseif ( $shortContext ) {
+                               $s = wfMessage( $shortContext, "\n$s\n" )->inLanguage( $lang )->plain();
+                       }
                }
-               $xml .= "</errors>\n";
-               return $xml;
+               return $s;
        }
 
        /**
-        * Get the error list as a wikitext formatted list
+        * Get a bullet list of the errors as a Message object.
         *
-        * @param $shortContext String: a short enclosing context message name, to
-        *        be used when there is a single error
-        * @param $longContext String: a long enclosing context message name, for a list
-        * @return String
+        * $shortContext and $longContext can be used to wrap the error list in some text.
+        * $shortContext will be preferred when there is a single error; $longContext will be
+        * preferred when there are multiple ones. In either case, $1 will be replaced with
+        * the list of errors.
+        *
+        * $shortContext is assumed to use $1 as an inline parameter: if there is a single item,
+        * it will not be made into a list; if there are multiple items, newlines will be inserted
+        * around the list.
+        * $longContext is assumed to use $1 as a standalone parameter; it will always receive a list.
+        *
+        * If both parameters are missing, and there is only one error, no bullet will be added.
+        *
+        * @param string|string[]|bool $shortContext A message name or an array of message names.
+        * @param string|string[]|bool $longContext A message name or an array of message names.
+        * @param string|Language $lang Language to use for processing messages
+        * @return Message
         */
-       function getWikiText( $shortContext = false, $longContext = false ) {
-               if ( count( $this->errors ) == 0 ) {
-                       if ( $this->ok ) {
+       public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
+               $lang = $this->languageFromParam( $lang );
+
+               $rawErrors = $this->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->isOK() ) {
                                $this->fatal( 'internalerror_info',
-                                       __METHOD__." called for a good result, this is incorrect\n" );
+                                       __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
                                $this->fatal( 'internalerror_info',
-                                       __METHOD__.": Invalid result object: no error text but not OK\n" );
+                                       __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
+                       $rawErrors = $this->getErrors(); // just added a fatal
                }
-               if ( count( $this->errors ) == 1 ) {
-                       $s = $this->getWikiTextForError( $this->errors[0], $this->errors[0]  );
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0], $lang );
                        if ( $shortContext ) {
-                               $s = wfMsgNoTrans( $shortContext, $s );
+                               $s = wfMessage( $shortContext, $s )->inLanguage( $lang );
                        } elseif ( $longContext ) {
-                               $s = wfMsgNoTrans( $longContext, "* $s\n" );
+                               $wrapper = new RawMessage( "* \$1\n" );
+                               $wrapper->params( $s )->parse();
+                               $s = wfMessage( $longContext, $wrapper )->inLanguage( $lang );
                        }
                } else {
-                       $s = '* '. implode("\n* ",
-                               $this->getWikiTextArray( $this->errors ) ) . "\n";
+                       $msgs = $this->getErrorMessageArray( $rawErrors, $lang );
+                       $msgCount = count( $msgs );
+
+                       $s = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
+                       $s->params( $msgs )->parse();
+
                        if ( $longContext ) {
-                               $s = wfMsgNoTrans( $longContext, $s );
+                               $s = wfMessage( $longContext, $s )->inLanguage( $lang );
                        } elseif ( $shortContext ) {
-                               $s = wfMsgNoTrans( $shortContext, "\n$s\n" );
+                               $wrapper = new RawMessage( "\n\$1\n", [ $s ] );
+                               $wrapper->parse();
+                               $s = wfMessage( $shortContext, $wrapper )->inLanguage( $lang );
                        }
                }
+
                return $s;
        }
 
        /**
-        * Return the wiki text for a single error.
-        * @param $error Mixed With an array & two values keyed by
-        * 'message' and 'params', use those keys-value pairs.
-        * Otherwise, if its an array, just use the first value as the
-        * message and the remaining items as the params.
+        * Return the message for a single error
+        *
+        * The code string can be used a message key with per-language versions.
+        * If $error is an array, the "params" field is a list of parameters for the message.
         *
-        * @return String
+        * @param array|string $error Code string or (key: code string, params: string[]) map
+        * @param string|Language $lang Language to use for processing messages
+        * @return Message
         */
-       protected function getWikiTextForError( $error ) {
+       protected function getErrorMessage( $error, $lang = null ) {
                if ( is_array( $error ) ) {
-                       if ( isset( $error['message'] ) && isset( $error['params'] ) ) {
-                               return wfMsgReal( $error['message'],
-                                       array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ),
-                                       true, false, false );
+                       if ( isset( $error['message'] ) && $error['message'] instanceof Message ) {
+                               $msg = $error['message'];
+                       } elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
+                               $msg = wfMessage( $error['message'],
+                                       array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
                        } else {
-                               $message = array_shift($error);
-                               return wfMsgReal( $message,
-                                       array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ),
-                                       true, false, false );
+                               $msgName = array_shift( $error );
+                               $msg = wfMessage( $msgName,
+                                       array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
                        }
+               } elseif ( is_string( $error ) ) {
+                       $msg = wfMessage( $error );
                } else {
-                       return wfMsgReal( $error, array(), true, false, false);
+                       throw new UnexpectedValueException( "Got " . get_class( $error ) . " for key." );
                }
+
+               $msg->inLanguage( $this->languageFromParam( $lang ) );
+               return $msg;
        }
 
        /**
-        * Return an array with the wikitext for each item in the array.
-        * @param $errors Array
-        * @return Array
+        * Get the error message as HTML. This is done by parsing the wikitext error message
+        * @param string|bool $shortContext A short enclosing context message name, to
+        *        be used when there is a single error
+        * @param string|bool $longContext A long enclosing context message name, for a list
+        * @param string|Language|null $lang Language to use for processing messages
+        * @return string
         */
-       function getWikiTextArray( $errors ) {
-               return array_map( array( $this, 'getWikiTextForError' ), $errors );
+       public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
+               $lang = $this->languageFromParam( $lang );
+               $text = $this->getWikiText( $shortContext, $longContext, $lang );
+               $out = MessageCache::singleton()->parse( $text, null, true, true, $lang );
+               return $out instanceof ParserOutput ? $out->getText() : $out;
        }
 
        /**
-        * Merge another status object into this one
-        *
-        * @param $other Other Status object
-        * @param $overwriteValue Boolean: whether to override the "value" member
+        * Return an array with a Message object for each error.
+        * @param array $errors
+        * @param string|Language $lang Language to use for processing messages
+        * @return Message[]
         */
-       function merge( $other, $overwriteValue = false ) {
-               $this->errors = array_merge( $this->errors, $other->errors );
-               $this->ok = $this->ok && $other->ok;
-               if ( $overwriteValue ) {
-                       $this->value = $other->value;
-               }
-               $this->successCount += $other->successCount;
-               $this->failCount += $other->failCount;
+       protected function getErrorMessageArray( $errors, $lang = null ) {
+               $lang = $this->languageFromParam( $lang );
+               return array_map( function ( $e ) use ( $lang ) {
+                       return $this->getErrorMessage( $e, $lang );
+               }, $errors );
        }
 
        /**
         * Get the list of errors (but not warnings)
         *
-        * @return Array
+        * @return array A list in which each entry is an array with a message key as its first element.
+        *         The remaining array elements are the message parameters.
+        * @deprecated since 1.25
         */
-       function getErrorsArray() {
-               return $this->getStatusArray( "error" );
+       public function getErrorsArray() {
+               return $this->getStatusArray( 'error' );
        }
 
        /**
         * Get the list of warnings (but not errors)
         *
-        * @return Array
+        * @return array A list in which each entry is an array with a message key as its first element.
+        *         The remaining array elements are the message parameters.
+        * @deprecated since 1.25
         */
-       function getWarningsArray() {
-               return $this->getStatusArray( "warning" );
+       public function getWarningsArray() {
+               return $this->getStatusArray( 'warning' );
        }
 
        /**
-        * Returns a list of status messages of the given type
-        * @param $type String
+        * Returns a list of status messages of the given type (or all if false)
         *
-        * @return Array
+        * @note: this handles RawMessage poorly
+        *
+        * @param string|bool $type
+        * @return array
         */
-       protected function getStatusArray( $type ) {
-               $result = array();
-               foreach ( $this->errors as $error ) {
-                       if ( $error['type'] === $type ) {
-                               if( $error['params'] ) {
-                                       $result[] = array_merge( array( $error['message'] ), $error['params'] );
+       protected function getStatusArray( $type = false ) {
+               $result = [];
+
+               foreach ( $this->getErrors() as $error ) {
+                       if ( $type === false || $error['type'] === $type ) {
+                               if ( $error['message'] instanceof MessageSpecifier ) {
+                                       $result[] = array_merge(
+                                               [ $error['message']->getKey() ],
+                                               $error['message']->getParams()
+                                       );
+                               } elseif ( $error['params'] ) {
+                                       $result[] = array_merge( [ $error['message'] ], $error['params'] );
                                } else {
-                                       $result[] = $error['message'];
+                                       $result[] = [ $error['message'] ];
                                }
                        }
                }
+
                return $result;
        }
-       /**
-        * Returns true if the specified message is present as a warning or error
-        *
-        * @param $msg String: message name
-        * @return Boolean
-        */
-       function hasMessage( $msg ) {
-               foreach ( $this->errors as $error ) {
-                       if ( $error['message'] === $msg ) {
-                               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.
-        *
-        * Return true if the replacement was done, false otherwise.
+        * Don't save the callback when serializing, because Closures can't be
+        * serialized and we're going to clear it in __wakeup anyway.
         */
-       function replaceMessage( $source, $dest ) {
-               $replaced = false;
-               foreach ( $this->errors as $index => $error ) {
-                       if ( $error['message'] === $source ) {
-                               $this->errors[$index]['message'] = $dest;
-                               $replaced = true;
-                       }
-               }
-               return $replaced;
+       function __sleep() {
+               $keys = array_keys( get_object_vars( $this ) );
+               return array_diff( $keys, [ 'cleanCallback' ] );
        }
 
        /**
-        * Backward compatibility function for WikiError -> Status migration
-        *
-        * @return String
+        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
         */
-       public function getMessage() {
-               return $this->getWikiText();
+       function __wakeup() {
+               $this->cleanCallback = false;
        }
 }