Merge "Allow constructing a Message from a MessageSpecifier"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 20 Jul 2015 23:05:26 +0000 (23:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 20 Jul 2015 23:05:26 +0000 (23:05 +0000)
1  2 
includes/GlobalFunctions.php
includes/Message.php
tests/phpunit/includes/MessageTest.php

@@@ -170,13 -170,13 +170,13 @@@ if ( !function_exists( 'hash_equals' ) 
   * This queues an extension to be loaded through
   * the ExtensionRegistry system.
   *
 - * @param string $name Name of the extension to load
 + * @param string $ext Name of the extension to load
   * @param string|null $path Absolute path of where to find the extension.json file
   */
 -function wfLoadExtension( $name, $path = null ) {
 +function wfLoadExtension( $ext, $path = null ) {
        if ( !$path ) {
 -              global $IP;
 -              $path = "$IP/extensions/$name/extension.json";
 +              global $wgExtensionDirectory;
 +              $path = "$wgExtensionDirectory/$ext/extension.json";
        }
        ExtensionRegistry::getInstance()->queue( $path );
  }
   * @param string[] $exts Array of extension names to load
   */
  function wfLoadExtensions( array $exts ) {
 -      global $IP;
 +      global $wgExtensionDirectory;
        $registry = ExtensionRegistry::getInstance();
        foreach ( $exts as $ext ) {
 -              $registry->queue( "$IP/extensions/$ext/extension.json" );
 +              $registry->queue( "$wgExtensionDirectory/$ext/extension.json" );
        }
  }
  
   * Load a skin
   *
   * @see wfLoadExtension
 - * @param string $name Name of the extension to load
 + * @param string $skin Name of the extension to load
   * @param string|null $path Absolute path of where to find the skin.json file
   */
 -function wfLoadSkin( $name, $path = null ) {
 +function wfLoadSkin( $skin, $path = null ) {
        if ( !$path ) {
 -              global $IP;
 -              $path = "$IP/skins/$name/skin.json";
 +              global $wgStyleDirectory;
 +              $path = "$wgStyleDirectory/$skin/skin.json";
        }
        ExtensionRegistry::getInstance()->queue( $path );
  }
   * @param string[] $skins Array of extension names to load
   */
  function wfLoadSkins( array $skins ) {
 -      global $IP;
 +      global $wgStyleDirectory;
        $registry = ExtensionRegistry::getInstance();
        foreach ( $skins as $skin ) {
 -              $registry->queue( "$IP/skins/$skin/skin.json" );
 +              $registry->queue( "$wgStyleDirectory/$skin/skin.json" );
        }
  }
  
@@@ -402,17 -402,12 +402,17 @@@ function wfRandomString( $length = 32 
   *
   * ;:@&=$-_.+!*'(),
   *
 + * RFC 1738 says ~ is unsafe, however RFC 3986 considers it an unreserved
 + * character which should not be encoded. More importantly, google chrome
 + * always converts %7E back to ~, and converting it in this function can
 + * cause a redirect loop (T105265).
 + *
   * But + is not safe because it's used to indicate a space; &= are only safe in
   * paths and not in queries (and we don't distinguish here); ' seems kind of
   * scary; and urlencode() doesn't touch -_. to begin with.  Plus, although /
   * is reserved, we don't care.  So the list we unescape is:
   *
 - * ;:@$!*(),/
 + * ;:@$!*(),/~
   *
   * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
   * so no fancy : for IIS7.
@@@ -431,7 -426,7 +431,7 @@@ function wfUrlencode( $s ) 
        }
  
        if ( is_null( $needle ) ) {
 -              $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
 +              $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%7E' );
                if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
                        ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
                ) {
        $s = urlencode( $s );
        $s = str_ireplace(
                $needle,
 -              array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
 +              array( ';', '@', '$', '!', '*', '(', ')', ',', '/', '~', ':' ),
                $s
        );
  
@@@ -865,9 -860,9 +865,9 @@@ function wfParseUrl( $url ) 
        if ( $wasRelative ) {
                $url = "http:$url";
        }
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        $bits = parse_url( $url );
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
        // parse_url() returns an array without scheme for some invalid URLs, e.g.
        // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
        if ( !$bits || !isset( $bits['scheme'] ) ) {
@@@ -1253,7 -1248,7 +1253,7 @@@ function wfLogProfilingData() 
        $profiler->logData();
  
        $config = $context->getConfig();
 -      if ( $config->has( 'StatsdServer' ) ) {
 +      if ( $config->get( 'StatsdServer' ) ) {
                $statsdServer = explode( ':', $config->get( 'StatsdServer' ) );
                $statsdHost = $statsdServer[0];
                $statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
@@@ -1351,14 -1346,9 +1351,14 @@@ function wfReadOnlyReason() 
                }
                // Callers use this method to be aware that data presented to a user
                // may be very stale and thus allowing submissions can be problematic.
 -              if ( $wgReadOnly === false && wfGetLB()->getLaggedSlaveMode() ) {
 +              try {
 +                      if ( $wgReadOnly === false && wfGetLB()->getLaggedSlaveMode() ) {
 +                              $wgReadOnly = 'The database has been automatically locked ' .
 +                                      'while the slave database servers catch up to the master';
 +                      }
 +              } catch ( DBConnectionError $e ) {
                        $wgReadOnly = 'The database has been automatically locked ' .
 -                              'while the slave database servers catch up to the master';
 +                              'until the slave database servers become available';
                }
        }
  
@@@ -1421,7 -1411,7 +1421,7 @@@ function wfGetLangObj( $langcode = fals
   *
   * This function replaces all old wfMsg* functions.
   *
-  * @param string|string[] $key Message key, or array of keys
+  * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
   * @param mixed $params,... Normal message parameters
   * @return Message
   *
@@@ -1761,7 -1751,7 +1761,7 @@@ function wfMsgExt( $key, $options ) 
        }
  
        if ( in_array( 'escape', $options, true ) ) {
 -              $string = htmlspecialchars ( $string );
 +              $string = htmlspecialchars( $string );
        } elseif ( in_array( 'escapenoentities', $options, true ) ) {
                $string = Sanitizer::escapeHtmlAllowEntities( $string );
        }
@@@ -2134,14 -2124,15 +2134,14 @@@ function wfVarDump( $var ) 
   */
  function wfHttpError( $code, $label, $desc ) {
        global $wgOut;
 -      header( "HTTP/1.0 $code $label" );
 -      header( "Status: $code $label" );
 +      HttpStatus::header( $code );
        if ( $wgOut ) {
                $wgOut->disable();
                $wgOut->sendCacheControl();
        }
  
        header( 'Content-type: text/html; charset=utf-8' );
 -      print "<!doctype html>" .
 +      print '<!DOCTYPE html>' .
                '<html><head><title>' .
                htmlspecialchars( $label ) .
                '</title></head><body><h1>' .
@@@ -2327,19 -2318,40 +2327,19 @@@ function wfNegotiateType( $cprefs, $spr
  /**
   * Reference-counted warning suppression
   *
 + * @deprecated since 1.26, use MediaWiki\suppressWarnings() directly
   * @param bool $end
   */
  function wfSuppressWarnings( $end = false ) {
 -      static $suppressCount = 0;
 -      static $originalLevel = false;
 -
 -      if ( $end ) {
 -              if ( $suppressCount ) {
 -                      --$suppressCount;
 -                      if ( !$suppressCount ) {
 -                              error_reporting( $originalLevel );
 -                      }
 -              }
 -      } else {
 -              if ( !$suppressCount ) {
 -                      $originalLevel = error_reporting( E_ALL & ~(
 -                              E_WARNING |
 -                              E_NOTICE |
 -                              E_USER_WARNING |
 -                              E_USER_NOTICE |
 -                              E_DEPRECATED |
 -                              E_USER_DEPRECATED |
 -                              E_STRICT
 -                      ) );
 -              }
 -              ++$suppressCount;
 -      }
 +      MediaWiki\suppressWarnings( $end );
  }
  
  /**
 + * @deprecated since 1.26, use MediaWiki\restoreWarnings() directly
   * Restore error level to previous value
   */
  function wfRestoreWarnings() {
 -      wfSuppressWarnings( true );
 +      MediaWiki\suppressWarnings( true );
  }
  
  # Autodetect, convert and provide timestamps of various types
@@@ -2447,7 -2459,7 +2447,7 @@@ function wfTimestampNow() 
  function wfIsWindows() {
        static $isWindows = null;
        if ( $isWindows === null ) {
 -              $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
 +              $isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
        }
        return $isWindows;
  }
@@@ -2461,6 -2473,20 +2461,6 @@@ function wfIsHHVM() 
        return defined( 'HHVM_VERSION' );
  }
  
 -/**
 - * Swap two variables
 - *
 - * @deprecated since 1.24
 - * @param mixed $x
 - * @param mixed $y
 - */
 -function swap( &$x, &$y ) {
 -      wfDeprecated( __FUNCTION__, '1.24' );
 -      $z = $x;
 -      $x = $y;
 -      $y = $z;
 -}
 -
  /**
   * Tries to get the system directory for temporary files. First
   * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
@@@ -2509,7 -2535,7 +2509,7 @@@ function wfMkdirParents( $dir, $mode = 
                wfDebug( "$caller: called wfMkdirParents($dir)\n" );
        }
  
 -      if ( strval( $dir ) === '' || ( file_exists( $dir ) && is_dir( $dir ) ) ) {
 +      if ( strval( $dir ) === '' || is_dir( $dir ) ) {
                return true;
        }
  
        }
  
        // Turn off the normal warning, we're doing our own below
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        $ok = mkdir( $dir, $mode, true ); // PHP5 <3
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
  
        if ( !$ok ) {
                //directory may have been created on another request since we last checked
@@@ -2763,7 -2789,7 +2763,7 @@@ function wfShellExec( $cmd, &$retval = 
  
        $useLogPipe = false;
        if ( is_executable( '/bin/bash' ) ) {
 -              $time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
 +              $time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
                if ( isset( $limits['walltime'] ) ) {
                        $wallTime = intval( $limits['walltime'] );
                } elseif ( isset( $limits['time'] ) ) {
                } else {
                        $wallTime = intval( $wgMaxShellWallClockTime );
                }
 -              $mem = intval ( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
 -              $filesize = intval ( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
 +              $mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
 +              $filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
  
                if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
                        $cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
@@@ -3017,9 -3043,9 +3017,9 @@@ function wfMerge( $old, $mine, $yours, 
  
        # This check may also protect against code injection in
        # case of broken installations.
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
  
        if ( !$haveDiff3 ) {
                wfDebug( "diff3 not found\n" );
@@@ -3096,9 -3122,9 +3096,9 @@@ function wfDiff( $before, $after, $para
        }
  
        global $wgDiff;
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        $haveDiff = $wgDiff && file_exists( $wgDiff );
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
  
        # This check may also protect against code injection in
        # case of broken installations.
@@@ -3336,7 -3362,7 +3336,7 @@@ function wfBaseConvert( $input, $source
                // Removing leading zeros works around broken base detection code in
                // some PHP versions (see <https://bugs.php.net/bug.php?id=50175> and
                // <https://bugs.php.net/bug.php?id=55398>).
 -              $result = gmp_strval( gmp_init( ltrim( $input, '0' ), $sourceBase ), $destBase );
 +              $result = gmp_strval( gmp_init( ltrim( $input, '0' ) ?: '0', $sourceBase ), $destBase );
        } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
                $decimal = '0';
                foreach ( str_split( strtolower( $input ) ) as $char ) {
@@@ -3475,9 -3501,9 +3475,9 @@@ function wfSetupSession( $sessionId = f
        } else {
                wfFixSessionID();
        }
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        session_start();
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
  }
  
  /**
@@@ -3500,7 -3526,7 +3500,7 @@@ function wfGetPrecompiledData( $name ) 
  }
  
  /**
 - * Get a cache key
 + * Make a cache key for the local wiki.
   *
   * @param string $args,...
   * @return string
@@@ -3510,13 -3536,12 +3510,13 @@@ function wfMemcKey( /*...*/ ) 
        $prefix = $wgCachePrefix === false ? wfWikiID() : $wgCachePrefix;
        $args = func_get_args();
        $key = $prefix . ':' . implode( ':', $args );
 -      $key = str_replace( ' ', '_', $key );
 -      return $key;
 +      return strtr( $key, ' ', '_' );
  }
  
  /**
 - * Get a cache key for a foreign DB
 + * Make a cache key for a foreign DB.
 + *
 + * Must match what wfMemcKey() would produce in context of the foreign wiki.
   *
   * @param string $db
   * @param string $prefix
  function wfForeignMemcKey( $db, $prefix /*...*/ ) {
        $args = array_slice( func_get_args(), 2 );
        if ( $prefix ) {
 +              // Match wfWikiID() logic
                $key = "$db-$prefix:" . implode( ':', $args );
        } else {
                $key = $db . ':' . implode( ':', $args );
        }
 -      return str_replace( ' ', '_', $key );
 +      return strtr( $key, ' ', '_' );
 +}
 +
 +/**
 + * Make a cache key with database-agnostic prefix.
 + *
 + * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
 + * instead. Must have a prefix as otherwise keys that use a database name
 + * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
 + *
 + * @since 1.26
 + * @param string $args,...
 + * @return string
 + */
 +function wfGlobalCacheKey( /*...*/ ) {
 +      $args = func_get_args();
 +      $key = 'global:' . implode( ':', $args );
 +      return strtr( $key, ' ', '_' );
  }
  
  /**
@@@ -3757,7 -3764,6 +3757,7 @@@ function wfWaitForSlaves
        }
  
        // Figure out which clusters need to be checked
 +      /** @var LoadBalancer[] $lbs */
        $lbs = array();
        if ( $cluster === '*' ) {
                wfGetLBFactory()->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) {
        // time needed to wait on the next clusters.
        $masterPositions = array_fill( 0, count( $lbs ), false );
        foreach ( $lbs as $i => $lb ) {
 -              // bug 27975 - Don't try to wait for slaves if there are none
 -              // Prevents permission error when getting master position
 -              if ( $lb->getServerCount() > 1 ) {
 -                      if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
 -                              continue; // assume no writes done
 -                      }
 -                      // Use the empty string to not trigger selectDB() since the connection
 -                      // may have been to a server that does not have a DB for the current wiki.
 -                      $dbw = $lb->getConnection( DB_MASTER, array(), '' );
 -                      if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
 -                              continue; // no writes since the last wait
 -                      }
 -                      $masterPositions[$i] = $dbw->getMasterPos();
 +              if ( $lb->getServerCount() <= 1 ) {
 +                      // Bug 27975 - Don't try to wait for slaves if there are none
 +                      // Prevents permission error when getting master position
 +                      continue;
 +              } elseif ( $ifWritesSince && $lb->lastMasterChangeTimestamp() < $ifWritesSince ) {
 +                      continue; // no writes since the last wait
                }
 +              $masterPositions[$i] = $lb->getMasterPos();
        }
  
        $ok = true;
@@@ -3849,15 -3861,15 +3849,15 @@@ function wfMemoryLimit() 
                $conflimit = wfShorthandToInteger( $wgMemoryLimit );
                if ( $conflimit == -1 ) {
                        wfDebug( "Removing PHP's memory limit\n" );
 -                      wfSuppressWarnings();
 +                      MediaWiki\suppressWarnings();
                        ini_set( 'memory_limit', $conflimit );
 -                      wfRestoreWarnings();
 +                      MediaWiki\restoreWarnings();
                        return $conflimit;
                } elseif ( $conflimit > $memlimit ) {
                        wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
 -                      wfSuppressWarnings();
 +                      MediaWiki\suppressWarnings();
                        ini_set( 'memory_limit', $conflimit );
 -                      wfRestoreWarnings();
 +                      MediaWiki\restoreWarnings();
                        return $conflimit;
                }
        }
@@@ -4002,9 -4014,9 +4002,9 @@@ function wfUnpack( $format, $data, $len
                }
        }
  
 -      wfSuppressWarnings();
 +      MediaWiki\suppressWarnings();
        $result = unpack( $format, $data );
 -      wfRestoreWarnings();
 +      MediaWiki\restoreWarnings();
  
        if ( $result === false ) {
                // If it cannot extract the packed data.
diff --combined includes/Message.php
   *
   * @since 1.17
   */
 -class Message implements MessageSpecifier {
 +class Message implements MessageSpecifier, Serializable {
  
        /**
         * In which language to get this message. True, which is the default,
        /**
         * @since 1.17
         *
-        * @param string|string[] $key Message key or array of message keys to try and use the first
-        * non-empty message for.
+        * @param string|string[]|MessageSpecifier $key Message key, or array of
+        * message keys to try and use the first non-empty message for, or a
+        * MessageSpecifier to copy from.
         * @param array $params Message parameters.
         * @param Language $language Optional language of the message, defaults to $wgLang.
         *
        public function __construct( $key, $params = array(), Language $language = null ) {
                global $wgLang;
  
+               if ( $key instanceof MessageSpecifier ) {
+                       if ( $params ) {
+                               throw new InvalidArgumentException(
+                                       '$params must be empty if $key is a MessageSpecifier'
+                               );
+                       }
+                       $params = $key->getParams();
+                       $key = $key->getKey();
+               }
                if ( !is_string( $key ) && !is_array( $key ) ) {
                        throw new InvalidArgumentException( '$key must be a string or an array' );
                }
                $this->language = $language ?: $wgLang;
        }
  
 +      /**
 +       * @see Serializable::serialize()
 +       * @since 1.26
 +       * @return string
 +       */
 +      public function serialize() {
 +              return serialize( array(
 +                      'interface' => $this->interface,
 +                      'language' => $this->language->getCode(),
 +                      'key' => $this->key,
 +                      'keysToTry' => $this->keysToTry,
 +                      'parameters' => $this->parameters,
 +                      'format' => $this->format,
 +                      'useDatabase' => $this->useDatabase,
 +                      'title' => $this->title,
 +              ) );
 +      }
 +
 +      /**
 +       * @see Serializable::unserialize()
 +       * @since 1.26
 +       * @param string $serialized
 +       */
 +      public function unserialize( $serialized ) {
 +              $data = unserialize( $serialized );
 +              $this->interface = $data['interface'];
 +              $this->key = $data['key'];
 +              $this->keysToTry = $data['keysToTry'];
 +              $this->parameters = $data['parameters'];
 +              $this->format = $data['format'];
 +              $this->useDatabase = $data['useDatabase'];
 +              $this->language = Language::factory( $data['language'] );
 +              $this->title = $data['title'];
 +      }
 +
        /**
         * @since 1.24
         *
         *
         * @since 1.17
         *
-        * @param string|string[] $key Message key or array of keys.
+        * @param string|string[]|MessageSpecifier $key
         * @param mixed $param,... Parameters as strings.
         *
         * @return Message
                return new self( $keys );
        }
  
 +      /**
 +       * Get a title object for a mediawiki message, where it can be found in the mediawiki namespace.
 +       * The title will be for the current language, if the message key is in
 +       * $wgForceUIMsgAsContentMsg it will be append with the language code (except content
 +       * language), because Message::inContentLanguage will also return in user language.
 +       *
 +       * @see $wgForceUIMsgAsContentMsg
 +       * @return Title
 +       * @since 1.26
 +       */
 +      public function getTitle() {
 +              global $wgContLang, $wgForceUIMsgAsContentMsg;
 +
 +              $code = $this->language->getCode();
 +              $title = $this->key;
 +              if (
 +                      $wgContLang->getCode() !== $code
 +                      && in_array( $this->key, (array)$wgForceUIMsgAsContentMsg )
 +              ) {
 +                      $title .= '/' . $code;
 +              }
 +
 +              return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( strtr( $title, ' ', '_' ) ) );
 +      }
 +
        /**
         * Adds parameters to the parameter list of this message.
         *
                if ( $lang instanceof Language || $lang instanceof StubUserLang ) {
                        $this->language = $lang;
                } elseif ( is_string( $lang ) ) {
 -                      if ( $this->language->getCode() != $lang ) {
 +                      if ( !$this->language instanceof Language || $this->language->getCode() != $lang ) {
                                $this->language = Language::factory( $lang );
                        }
                } else {
@@@ -21,6 -21,17 +21,17 @@@ class MessageTest extends MediaWikiLang
                $this->assertEquals( $key, $message->getKey() );
                $this->assertEquals( $params, $message->getParams() );
                $this->assertEquals( $expectedLang, $message->getLanguage() );
+               $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' );
+               $messageSpecifier->expects( $this->any() )
+                       ->method( 'getKey' )->will( $this->returnValue( $key ) );
+               $messageSpecifier->expects( $this->any() )
+                       ->method( 'getParams' )->will( $this->returnValue( $params ) );
+               $message = new Message( $messageSpecifier, array(), $language );
+               $this->assertEquals( $key, $message->getKey() );
+               $this->assertEquals( $params, $message->getParams() );
+               $this->assertEquals( $expectedLang, $message->getLanguage() );
        }
  
        public static function provideConstructor() {
        public function testInLanguageThrows() {
                wfMessage( 'foo' )->inLanguage( 123 );
        }
 +
 +      /**
 +       * @covers Message::serialize
 +       * @covers Message::unserialize
 +       */
 +      public function testSerialization() {
 +              $msg = new Message( 'parentheses' );
 +              $msg->rawParams( '<a>foo</a>' );
 +              $msg->title( Title::newFromText( 'Testing' ) );
 +              $this->assertEquals( '(<a>foo</a>)', $msg->parse(), 'Sanity check' );
 +              $msg = unserialize( serialize( $msg ) );
 +              $this->assertEquals( '(<a>foo</a>)', $msg->parse() );
 +              $title = TestingAccessWrapper::newFromObject( $msg )->title;
 +              $this->assertInstanceOf( 'Title', $title );
 +              $this->assertEquals( 'Testing', $title->getFullText() );
 +
 +              $msg = new Message( 'mainpage' );
 +              $msg->inLanguage( 'de' );
 +              $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' );
 +              $msg = unserialize( serialize( $msg ) );
 +              $this->assertEquals( 'Hauptseite', $msg->plain() );
 +      }
  }