Merge "EditPage: Allow the 'save' button's label to be 'publish' for public wikis"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 24 Aug 2016 00:17:38 +0000 (00:17 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 24 Aug 2016 00:17:38 +0000 (00:17 +0000)
1  2 
includes/DefaultSettings.php
includes/EditPage.php

@@@ -284,10 -284,10 +284,10 @@@ $wgLogo = false
   *
   * @par Example:
   * @code
 - * $wgLogoHD = array(
 + * $wgLogoHD = [
   *    "1.5x" => "path/to/1.5x_version.png",
   *    "2x" => "path/to/2x_version.png"
 - * );
 + * ];
   * @endcode
   *
   * @since 1.25
@@@ -398,13 -398,9 +398,13 @@@ $wgAllowImageMoving = true
  $wgEnableAsyncUploads = false;
  
  /**
 - * These are additional characters that should be replaced with '-' in filenames
 + * Additional characters that are not allowed in filenames. They are replaced with '-' when
 + * uploading. Like $wgLegalTitleChars, this is a regexp character class.
 + *
 + * Slashes and backslashes are disallowed regardless of this setting, but included here for
 + * completeness.
   */
 -$wgIllegalFileChars = ":";
 +$wgIllegalFileChars = ":\\/\\\\";
  
  /**
   * What directory to place deleted uploads in.
@@@ -536,7 -532,7 +536,7 @@@ $wgUseInstantCommons = false
   * The string 'local' signifies the default local file repository.
   *
   * Example:
 - * $wgForeignUploadTargets = array( 'shared' );
 + * $wgForeignUploadTargets = [ 'shared' ];
   */
  $wgForeignUploadTargets = [ 'local' ];
  
@@@ -685,7 -681,7 +685,7 @@@ $wgUseSharedUploads = false
  /**
   * Full path on the web server where shared uploads can be found
   */
 -$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
 +$wgSharedUploadPath = null;
  
  /**
   * Fetch commons image description pages and display them on the local wiki?
@@@ -695,7 -691,7 +695,7 @@@ $wgFetchCommonsDescriptions = false
  /**
   * Path on the file system where shared uploads can be found.
   */
 -$wgSharedUploadDirectory = "/var/www/wiki3/images";
 +$wgSharedUploadDirectory = null;
  
  /**
   * DB name with metadata about shared directory.
@@@ -759,10 -755,10 +759,10 @@@ $wgCopyUploadTimeout = false
   *
   * @par Example:
   * @code
 - * $wgMaxUploadSize = array(
 + * $wgMaxUploadSize = [
   *     '*' => 250 * 1024,
   *     'url' => 500 * 1024,
 - * );
 + * ];
   * @endcode
   * Sets the maximum for all uploads to 250 kB except for upload-by-url, which
   * will have a maximum of 500 kB.
@@@ -945,11 -941,22 +945,11 @@@ $wgTrustedMediaFormats = 
  /**
   * Plugins for media file type handling.
   * Each entry in the array maps a MIME type to a class name
 + *
 + * Core media handlers are listed in MediaHandlerFactory,
 + * and extensions should use extension.json.
   */
 -$wgMediaHandlers = [
 -      'image/jpeg' => 'JpegHandler',
 -      'image/png' => 'PNGHandler',
 -      'image/gif' => 'GIFHandler',
 -      'image/tiff' => 'TiffHandler',
 -      'image/webp' => 'WebPHandler',
 -      'image/x-ms-bmp' => 'BmpHandler',
 -      'image/x-bmp' => 'BmpHandler',
 -      'image/x-xcf' => 'XCFHandler',
 -      'image/svg+xml' => 'SvgHandler', // official
 -      'image/svg' => 'SvgHandler', // compat
 -      'image/vnd.djvu' => 'DjVuHandler', // official
 -      'image/x.djvu' => 'DjVuHandler', // compat
 -      'image/x-djvu' => 'DjVuHandler', // compat
 -];
 +$wgMediaHandlers = [];
  
  /**
   * Plugins for page content model handling.
@@@ -1156,9 -1163,9 +1156,9 @@@ $wgMaxAnimatedGifArea = 1.25e7
   * @par Example:
   * @code
   *  // PNG is lossless, but inefficient for photos
 - *  $wgTiffThumbnailType = array( 'png', 'image/png' );
 + *  $wgTiffThumbnailType = [ 'png', 'image/png' ];
   *  // JPEG is good for photos, but has no transparency support. Bad for diagrams.
 - *  $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
 + *  $wgTiffThumbnailType = [ 'jpg', 'image/jpeg' ];
   * @endcode
   */
  $wgTiffThumbnailType = false;
@@@ -1314,7 -1321,7 +1314,7 @@@ $wgTrivialMimeDetection = false
  
  /**
   * Additional XML types we can allow via MIME-detection.
 - * array = ( 'rootElement' => 'associatedMimeType' )
 + * array = [ 'rootElement' => 'associatedMimeType' ]
   */
  $wgXMLMimeTypes = [
        'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
@@@ -1371,7 -1378,7 +1371,7 @@@ $wgThumbnailBuckets = null
   * needs in order to be used as the reference for a given thumbnail. For example, with the
   * following buckets:
   *
 - * $wgThumbnailBuckets = array ( 128, 256, 512 );
 + * $wgThumbnailBuckets = [ 128, 256, 512 ];
   *
   * and a distance of 50:
   *
@@@ -1437,10 -1444,7 +1437,10 @@@ $wgGalleryOptions = 
        'imagesPerRow' => 0, // Default number of images per-row in the gallery. 0 -> Adapt to screensize
        'imageWidth' => 120, // Width of the cells containing images in galleries (in "px")
        'imageHeight' => 120, // Height of the cells containing images in galleries (in "px")
 -      'captionLength' => 25, // Length of caption to truncate (in characters)
 +      'captionLength' => true, // Deprecated @since 1.28
 +                               // Length to truncate filename to in caption when using "showfilename".
 +                               // A value of 'true' will truncate the filename to one line using CSS
 +                               // and will be the behaviour after deprecation.
        'showBytes' => true, // Show the filesize in bytes in categories
        'mode' => 'traditional',
  ];
@@@ -1620,14 -1624,14 +1620,14 @@@ $wgPasswordExpireGrace = 3600 * 24 * 7
   * Default to false or fill an array :
   *
   * @code
 - * $wgSMTP = array(
 + * $wgSMTP = [
   *     'host'     => 'SMTP domain',
   *     'IDHost'   => 'domain for MessageID',
   *     'port'     => '25',
   *     'auth'     => [true|false],
   *     'username' => [SMTP username],
   *     'password' => [SMTP password],
 - * );
 + * ];
   * @endcode
   */
  $wgSMTP = false;
@@@ -1670,9 -1674,6 +1670,9 @@@ $wgEnotifWatchlist = false
  /**
   * Allow users to enable email notification ("enotif") when someone edits their
   * user talk page.
 + *
 + * The owner of the user talk page must also have the 'enotifusertalkpages' user
 + * preference set to true.
   */
  $wgEnotifUserTalk = false;
  
  $wgEnotifRevealEditorAddress = false;
  
  /**
 - * Send notification mails on minor edits to watchlist pages. This is enabled
 - * by default. User talk notifications are affected by this, $wgEnotifUserTalk, and
 - * the nominornewtalk user right.
 + * Potentially send notification mails on minor edits to pages. This is enabled
 + * by default.  If this is false, users will never be notified on minor edits.
 + *
 + * If it is true, editors with the 'nominornewtalk' right (typically bots) will still not
 + * trigger notifications for minor edits they make (to any page, not just user talk).
 + *
 + * Finally, if the watcher/recipient has the 'enotifminoredits' user preference set to
 + * false, they will not receive notifications for minor edits.
 + *
 + * User talk notifications are also affected by $wgEnotifMinorEdits, the above settings,
 + * $wgEnotifUserTalk, and the preference described there.
   */
  $wgEnotifMinorEdits = true;
  
@@@ -2075,7 -2068,7 +2075,7 @@@ $wgCompressRevisions = false
   *
   * Short names of ExternalStore classes may be specified in an array here:
   * @code
 - * $wgExternalStores = array("http","file","custom")...
 + * $wgExternalStores = [ "http","file","custom" ]...
   * @endcode
   *
   * CAUTION: Access to database might lead to code execution
@@@ -2088,9 -2081,9 +2088,9 @@@ $wgExternalStores = []
   * @par Example:
   * Create a cluster named 'cluster1' containing three servers:
   * @code
 - * $wgExternalServers = array(
 - *     'cluster1' => array( 'srv28', 'srv29', 'srv30' )
 - * );
 + * $wgExternalServers = [
 + *     'cluster1' => [ 'srv28', 'srv29', 'srv30' ]
 + * ];
   * @endcode
   *
   * Used by LBFactorySimple, may be ignored if $wgLBFactoryConf is set to
@@@ -2107,7 -2100,7 +2107,7 @@@ $wgExternalServers = []
   *
   * @par Example:
   * @code
 - * $wgDefaultExternalStore = array( 'DB://cluster1', 'DB://cluster2' );
 + * $wgDefaultExternalStore = [ 'DB://cluster1', 'DB://cluster2' ];
   * @endcode
   *
   * @var array
@@@ -2524,7 -2517,7 +2524,7 @@@ $wgFileCacheDepth = 2
  
  /**
   * Kept for extension compatibility; see $wgParserCacheType
 - * @deprecated 1.26
 + * @deprecated since 1.26
   */
  $wgEnableParserCache = true;
  
@@@ -2745,16 -2738,16 +2745,16 @@@ $wgSquidPurgeUseHostHeader = true
   * @par Example configuration to send purges for upload.wikimedia.org to one
   * multicast group and all other purges to another:
   * @code
 - * $wgHTCPRouting = array(
 - *         '|^https?://upload\.wikimedia\.org|' => array(
 + * $wgHTCPRouting = [
 + *         '|^https?://upload\.wikimedia\.org|' => [
   *                 'host' => '239.128.0.113',
   *                 'port' => 4827,
 - *         ),
 - *         '' => array(
 + *         ],
 + *         '' => [
   *                 'host' => '239.128.0.112',
   *                 'port' => 4827,
 - *         ),
 - * );
 + *         ],
 + * ];
   * @endcode
   *
   * You can also pass an array of hosts to send purges too. This is useful when
   *
   * @par Example of sending purges to multiple hosts:
   * @code
 - * $wgHTCPRouting = array(
 - *     '' => array(
 + * $wgHTCPRouting = [
 + *     '' => [
   *         // Purges to text caches using multicast
 - *         array( 'host' => '239.128.0.114', 'port' => '4827' ),
 + *         [ 'host' => '239.128.0.114', 'port' => '4827' ],
   *         // Purges to a hardcoded list of caches
 - *         array( 'host' => '10.88.66.1', 'port' => '4827' ),
 - *         array( 'host' => '10.88.66.2', 'port' => '4827' ),
 - *         array( 'host' => '10.88.66.3', 'port' => '4827' ),
 - *     ),
 - * );
 + *         [ 'host' => '10.88.66.1', 'port' => '4827' ],
 + *         [ 'host' => '10.88.66.2', 'port' => '4827' ],
 + *         [ 'host' => '10.88.66.3', 'port' => '4827' ],
 + *     ],
 + * ];
   * @endcode
   *
   * @since 1.22
@@@ -2889,6 -2882,15 +2889,6 @@@ $wgDummyLanguageCodes = 
        'zh-yue' => 'yue',
  ];
  
 -/**
 - * Character set for use in the article edit box. Language-specific encodings
 - * may be defined.
 - *
 - * This historic feature is one of the first that was added by former MediaWiki
 - * team leader Brion Vibber, and is used to support the Esperanto x-system.
 - */
 -$wgEditEncoding = '';
 -
  /**
   * Set this to true to replace Arabic presentation forms with their standard
   * forms in the U+0600-U+06FF block. This only works if $wgLanguageCode is
@@@ -3086,7 -3088,7 +3086,7 @@@ $wgLoginLanguageSelector = false
   * To allow language-specific main page and community
   * portal:
   * @code
 - *     $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
 + *     $wgForceUIMsgAsContentMsg = [ 'mainpage', 'portal-url' ];
   * @endcode
   */
  $wgForceUIMsgAsContentMsg = [];
@@@ -3193,6 -3195,15 +3193,15 @@@ $wgHTMLFormAllowTableFormat = true
   */
  $wgUseMediaWikiUIEverywhere = false;
  
+ /**
+  * Whether to label the store-to-database-and-show-to-others button in the editor
+  * as "Save page"/"Save changes" if false (the default) or, if true, instead as
+  * "Publish page"/"Publish changes".
+  *
+  * @since 1.28
+  */
+ $wgEditButtonPublishNotSave = false;
  /**
   * Permit other namespaces in addition to the w3.org default.
   *
@@@ -3456,13 -3467,13 +3465,13 @@@ $wgMangleFlashPolicy = true
   *
   * @par Example:
   * @code
 - *   $wgResourceModules['ext.myExtension'] = array(
 + *   $wgResourceModules['ext.myExtension'] = [
   *      'scripts' => 'myExtension.js',
   *      'styles' => 'myExtension.css',
 - *      'dependencies' => array( 'jquery.cookie', 'jquery.tabIndex' ),
 + *      'dependencies' => [ 'jquery.cookie', 'jquery.tabIndex' ],
   *      'localBasePath' => __DIR__,
   *      'remoteExtPath' => 'MyExtension',
 - *   );
 + *   ];
   * @endcode
   */
  $wgResourceModules = [];
   *
   * @par Example:
   * @code
 - *   $wgResourceModules['bar'] = array(
 + *   $wgResourceModules['bar'] = [
   *     'scripts' => 'resources/bar/bar.js',
   *     'styles' => 'resources/bar/main.css',
 - *   );
 + *   ];
   *
 - *   $wgResourceModuleSkinStyles['foo'] = array(
 + *   $wgResourceModuleSkinStyles['foo'] = [
   *     'bar' => 'skins/Foo/bar.css',
 - *   );
 + *   ];
   * @endcode
   *
   * This is mostly equivalent to:
   *
   * @par Equivalent:
   * @code
 - *   $wgResourceModules['bar'] = array(
 + *   $wgResourceModules['bar'] = [
   *     'scripts' => 'resources/bar/bar.js',
   *     'styles' => 'resources/bar/main.css',
 - *     'skinStyles' => array(
 + *     'skinStyles' => [
   *       'foo' => skins/Foo/bar.css',
 - *     ),
 - *   );
 + *     ],
 + *   ];
   * @endcode
   *
   * If the module already defines its own entry in `skinStyles` for a given skin, then
   *
   * @par Example:
   * @code
 - *   $wgResourceModules['bar'] = array(
 + *   $wgResourceModules['bar'] = [
   *     'scripts' => 'resources/bar/bar.js',
   *     'styles' => 'resources/bar/basic.css',
 - *     'skinStyles' => array(
 - *       'default' => 'resources/bar/additional.css',
 - *     ),
 - *   );
 + *     'skinStyles' => [
 + *      'default' => 'resources/bar/additional.css',
 + *     ],
 + *   ];
   *   // Note the '+' character:
 - *   $wgResourceModuleSkinStyles['foo'] = array(
 + *   $wgResourceModuleSkinStyles['foo'] = [
   *     '+bar' => 'skins/Foo/bar.css',
 - *   );
 + *   ];
   * @endcode
   *
   * This is mostly equivalent to:
   *
   * @par Equivalent:
   * @code
 - *   $wgResourceModules['bar'] = array(
 + *   $wgResourceModules['bar'] = [
   *     'scripts' => 'resources/bar/bar.js',
   *     'styles' => 'resources/bar/basic.css',
 - *     'skinStyles' => array(
 + *     'skinStyles' => [
   *       'default' => 'resources/bar/additional.css',
 - *       'foo' => array(
 + *       'foo' => [
   *         'resources/bar/additional.css',
   *         'skins/Foo/bar.css',
 - *       ),
 - *     ),
 - *   );
 + *       ],
 + *     ],
 + *   ];
   * @endcode
   *
   * In other words, as a module author, use the `styles` list for stylesheets that may not be
   *
   * @par Example:
   * @code
 - *   $wgResourceModuleSkinStyles['foo'] = array(
 + *   $wgResourceModuleSkinStyles['foo'] = [
   *     'bar' => 'bar.css',
   *     'quux' => 'quux.css',
   *     'remoteSkinPath' => 'Foo',
   *     'localBasePath' => __DIR__,
 - *   );
 + *   ];
   * @endcode
   */
  $wgResourceModuleSkinStyles = [];
@@@ -3709,11 -3720,11 +3718,11 @@@ $wgResourceLoaderValidateStaticJS = fal
   *
   * @par Example:
   * @code
 - *   $wgResourceLoaderLESSVars = array(
 + *   $wgResourceLoaderLESSVars = [
   *     'baseFontSize'  => '1em',
   *     'smallFontSize' => '0.75em',
   *     'WikimediaBlue' => '#006699',
 - *   );
 + *   ];
   * @endcode
   * @since 1.22
   */
@@@ -3811,12 -3822,12 +3820,12 @@@ $wgMetaNamespaceTalk = false
   *
   * @par Example:
   * @code
 - * $wgExtraNamespaces = array(
 + * $wgExtraNamespaces = [
   *    100 => "Hilfe",
   *    101 => "Hilfe_Diskussion",
   *    102 => "Aide",
   *    103 => "Discussion_Aide"
 - * );
 + * ];
   * @endcode
   *
   * @todo Add a note about maintenance/namespaceDupes.php
@@@ -3843,10 -3854,10 +3852,10 @@@ $wgExtraGenderNamespaces = []
   *
   * @par Example:
   * @code
 - *    $wgNamespaceAliases = array(
 + *    $wgNamespaceAliases = [
   *        'Wikipedian' => NS_USER,
   *        'Help' => 100,
 - *    );
 + *    ];
   * @endcode
   */
  $wgNamespaceAliases = [];
@@@ -4173,7 -4184,7 +4182,7 @@@ $wgAllowExternalImages = false
   * @par Examples:
   * @code
   * $wgAllowExternalImagesFrom = 'http://127.0.0.1/';
 - * $wgAllowExternalImagesFrom = array( 'http://127.0.0.1/', 'http://example.com' );
 + * $wgAllowExternalImagesFrom = [ 'http://127.0.0.1/', 'http://example.com' ];
   * @endcode
   */
  $wgAllowExternalImagesFrom = '';
@@@ -4214,8 -4225,6 +4223,8 @@@ $wgAllowImageTag = false
   *    - RaggettInternalHHVM: Use the limited-functionality HHVM extension
   *    - RaggettInternalPHP: Use the PECL extension
   *    - RaggettExternal: Shell out to an external binary (tidyBin)
 + *    - Html5Depurate: Use external Depurate service
 + *    - Html5Internal: Use the built-in HTML5 balancer
   *
   *  - tidyConfigFile: Path to configuration file for any of the Raggett drivers
   *  - debugComment: True to add a comment to the output with warning messages
@@@ -4297,7 -4306,8 +4306,7 @@@ $wgNoFollowNsExceptions = []
   * (or any subdomains) will not be set to rel="nofollow" regardless of the
   * value of $wgNoFollowLinks.  For instance:
   *
 - * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org',
 - * 'mediawiki.org' );
 + * $wgNoFollowDomainExceptions = [ 'en.wikipedia.org', 'wiktionary.org', 'mediawiki.org' ];
   *
   * This would add rel="nofollow" to links to de.wikipedia.org, but not
   * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
@@@ -4458,6 -4468,13 +4467,6 @@@ $wgPasswordPolicy = 
        ],
  ];
  
 -/**
 - * Disable AuthManager
 - * @since 1.27
 - * @deprecated since 1.27, for use during development only
 - */
 -$wgDisableAuthManager = false;
 -
  /**
   * Configure AuthManager
   *
@@@ -4677,14 -4694,14 +4686,14 @@@ $wgPasswordDefault = 'pbkdf2'
   *
   * An advanced example:
   * @code
 - * $wgPasswordConfig['bcrypt-peppered'] = array(
 + * $wgPasswordConfig['bcrypt-peppered'] = [
   *     'class' => 'EncryptedPassword',
   *     'underlying' => 'bcrypt',
 - *     'secrets' => array(),
 + *     'secrets' => [],
   *     'cipher' => MCRYPT_RIJNDAEL_256,
   *     'mode' => MCRYPT_MODE_CBC,
   *     'cost' => 5,
 - * );
 + * ];
   * @endcode
   *
   * @since 1.24
@@@ -4973,7 -4990,7 +4982,7 @@@ $wgWhitelistRead = false
   * @par Example:
   * To whitelist [[Main Page]]:
   * @code
 - * $wgWhitelistReadRegexp = array( "/Main Page/" );
 + * $wgWhitelistReadRegexp = [ "/Main Page/" ];
   * @endcode
   *
   * @note Unless ^ and/or $ is specified, a regular expression might match
   * @par Example:
   * To allow reading any page starting with 'User' regardless of the case:
   * @code
 - * $wgWhitelistReadRegexp = array( "@^UsEr.*@i" );
 + * $wgWhitelistReadRegexp = [ "@^UsEr.*@i" ];
   * @endcode
   * Will allow both [[User is banned]] and [[User:JohnDoe]]
   *
@@@ -5018,7 -5035,7 +5027,7 @@@ $wgHideIdentifiableRedirects = true
   * combined with the permissions of all groups that a given user is listed
   * in in the user_groups table.
   *
 - * Note: Don't set $wgGroupPermissions = array(); unless you know what you're
 + * Note: Don't set $wgGroupPermissions = []; unless you know what you're
   * doing! This will wipe all permissions, and may mean that your users are
   * unable to perform certain essential tasks or access new functionality
   * when new permissions are introduced and default grants established.
@@@ -5062,7 -5079,7 +5071,7 @@@ $wgGroupPermissions['user']['upload'] 
  $wgGroupPermissions['user']['reupload'] = true;
  $wgGroupPermissions['user']['reupload-shared'] = true;
  $wgGroupPermissions['user']['minoredit'] = true;
 -$wgGroupPermissions['user']['purge'] = true; // can use ?action=purge without clicking "ok"
 +$wgGroupPermissions['user']['purge'] = true;
  $wgGroupPermissions['user']['sendemail'] = true;
  $wgGroupPermissions['user']['applychangetags'] = true;
  $wgGroupPermissions['user']['changetags'] = true;
@@@ -5184,13 -5201,13 +5193,13 @@@ $wgImplicitGroups = [ '*', 'user', 'aut
   * @par Example:
   * To allow sysops to add themselves to the "bot" group:
   * @code
 - *    $wgGroupsAddToSelf = array( 'sysop' => array( 'bot' ) );
 + *    $wgGroupsAddToSelf = [ 'sysop' => [ 'bot' ] ];
   * @endcode
   *
   * @par Example:
   * Implicit groups may be used for the source group, for instance:
   * @code
 - *    $wgGroupsRemoveFromSelf = array( '*' => true );
 + *    $wgGroupsRemoveFromSelf = [ '*' => true ];
   * @endcode
   * This allows users in the '*' group (i.e. any user) to remove themselves from
   * any group that they happen to be in.
@@@ -5308,18 -5325,18 +5317,18 @@@ $wgAutoConfirmCount = 0
   * @todo Redocument $wgAutopromote
   *
   * The format is
 - *   array( '&' or '|' or '^' or '!', cond1, cond2, ... )
 + *   [ '&' or '|' or '^' or '!', cond1, cond2, ... ]
   * where cond1, cond2, ... are themselves conditions; *OR*
   *   APCOND_EMAILCONFIRMED, *OR*
 - *   array( APCOND_EMAILCONFIRMED ), *OR*
 - *   array( APCOND_EDITCOUNT, number of edits ), *OR*
 - *   array( APCOND_AGE, seconds since registration ), *OR*
 - *   array( APCOND_INGROUPS, group1, group2, ... ), *OR*
 - *   array( APCOND_ISIP, ip ), *OR*
 - *   array( APCOND_IPINRANGE, range ), *OR*
 - *   array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR*
 - *   array( APCOND_BLOCKED ), *OR*
 - *   array( APCOND_ISBOT ), *OR*
 + *   [ APCOND_EMAILCONFIRMED ], *OR*
 + *   [ APCOND_EDITCOUNT, number of edits ], *OR*
 + *   [ APCOND_AGE, seconds since registration ], *OR*
 + *   [ APCOND_INGROUPS, group1, group2, ... ], *OR*
 + *   [ APCOND_ISIP, ip ], *OR*
 + *   [ APCOND_IPINRANGE, range ], *OR*
 + *   [ APCOND_AGE_FROM_EDIT, seconds since first edit ], *OR*
 + *   [ APCOND_BLOCKED ], *OR*
 + *   [ APCOND_ISBOT ], *OR*
   *   similar constructs defined by extensions.
   *
   * If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any
@@@ -5340,7 -5357,7 +5349,7 @@@ $wgAutopromote = 
   *
   * The format is:
   * @code
 - *    array( event => criteria, ... )
 + *    [ event => criteria, ... ]
   * @endcode
   * Where event is either:
   *    - 'onEdit' (when user edits)
@@@ -5371,15 -5388,15 +5380,15 @@@ $wgAutopromoteOnceLogInRC = true
   * @endcode
   * Bureaucrats can only remove bots and sysops:
   * @code
 - * $wgRemoveGroups['bureaucrat'] = array( 'bot', 'sysop' );
 + * $wgRemoveGroups['bureaucrat'] = [ 'bot', 'sysop' ];
   * @endcode
   * Sysops can make bots:
   * @code
 - * $wgAddGroups['sysop'] = array( 'bot' );
 + * $wgAddGroups['sysop'] = [ 'bot' ];
   * @endcode
   * Sysops can disable other sysops in an emergency, and disable bots:
   * @code
 - * $wgRemoveGroups['sysop'] = array( 'sysop', 'bot' );
 + * $wgRemoveGroups['sysop'] = [ 'sysop', 'bot' ];
   * @endcode
   */
  $wgAddGroups = [];
@@@ -5450,15 -5467,15 +5459,15 @@@ $wgEnableDnsBlacklist = false
   *
   * @par Example:
   * @code
 - * $wgDnsBlacklistUrls = array(
 + * $wgDnsBlacklistUrls = [
   *   // String containing URL
   *   'http.dnsbl.sorbs.net.',
   *   // Array with URL and key, for services that require a key
 - *   array( 'dnsbl.httpbl.net.', 'mykey' ),
 + *   [ 'dnsbl.httpbl.net.', 'mykey' ],
   *   // Array with just the URL. While this works, it is recommended that you
   *   // just use a string as shown above
 - *   array( 'opm.tornevall.org.' )
 - * );
 + *   [ 'opm.tornevall.org.' ]
 + * ];
   * @endcode
   *
   * @note You should end the domain name with a . to avoid searching your
@@@ -5490,21 -5507,21 +5499,21 @@@ $wgApplyIpBlocksToXff = false
   * @par Example:
   * To set a generic maximum of 4 hits in 60 seconds:
   * @code
 - *     $wgRateLimits = array( 4, 60 );
 + *     $wgRateLimits = [ 4, 60 ];
   * @endcode
   *
   * @par Example:
   * You could also limit per action and then type of users.
   * @code
 - *     $wgRateLimits = array(
 - *         'edit' => array(
 - *             'anon' => array( x, y ), // any and all anonymous edits (aggregate)
 - *             'user' => array( x, y ), // each logged-in user
 - *             'newbie' => array( x, y ), // each new autoconfirmed accounts; overrides 'user'
 - *             'ip' => array( x, y ), // each anon and recent account
 - *             'subnet' => array( x, y ), // ... within a /24 subnet in IPv4 or /64 in IPv6
 - *         )
 - *     )
 + *     $wgRateLimits = [
 + *         'edit' => [
 + *             'anon' => [ x, y ], // any and all anonymous edits (aggregate)
 + *             'user' => [ x, y ], // each logged-in user
 + *             'newbie' => [ x, y ], // each new autoconfirmed accounts; overrides 'user'
 + *             'ip' => [ x, y ], // each anon and recent account
 + *             'subnet' => [ x, y ], // ... within a /24 subnet in IPv4 or /64 in IPv6
 + *         ]
 + *     ]
   * @endcode
   *
   * @warning Requires that $wgMainCacheType is set to something persistent
@@@ -5710,8 -5727,6 +5719,8 @@@ $wgGrantPermissions['sendemail']['sende
  
  $wgGrantPermissions['createaccount']['createaccount'] = true;
  
 +$wgGrantPermissions['privateinfo']['viewmyprivateinfo'] = true;
 +
  /**
   * @var Array Map of grants to their UI grouping
   * @since 1.27
@@@ -5745,8 -5760,6 +5754,8 @@@ $wgGrantPermissionGroups = 
        'createaccount'       => 'administration',
  
        'highvolume'          => 'high-volume',
 +
 +      'privateinfo'         => 'private-information',
  ];
  
  /**
@@@ -6005,11 -6018,11 +6014,11 @@@ $wgTrxProfilerLimits = 
   *
   * @par Advanced example:
   * @code
 - * $wgDebugLogGroups['memcached'] = array(
 + * $wgDebugLogGroups['memcached'] = [
   *     'destination' => '/var/log/mediawiki/memcached.log',
   *     'sample' => 1000,  // log 1 message out of every 1,000.
   *     'level' => \Psr\Log\LogLevel::WARNING
 - * );
 + * ];
   * @endcode
   */
  $wgDebugLogGroups = [];
   *
   * @par To completely disable logging:
   * @code
 - * $wgMWLoggerDefaultSpi = array( 'class' => '\\MediaWiki\\Logger\\NullSpi' );
 + * $wgMWLoggerDefaultSpi = [ 'class' => '\\MediaWiki\\Logger\\NullSpi' ];
   * @endcode
   *
   * @since 1.25
@@@ -6338,10 -6351,10 +6347,10 @@@ $wgSitemapNamespaces = false
   * This should be a map of namespace IDs to priority
   * @par Example:
   * @code
 - *  $wgSitemapNamespacesPriorities = array(
 + *  $wgSitemapNamespacesPriorities = [
   *      NS_USER => '0.9',
   *      NS_HELP => '0.0',
 - *  );
 + *  ];
   * @endcode
   */
  $wgSitemapNamespacesPriorities = false;
@@@ -6548,18 -6561,18 +6557,18 @@@ $wgRCLinkDays = [ 1, 3, 7, 14, 30 ]
   *  The JSON-specific options are:
   *   * 'channel' -- if set, the 'channel' parameter is also set in JSON values.
   *
 - * @example $wgRCFeeds['example'] = array(
 + * @example $wgRCFeeds['example'] = [
   *            'formatter' => 'JSONRCFeedFormatter',
   *            'uri' => "udp://localhost:1336",
   *            'add_interwiki_prefix' => false,
   *            'omit_bots' => true,
 - *    );
 - * @example $wgRCFeeds['exampleirc'] = array(
 + *    ];
 + * @example $wgRCFeeds['exampleirc'] = [
   *            'formatter' => 'IRCColourfulRCFeedFormatter',
   *            'uri' => "udp://localhost:1338",
   *            'add_interwiki_prefix' => false,
   *            'omit_bots' => true,
 - *    );
 + *    ];
   * @since 1.22
   */
  $wgRCFeeds = [];
@@@ -6721,7 -6734,7 +6730,7 @@@ $wgUnwatchedPageThreshold = false
   *
   * To register a new one:
   * @code
 - * $wgRecentChangesFlags['flag'] => array(
 + * $wgRecentChangesFlags['flag'] => [
   *   // message for the letter displayed next to rows on changes lists
   *   'letter' => 'letter-msg',
   *   // message for the tooltip of the letter
   *   // will set the top-level flag if any line contains the flag, 'all' will
   *   // only be set if all lines contain the flag.
   *   'grouping' => 'any',
 - * );
 + * ];
   * @endcode
   *
   * @since 1.22
@@@ -6840,11 -6853,11 +6849,11 @@@ $wgShowCreditsIfMax = true
   * subprojects on the interwiki map of the target wiki, or a mix of the two,
   * e.g.
   * @code
 - *     $wgImportSources = array(
 - *         'wikipedia' => array( 'cs', 'en', 'fr', 'zh' ),
 + *     $wgImportSources = [
 + *         'wikipedia' => [ 'cs', 'en', 'fr', 'zh' ],
   *         'wikispecies',
 - *         'wikia' => array( 'animanga', 'brickipedia', 'desserts' ),
 - *     );
 + *         'wikia' => [ 'animanga', 'brickipedia', 'desserts' ],
 + *     ];
   * @endcode
   *
   * If you have a very complex import sources setup, you can lazy-load it using
@@@ -6972,11 -6985,11 +6981,11 @@@ $wgExtensionMessagesFiles = []
   *
   * @par Complex example:
   * @code
 - *    $wgMessagesDirs['Example'] = array(
 + *    $wgMessagesDirs['Example'] = [
   *        __DIR__ . '/lib/ve/i18n',
   *        __DIR__ . '/lib/oojs-ui/i18n',
   *        __DIR__ . '/i18n',
 - *    )
 + *    ]
   * @endcode
   * @since 1.23
   */
@@@ -7046,18 -7059,18 +7055,18 @@@ $wgAutoloadAttemptLowercase = true
   * All but 'name', 'path' and 'author' can be omitted.
   *
   * @code
 - * $wgExtensionCredits[$type][] = array(
 + * $wgExtensionCredits[$type][] = [
   *     'path' => __FILE__,
   *     'name' => 'Example extension',
   *     'namemsg' => 'exampleextension-name',
 - *     'author' => array(
 + *     'author' => [
   *         'Foo Barstein',
 - *     ),
 + *     ],
   *     'version' => '1.9.0',
   *     'url' => 'http://example.org/example-extension/',
   *     'descriptionmsg' => 'exampleextension-desc',
   *     'license-name' => 'GPL-2.0+',
 - * );
 + * ];
   * @endcode
   *
   * The extensions are listed on Special:Version. This page also looks for a file
@@@ -7116,11 -7129,11 +7125,11 @@@ $wgAuth = null
   * @endcode
   * - A function with some data:
   * @code
 - *     $wgHooks['event_name'][] = array( $function, $data );
 + *     $wgHooks['event_name'][] = [ $function, $data ];
   * @endcode
   * - A an object method:
   * @code
 - *     $wgHooks['event_name'][] = array( $object, 'method' );
 + *     $wgHooks['event_name'][] = [ $object, 'method' ];
   * @endcode
   * - A closure:
   * @code
@@@ -7245,7 -7258,7 +7254,7 @@@ $wgSpecialPageCacheUpdates = 
   * Hooks that are used for outputting exceptions.  Format is:
   *   $wgExceptionHooks[] = $funcname
   * or:
 - *   $wgExceptionHooks[] = array( $class, $funcname )
 + *   $wgExceptionHooks[] = [ $class, $funcname ]
   * Hooks should return strings or false
   */
  $wgExceptionHooks = [];
@@@ -7361,7 -7374,10 +7370,7 @@@ $wgLogRestrictions = 
   *
   * @par Example:
   * @code
 - *   $wgFilterLogTypes = array(
 - *      'move' => true,
 - *      'import' => false,
 - *   );
 + *   $wgFilterLogTypes = [ 'move' => true, 'import' => false ];
   * @endcode
   *
   * Will display show/hide links for the move and import logs. Move logs will be
@@@ -7645,7 -7661,7 +7654,7 @@@ $wgDefaultRobotPolicy = 'index,follow'
   *
   * @par Example:
   * @code
 - *   $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' );
 + *   $wgNamespaceRobotPolicies = [ NS_TALK => 'noindex' ];
   * @endcode
   */
  $wgNamespaceRobotPolicies = [];
   *
   * @par Example:
   * @code
 - * $wgArticleRobotPolicies = array(
 + * $wgArticleRobotPolicies = [
   *         'Main Page' => 'noindex,follow',
   *         'User:Bob' => 'index,follow',
 - * );
 + * ];
   * @endcode
   *
   * @par Example that DOES NOT WORK because the names are not canonical text
   * forms:
   * @code
 - *   $wgArticleRobotPolicies = array(
 + *   $wgArticleRobotPolicies = [
   *     # Underscore, not space!
   *     'Main_Page' => 'noindex,follow',
   *     # "Project", not the actual project name!
   *     'Project:X' => 'index,follow',
   *     # Needs to be "Abc", not "abc" (unless $wgCapitalLinks is false for that namespace)!
   *     'abc' => 'noindex,nofollow'
 - *   );
 + *   ];
   * @endcode
   */
  $wgArticleRobotPolicies = [];
   *
   * @par Example:
   * @code
 - *   $wgExemptFromUserRobotsControl = array( NS_MAIN, NS_TALK, NS_PROJECT );
 + *   $wgExemptFromUserRobotsControl = [ NS_MAIN, NS_TALK, NS_PROJECT ];
   * @endcode
   */
  $wgExemptFromUserRobotsControl = null;
@@@ -7754,14 -7770,14 +7763,14 @@@ $wgDebugAPI = false
   *
   * @code
   *  $wgAPIModules['foo'] = 'ApiFoo';
 - *  $wgAPIModules['bar'] = array(
 + *  $wgAPIModules['bar'] = [
   *    'class' => 'ApiBar',
   *    'factory' => function( $main, $name ) { ... }
 - *  );
 - *  $wgAPIModules['xyzzy'] = array(
 + *  ];
 + *  $wgAPIModules['xyzzy'] = [
   *    'class' => 'ApiXyzzy',
 - *    'factory' => array( 'XyzzyFactory', 'newApiModule' )
 - *  );
 + *    'factory' => [ 'XyzzyFactory', 'newApiModule' ]
 + *  ];
   * @endcode
   *
   * Extension modules may override the core modules.
@@@ -7891,12 -7907,12 +7900,12 @@@ $wgAjaxEditStash = true
   *
   * @par Example:
   * @code
 - * $wgCrossSiteAJAXdomains = array(
 + * $wgCrossSiteAJAXdomains = [
   *     'www.mediawiki.org',
   *     '*.wikipedia.org',
   *     '*.wikimedia.org',
   *     '*.wiktionary.org',
 - * );
 + * ];
   * @endcode
   */
  $wgCrossSiteAJAXdomains = [];
@@@ -8033,13 -8049,9 +8042,13 @@@ $wgJobRunRate = 1
   * When $wgJobRunRate > 0, try to run jobs asynchronously, spawning a new process
   * to handle the job execution, instead of blocking the request until the job
   * execution finishes.
 + *
   * @since 1.23
   */
 -$wgRunJobsAsync = true;
 +$wgRunJobsAsync = (
 +      !function_exists( 'register_postsend_function' ) &&
 +      !function_exists( 'fastcgi_finish_request' )
 +);
  
  /**
   * Number of rows to update per job
@@@ -8060,9 -8072,10 +8069,9 @@@ $wgUpdateRowsPerQuery = 100
  
  /**
   * Name of the external diff engine to use. Supported values:
 - * * false: default PHP implementation
 - * * 'wikidiff2': Wikimedia's fast difference engine implemented as a PHP/HHVM module
 - * * 'wikidiff' and 'wikidiff3' are treated as false for backwards compatibility
 - * * any other string is treated as a path to external diff executable
 + * * string: path to an external diff executable
 + * * false: wikidiff2 PHP/HHVM module if installed, otherwise the default PHP implementation
 + * * 'wikidiff', 'wikidiff2', and 'wikidiff3' are treated as false for backwards compatibility
   */
  $wgExternalDiffEngine = false;
  
@@@ -8116,13 -8129,13 +8125,13 @@@ $wgRedirectOnLogin = null
   *
   * @par Example:
   * @code
 - *   $wgPoolCounterConf = array( 'ArticleView' => array(
 + *   $wgPoolCounterConf = [ 'ArticleView' => [
   *     'class' => 'PoolCounter_Client',
   *     'timeout' => 15, // wait timeout in seconds
   *     'workers' => 5, // maximum number of active threads in each pool
   *     'maxqueue' => 50, // maximum number of total threads in each pool
   *     ... any extension-specific options...
 - *   );
 + *   ];
   * @endcode
   */
  $wgPoolCounterConf = null;
@@@ -8185,6 -8198,13 +8194,6 @@@ $wgTextModelsToParse = 
        CONTENT_MODEL_CSS, // Make categories etc work, people put them into comments.
  ];
  
 -/**
 - * Whether the user must enter their password to change their e-mail address
 - *
 - * @since 1.20
 - */
 -$wgRequirePasswordforEmailChange = true;
 -
  /**
   * Register handlers for specific types of sites.
   *
@@@ -8262,11 -8282,11 +8271,11 @@@ $wgPageLanguageUseDB = false
   *
   * Example config for Parsoid:
   *
 - *   $wgVirtualRestConfig['modules']['parsoid'] = array(
 + *   $wgVirtualRestConfig['modules']['parsoid'] = [
   *     'url' => 'http://localhost:8000',
   *     'prefix' => 'enwiki',
   *     'domain' => 'en.wikipedia.org',
 - *   );
 + *   ];
   *
   * @var array
   * @since 1.25
@@@ -8338,21 -8358,6 +8347,21 @@@ $wgEventRelayerConfig = 
        ]
  ];
  
 +/**
 + * Share data about this installation with MediaWiki developers
 + *
 + * When set to true, MediaWiki will periodically ping https://www.mediawiki.org/ with basic
 + * data about this MediaWiki instance. This data includes, for example, the type of system,
 + * PHP version, and chosen database backend. The Wikimedia Foundation shares this data with
 + * MediaWiki developers to help guide future development efforts.
 + *
 + * For details about what data is sent, see: https://www.mediawiki.org/wiki/Manual:$wgPingback
 + *
 + * @var bool
 + * @since 1.28
 + */
 +$wgPingback = false;
 +
  /**
   * For really cool vim folding this needs to be at the end:
   * vim: foldmarker=@{,@} foldmethod=marker
diff --combined includes/EditPage.php
@@@ -20,8 -20,6 +20,8 @@@
   * @file
   */
  
 +use MediaWiki\Logger\LoggerFactory;
 +
  /**
   * The edit page/HTML interface (split from Article)
   * The actual database and text munging is still in Article,
@@@ -260,6 -258,9 +260,6 @@@ class EditPage 
        /** @var bool */
        public $tooBig = false;
  
 -      /** @var bool */
 -      public $kblength = false;
 -
        /** @var bool */
        public $missingComment = false;
  
        /** @var bool */
        protected $edit;
  
 +      /** @var bool|int */
 +      protected $contentLength = false;
 +
        /**
         * @var bool Set in ApiEditPage, based on ContentHandler::allowsDirectApiEditing
         */
  
                        return $handler->makeEmptyContent();
                } else {
 -                      # nasty side-effect, but needed for consistency
 -                      $this->contentModel = $rev->getContentModel();
 -                      $this->contentFormat = $rev->getContentFormat();
 +                      // Content models should always be the same since we error
 +                      // out if they are different before this point.
 +                      $logger = LoggerFactory::getInstance( 'editpage' );
 +                      if ( $this->contentModel !== $rev->getContentModel() ) {
 +                              $logger->warning( "Overriding content model from current edit {prev} to {new}", [
 +                                      'prev' => $this->contentModel,
 +                                      'new' => $rev->getContentModel(),
 +                                      'title' => $this->getTitle()->getPrefixedDBkey(),
 +                                      'method' => __METHOD__
 +                              ] );
 +                              $this->contentModel = $rev->getContentModel();
 +                      }
 +
 +                      // Given that the content models should match, the current selected
 +                      // format should be supported.
 +                      if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
 +                              $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
 +
 +                                      'prev' => $this->contentFormat,
 +                                      'new' => $rev->getContentFormat(),
 +                                      'title' => $this->getTitle()->getPrefixedDBkey(),
 +                                      'method' => __METHOD__
 +                              ] );
 +                              $this->contentFormat = $rev->getContentFormat();
 +                      }
  
                        return $content;
                }
                        return $this->mPreloadContent;
                }
  
 -              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +              $handler = ContentHandler::getForModelID( $this->contentModel );
  
                if ( $preload === '' ) {
                        return $handler->makeEmptyContent();
  
                        case self::AS_CANNOT_USE_CUSTOM_MODEL:
                        case self::AS_PARSE_ERROR:
 -                              $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
 +                              $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
                                return true;
  
                        case self::AS_SUCCESS_NEW_ARTICLE:
                                // is if an extension hook aborted from inside ArticleSave.
                                // Render the status object into $this->hookError
                                // FIXME this sucks, we should just use the Status object throughout
 -                              $this->hookError = '<div class="error">' . $status->getWikiText() .
 +                              $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
                                        '</div>';
                                return true;
                }
                        return $status;
                }
  
 -              $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
 -              if ( $this->kblength > $wgMaxArticleSize ) {
 +              $this->contentLength = strlen( $this->textbox1 );
 +              if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
                        // Error will be displayed by showEditForm()
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_CONTENT_TOO_BIG );
                }
  
                // Check for length errors again now that the section is merged in
 -              $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
 -              if ( $this->kblength > $wgMaxArticleSize ) {
 +              $this->contentLength = strlen( $this->toEditText( $content ) );
 +              if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
                        return $status;
                $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
                        Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
  
 -              $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
 -                      self::getPreviewLimitReport( $this->mParserOutput ) ) );
 +              if ( $this->mParserOutput ) {
 +                      $wgOut->setLimitReportData( $this->mParserOutput->getLimitReportData() );
 +              }
  
                $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
  
                                        );
                                }
                                if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
 +                                      $wgOut->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
 +                                              $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
 +                                      );
                                        if ( $this->formtype !== 'preview' ) {
                                                if ( $this->isCssSubpage && $wgAllowUserCss ) {
                                                        $wgOut->wrapWikiMsg(
                                        'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
                }
  
 -              if ( $this->kblength === false ) {
 -                      $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
 +              if ( $this->contentLength === false ) {
 +                      $this->contentLength = strlen( $this->textbox1 );
                }
  
 -              if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
 +              if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
                        $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
                                [
                                        'longpageerror',
 -                                      $wgLang->formatNum( $this->kblength ),
 +                                      $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
                                        $wgLang->formatNum( $wgMaxArticleSize )
                                ]
                        );
         * @param string $summary The text of the summary to display
         */
        protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
 -              global $wgOut, $wgContLang;
 +              global $wgOut;
                # Add a class if 'missingsummary' is triggered to allow styling of the summary line
                $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
                if ( $isSubjectPreview ) {
                                return;
                        }
                }
 -              $summary = $wgContLang->recodeForEdit( $summary );
                $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
                list( $label, $input ) = $this->getSummaryInput(
                        $summary,
                        return '';
                }
  
 -              $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
 -                      wfMessage( 'limitreport-title' )->parseAsBlock()
 +              return ResourceLoader::makeInlineScript(
 +                      ResourceLoader::makeConfigSetScript(
 +                              [ 'wgPageParseReport' => $output->getLimitReportData() ],
 +                              true
 +                      )
                );
 -
 -              // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
 -              $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
 -
 -              $limitReport .= Html::openElement( 'table', [
 -                      'class' => 'preview-limit-report wikitable'
 -              ] ) .
 -                      Html::openElement( 'tbody' );
 -
 -              foreach ( $output->getLimitReportData() as $key => $value ) {
 -                      if ( Hooks::run( 'ParserLimitReportFormat',
 -                              [ $key, &$value, &$limitReport, true, true ]
 -                      ) ) {
 -                              $keyMsg = wfMessage( $key );
 -                              $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
 -                              if ( !$valueMsg->exists() ) {
 -                                      $valueMsg = new RawMessage( '$1' );
 -                              }
 -                              if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
 -                                      $limitReport .= Html::openElement( 'tr' ) .
 -                                              Html::rawElement( 'th', null, $keyMsg->parse() ) .
 -                                              Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
 -                                              Html::closeElement( 'tr' );
 -                              }
 -                      }
 -              }
 -
 -              $limitReport .= Html::closeElement( 'tbody' ) .
 -                      Html::closeElement( 'table' ) .
 -                      Html::closeElement( 'div' );
 -
 -              return $limitReport;
        }
  
        protected function showStandardInputs( &$tabindex = 2 ) {
                if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$this, &$wgOut ] ) ) {
                        $stats = $wgOut->getContext()->getStats();
                        $stats->increment( 'edit.failures.conflict' );
 -                      if ( $this->mTitle->isTalkPage() ) {
 -                              $stats->increment( 'edit.failures.conflict.byType.talk' );
 -                      } else {
 -                              $stats->increment( 'edit.failures.conflict.byType.subject' );
 -                      }
 -                      if ( $this->mTitle->getNamespace() === NS_PROJECT ) {
 -                              $stats->increment( 'edit.failures.conflict.byNamespace.project' );
 +                      // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
 +                      if (
 +                              $this->mTitle->getNamespace() >= NS_MAIN &&
 +                              $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
 +                      ) {
 +                              $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
                        }
  
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
         * @return string
         */
        function getPreviewText() {
 -              global $wgOut, $wgUser, $wgRawHtml, $wgLang;
 +              global $wgOut, $wgRawHtml, $wgLang;
                global $wgAllowUserCss, $wgAllowUserJs;
  
                $stats = $wgOut->getContext()->getStats();
                                $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
                        }
  
 -                      $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
 -                      $parserOptions->setIsPreview( true );
 -                      $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
 -
                        # don't parse non-wikitext pages, show message about preview
                        if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
                                if ( $this->mTitle->isCssJsSubpage() ) {
                        ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
                        Hooks::run( 'EditPageGetPreviewContent', $hook_args );
  
 -                      $parserOptions->enableLimitReport();
 -
 -                      # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
 -                      # But it's now deprecated, so never mind
 -
 -                      $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 -                      $scopedCallback = $parserOptions->setupFakeRevision(
 -                              $this->mTitle, $pstContent, $wgUser );
 -                      $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
 -
 -                      $parserOutput->setEditSectionTokens( false ); // no section edit links
 -                      $previewHTML = $parserOutput->getText();
 +                      $parserResult = $this->doPreviewParse( $content );
 +                      $parserOutput = $parserResult['parserOutput'];
 +                      $previewHTML = $parserResult['html'];
                        $this->mParserOutput = $parserOutput;
                        $wgOut->addParserOutputMetadata( $parserOutput );
  
                                $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
                        }
  
 -                      ScopedCallback::consume( $scopedCallback );
                } catch ( MWContentSerializationException $ex ) {
                        $m = wfMessage(
                                'content-failed-to-parse',
                return $previewhead . $previewHTML . $this->previewTextAfterContent;
        }
  
 +      /**
 +       * Get parser options for a preview
 +       * @return ParserOptions
 +       */
 +      protected function getPreviewParserOptions() {
 +              $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
 +              $parserOptions->setIsPreview( true );
 +              $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
 +              $parserOptions->enableLimitReport();
 +              return $parserOptions;
 +      }
 +
 +      /**
 +       * Parse the page for a preview. Subclasses may override this class, in order
 +       * to parse with different options, or to otherwise modify the preview HTML.
 +       *
 +       * @param Content @content The page content
 +       * @return Associative array with keys:
 +       *   - parserOutput: The ParserOutput object
 +       *   - html: The HTML to be displayed
 +       */
 +      protected function doPreviewParse( Content $content ) {
 +              global $wgUser;
 +              $parserOptions = $this->getPreviewParserOptions();
 +              $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 +              $scopedCallback = $parserOptions->setupFakeRevision(
 +                      $this->mTitle, $pstContent, $wgUser );
 +              $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
 +              ScopedCallback::consume( $scopedCallback );
 +              $parserOutput->setEditSectionTokens( false ); // no section edit links
 +              return [
 +                      'parserOutput' => $parserOutput,
 +                      'html' => $parserOutput->getText() ];
 +      }
 +
        /**
         * @return array
         */
        public function getEditButtons( &$tabindex ) {
                $buttons = [];
  
-               $buttonLabelKey = $this->isNew ? 'savearticle' : 'savechanges';
+               $labelAsPublish = $this->mArticle->getContext()->getConfig()->get( 'EditButtonPublishNotSave' );
+               if ( $labelAsPublish ) {
+                       $buttonLabelKey = $this->isNew ? 'publishpage' : 'publishchanges';
+               } else {
+                       $buttonLabelKey = $this->isNew ? 'savearticle' : 'savechanges';
+               }
                $buttonLabel = wfMessage( $buttonLabelKey )->text();
                $attribs = [
                        'id' => 'wpSave',
         * @return string
         */
        protected function safeUnicodeOutput( $text ) {
 -              global $wgContLang;
 -              $codedText = $wgContLang->recodeForEdit( $text );
                return $this->checkUnicodeCompliantBrowser()
 -                      ? $codedText
 -                      : $this->makeSafe( $codedText );
 +                      ? $text
 +                      : $this->makesafe( $text );
        }
  
        /**