Merge "Add datetime input widget"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 7 Jan 2016 22:11:00 +0000 (22:11 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 7 Jan 2016 22:11:00 +0000 (22:11 +0000)
104 files changed:
RELEASE-NOTES-1.27
autoload.php
docs/hooks.txt
includes/Block.php
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/GitInfo.php
includes/GlobalFunctions.php
includes/Setup.php
includes/WebRequest.php
includes/WebRequestUpload.php
includes/api/i18n/zh-hans.json
includes/changes/ChangesList.php
includes/changes/OldChangesList.php
includes/changes/RecentChange.php
includes/context/RequestContext.php
includes/db/Database.php
includes/diff/DifferenceEngine.php
includes/filerepo/file/LocalFile.php
includes/htmlform/HTMLForm.php
includes/import/WikiRevision.php
includes/installer/WebInstallerComplete.php [new file with mode: 0644]
includes/installer/WebInstallerCopying.php [new file with mode: 0644]
includes/installer/WebInstallerDBConnect.php [new file with mode: 0644]
includes/installer/WebInstallerDBSettings.php [new file with mode: 0644]
includes/installer/WebInstallerDocument.php [new file with mode: 0644]
includes/installer/WebInstallerExistingWiki.php [new file with mode: 0644]
includes/installer/WebInstallerInstall.php [new file with mode: 0644]
includes/installer/WebInstallerLanguage.php [new file with mode: 0644]
includes/installer/WebInstallerName.php [new file with mode: 0644]
includes/installer/WebInstallerOptions.php [new file with mode: 0644]
includes/installer/WebInstallerPage.php
includes/installer/WebInstallerReadme.php [new file with mode: 0644]
includes/installer/WebInstallerReleaseNotes.php [new file with mode: 0644]
includes/installer/WebInstallerRestart.php [new file with mode: 0644]
includes/installer/WebInstallerUpgrade.php [new file with mode: 0644]
includes/installer/WebInstallerUpgradeDoc.php [new file with mode: 0644]
includes/installer/WebInstallerWelcome.php [new file with mode: 0644]
includes/installer/i18n/ksh.json
includes/libs/composer/ComposerJson.php
includes/logging/LogEntry.php
includes/page/Article.php
includes/registration/ExtensionProcessor.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specials/SpecialBlock.php
includes/specials/SpecialExpandTemplates.php
includes/specials/SpecialNewimages.php
includes/specials/SpecialShortpages.php
includes/user/User.php
languages/FakeConverter.php
languages/Language.php
languages/LanguageConverter.php
languages/i18n/ast.json
languages/i18n/azb.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bgn.json
languages/i18n/bs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/fa.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/ilo.json
languages/i18n/ja.json
languages/i18n/ka.json
languages/i18n/ksh.json
languages/i18n/la.json
languages/i18n/lki.json
languages/i18n/mk.json
languages/i18n/nap.json
languages/i18n/nn.json
languages/i18n/pt-br.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/tt-cyrl.json
languages/i18n/ug-arab.json
maintenance/Maintenance.php
maintenance/convertExtensionToRegistration.php
maintenance/dumpBackup.php
maintenance/waitForSlave.php [deleted file]
package.json
resources/src/jquery/jquery.accessKeyLabel.js
resources/src/mediawiki.action/mediawiki.action.edit.preview.js
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.factory.js
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.ForeignUpload.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.Upload.js
resources/src/mediawiki/mediawiki.inspect.js
resources/src/mediawiki/page/ready.js
resources/src/startup.js
tests/parser/parserTest.inc
tests/phpunit/data/gitinfo/extension/gitinfo.json [new file with mode: 0644]
tests/phpunit/includes/GitInfoTest.php
tests/phpunit/includes/ImportLinkCacheIntegrationTest.php [deleted file]
tests/phpunit/includes/ImportTest.php [deleted file]
tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/import/ImportTest.php [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js
tests/qunit/suites/resources/startup.test.js

index 1355b4d..87cb2f5 100644 (file)
@@ -99,8 +99,11 @@ production.
 * Added a new hook, 'TitleMoveCompleting', which runs before a page move is committed.
 * $wgCdnReboundPurgeDelay was added to provide secondary delayed purges of URLs
   from CDN to mitigate DB replication lag and WAN cache purge lag.
+* (T49162) Installer will default to setting CACHE_ACCEL as the main cache type
+  if it is available.
 
 === External library changes in 1.27 ===
+
 ==== Upgraded external libraries ====
 * Updated oojs/oojs-ui from v0.12.12 to v0.13.3.
 * Updated composer/semver from v1.0.0 to v1.2.0.
@@ -216,6 +219,18 @@ changes to languages because of Phabricator reports.
 * wfMsgNoTrans() was removed (deprecated since 1.18).
 * Linker::makeImageLink2 was removed (deprecated since 1.20).
 * Title::userIsWatching() was removed (deprecated since 1.20).
+* Removed WaitForSlave maintenance script; use SELECT MASTER_POS_WAIT()
+  database function directly instead.
+* wfMsg() was removed (deprecated since 1.18).
+* wfMsgForContent() was removed (deprecated since 1.18).
+* wfMsgReal() was removed (deprecated since 1.18).
+* wfMsgGetKey() was removed (deprecated since 1.18).
+* wfMsgHtml() was removed (deprecated since 1.18).
+* wfMsgWikiHtml() was removed (deprecated since 1.18).
+* wfMsgExt() was removed (deprecated since 1.18).
+* Language::armourMath() was removed (deprecated since 1.22).
+* LanguageConverter::armourMath() was removed (deprecated since 1.22).
+* FakeConverter::armourMath() was removed (deprecated since 1.22).
 
 == Compatibility ==
 
index 5ccdaec..5ee73a5 100644 (file)
@@ -1355,7 +1355,6 @@ $wgAutoloadLocalClasses = array(
        'VirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTService.php',
        'VirtualRESTServiceClient' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTServiceClient.php',
        'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/WANObjectCache.php',
-       'WaitForSlave' => __DIR__ . '/maintenance/waitForSlave.php',
        'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
        'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
        'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
@@ -1365,24 +1364,24 @@ $wgAutoloadLocalClasses = array(
        'WatchedItem' => __DIR__ . '/includes/WatchedItem.php',
        'WatchlistCleanup' => __DIR__ . '/maintenance/cleanupWatchlist.php',
        'WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php',
-       'WebInstallerComplete' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerCopying' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerDBConnect' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerDBSettings' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerDocument' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerExistingWiki' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerInstall' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerLanguage' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerName' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerOptions' => __DIR__ . '/includes/installer/WebInstallerPage.php',
+       'WebInstallerComplete' => __DIR__ . '/includes/installer/WebInstallerComplete.php',
+       'WebInstallerCopying' => __DIR__ . '/includes/installer/WebInstallerCopying.php',
+       'WebInstallerDBConnect' => __DIR__ . '/includes/installer/WebInstallerDBConnect.php',
+       'WebInstallerDBSettings' => __DIR__ . '/includes/installer/WebInstallerDBSettings.php',
+       'WebInstallerDocument' => __DIR__ . '/includes/installer/WebInstallerDocument.php',
+       'WebInstallerExistingWiki' => __DIR__ . '/includes/installer/WebInstallerExistingWiki.php',
+       'WebInstallerInstall' => __DIR__ . '/includes/installer/WebInstallerInstall.php',
+       'WebInstallerLanguage' => __DIR__ . '/includes/installer/WebInstallerLanguage.php',
+       'WebInstallerName' => __DIR__ . '/includes/installer/WebInstallerName.php',
+       'WebInstallerOptions' => __DIR__ . '/includes/installer/WebInstallerOptions.php',
        'WebInstallerOutput' => __DIR__ . '/includes/installer/WebInstallerOutput.php',
        'WebInstallerPage' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerReadme' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerReleaseNotes' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerRestart' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerUpgrade' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerUpgradeDoc' => __DIR__ . '/includes/installer/WebInstallerPage.php',
-       'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerPage.php',
+       'WebInstallerReadme' => __DIR__ . '/includes/installer/WebInstallerReadme.php',
+       'WebInstallerReleaseNotes' => __DIR__ . '/includes/installer/WebInstallerReleaseNotes.php',
+       'WebInstallerRestart' => __DIR__ . '/includes/installer/WebInstallerRestart.php',
+       'WebInstallerUpgrade' => __DIR__ . '/includes/installer/WebInstallerUpgrade.php',
+       'WebInstallerUpgradeDoc' => __DIR__ . '/includes/installer/WebInstallerUpgradeDoc.php',
+       'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerWelcome.php',
        'WebPHandler' => __DIR__ . '/includes/media/WebP.php',
        'WebRequest' => __DIR__ . '/includes/WebRequest.php',
        'WebRequestUpload' => __DIR__ . '/includes/WebRequestUpload.php',
index c928aae..83ce62e 100644 (file)
@@ -2571,6 +2571,13 @@ $enotif: EmailNotification object
 
 'SetupAfterCache': Called in Setup.php, after cache objects are set
 
+'ShortPagesQuery': Allow extensions to modify the query used by
+Special:ShortPages.
+&$tables: tables to join in the query
+&$conds: conditions for the query
+&$joinConds: join conditions for the query
+&$options: options for the query
+
 'ShowMissingArticle': Called when generating the output for a non-existent page.
 $article: The article object corresponding to the page
 
index 4729f50..7b287dc 100644 (file)
@@ -296,7 +296,7 @@ class Block {
                        $block = self::newFromRow( $row );
 
                        # Don't use expired blocks
-                       if ( $block->deleteIfExpired() ) {
+                       if ( $block->isExpired() ) {
                                continue;
                        }
 
@@ -1140,7 +1140,7 @@ class Block {
                $blocks = array();
                foreach ( $rows as $row ) {
                        $block = self::newFromRow( $row );
-                       if ( !$block->deleteIfExpired() ) {
+                       if ( !$block->isExpired() ) {
                                $blocks[] = $block;
                        }
                }
index e2c31a6..2ceeed2 100644 (file)
@@ -713,7 +713,7 @@ class CategoryViewer extends ContextSource {
                        // quick due to the small number of entries.
                        $totalcnt = $rescnt;
                        $category = $this->cat;
-                       wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $category ) {
+                       DeferredUpdates::addCallableUpdate( function () use ( $category ) {
                                $category->refreshCounts();
                        } );
                } else {
index a6d34b0..d46bc03 100644 (file)
@@ -6178,7 +6178,8 @@ $wgRCEngines = array(
 $wgRCWatchCategoryMembership = false;
 
 /**
- * Use RC Patrolling to check for vandalism
+ * Use RC Patrolling to check for vandalism (from recent changes and watchlists)
+ * New pages and new files are included.
  */
 $wgUseRCPatrol = true;
 
@@ -6187,6 +6188,13 @@ $wgUseRCPatrol = true;
  */
 $wgUseNPPatrol = true;
 
+/**
+ * Use file patrolling to check new files on Special:Newfiles
+ *
+ * @since 1.27
+ */
+$wgUseFilePatrol = true;
+
 /**
  * Log autopatrol actions to the log table
  */
@@ -7347,6 +7355,7 @@ $wgUseAjax = true;
 /**
  * List of Ajax-callable functions.
  * Extensions acting as Ajax callbacks must register here
+ * @deprecated (officially) since 1.27; use the API instead
  */
 $wgAjaxExportList = array();
 
index 7f05bb0..14f3cc1 100644 (file)
@@ -96,7 +96,7 @@ class GitInfo {
         *
         * @param string $repoDir The root directory of the repo where .git can be found
         * @return string Path to GitInfo cache file in $wgGitInfoCacheDirectory or
-        * null if $wgGitInfoCacheDirectory is false (cache disabled).
+        * fallback in the extension directory itself
         * @since 1.24
         */
        protected static function getCacheFilePath( $repoDir ) {
@@ -119,9 +119,13 @@ class GitInfo {
                        // a filename
                        $repoName = strtr( $repoName, DIRECTORY_SEPARATOR, '-' );
                        $fileName = 'info' . $repoName . '.json';
-                       return "{$wgGitInfoCacheDirectory}/{$fileName}";
+                       $cachePath = "{$wgGitInfoCacheDirectory}/{$fileName}";
+                       if ( is_readable( $cachePath ) ) {
+                               return $cachePath;
+                       }
                }
-               return null;
+
+               return "$repoDir/gitinfo.json";
        }
 
        /**
index 2b018d9..26fb223 100644 (file)
@@ -1460,120 +1460,6 @@ function wfMessageFallback( /*...*/ ) {
        return call_user_func_array( 'Message::newFallbackSequence', $args );
 }
 
-/**
- * Get a message from anywhere, for the current user language.
- *
- * Use wfMsgForContent() instead if the message should NOT
- * change depending on the user preferences.
- *
- * @deprecated since 1.18
- *
- * @param string $key Lookup key for the message, usually
- *    defined in languages/Language.php
- *
- * Parameters to the message, which can be used to insert variable text into
- * it, can be passed to this function in the following formats:
- * - One per argument, starting at the second parameter
- * - As an array in the second parameter
- * These are not shown in the function definition.
- *
- * @return string
- */
-function wfMsg( $key ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       $args = func_get_args();
-       array_shift( $args );
-       return wfMsgReal( $key, $args );
-}
-
-/**
- * Get a message from anywhere, for the current global language
- * set with $wgLanguageCode.
- *
- * Use this if the message should NOT change dependent on the
- * language set in the user's preferences. This is the case for
- * most text written into logs, as well as link targets (such as
- * the name of the copyright policy page). Link titles, on the
- * other hand, should be shown in the UI language.
- *
- * Note that MediaWiki allows users to change the user interface
- * language in their preferences, but a single installation
- * typically only contains content in one language.
- *
- * Be wary of this distinction: If you use wfMsg() where you should
- * use wfMsgForContent(), a user of the software may have to
- * customize potentially hundreds of messages in
- * order to, e.g., fix a link in every possible language.
- *
- * @deprecated since 1.18
- *
- * @param string $key Lookup key for the message, usually
- *     defined in languages/Language.php
- * @return string
- */
-function wfMsgForContent( $key ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       global $wgForceUIMsgAsContentMsg;
-       $args = func_get_args();
-       array_shift( $args );
-       $forcontent = true;
-       if ( is_array( $wgForceUIMsgAsContentMsg )
-               && in_array( $key, $wgForceUIMsgAsContentMsg )
-       ) {
-               $forcontent = false;
-       }
-       return wfMsgReal( $key, $args, true, $forcontent );
-}
-
-/**
- * Really get a message
- *
- * @deprecated since 1.18
- *
- * @param string $key Key to get.
- * @param array $args
- * @param bool $useDB
- * @param string|bool $forContent Language code, or false for user lang, true for content lang.
- * @param bool $transform Whether or not to transform the message.
- * @return string The requested message.
- */
-function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
-       $message = wfMsgReplaceArgs( $message, $args );
-       return $message;
-}
-
-/**
- * Fetch a message string value, but don't replace any keys yet.
- *
- * @deprecated since 1.18
- *
- * @param string $key
- * @param bool $useDB
- * @param string|bool $langCode Code of the language to get the message for, or
- *   behaves as a content language switch if it is a boolean.
- * @param bool $transform Whether to parse magic words, etc.
- * @return string
- */
-function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       Hooks::run( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
-
-       $cache = MessageCache::singleton();
-       $message = $cache->get( $key, $useDB, $langCode );
-       if ( $message === false ) {
-               $message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
-       } elseif ( $transform ) {
-               $message = $cache->transform( $message );
-       }
-       return $message;
-}
-
 /**
  * Replace message parameter keys on the given formatted output.
  *
@@ -1602,143 +1488,6 @@ function wfMsgReplaceArgs( $message, $args ) {
        return $message;
 }
 
-/**
- * Return an HTML-escaped version of a message.
- * Parameter replacements, if any, are done *after* the HTML-escaping,
- * so parameters may contain HTML (eg links or form controls). Be sure
- * to pre-escape them if you really do want plaintext, or just wrap
- * the whole thing in htmlspecialchars().
- *
- * @deprecated since 1.18
- *
- * @param string $key
- * @param string $args,... Parameters
- * @return string
- */
-function wfMsgHtml( $key ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       $args = func_get_args();
-       array_shift( $args );
-       return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key ) ), $args );
-}
-
-/**
- * Return an HTML version of message
- * Parameter replacements, if any, are done *after* parsing the wiki-text message,
- * so parameters may contain HTML (eg links or form controls). Be sure
- * to pre-escape them if you really do want plaintext, or just wrap
- * the whole thing in htmlspecialchars().
- *
- * @deprecated since 1.18
- *
- * @param string $key
- * @param string $args,... Parameters
- * @return string
- */
-function wfMsgWikiHtml( $key ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       $args = func_get_args();
-       array_shift( $args );
-       return wfMsgReplaceArgs(
-               MessageCache::singleton()->parse( wfMsgGetKey( $key ), null,
-               /* can't be set to false */ true, /* interface */ true )->getText(),
-               $args );
-}
-
-/**
- * Returns message in the requested format
- *
- * @deprecated since 1.18
- *
- * @param string $key Key of the message
- * @param array $options Processing rules.
- *   Can take the following options:
- *     parse: parses wikitext to HTML
- *     parseinline: parses wikitext to HTML and removes the surrounding
- *       p's added by parser or tidy
- *     escape: filters message through htmlspecialchars
- *     escapenoentities: same, but allows entity references like &#160; through
- *     replaceafter: parameters are substituted after parsing or escaping
- *     parsemag: transform the message using magic phrases
- *     content: fetch message for content language instead of interface
- *   Also can accept a single associative argument, of the form 'language' => 'xx':
- *     language: Language object or language code to fetch message for
- *       (overridden by content).
- * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
- *
- * @return string
- */
-function wfMsgExt( $key, $options ) {
-       wfDeprecated( __METHOD__, '1.21' );
-
-       $args = func_get_args();
-       array_shift( $args );
-       array_shift( $args );
-       $options = (array)$options;
-       $validOptions = array( 'parse', 'parseinline', 'escape', 'escapenoentities', 'replaceafter',
-               'parsemag', 'content' );
-
-       foreach ( $options as $arrayKey => $option ) {
-               if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
-                       // An unknown index, neither numeric nor "language"
-                       wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
-               } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, $validOptions ) ) {
-                       // A numeric index with unknown value
-                       wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
-               }
-       }
-
-       if ( in_array( 'content', $options, true ) ) {
-               $forContent = true;
-               $langCode = true;
-               $langCodeObj = null;
-       } elseif ( array_key_exists( 'language', $options ) ) {
-               $forContent = false;
-               $langCode = wfGetLangObj( $options['language'] );
-               $langCodeObj = $langCode;
-       } else {
-               $forContent = false;
-               $langCode = false;
-               $langCodeObj = null;
-       }
-
-       $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
-
-       if ( !in_array( 'replaceafter', $options, true ) ) {
-               $string = wfMsgReplaceArgs( $string, $args );
-       }
-
-       $messageCache = MessageCache::singleton();
-       $parseInline = in_array( 'parseinline', $options, true );
-       if ( in_array( 'parse', $options, true ) || $parseInline ) {
-               $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj );
-               if ( $string instanceof ParserOutput ) {
-                       $string = $string->getText();
-               }
-
-               if ( $parseInline ) {
-                       $string = Parser::stripOuterParagraph( $string );
-               }
-       } elseif ( in_array( 'parsemag', $options, true ) ) {
-               $string = $messageCache->transform( $string,
-                               !$forContent, $langCodeObj );
-       }
-
-       if ( in_array( 'escape', $options, true ) ) {
-               $string = htmlspecialchars( $string );
-       } elseif ( in_array( 'escapenoentities', $options, true ) ) {
-               $string = Sanitizer::escapeHtmlAllowEntities( $string );
-       }
-
-       if ( in_array( 'replaceafter', $options, true ) ) {
-               $string = wfMsgReplaceArgs( $string, $args );
-       }
-
-       return $string;
-}
-
 /**
  * Fetch server name for use in error reporting etc.
  * Use real server name if available, so we know which machine
index c863722..2723258 100644 (file)
@@ -264,6 +264,7 @@ foreach ( $wgForeignFileRepos as &$repo ) {
 }
 unset( $repo ); // no global pollution; destroy reference
 
+$rcMaxAgeDays = $wgRCMaxAge / ( 3600 * 24 );
 if ( $wgRCFilterByAge ) {
        // Trim down $wgRCLinkDays so that it only lists links which are valid
        // as determined by $wgRCMaxAge.
@@ -273,12 +274,22 @@ if ( $wgRCFilterByAge ) {
        // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
        for ( $i = 0; $i < count( $wgRCLinkDays ); $i++ ) {
                // @codingStandardsIgnoreEnd
-               if ( $wgRCLinkDays[$i] >= $wgRCMaxAge / ( 3600 * 24 ) ) {
+               if ( $wgRCLinkDays[$i] >= $rcMaxAgeDays ) {
                        $wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i + 1, false );
                        break;
                }
        }
 }
+// Ensure that default user options are not invalid, since that breaks Special:Preferences
+$wgDefaultUserOptions['rcdays'] = min(
+       $wgDefaultUserOptions['rcdays'],
+       ceil( $rcMaxAgeDays )
+);
+$wgDefaultUserOptions['watchlistdays'] = min(
+       $wgDefaultUserOptions['watchlistdays'],
+       ceil( $rcMaxAgeDays )
+);
+unset( $rcMaxAgeDays );
 
 if ( $wgSkipSkin ) {
        $wgSkipSkins[] = $wgSkipSkin;
@@ -606,15 +617,13 @@ if ( !$wgDBerrorLogTZ ) {
        $wgDBerrorLogTZ = $wgLocaltimezone;
 }
 
+// initialize the request object in $wgRequest
+$wgRequest = RequestContext::getMain()->getRequest(); // BackCompat
+
 // Useful debug output
 if ( $wgCommandLineMode ) {
-       $wgRequest = new FauxRequest( array() );
-
        wfDebug( "\n\nStart command line script $self\n" );
 } else {
-       // Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
-       $wgRequest = new WebRequest;
-
        $debug = "\n\nStart request {$wgRequest->getMethod()} {$wgRequest->getRequestURL()}\n";
 
        if ( $wgDebugPrintHttpHeaders ) {
index aa55e00..7b76592 100644 (file)
  * URL or via a POSTed form stripping illegal input characters and
  * normalizing Unicode sequences.
  *
- * Usually this is used via a global singleton, $wgRequest. You should
- * not create a second WebRequest object; make a FauxRequest object if
- * you want to pass arbitrary data to some function in place of the web
- * input.
- *
  * @ingroup HTTP
  */
 class WebRequest {
@@ -731,47 +726,27 @@ class WebRequest {
                return wfExpandUrl( $this->getRequestURL(), PROTO_CURRENT );
        }
 
-       /**
-        * Take an arbitrary query and rewrite the present URL to include it
-        * @deprecated Use appendQueryValue/appendQueryArray instead
-        * @param string $query Query string fragment; do not include initial '?'
-        * @return string
-        */
-       public function appendQuery( $query ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->appendQueryArray( wfCgiToArray( $query ) );
-       }
-
        /**
         * @param string $key
         * @param string $value
-        * @param bool $onlyquery [deprecated]
         * @return string
         */
-       public function appendQueryValue( $key, $value, $onlyquery = true ) {
-               return $this->appendQueryArray( array( $key => $value ), $onlyquery );
+       public function appendQueryValue( $key, $value ) {
+               return $this->appendQueryArray( array( $key => $value ) );
        }
 
        /**
         * Appends or replaces value of query variables.
         *
         * @param array $array Array of values to replace/add to query
-        * @param bool $onlyquery Whether to only return the query string
-        *  and not the complete URL [deprecated]
         * @return string
         */
-       public function appendQueryArray( $array, $onlyquery = true ) {
-               global $wgTitle;
+       public function appendQueryArray( $array ) {
                $newquery = $this->getQueryValues();
                unset( $newquery['title'] );
                $newquery = array_merge( $newquery, $array );
-               $query = wfArrayToCgi( $newquery );
-               if ( !$onlyquery ) {
-                       wfDeprecated( __METHOD__, '1.25' );
-                       return $wgTitle->getLocalURL( $query );
-               }
 
-               return $query;
+               return wfArrayToCgi( $newquery );
        }
 
        /**
index c99b0e3..0881a16 100644 (file)
@@ -126,7 +126,7 @@ class WebRequestUpload {
                        return true;
                }
 
-               $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
+               $contentLength = $this->request->getHeader( 'CONTENT-LENGTH' );
                $maxPostSize = wfShorthandToInteger(
                        ini_get( 'post_max_size' ) ?: ini_get( 'hhvm.server.max_post_size' ),
                        0
index 6ef0a80..c7c6a9c 100644 (file)
@@ -16,7 +16,8 @@
                        "御坂美琴",
                        "RyRubyy",
                        "Umherirrender",
-                       "Apflu"
+                       "Apflu",
+                       "Hzy980512"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|文档]]\n* [[mw:API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>本页所展示的所有特性都应正常工作,但是API仍在开发当中,将会随时变化。请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:API:Errors_and_warnings|API: 错误与警告]]。",
        "apihelp-query+contributors-param-limit": "返回的贡献数。",
        "apihelp-query+contributors-example-simple": "显示<kbd>Main Page</kbd>的贡献。",
        "apihelp-query+deletedrevisions-description": "获得删除修订版本信息。\n\n可在很多途径中使用:\n# 获得一组页面的已删除修订,通过设置标题或页面ID。以标题和时间戳排序。\n# 通过设置它们的ID与修订ID获得关于一组已删除修订。以修订ID排序。",
+       "apihelp-query+deletedrevisions-param-start": "要开始枚举的时间戳。在处理修订ID列表时无视。",
+       "apihelp-query+deletedrevisions-param-end": "要停止枚举的时间戳。在处理修订ID列表时无视。",
        "apihelp-query+deletedrevisions-param-tag": "只列出被此标签标记的修订。",
        "apihelp-query+deletedrevisions-param-user": "只列出此用户做出的修订。",
        "apihelp-query+deletedrevisions-param-excludeuser": "不要列出此用户做出的修订。",
        "apihelp-query+deletedrevs-param-user": "只列出此用户做出的修订。",
        "apihelp-query+deletedrevs-param-excludeuser": "不要列出此用户做出的修订。",
        "apihelp-query+deletedrevs-param-namespace": "只列出此名字空间的页面。",
+       "apihelp-query+deletedrevs-param-limit": "要列出的最大修订数量。",
        "apihelp-query+deletedrevs-param-prop": "要获取的属性:\n;revid:添加被删除修订的修订ID。\n;parentid:添加上一修订的修订ID至页面。\n;user:添加做出修订的用户。\n;userid:添加做出修订的用户ID。\n;comment:添加修订摘要。\n;parsedcomment:添加解析过的修订摘要。\n;minor:如果修订是小编辑则加标签。\n;len:添加修订长度(字节)。\n;sha1:添加修订的SHA-1(base 16)。\n;content:添加修订内容。\n;token:<span class=\"apihelp-deprecated\">已弃用。</span>提供编辑令牌。\n;tags:修订标签。",
        "apihelp-query+deletedrevs-example-mode1": "列出最近已删除的对页面<kbd>Main Page</kbd>和<kbd>Talk:Main Page</kbd>的贡献,带内容(模式1)。",
        "apihelp-query+deletedrevs-example-mode2": "列出由<kbd>Bob</kbd>作出的最近50次已删除贡献(模式2)。",
        "apihelp-query+deletedrevs-example-mode3-main": "列出前50次主名字空间已删除贡献(模式3)。",
        "apihelp-query+deletedrevs-example-mode3-talk": "列出前50次{{ns:talk}}名字空间已删除页面(模式3)。",
        "apihelp-query+disabled-description": "此查询模块已被禁用。",
+       "apihelp-query+duplicatefiles-description": "根据哈希值列出此给定文件的所有副本。",
        "apihelp-query+duplicatefiles-param-limit": "返回多少重复文件。",
        "apihelp-query+duplicatefiles-param-dir": "罗列所采用的方向。",
        "apihelp-query+duplicatefiles-param-localonly": "只看本地存储库的文件。",
        "apihelp-query+users-paramvalue-prop-emailable": "Tags if the user can and wants to receive email through [[Special:Emailuser]].",
        "apihelp-query+users-paramvalue-prop-gender": "Tags the gender of the user. Returns \"male\", \"female\", or \"unknown\".",
        "apihelp-query+users-paramvalue-prop-centralids": "添加中心ID并为用户附加状态。",
+       "apihelp-query+users-param-users": "要获取信息的用户列表。",
        "apihelp-query+users-param-token": "请改用<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>。",
        "apihelp-query+users-example-simple": "返回用户<kbd>Example</kbd>的信息。",
        "apihelp-query+watchlist-description": "在当前用户的监视列表中获取对页面的最近更改。",
        "apihelp-query+watchlistraw-param-fromtitle": "要列举的起始标题(带名字空间前缀)。",
        "apihelp-query+watchlistraw-param-totitle": "要列举的最终标题(带名字空间前缀)。",
        "apihelp-query+watchlistraw-example-simple": "列出当前用户的监视列表中的页面。",
+       "apihelp-query+watchlistraw-example-generator": "获取在此用户的监视列表中的页面的信息。",
        "apihelp-revisiondelete-description": "删除和恢复修订版本。",
        "apihelp-revisiondelete-param-type": "正在执行的修订版本删除类型。",
        "apihelp-revisiondelete-param-target": "要进行修订版本删除的页面标题,如果对某一类型需要。",
index 7eadcc8..1741e64 100644 (file)
@@ -625,9 +625,11 @@ class ChangesList extends ContextSource {
                if ( $rc instanceof RecentChange ) {
                        $isPatrolled = $rc->mAttribs['rc_patrolled'];
                        $rcType = $rc->mAttribs['rc_type'];
+                       $rcLogType = $rc->mAttribs['rc_log_type'];
                } else {
                        $isPatrolled = $rc->rc_patrolled;
                        $rcType = $rc->rc_type;
+                       $rcLogType = $rc->rc_log_type;
                }
 
                if ( !$isPatrolled ) {
@@ -637,6 +639,9 @@ class ChangesList extends ContextSource {
                        if ( $user->useNPPatrol() && $rcType == RC_NEW ) {
                                return true;
                        }
+                       if ( $user->useFilePatrol() && $rcLogType == 'upload' ) {
+                               return true;
+                       }
                }
 
                return false;
index 58a57a4..6f05dc4 100644 (file)
@@ -74,10 +74,16 @@ class OldChangesList extends ChangesList {
         */
        private function formatChangeLine( RecentChange $rc, array &$classes, $watched ) {
                $html = '';
+               $unpatrolled = $this->showAsUnpatrolled( $rc );
 
                if ( $rc->mAttribs['rc_log_type'] ) {
                        $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] );
                        $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'] );
+                       $flags = $this->recentChangesFlags( array( 'unpatrolled' =>$unpatrolled,
+                               'bot' => $rc->mAttribs['rc_bot'] ), '' );
+                       if ( $flags !== '' ) {
+                               $html .= ' ' . $flags;
+                       }
                // Log entries (old format) or log targets, and special pages
                } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
                        list( $name, $htmlubpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
@@ -86,7 +92,6 @@ class OldChangesList extends ChangesList {
                        }
                // Regular entries
                } else {
-                       $unpatrolled = $this->showAsUnpatrolled( $rc );
                        $this->insertDiffHist( $html, $rc, $unpatrolled );
                        # M, N, b and ! (minor, new, bot and unpatrolled)
                        $html .= $this->recentChangesFlags(
index 408b5ee..9ae30c0 100644 (file)
@@ -456,11 +456,13 @@ class RecentChange {
         * @return array Array of permissions errors, see Title::getUserPermissionsErrors()
         */
        public function doMarkPatrolled( User $user, $auto = false ) {
-               global $wgUseRCPatrol, $wgUseNPPatrol;
+               global $wgUseRCPatrol, $wgUseNPPatrol, $wgUseFilePatrol;
                $errors = array();
-               // If recentchanges patrol is disabled, only new pages
-               // can be patrolled
-               if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) {
+               // If recentchanges patrol is disabled, only new pages or new file versions
+               // can be patrolled, provided the appropriate config variable is set
+               if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) &&
+                       ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG &&
+                       $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) {
                        $errors[] = array( 'rcpatroldisabled' );
                }
                // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
@@ -700,10 +702,12 @@ class RecentChange {
         * @param string $params
         * @param int $newId
         * @param string $actionCommentIRC
+        * @param int $revId Id of associated revision, if any
         * @return RecentChange
         */
        public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip,
-               $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) {
+               $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '',
+               $revId = 0 ) {
                global $wgRequest;
 
                # # Get pageStatus for email notification
@@ -727,6 +731,10 @@ class RecentChange {
                                break;
                }
 
+               // Allow unpatrolled status when an associated rev id is passed
+               // May be used in core by moves and uploads
+               $markPatrolled = ( $revId > 0 ) ? $user->isAllowed( 'autopatrol' ) : true;
+
                $rc = new RecentChange;
                $rc->mTitle = $target;
                $rc->mPerformer = $user;
@@ -741,11 +749,11 @@ class RecentChange {
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
                        'rc_comment' => $logComment,
-                       'rc_this_oldid' => 0,
+                       'rc_this_oldid' => $revId,
                        'rc_last_oldid' => 0,
                        'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
                        'rc_ip' => self::checkIPAddress( $ip ),
-                       'rc_patrolled' => 1,
+                       'rc_patrolled' => $markPatrolled ? 1 : 0,
                        'rc_new' => 0, # obsolete
                        'rc_old_len' => null,
                        'rc_new_len' => null,
index 9b6e1f3..36c644a 100644 (file)
@@ -123,8 +123,13 @@ class RequestContext implements IContextSource, MutableContext {
         */
        public function getRequest() {
                if ( $this->request === null ) {
-                       global $wgRequest; # fallback to $wg till we can improve this
-                       $this->request = $wgRequest;
+                       global $wgCommandLineMode;
+                       // create the WebRequest object on the fly
+                       if ( $wgCommandLineMode ) {
+                               $this->request = new FauxRequest( array() );
+                       } else {
+                               $this->request = new WebRequest();
+                       }
                }
 
                return $this->request;
index 3fec522..37c9d6f 100644 (file)
@@ -166,13 +166,6 @@ abstract class DatabaseBase implements IDatabase {
        /** @var TransactionProfiler */
        protected $trxProfiler;
 
-       /**
-        * A string describing the current software version, and possibly
-        * other details in a user-friendly way. Will be listed on Special:Version, etc.
-        * Use getServerVersion() to get machine-friendly information.
-        *
-        * @return string Version information from the database server
-        */
        public function getServerInfo() {
                return $this->getServerVersion();
        }
@@ -197,27 +190,6 @@ abstract class DatabaseBase implements IDatabase {
                return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
        }
 
-       /**
-        * Turns buffering of SQL result sets on (true) or off (false). Default is
-        * "on".
-        *
-        * Unbuffered queries are very troublesome in MySQL:
-        *
-        *   - If another query is executed while the first query is being read
-        *     out, the first query is killed. This means you can't call normal
-        *     MediaWiki functions while you are reading an unbuffered query result
-        *     from a normal wfGetDB() connection.
-        *
-        *   - Unbuffered queries cause the MySQL server to use large amounts of
-        *     memory and to hold broad locks which block other queries.
-        *
-        * If you want to limit client-side memory, it's almost always better to
-        * split up queries into batches using a LIMIT clause than to switch off
-        * buffering.
-        *
-        * @param null|bool $buffer
-        * @return null|bool The previous value of the flag
-        */
        public function bufferResults( $buffer = null ) {
                if ( is_null( $buffer ) ) {
                        return !(bool)( $this->mFlags & DBO_NOBUFFER );
@@ -242,45 +214,18 @@ abstract class DatabaseBase implements IDatabase {
                return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
        }
 
-       /**
-        * Gets the current transaction level.
-        *
-        * Historically, transactions were allowed to be "nested". This is no
-        * longer supported, so this function really only returns a boolean.
-        *
-        * @return int The previous value
-        */
        public function trxLevel() {
                return $this->mTrxLevel;
        }
 
-       /**
-        * Get the UNIX timestamp of the time that the transaction was established
-        *
-        * This can be used to reason about the staleness of SELECT data
-        * in REPEATABLE-READ transaction isolation level.
-        *
-        * @return float|null Returns null if there is not active transaction
-        * @since 1.25
-        */
        public function trxTimestamp() {
                return $this->mTrxLevel ? $this->mTrxTimestamp : null;
        }
 
-       /**
-        * Get/set the table prefix.
-        * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
-        * @return string The previous table prefix.
-        */
        public function tablePrefix( $prefix = null ) {
                return wfSetVar( $this->mTablePrefix, $prefix );
        }
 
-       /**
-        * Get/set the db schema.
-        * @param string $schema The database schema to set, or omitted to leave it unchanged.
-        * @return string The previous db schema.
-        */
        public function dbSchema( $schema = null ) {
                return wfSetVar( $this->mSchema, $schema );
        }
@@ -294,15 +239,6 @@ abstract class DatabaseBase implements IDatabase {
                $this->fileHandle = $fh;
        }
 
-       /**
-        * Get properties passed down from the server info array of the load
-        * balancer.
-        *
-        * @param string $name The entry of the info array to get, or null to get the
-        *   whole array
-        *
-        * @return array|mixed|null
-        */
        public function getLBInfo( $name = null ) {
                if ( is_null( $name ) ) {
                        return $this->mLBInfo;
@@ -315,14 +251,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Set the LB info array, or a member of it. If called with one parameter,
-        * the LB info array is set to that parameter. If it is called with two
-        * parameters, the member with the given name is set to the given value.
-        *
-        * @param string $name
-        * @param array $value
-        */
        public function setLBInfo( $name, $value = null ) {
                if ( is_null( $value ) ) {
                        $this->mLBInfo = $name;
@@ -396,21 +324,10 @@ abstract class DatabaseBase implements IDatabase {
                return false;
        }
 
-       /**
-        * Returns true if this database does an implicit sort when doing GROUP BY
-        *
-        * @return bool
-        */
        public function implicitGroupby() {
                return true;
        }
 
-       /**
-        * Returns true if this database does an implicit order by when the column has an index
-        * For example: SELECT page_title FROM page LIMIT 1
-        *
-        * @return bool
-        */
        public function implicitOrderby() {
                return true;
        }
@@ -434,132 +351,52 @@ abstract class DatabaseBase implements IDatabase {
                return false;
        }
 
-       /**
-        * Return the last query that went through DatabaseBase::query()
-        * @return string
-        */
        public function lastQuery() {
                return $this->mLastQuery;
        }
 
-       /**
-        * Returns true if the connection may have been used for write queries.
-        * Should return true if unsure.
-        *
-        * @return bool
-        */
        public function doneWrites() {
                return (bool)$this->mDoneWrites;
        }
 
-       /**
-        * Returns the last time the connection may have been used for write queries.
-        * Should return a timestamp if unsure.
-        *
-        * @return int|float UNIX timestamp or false
-        * @since 1.24
-        */
        public function lastDoneWrites() {
                return $this->mDoneWrites ?: false;
        }
 
-       /**
-        * @return bool Whether there is a transaction open with possible write queries
-        * @since 1.27
-        */
        public function writesPending() {
                return $this->mTrxLevel && $this->mTrxDoneWrites;
        }
 
-       /**
-        * Returns true if there is a transaction open with possible write
-        * queries or transaction pre-commit/idle callbacks waiting on it to finish.
-        *
-        * @return bool
-        */
        public function writesOrCallbacksPending() {
                return $this->mTrxLevel && (
                        $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
                );
        }
 
-       /**
-        * Get the time spend running write queries for this
-        *
-        * High times could be due to scanning, updates, locking, and such
-        *
-        * @return float|bool Returns false if not transaction is active
-        * @since 1.26
-        */
        public function pendingWriteQueryDuration() {
                return $this->mTrxLevel ? $this->mTrxWriteDuration : false;
        }
 
-       /**
-        * Is a connection to the database open?
-        * @return bool
-        */
        public function isOpen() {
                return $this->mOpened;
        }
 
-       /**
-        * Set a flag for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
-        *       and removes it in command line mode
-        *   - DBO_PERSISTENT: use persistant database connection
-        */
        public function setFlag( $flag ) {
                $this->mFlags |= $flag;
        }
 
-       /**
-        * Clear a flag for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
-        *       and removes it in command line mode
-        *   - DBO_PERSISTENT: use persistant database connection
-        */
        public function clearFlag( $flag ) {
                $this->mFlags &= ~$flag;
        }
 
-       /**
-        * Returns a boolean whether the flag $flag is set for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_PERSISTENT: use persistant database connection
-        * @return bool
-        */
        public function getFlag( $flag ) {
                return !!( $this->mFlags & $flag );
        }
 
-       /**
-        * General read-only accessor
-        *
-        * @param string $name
-        * @return string
-        */
        public function getProperty( $name ) {
                return $this->$name;
        }
 
-       /**
-        * @return string
-        */
        public function getWikiID() {
                if ( $this->mTablePrefix ) {
                        return "{$this->mDBname}-{$this->mTablePrefix}";
@@ -830,13 +667,6 @@ abstract class DatabaseBase implements IDatabase {
                );
        }
 
-       /**
-        * Closes a database connection.
-        * if it is open : commits any open transactions
-        *
-        * @throws MWException
-        * @return bool Operation success. true if already closed.
-        */
        public function close() {
                if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
                        throw new MWException( "Transaction idle callbacks still pending." );
@@ -879,10 +709,6 @@ abstract class DatabaseBase implements IDatabase {
         */
        abstract protected function closeConnection();
 
-       /**
-        * @param string $error Fallback error message, used if none is given by DB
-        * @throws DBConnectionError
-        */
        function reportConnectionError( $error = 'Unknown error' ) {
                $myError = $this->lastError();
                if ( $myError ) {
@@ -927,28 +753,6 @@ abstract class DatabaseBase implements IDatabase {
                return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) );
        }
 
-       /**
-        * Run an SQL query and return the result. Normally throws a DBQueryError
-        * on failure. If errors are ignored, returns false instead.
-        *
-        * In new code, the query wrappers select(), insert(), update(), delete(),
-        * etc. should be used where possible, since they give much better DBMS
-        * independence and automatically quote or validate user input in a variety
-        * of contexts. This function is generally only useful for queries which are
-        * explicitly DBMS-dependent and are unsupported by the query wrappers, such
-        * as CREATE TABLE.
-        *
-        * However, the query wrappers themselves should call this function.
-        *
-        * @param string $sql SQL query
-        * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
-        *     comment (you can use __METHOD__ or add some extra info)
-        * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
-        *     maybe best to catch the exception instead?
-        * @throws MWException
-        * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
-        *     for a successful read query, or false on failure if $tempIgnore set
-        */
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
                global $wgUser;
 
@@ -1082,17 +886,6 @@ abstract class DatabaseBase implements IDatabase {
                return $res;
        }
 
-       /**
-        * Report a query error. Log the error, and if neither the object ignore
-        * flag nor the $tempIgnore flag is set, throw a DBQueryError.
-        *
-        * @param string $error
-        * @param int $errno
-        * @param string $sql
-        * @param string $fname
-        * @param bool $tempIgnore
-        * @throws DBQueryError
-        */
        public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
                if ( $this->ignoreErrors() || $tempIgnore ) {
                        wfDebug( "SQL ERROR (ignored): $error\n" );
@@ -1217,34 +1010,9 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Free a result object returned by query() or select(). It's usually not
-        * necessary to call this, just use unset() or let the variable holding
-        * the result object go out of scope.
-        *
-        * @param mixed $res A SQL result
-        */
        public function freeResult( $res ) {
        }
 
-       /**
-        * A SELECT wrapper which returns a single field from a single result row.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
-        *
-        * If no result rows are returned from the query, false is returned.
-        *
-        * @param string|array $table Table name. See DatabaseBase::select() for details.
-        * @param string $var The field name to select. This must be a valid SQL
-        *   fragment: do not use unvalidated user input.
-        * @param string|array $cond The condition array. See DatabaseBase::select() for details.
-        * @param string $fname The function name of the caller.
-        * @param string|array $options The query options. See DatabaseBase::select() for details.
-        *
-        * @return bool|mixed The value from the field, or false on failure.
-        * @throws DBUnexpectedError
-        */
        public function selectField(
                $table, $var, $cond = '', $fname = __METHOD__, $options = array()
        ) {
@@ -1272,26 +1040,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * A SELECT wrapper which returns a list of single field values from result rows.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
-        *
-        * If no result rows are returned from the query, false is returned.
-        *
-        * @param string|array $table Table name. See DatabaseBase::select() for details.
-        * @param string $var The field name to select. This must be a valid SQL
-        *   fragment: do not use unvalidated user input.
-        * @param string|array $cond The condition array. See DatabaseBase::select() for details.
-        * @param string $fname The function name of the caller.
-        * @param string|array $options The query options. See DatabaseBase::select() for details.
-        * @param string|array $join_conds The join conditions. See DatabaseBase::select() for details.
-        *
-        * @return bool|array The values from the field, or false on failure
-        * @throws DBUnexpectedError
-        * @since 1.25
-        */
        public function selectFieldValues(
                $table, $var, $cond = '', $fname = __METHOD__, $options = array(), $join_conds = array()
        ) {
@@ -1447,147 +1195,6 @@ abstract class DatabaseBase implements IDatabase {
                return '';
        }
 
-       /**
-        * Execute a SELECT query constructed using the various parameters provided.
-        * See below for full details of the parameters.
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param string|array $conds Conditions
-        * @param string $fname Caller function name
-        * @param array $options Query options
-        * @param array $join_conds Join conditions
-        *
-        *
-        * @param string|array $table
-        *
-        * May be either an array of table names, or a single string holding a table
-        * name. If an array is given, table aliases can be specified, for example:
-        *
-        *    array( 'a' => 'user' )
-        *
-        * This includes the user table in the query, with the alias "a" available
-        * for use in field names (e.g. a.user_name).
-        *
-        * All of the table names given here are automatically run through
-        * DatabaseBase::tableName(), which causes the table prefix (if any) to be
-        * added, and various other table name mappings to be performed.
-        *
-        *
-        * @param string|array $vars
-        *
-        * May be either a field name or an array of field names. The field names
-        * can be complete fragments of SQL, for direct inclusion into the SELECT
-        * query. If an array is given, field aliases can be specified, for example:
-        *
-        *   array( 'maxrev' => 'MAX(rev_id)' )
-        *
-        * This includes an expression with the alias "maxrev" in the query.
-        *
-        * If an expression is given, care must be taken to ensure that it is
-        * DBMS-independent.
-        *
-        *
-        * @param string|array $conds
-        *
-        * May be either a string containing a single condition, or an array of
-        * conditions. If an array is given, the conditions constructed from each
-        * element are combined with AND.
-        *
-        * Array elements may take one of two forms:
-        *
-        *   - Elements with a numeric key are interpreted as raw SQL fragments.
-        *   - Elements with a string key are interpreted as equality conditions,
-        *     where the key is the field name.
-        *     - If the value of such an array element is a scalar (such as a
-        *       string), it will be treated as data and thus quoted appropriately.
-        *       If it is null, an IS NULL clause will be added.
-        *     - If the value is an array, an IN (...) clause will be constructed
-        *       from its non-null elements, and an IS NULL clause will be added
-        *       if null is present, such that the field may match any of the
-        *       elements in the array. The non-null elements will be quoted.
-        *
-        * Note that expressions are often DBMS-dependent in their syntax.
-        * DBMS-independent wrappers are provided for constructing several types of
-        * expression commonly used in condition queries. See:
-        *    - DatabaseBase::buildLike()
-        *    - DatabaseBase::conditional()
-        *
-        *
-        * @param string|array $options
-        *
-        * Optional: Array of query options. Boolean options are specified by
-        * including them in the array as a string value with a numeric key, for
-        * example:
-        *
-        *    array( 'FOR UPDATE' )
-        *
-        * The supported options are:
-        *
-        *   - OFFSET: Skip this many rows at the start of the result set. OFFSET
-        *     with LIMIT can theoretically be used for paging through a result set,
-        *     but this is discouraged in MediaWiki for performance reasons.
-        *
-        *   - LIMIT: Integer: return at most this many rows. The rows are sorted
-        *     and then the first rows are taken until the limit is reached. LIMIT
-        *     is applied to a result set after OFFSET.
-        *
-        *   - FOR UPDATE: Boolean: lock the returned rows so that they can't be
-        *     changed until the next COMMIT.
-        *
-        *   - DISTINCT: Boolean: return only unique result rows.
-        *
-        *   - GROUP BY: May be either an SQL fragment string naming a field or
-        *     expression to group by, or an array of such SQL fragments.
-        *
-        *   - HAVING: May be either an string containing a HAVING clause or an array of
-        *     conditions building the HAVING clause. If an array is given, the conditions
-        *     constructed from each element are combined with AND.
-        *
-        *   - ORDER BY: May be either an SQL fragment giving a field name or
-        *     expression to order by, or an array of such SQL fragments.
-        *
-        *   - USE INDEX: This may be either a string giving the index name to use
-        *     for the query, or an array. If it is an associative array, each key
-        *     gives the table name (or alias), each value gives the index name to
-        *     use for that table. All strings are SQL fragments and so should be
-        *     validated by the caller.
-        *
-        *   - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
-        *     instead of SELECT.
-        *
-        * And also the following boolean MySQL extensions, see the MySQL manual
-        * for documentation:
-        *
-        *    - LOCK IN SHARE MODE
-        *    - STRAIGHT_JOIN
-        *    - HIGH_PRIORITY
-        *    - SQL_BIG_RESULT
-        *    - SQL_BUFFER_RESULT
-        *    - SQL_SMALL_RESULT
-        *    - SQL_CALC_FOUND_ROWS
-        *    - SQL_CACHE
-        *    - SQL_NO_CACHE
-        *
-        *
-        * @param string|array $join_conds
-        *
-        * Optional associative array of table-specific join conditions. In the
-        * most common case, this is unnecessary, since the join condition can be
-        * in $conds. However, it is useful for doing a LEFT JOIN.
-        *
-        * The key of the array contains the table name or alias. The value is an
-        * array with two elements, numbered 0 and 1. The first gives the type of
-        * join, the second is an SQL fragment giving the join condition for that
-        * table. For example:
-        *
-        *    array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) )
-        *
-        * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
-        *   with no rows in it will be returned. If there was a query error, a
-        *   DBQueryError exception will be thrown, except if the "ignore errors"
-        *   option was set, in which case false will be returned.
-        */
        public function select( $table, $vars, $conds = '', $fname = __METHOD__,
                $options = array(), $join_conds = array() ) {
                $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1595,22 +1202,6 @@ abstract class DatabaseBase implements IDatabase {
                return $this->query( $sql, $fname );
        }
 
-       /**
-        * The equivalent of DatabaseBase::select() except that the constructed SQL
-        * is returned, instead of being immediately executed. This can be useful for
-        * doing UNION queries, where the SQL text of each query is needed. In general,
-        * however, callers outside of Database classes should just use select().
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param string|array $conds Conditions
-        * @param string $fname Caller function name
-        * @param string|array $options Query options
-        * @param string|array $join_conds Join conditions
-        *
-        * @return string SQL query string.
-        * @see DatabaseBase::select()
-        */
        public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
                $options = array(), $join_conds = array()
        ) {
@@ -1662,20 +1253,6 @@ abstract class DatabaseBase implements IDatabase {
                return $sql;
        }
 
-       /**
-        * Single row SELECT wrapper. Equivalent to DatabaseBase::select(), except
-        * that a single row object is returned. If the query returns no rows,
-        * false is returned.
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param array $conds Conditions
-        * @param string $fname Caller function name
-        * @param string|array $options Query options
-        * @param array|string $join_conds Join conditions
-        *
-        * @return stdClass|bool
-        */
        public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
                $options = array(), $join_conds = array()
        ) {
@@ -1696,26 +1273,6 @@ abstract class DatabaseBase implements IDatabase {
                return $obj;
        }
 
-       /**
-        * Estimate the number of rows in dataset
-        *
-        * MySQL allows you to estimate the number of rows that would be returned
-        * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
-        * index cardinality statistics, and is notoriously inaccurate, especially
-        * when large numbers of rows have recently been added or deleted.
-        *
-        * For DBMSs that don't support fast result size estimation, this function
-        * will actually perform the SELECT COUNT(*).
-        *
-        * Takes the same arguments as DatabaseBase::select().
-        *
-        * @param string $table Table name
-        * @param string $vars Unused
-        * @param array|string $conds Filters on the table
-        * @param string $fname Function name for profiling
-        * @param array $options Options for select
-        * @return int Row count
-        */
        public function estimateRowCount(
                $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
        ) {
@@ -1730,23 +1287,6 @@ abstract class DatabaseBase implements IDatabase {
                return $rows;
        }
 
-       /**
-        * Get the number of rows in dataset
-        *
-        * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
-        *
-        * Takes the same arguments as DatabaseBase::select().
-        *
-        * @since 1.27 Added $join_conds parameter
-        *
-        * @param array|string $tables Table names
-        * @param string $vars Unused
-        * @param array|string $conds Filters on the table
-        * @param string $fname Function name for profiling
-        * @param array $options Options for select
-        * @param array $join_conds Join conditions (since 1.27)
-        * @return int Row count
-        */
        public function selectRowCount(
                $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array()
        ) {
@@ -1792,30 +1332,12 @@ abstract class DatabaseBase implements IDatabase {
                return $sql;
        }
 
-       /**
-        * Determines whether a field exists in a table
-        *
-        * @param string $table Table name
-        * @param string $field Filed to check on that table
-        * @param string $fname Calling function name (optional)
-        * @return bool Whether $table has filed $field
-        */
        public function fieldExists( $table, $field, $fname = __METHOD__ ) {
                $info = $this->fieldInfo( $table, $field );
 
                return (bool)$info;
        }
 
-       /**
-        * Determines whether an index exists
-        * Usually throws a DBQueryError on failure
-        * If errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
        public function indexExists( $table, $index, $fname = __METHOD__ ) {
                if ( !$this->tableExists( $table ) ) {
                        return null;
@@ -1829,13 +1351,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Query whether a given table exists
-        *
-        * @param string $table
-        * @param string $fname
-        * @return bool
-        */
        public function tableExists( $table, $fname = __METHOD__ ) {
                $table = $this->tableName( $table );
                $old = $this->ignoreErrors( true );
@@ -1845,14 +1360,6 @@ abstract class DatabaseBase implements IDatabase {
                return (bool)$res;
        }
 
-       /**
-        * Determines if a given index is unique
-        *
-        * @param string $table
-        * @param string $index
-        *
-        * @return bool
-        */
        public function indexUnique( $table, $index ) {
                $indexInfo = $this->indexInfo( $table, $index );
 
@@ -1873,39 +1380,6 @@ abstract class DatabaseBase implements IDatabase {
                return implode( ' ', $options );
        }
 
-       /**
-        * INSERT wrapper, inserts an array into a table.
-        *
-        * $a may be either:
-        *
-        *   - A single associative array. The array keys are the field names, and
-        *     the values are the values to insert. The values are treated as data
-        *     and will be quoted appropriately. If NULL is inserted, this will be
-        *     converted to a database NULL.
-        *   - An array with numeric keys, holding a list of associative arrays.
-        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
-        *     each subarray must be identical to each other, and in the same order.
-        *
-        * $options is an array of options, with boolean options encoded as values
-        * with numeric keys, in the same style as $options in
-        * DatabaseBase::select(). Supported options are:
-        *
-        *   - IGNORE: Boolean: if present, duplicate key errors are ignored, and
-        *     any rows which cause duplicate key errors are not inserted. It's
-        *     possible to determine how many rows were successfully inserted using
-        *     DatabaseBase::affectedRows().
-        *
-        * @param string $table Table name. This will be passed through
-        *   DatabaseBase::tableName().
-        * @param array $a Array of rows to insert
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @param array $options Array of options
-        *
-        * @throws DBQueryError Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
-        * @return bool
-        */
        public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
                # No rows to insert, easy just return now
                if ( !count( $a ) ) {
@@ -1994,24 +1468,6 @@ abstract class DatabaseBase implements IDatabase {
                return implode( ' ', $opts );
        }
 
-       /**
-        * UPDATE wrapper. Takes a condition array and a SET array.
-        *
-        * @param string $table Name of the table to UPDATE. This will be passed through
-        *   DatabaseBase::tableName().
-        * @param array $values An array of values to SET. For each array element,
-        *   the key gives the field name, and the value gives the data to set
-        *   that field to. The data will be quoted by DatabaseBase::addQuotes().
-        * @param array $conds An array of conditions (WHERE). See
-        *   DatabaseBase::select() for the details of the format of condition
-        *   arrays. Use '*' to update all rows.
-        * @param string $fname The function name of the caller (from __METHOD__),
-        *   for logging and profiling.
-        * @param array $options An array of UPDATE options, can be:
-        *   - IGNORE: Ignore unique key conflicts
-        *   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
-        * @return bool
-        */
        function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
                $table = $this->tableName( $table );
                $opts = $this->makeUpdateOptions( $options );
@@ -2024,20 +1480,6 @@ abstract class DatabaseBase implements IDatabase {
                return $this->query( $sql, $fname );
        }
 
-       /**
-        * Makes an encoded list of strings from an array
-        *
-        * @param array $a Containing the data
-        * @param int $mode Constant
-        *    - LIST_COMMA: Comma separated, no field names
-        *    - LIST_AND:   ANDed WHERE clause (without the WHERE). See the
-        *      documentation for $conds in DatabaseBase::select().
-        *    - LIST_OR:    ORed WHERE clause (without the WHERE)
-        *    - LIST_SET:   Comma separated with field names, like a SET clause
-        *    - LIST_NAMES: Comma separated field names
-        * @throws MWException|DBUnexpectedError
-        * @return string
-        */
        public function makeList( $a, $mode = LIST_COMMA ) {
                if ( !is_array( $a ) ) {
                        throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
@@ -2113,16 +1555,6 @@ abstract class DatabaseBase implements IDatabase {
                return $list;
        }
 
-       /**
-        * Build a partial where clause from a 2-d array such as used for LinkBatch.
-        * The keys on each level may be either integers or strings.
-        *
-        * @param array $data Organized as 2-d
-        *    array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
-        * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
-        * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
-        * @return string|bool SQL fragment, or false if no items in array
-        */
        public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
                $conds = array();
 
@@ -2154,58 +1586,22 @@ abstract class DatabaseBase implements IDatabase {
                return $valuename;
        }
 
-       /**
-        * @param string $field
-        * @return string
-        */
        public function bitNot( $field ) {
                return "(~$field)";
        }
 
-       /**
-        * @param string $fieldLeft
-        * @param string $fieldRight
-        * @return string
-        */
        public function bitAnd( $fieldLeft, $fieldRight ) {
                return "($fieldLeft & $fieldRight)";
        }
 
-       /**
-        * @param string $fieldLeft
-        * @param string $fieldRight
-        * @return string
-        */
        public function bitOr( $fieldLeft, $fieldRight ) {
                return "($fieldLeft | $fieldRight)";
        }
 
-       /**
-        * Build a concatenation list to feed into a SQL query
-        * @param array $stringList List of raw SQL expressions; caller is
-        *   responsible for any quoting
-        * @return string
-        */
        public function buildConcat( $stringList ) {
                return 'CONCAT(' . implode( ',', $stringList ) . ')';
        }
 
-       /**
-        * Build a GROUP_CONCAT or equivalent statement for a query.
-        *
-        * This is useful for combining a field for several rows into a single string.
-        * NULL values will not appear in the output, duplicated values will appear,
-        * and the resulting delimiter-separated values have no defined sort order.
-        * Code using the results may need to use the PHP unique() or sort() methods.
-        *
-        * @param string $delim Glue to bind the results together
-        * @param string|array $table Table name
-        * @param string $field Field name
-        * @param string|array $conds Conditions
-        * @param string|array $join_conds Join conditions
-        * @return string SQL text
-        * @since 1.23
-        */
        public function buildGroupConcatField(
                $delim, $table, $field, $conds = '', $join_conds = array()
        ) {
@@ -2214,15 +1610,6 @@ abstract class DatabaseBase implements IDatabase {
                return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
        }
 
-       /**
-        * Change the current database
-        *
-        * @todo Explain what exactly will fail if this is not overridden.
-        *
-        * @param string $db
-        *
-        * @return bool Success or failure
-        */
        public function selectDB( $db ) {
                # Stub. Shouldn't cause serious problems if it's not overridden, but
                # if your database engine supports a concept similar to MySQL's
@@ -2232,18 +1619,10 @@ abstract class DatabaseBase implements IDatabase {
                return true;
        }
 
-       /**
-        * Get the current DB name
-        * @return string
-        */
        public function getDBname() {
                return $this->mDBname;
        }
 
-       /**
-        * Get the server hostname or IP address
-        * @return string
-        */
        public function getServer() {
                return $this->mServer;
        }
@@ -2543,12 +1922,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Adds quotes and backslashes.
-        *
-        * @param string|Blob $s
-        * @return string
-        */
        public function addQuotes( $s ) {
                if ( $s instanceof Blob ) {
                        $s = $s->fetch();
@@ -2596,22 +1969,6 @@ abstract class DatabaseBase implements IDatabase {
                return addcslashes( $s, '\%_' );
        }
 
-       /**
-        * LIKE statement wrapper, receives a variable-length argument list with
-        * parts of pattern to match containing either string literals that will be
-        * escaped or tokens returned by anyChar() or anyString(). Alternatively,
-        * the function could be provided with an array of aforementioned
-        * parameters.
-        *
-        * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
-        * a LIKE clause that searches for subpages of 'My page title'.
-        * Alternatively:
-        *   $pattern = array( 'My_page_title/', $dbr->anyString() );
-        *   $query .= $dbr->buildLike( $pattern );
-        *
-        * @since 1.16
-        * @return string Fully built LIKE statement
-        */
        public function buildLike() {
                $params = func_get_args();
 
@@ -2632,35 +1989,14 @@ abstract class DatabaseBase implements IDatabase {
                return " LIKE {$this->addQuotes( $s )} ";
        }
 
-       /**
-        * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
-        *
-        * @return LikeMatch
-        */
        public function anyChar() {
                return new LikeMatch( '_' );
        }
 
-       /**
-        * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
-        *
-        * @return LikeMatch
-        */
        public function anyString() {
                return new LikeMatch( '%' );
        }
 
-       /**
-        * Returns an appropriately quoted sequence value for inserting a new row.
-        * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
-        * subclass will return an integer, and save the value for insertId()
-        *
-        * Any implementation of this function should *not* involve reusing
-        * sequence numbers created for rolled-back transactions.
-        * See http://bugs.mysql.com/bug.php?id=30767 for details.
-        * @param string $seqName
-        * @return null|int
-        */
        public function nextSequenceValue( $seqName ) {
                return null;
        }
@@ -2679,28 +2015,6 @@ abstract class DatabaseBase implements IDatabase {
                return '';
        }
 
-       /**
-        * REPLACE query wrapper.
-        *
-        * REPLACE is a very handy MySQL extension, which functions like an INSERT
-        * except that when there is a duplicate key error, the old row is deleted
-        * and the new row is inserted in its place.
-        *
-        * We simulate this with standard SQL with a DELETE followed by INSERT. To
-        * perform the delete, we need to know what the unique indexes are so that
-        * we know how to find the conflicting rows.
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely
-        * to collide. However if you do this, you run the risk of encountering
-        * errors which wouldn't have occurred in MySQL.
-        *
-        * @param string $table The table to replace the row(s) in.
-        * @param array $uniqueIndexes Is an array of indexes. Each element may be either
-        *    a field name or an array of field names
-        * @param array $rows Can be either a single row to insert, or multiple rows,
-        *    in the same format as for DatabaseBase::insert()
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        */
        public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
                $quotedTable = $this->tableName( $table );
 
@@ -2783,40 +2097,6 @@ abstract class DatabaseBase implements IDatabase {
                return $this->query( $sql, $fname );
        }
 
-       /**
-        * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
-        *
-        * This updates any conflicting rows (according to the unique indexes) using
-        * the provided SET clause and inserts any remaining (non-conflicted) rows.
-        *
-        * $rows may be either:
-        *   - A single associative array. The array keys are the field names, and
-        *     the values are the values to insert. The values are treated as data
-        *     and will be quoted appropriately. If NULL is inserted, this will be
-        *     converted to a database NULL.
-        *   - An array with numeric keys, holding a list of associative arrays.
-        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
-        *     each subarray must be identical to each other, and in the same order.
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely
-        * to collide. However if you do this, you run the risk of encountering
-        * errors which wouldn't have occurred in MySQL.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
-        * @since 1.22
-        *
-        * @param string $table Table name. This will be passed through DatabaseBase::tableName().
-        * @param array $rows A single row or list of rows to insert
-        * @param array $uniqueIndexes List of single field names or field name tuples
-        * @param array $set An array of values to SET. For each array element, the
-        *   key gives the field name, and the value gives the data to set that
-        *   field to. The data will be quoted by DatabaseBase::addQuotes().
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws Exception
-        * @return bool
-        */
        public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
                $fname = __METHOD__
        ) {
@@ -2871,26 +2151,6 @@ abstract class DatabaseBase implements IDatabase {
                return $ok;
        }
 
-       /**
-        * DELETE where the condition is a join.
-        *
-        * MySQL overrides this to use a multi-table DELETE syntax, in other databases
-        * we use sub-selects
-        *
-        * For safety, an empty $conds will not delete everything. If you want to
-        * delete all rows where the join condition matches, set $conds='*'.
-        *
-        * DO NOT put the join condition in $conds.
-        *
-        * @param string $delTable The table to delete from.
-        * @param string $joinTable The other table.
-        * @param string $delVar The variable to join on, in the first table.
-        * @param string $joinVar The variable to join on, in the second table.
-        * @param array $conds Condition array of field names mapped to variables,
-        *   ANDed together in the WHERE clause
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws DBUnexpectedError
-        */
        public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
                $fname = __METHOD__
        ) {
@@ -2946,16 +2206,6 @@ abstract class DatabaseBase implements IDatabase {
                return '';
        }
 
-       /**
-        * DELETE query wrapper.
-        *
-        * @param array $table Table name
-        * @param string|array $conds Array of conditions. See $conds in DatabaseBase::select()
-        *   for the format. Use $conds == "*" to delete all rows
-        * @param string $fname Name of the calling function
-        * @throws DBUnexpectedError
-        * @return bool|ResultWrapper
-        */
        public function delete( $table, $conds, $fname = __METHOD__ ) {
                if ( !$conds ) {
                        throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
@@ -2974,32 +2224,6 @@ abstract class DatabaseBase implements IDatabase {
                return $this->query( $sql, $fname );
        }
 
-       /**
-        * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
-        * into another table.
-        *
-        * @param string $destTable The table name to insert into
-        * @param string|array $srcTable May be either a table name, or an array of table names
-        *    to include in a join.
-        *
-        * @param array $varMap Must be an associative array of the form
-        *    array( 'dest1' => 'source1', ...). Source items may be literals
-        *    rather than field names, but strings should be quoted with
-        *    DatabaseBase::addQuotes()
-        *
-        * @param array $conds Condition array. See $conds in DatabaseBase::select() for
-        *    the details of the format of condition arrays. May be "*" to copy the
-        *    whole table.
-        *
-        * @param string $fname The function name of the caller, from __METHOD__
-        *
-        * @param array $insertOptions Options for the INSERT part of the query, see
-        *    DatabaseBase::insert() for details.
-        * @param array $selectOptions Options for the SELECT part of the query, see
-        *    DatabaseBase::select() for details.
-        *
-        * @return ResultWrapper
-        */
        public function insertSelect( $destTable, $srcTable, $varMap, $conds,
                $fname = __METHOD__,
                $insertOptions = array(), $selectOptions = array()
@@ -3069,38 +2293,16 @@ abstract class DatabaseBase implements IDatabase {
                        . "{$limit} ";
        }
 
-       /**
-        * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
-        * within the UNION construct.
-        * @return bool
-        */
        public function unionSupportsOrderAndLimit() {
                return true; // True for almost every DB supported
        }
 
-       /**
-        * Construct a UNION query
-        * This is used for providing overload point for other DB abstractions
-        * not compatible with the MySQL syntax.
-        * @param array $sqls SQL statements to combine
-        * @param bool $all Use UNION ALL
-        * @return string SQL fragment
-        */
        public function unionQueries( $sqls, $all ) {
                $glue = $all ? ') UNION ALL (' : ') UNION (';
 
                return '(' . implode( $glue, $sqls ) . ')';
        }
 
-       /**
-        * Returns an SQL expression for a simple conditional. This doesn't need
-        * to be overridden unless CASE isn't supported in your DBMS.
-        *
-        * @param string|array $cond SQL expression which will result in a boolean value
-        * @param string $trueVal SQL expression to return if true
-        * @param string $falseVal SQL expression to return if false
-        * @return string SQL fragment
-        */
        public function conditional( $cond, $trueVal, $falseVal ) {
                if ( is_array( $cond ) ) {
                        $cond = $this->makeList( $cond, LIST_AND );
@@ -3109,67 +2311,26 @@ abstract class DatabaseBase implements IDatabase {
                return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
        }
 
-       /**
-        * Returns a comand for str_replace function in SQL query.
-        * Uses REPLACE() in MySQL
-        *
-        * @param string $orig Column to modify
-        * @param string $old Column to seek
-        * @param string $new Column to replace with
-        *
-        * @return string
-        */
        public function strreplace( $orig, $old, $new ) {
                return "REPLACE({$orig}, {$old}, {$new})";
        }
 
-       /**
-        * Determines how long the server has been up
-        * STUB
-        *
-        * @return int
-        */
        public function getServerUptime() {
                return 0;
        }
 
-       /**
-        * Determines if the last failure was due to a deadlock
-        * STUB
-        *
-        * @return bool
-        */
        public function wasDeadlock() {
                return false;
        }
 
-       /**
-        * Determines if the last failure was due to a lock timeout
-        * STUB
-        *
-        * @return bool
-        */
        public function wasLockTimeout() {
                return false;
        }
 
-       /**
-        * Determines if the last query error was something that should be dealt
-        * with by pinging the connection and reissuing the query.
-        * STUB
-        *
-        * @return bool
-        */
        public function wasErrorReissuable() {
                return false;
        }
 
-       /**
-        * Determines if the last failure was due to the database being read-only.
-        * STUB
-        *
-        * @return bool
-        */
        public function wasReadOnlyError() {
                return false;
        }
@@ -3200,9 +2361,9 @@ abstract class DatabaseBase implements IDatabase {
         * Returns whatever the callback function returned on its successful,
         * iteration, or false on error, for example if the retry limit was
         * reached.
-        *
         * @return mixed
-        * @throws DBQueryError
+        * @throws DBUnexpectedError
+        * @throws Exception
         */
        public function deadlockLoop() {
                $args = func_get_args();
@@ -3212,6 +2373,7 @@ abstract class DatabaseBase implements IDatabase {
                $this->begin( __METHOD__ );
 
                $retVal = null;
+               /** @var Exception $e */
                $e = null;
                do {
                        try {
@@ -3239,55 +2401,21 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Wait for the slave to catch up to a given master position.
-        *
-        * @param DBMasterPos $pos
-        * @param int $timeout The maximum number of seconds to wait for
-        *   synchronisation
-        * @return int Zero if the slave was past that position already,
-        *   greater than zero if we waited for some period of time, less than
-        *   zero if we timed out.
-        */
        public function masterPosWait( DBMasterPos $pos, $timeout ) {
                # Real waits are implemented in the subclass.
                return 0;
        }
 
-       /**
-        * Get the replication position of this slave
-        *
-        * @return DBMasterPos|bool False if this is not a slave.
-        */
        public function getSlavePos() {
                # Stub
                return false;
        }
 
-       /**
-        * Get the position of this master
-        *
-        * @return DBMasterPos|bool False if this is not a master
-        */
        public function getMasterPos() {
                # Stub
                return false;
        }
 
-       /**
-        * Run an anonymous function as soon as there is no transaction pending.
-        * If there is a transaction and it is rolled back, then the callback is cancelled.
-        * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
-        * Callbacks must commit any transactions that they begin.
-        *
-        * This is useful for updates to different systems or when separate transactions are needed.
-        * For example, one might want to enqueue jobs into a system outside the database, but only
-        * after the database is updated so that the jobs will see the data when they actually run.
-        * It can also be used for updates that easily cause deadlocks if locks are held too long.
-        *
-        * @param callable $callback
-        * @since 1.20
-        */
        final public function onTransactionIdle( $callback ) {
                $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
                if ( !$this->mTrxLevel ) {
@@ -3295,17 +2423,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Run an anonymous function before the current transaction commits or now if there is none.
-        * If there is a transaction and it is rolled back, then the callback is cancelled.
-        * Callbacks must not start nor commit any transactions.
-        *
-        * This is useful for updates that easily cause deadlocks if locks are held too long
-        * but where atomicity is strongly desired for these updates and some related updates.
-        *
-        * @param callable $callback
-        * @since 1.22
-        */
        final public function onTransactionPreCommitOrIdle( $callback ) {
                if ( $this->mTrxLevel ) {
                        $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
@@ -3383,30 +2500,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Begin an atomic section of statements
-        *
-        * If a transaction has been started already, just keep track of the given
-        * section name to make sure the transaction is not committed pre-maturely.
-        * This function can be used in layers (with sub-sections), so use a stack
-        * to keep track of the different atomic sections. If there is no transaction,
-        * start one implicitly.
-        *
-        * The goal of this function is to create an atomic section of SQL queries
-        * without having to start a new transaction if it already exists.
-        *
-        * Atomic sections are more strict than transactions. With transactions,
-        * attempting to begin a new transaction when one is already running results
-        * in MediaWiki issuing a brief warning and doing an implicit commit. All
-        * atomic levels *must* be explicitly closed using DatabaseBase::endAtomic(),
-        * and any database transactions cannot be began or committed until all atomic
-        * levels are closed. There is no such thing as implicitly opening or closing
-        * an atomic section.
-        *
-        * @since 1.23
-        * @param string $fname
-        * @throws DBError
-        */
        final public function startAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
                        $this->begin( $fname );
@@ -3421,17 +2514,6 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxAtomicLevels[] = $fname;
        }
 
-       /**
-        * Ends an atomic section of SQL statements
-        *
-        * Ends the next section of atomic SQL statements and commits the transaction
-        * if necessary.
-        *
-        * @since 1.23
-        * @see DatabaseBase::startAtomic
-        * @param string $fname
-        * @throws DBError
-        */
        final public function endAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
                        throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
@@ -3462,21 +2544,6 @@ abstract class DatabaseBase implements IDatabase {
                $this->endAtomic( $fname );
        }
 
-       /**
-        * Begin a transaction. If a transaction is already in progress,
-        * that transaction will be committed before the new transaction is started.
-        *
-        * Note that when the DBO_TRX flag is set (which is usually the case for web
-        * requests, but not for maintenance scripts), any previous database query
-        * will have started a transaction automatically.
-        *
-        * Nesting of transactions is not supported. Attempts to nest transactions
-        * will cause a warning, unless the current transaction was started
-        * automatically because of the DBO_TRX flag.
-        *
-        * @param string $fname
-        * @throws DBError
-        */
        final public function begin( $fname = __METHOD__ ) {
                if ( $this->mTrxLevel ) { // implicit commit
                        if ( $this->mTrxAtomicLevels ) {
@@ -3551,20 +2618,6 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxLevel = 1;
        }
 
-       /**
-        * Commits a transaction previously started using begin().
-        * If no transaction is in progress, a warning is issued.
-        *
-        * Nesting of transactions is not supported.
-        *
-        * @param string $fname
-        * @param string $flush Flush flag, set to 'flush' to disable warnings about
-        *   explicitly committing implicit transactions, or calling commit when no
-        *   transaction is in progress. This will silently break any ongoing
-        *   explicit transaction. Only set the flush flag if you are sure that it
-        *   is safe to ignore these warnings in your context.
-        * @throws DBUnexpectedError
-        */
        final public function commit( $fname = __METHOD__, $flush = '' ) {
                if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
                        // There are still atomic sections open. This cannot be ignored
@@ -3617,20 +2670,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Rollback a transaction previously started using begin().
-        * If no transaction is in progress, a warning is issued.
-        *
-        * No-op on non-transactional databases.
-        *
-        * @param string $fname
-        * @param string $flush Flush flag, set to 'flush' to disable warnings about
-        *   calling rollback when no transaction is in progress. This will silently
-        *   break any ongoing explicit transaction. Only set the flush flag if you
-        *   are sure that it is safe to ignore these warnings in your context.
-        * @throws DBUnexpectedError
-        * @since 1.23 Added $flush parameter
-        */
        final public function rollback( $fname = __METHOD__, $flush = '' ) {
                if ( $flush !== 'flush' ) {
                        if ( !$this->mTrxLevel ) {
@@ -3695,14 +2734,6 @@ abstract class DatabaseBase implements IDatabase {
                        'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
        }
 
-       /**
-        * List all tables on the database
-        *
-        * @param string $prefix Only show tables with this prefix, e.g. mw_
-        * @param string $fname Calling function name
-        * @throws MWException
-        * @return array
-        */
        function listTables( $prefix = null, $fname = __METHOD__ ) {
                throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
        }
@@ -3743,34 +2774,10 @@ abstract class DatabaseBase implements IDatabase {
                throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
        }
 
-       /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS.
-        *
-        * The result is unquoted, and needs to be passed through addQuotes()
-        * before it can be included in raw SQL.
-        *
-        * @param string|int $ts
-        *
-        * @return string
-        */
        public function timestamp( $ts = 0 ) {
                return wfTimestamp( TS_MW, $ts );
        }
 
-       /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS. If
-        * NULL is input, it is passed through, allowing NULL values to be inserted
-        * into timestamp fields.
-        *
-        * The result is unquoted, and needs to be passed through addQuotes()
-        * before it can be included in raw SQL.
-        *
-        * @param string|int $ts
-        *
-        * @return string
-        */
        public function timestampOrNull( $ts = null ) {
                if ( is_null( $ts ) ) {
                        return null;
@@ -3805,11 +2812,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Ping the server and try to reconnect if it there is no connection
-        *
-        * @return bool Success or failure
-        */
        public function ping() {
                # Stub. Not essential to override.
                return true;
@@ -3888,36 +2890,14 @@ abstract class DatabaseBase implements IDatabase {
                return 0;
        }
 
-       /**
-        * Return the maximum number of items allowed in a list, or 0 for unlimited.
-        *
-        * @return int
-        */
        function maxListLen() {
                return 0;
        }
 
-       /**
-        * Some DBMSs have a special format for inserting into blob fields, they
-        * don't allow simple quoted strings to be inserted. To insert into such
-        * a field, pass the data through this function before passing it to
-        * DatabaseBase::insert().
-        *
-        * @param string $b
-        * @return string
-        */
        public function encodeBlob( $b ) {
                return $b;
        }
 
-       /**
-        * Some DBMSs return a special placeholder object representing blob fields
-        * in result objects. Pass the object through this function to return the
-        * original string.
-        *
-        * @param string|Blob $b
-        * @return string
-        */
        public function decodeBlob( $b ) {
                if ( $b instanceof Blob ) {
                        $b = $b->fetch();
@@ -3925,16 +2905,6 @@ abstract class DatabaseBase implements IDatabase {
                return $b;
        }
 
-       /**
-        * Override database's default behavior. $options include:
-        *     'connTimeout' : Set the connection timeout value in seconds.
-        *                     May be useful for very long batch queries such as
-        *                     full-wiki dumps, where a single query reads out over
-        *                     hours or days.
-        *
-        * @param array $options
-        * @return void
-        */
        public function setSessionOptions( array $options ) {
        }
 
@@ -4000,13 +2970,6 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       /**
-        * Set variables to be used in sourceFile/sourceStream, in preference to the
-        * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
-        * all. If it's set to false, $GLOBALS will be used.
-        *
-        * @param bool|array $vars Mapping variable name to value.
-        */
        public function setSchemaVars( $vars ) {
                $this->mSchemaVars = $vars;
        }
@@ -4173,54 +3136,18 @@ abstract class DatabaseBase implements IDatabase {
                return array();
        }
 
-       /**
-        * Check to see if a named lock is available (non-blocking)
-        *
-        * @param string $lockName Name of lock to poll
-        * @param string $method Name of method calling us
-        * @return bool
-        * @since 1.20
-        */
        public function lockIsFree( $lockName, $method ) {
                return true;
        }
 
-       /**
-        * Acquire a named lock
-        *
-        * Named locks are not related to transactions
-        *
-        * @param string $lockName Name of lock to aquire
-        * @param string $method Name of method calling us
-        * @param int $timeout
-        * @return bool
-        */
        public function lock( $lockName, $method, $timeout = 5 ) {
                return true;
        }
 
-       /**
-        * Release a lock
-        *
-        * Named locks are not related to transactions
-        *
-        * @param string $lockName Name of lock to release
-        * @param string $method Name of method calling us
-        *
-        * @return int Returns 1 if the lock was released, 0 if the lock was not established
-        * by this thread (in which case the lock is not released), and NULL if the named
-        * lock did not exist
-        */
        public function unlock( $lockName, $method ) {
                return true;
        }
 
-       /**
-        * Check to see if a named lock used by lock() use blocking queues
-        *
-        * @return bool
-        * @since 1.26
-        */
        public function namedLocksEnqueue() {
                return false;
        }
@@ -4277,51 +3204,22 @@ abstract class DatabaseBase implements IDatabase {
                return 'SearchEngineDummy';
        }
 
-       /**
-        * Find out when 'infinity' is. Most DBMSes support this. This is a special
-        * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
-        * because "i" sorts after all numbers.
-        *
-        * @return string
-        */
        public function getInfinity() {
                return 'infinity';
        }
 
-       /**
-        * Encode an expiry time into the DBMS dependent format
-        *
-        * @param string $expiry Timestamp for expiry, or the 'infinity' string
-        * @return string
-        */
        public function encodeExpiry( $expiry ) {
                return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
                        ? $this->getInfinity()
                        : $this->timestamp( $expiry );
        }
 
-       /**
-        * Decode an expiry time into a DBMS independent format
-        *
-        * @param string $expiry DB timestamp field value for expiry
-        * @param int $format TS_* constant, defaults to TS_MW
-        * @return string
-        */
        public function decodeExpiry( $expiry, $format = TS_MW ) {
                return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
                        ? 'infinity'
                        : wfTimestamp( $format, $expiry );
        }
 
-       /**
-        * Allow or deny "big selects" for this session only. This is done by setting
-        * the sql_big_selects session variable.
-        *
-        * This is a MySQL-specific feature.
-        *
-        * @param bool|string $value True for allow, false for deny, or "default" to
-        *   restore the initial value
-        */
        public function setBigSelects( $value = true ) {
                // no-op
        }
index 5e37663..d588d51 100644 (file)
@@ -433,7 +433,7 @@ class DifferenceEngine extends ContextSource {
                                        array( $msg ) );
                        } else {
                                # Give explanation and add a link to view the diff...
-                               $query = $this->getRequest()->appendQueryValue( 'unhide', '1', true );
+                               $query = $this->getRequest()->appendQueryValue( 'unhide', '1' );
                                $link = $this->getTitle()->getFullURL( $query );
                                $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
                                $out->wrapWikiMsg(
index 0da1ae8..b7d6f98 100644 (file)
@@ -1330,6 +1330,7 @@ class LocalFile extends File {
                }
 
                $descTitle = $this->getTitle();
+               $descId = $descTitle->getArticleID();
                $wikiPage = new WikiFilePage( $descTitle );
                $wikiPage->setFile( $this );
 
@@ -1362,7 +1363,7 @@ class LocalFile extends File {
 
                        $nullRevision = Revision::newNullRevision(
                                $dbw,
-                               $descTitle->getArticleID(),
+                               $descId,
                                $editSummary,
                                false,
                                $user
@@ -1374,6 +1375,8 @@ class LocalFile extends File {
                                        array( $wikiPage, $nullRevision, $nullRevision->getParentId(), $user )
                                );
                                $wikiPage->updateRevisionOn( $dbw, $nullRevision );
+                               // Associate null revision id
+                               $logEntry->setAssociatedRevId( $nullRevision->getId() );
                        }
 
                        $newPageContent = null;
@@ -1391,11 +1394,12 @@ class LocalFile extends File {
                # b) They won't cause rollback of the log publish/update above
                $that = $this;
                $dbw->onTransactionIdle( function () use (
-                       $that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId
+                       $that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId, $descId
                ) {
                        # Update memcache after the commit
                        $that->invalidateCache();
 
+                       $updateLogPage = false;
                        if ( $newPageContent ) {
                                # New file page; create the description page.
                                # There's already a log entry, so don't make a second RC entry
@@ -1408,25 +1412,51 @@ class LocalFile extends File {
                                        $user
                                );
 
+                               if ( isset( $status->value['revision'] ) ) {
+                                       // Associate new page revision id
+                                       $logEntry->setAssociatedRevId( $status->value['revision']->getId() );
+                               }
                                // This relies on the resetArticleID() call in WikiPage::insertOn(),
                                // which is triggered on $descTitle by doEditContent() above.
                                if ( isset( $status->value['revision'] ) ) {
                                        /** @var $rev Revision */
                                        $rev = $status->value['revision'];
-                                       $that->getRepo()->getMasterDB()->update(
-                                               'logging',
-                                               array( 'log_page' => $rev->getPage() ),
-                                               array( 'log_id' => $logId ),
-                                               __METHOD__
-                                       );
+                                       $updateLogPage = $rev->getPage();
                                }
                        } else {
                                # Existing file page: invalidate description page cache
                                $wikiPage->getTitle()->invalidateCache();
                                $wikiPage->getTitle()->purgeSquid();
+                               # Allow the new file version to be patrolled from the page footer
+                               Article::purgePatrolFooterCache( $descId );
                        }
 
-                       # Now that the page exists, make an RC entry.
+                       # Update associated rev id. This should be done by $logEntry->insert() earlier,
+                       # but setAssociatedRevId() wasn't called at that point yet...
+                       $logParams = $logEntry->getParameters();
+                       $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
+                       $update = array( 'log_params' => LogEntryBase::makeParamBlob( $logParams ) );
+                       if ( $updateLogPage ) {
+                               # Also log page, in case where we just created it above
+                               $update['log_page'] = $updateLogPage;
+                       }
+                       $that->getRepo()->getMasterDB()->update(
+                               'logging',
+                               $update,
+                               array( 'log_id' => $logId ),
+                               __METHOD__
+                       );
+                       $that->getRepo()->getMasterDB()->insert(
+                               'log_search',
+                               array(
+                                       'ls_field' => 'associated_rev_id',
+                                       'ls_value' => $logEntry->getAssociatedRevId(),
+                                       'ls_log_id' => $logId,
+                               ),
+                               __METHOD__
+                       );
+
+                       # Now that the log entry is up-to-date, make an RC entry.
                        $logEntry->publish( $logId );
                        # Run hook for other updates (typically more cache purging)
                        Hooks::run( 'FileUpload', array( $that, $reupload, !$newPageContent ) );
index 8bb1bba..ee7faa5 100644 (file)
@@ -671,10 +671,10 @@ class HTMLForm extends ContextSource {
        }
 
        /**
-        * Set the introductory message, overwriting any existing message.
+        * Set the introductory message HTML, overwriting any existing message.
         * @since 1.19
         *
-        * @param string $msg Complete text of message to display
+        * @param string $msg Complete HTML of message to display
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
@@ -685,9 +685,9 @@ class HTMLForm extends ContextSource {
        }
 
        /**
-        * Add introductory text.
+        * Add HTML to introductory message.
         *
-        * @param string $msg Complete text of message to display
+        * @param string $msg Complete HTML of message to display
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
@@ -698,9 +698,9 @@ class HTMLForm extends ContextSource {
        }
 
        /**
-        * Add header text, inside the form.
+        * Add HTML to the header, inside the form.
         *
-        * @param string $msg Complete text of message to display
+        * @param string $msg Additional HTML to display in header
         * @param string|null $section The section to add the header to
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
@@ -722,7 +722,7 @@ class HTMLForm extends ContextSource {
         * Set header text, inside the form.
         * @since 1.19
         *
-        * @param string $msg Complete text of message to display
+        * @param string $msg Complete HTML of header to display
         * @param string|null $section The section to add the header to
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
@@ -742,7 +742,7 @@ class HTMLForm extends ContextSource {
         *
         * @param string|null $section The section to get the header text for
         * @since 1.26
-        * @return string
+        * @return string HTML
         */
        function getHeaderText( $section = null ) {
                if ( is_null( $section ) ) {
@@ -957,7 +957,7 @@ class HTMLForm extends ContextSource {
         *
         * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit()
         *
-        * @return string
+        * @return string HTML
         */
        function getHTML( $submitResult ) {
                # For good measure (it is the default)
index 90b048a..8705bb0 100644 (file)
@@ -500,7 +500,7 @@ class WikiRevision {
                $prevId = $dbw->selectField( 'revision', 'rev_id',
                        array(
                                'rev_page' => $pageId,
-                               'rev_timestamp <= ' . $dbw->timestamp( $this->timestamp ),
+                               'rev_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) ),
                        ),
                        __METHOD__,
                        array( 'ORDER BY' => array(
@@ -544,6 +544,16 @@ class WikiRevision {
 
        function importLogItem() {
                $dbw = wfGetDB( DB_MASTER );
+
+               $user = User::newFromName( $this->getUser() );
+               if ( $user ) {
+                       $userId = intval( $user->getId() );
+                       $userText = $user->getName();
+               } else {
+                       $userId = 0;
+                       $userText = $this->getUser();
+               }
+
                # @todo FIXME: This will not record autoblocks
                if ( !$this->getTitle() ) {
                        wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
@@ -576,8 +586,8 @@ class WikiRevision {
                        'log_type' => $this->type,
                        'log_action' => $this->action,
                        'log_timestamp' => $dbw->timestamp( $this->timestamp ),
-                       'log_user' => User::idFromName( $this->user_text ),
-                       # 'log_user_text' => $this->user_text,
+                       'log_user' => $userId,
+                       'log_user_text' => $userText,
                        'log_namespace' => $this->getTitle()->getNamespace(),
                        'log_title' => $this->getTitle()->getDBkey(),
                        'log_comment' => $this->getComment(),
diff --git a/includes/installer/WebInstallerComplete.php b/includes/installer/WebInstallerComplete.php
new file mode 100644 (file)
index 0000000..a54264c
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerComplete extends WebInstallerPage {
+
+       public function execute() {
+               // Pop up a dialog box, to make it difficult for the user to forget
+               // to download the file
+               $lsUrl = $this->getVar( 'wgServer' ) . $this->parent->getURL( array( 'localsettings' => 1 ) );
+               if ( isset( $_SERVER['HTTP_USER_AGENT'] ) &&
+                       strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false
+               ) {
+                       // JS appears to be the only method that works consistently with IE7+
+                       $this->addHtml( "\n<script>jQuery( function () { location.href = " .
+                               Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
+               } else {
+                       $this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
+               }
+
+               $this->startForm();
+               $this->parent->disableLinkPopups();
+               $this->addHTML(
+                       $this->parent->getInfoBox(
+                               wfMessage( 'config-install-done',
+                                       $lsUrl,
+                                       $this->getVar( 'wgServer' ) .
+                                       $this->getVar( 'wgScriptPath' ) . '/index.php',
+                                       '<downloadlink/>'
+                               )->plain(), 'tick-32.png'
+                       )
+               );
+               $this->addHTML( $this->parent->getInfoBox(
+                       wfMessage( 'config-extension-link' )->text() ) );
+
+               $this->parent->restoreLinkPopups();
+               $this->endForm( false, false );
+       }
+
+}
+
+
diff --git a/includes/installer/WebInstallerCopying.php b/includes/installer/WebInstallerCopying.php
new file mode 100644 (file)
index 0000000..c539ed2
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerCopying extends WebInstallerDocument {
+
+       /**
+        * @return string
+        */
+       protected function getFileName() {
+               return 'COPYING';
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerDBConnect.php b/includes/installer/WebInstallerDBConnect.php
new file mode 100644 (file)
index 0000000..bf1787f
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerDBConnect extends WebInstallerPage {
+
+       /**
+        * @return string|null When string, "skip" or "continue"
+        */
+       public function execute() {
+               if ( $this->getVar( '_ExistingDBSettings' ) ) {
+                       return 'skip';
+               }
+
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $status = $this->submit();
+
+                       if ( $status->isGood() ) {
+                               $this->setVar( '_UpgradeDone', false );
+
+                               return 'continue';
+                       } else {
+                               $this->parent->showStatusBox( $status );
+                       }
+               }
+
+               $this->startForm();
+
+               $types = "<ul class=\"config-settings-block\">\n";
+               $settings = '';
+               $defaultType = $this->getVar( 'wgDBtype' );
+
+               // Messages: config-dbsupport-mysql, config-dbsupport-postgres, config-dbsupport-oracle,
+               // config-dbsupport-sqlite, config-dbsupport-mssql
+               $dbSupport = '';
+               foreach ( Installer::getDBTypes() as $type ) {
+                       $dbSupport .= wfMessage( "config-dbsupport-$type" )->plain() . "\n";
+               }
+               $this->addHTML( $this->parent->getInfoBox(
+                       wfMessage( 'config-support-info', trim( $dbSupport ) )->text() ) );
+
+               // It's possible that the library for the default DB type is not compiled in.
+               // In that case, instead select the first supported DB type in the list.
+               $compiledDBs = $this->parent->getCompiledDBs();
+               if ( !in_array( $defaultType, $compiledDBs ) ) {
+                       $defaultType = $compiledDBs[0];
+               }
+
+               foreach ( $compiledDBs as $type ) {
+                       $installer = $this->parent->getDBInstaller( $type );
+                       $types .=
+                               '<li>' .
+                               Xml::radioLabel(
+                                       $installer->getReadableName(),
+                                       'DBType',
+                                       $type,
+                                       "DBType_$type",
+                                       $type == $defaultType,
+                                       array( 'class' => 'dbRadio', 'rel' => "DB_wrapper_$type" )
+                               ) .
+                               "</li>\n";
+
+                       // Messages: config-header-mysql, config-header-postgres, config-header-oracle,
+                       // config-header-sqlite
+                       $settings .= Html::openElement(
+                                       'div',
+                                       array(
+                                               'id' => 'DB_wrapper_' . $type,
+                                               'class' => 'dbWrapper'
+                                       )
+                               ) .
+                               Html::element( 'h3', array(), wfMessage( 'config-header-' . $type )->text() ) .
+                               $installer->getConnectForm() .
+                               "</div>\n";
+               }
+
+               $types .= "</ul><br style=\"clear: left\"/>\n";
+
+               $this->addHTML( $this->parent->label( 'config-db-type', false, $types ) . $settings );
+               $this->endForm();
+
+               return null;
+       }
+
+       /**
+        * @return Status
+        */
+       public function submit() {
+               $r = $this->parent->request;
+               $type = $r->getVal( 'DBType' );
+               if ( !$type ) {
+                       return Status::newFatal( 'config-invalid-db-type' );
+               }
+               $this->setVar( 'wgDBtype', $type );
+               $installer = $this->parent->getDBInstaller( $type );
+               if ( !$installer ) {
+                       return Status::newFatal( 'config-invalid-db-type' );
+               }
+
+               return $installer->submitConnectForm();
+       }
+
+}
+
+
diff --git a/includes/installer/WebInstallerDBSettings.php b/includes/installer/WebInstallerDBSettings.php
new file mode 100644 (file)
index 0000000..9b95c0a
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerDBSettings extends WebInstallerPage {
+
+       /**
+        * @return string|null
+        */
+       public function execute() {
+               $installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
+
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $status = $installer->submitSettingsForm();
+                       if ( $status === false ) {
+                               return 'skip';
+                       } elseif ( $status->isGood() ) {
+                               return 'continue';
+                       } else {
+                               $this->parent->showStatusBox( $status );
+                       }
+               }
+
+               $form = $installer->getSettingsForm();
+               if ( $form === false ) {
+                       return 'skip';
+               }
+
+               $this->startForm();
+               $this->addHTML( $form );
+               $this->endForm();
+
+               return null;
+       }
+
+}
+
+
diff --git a/includes/installer/WebInstallerDocument.php b/includes/installer/WebInstallerDocument.php
new file mode 100644 (file)
index 0000000..2b35bc6
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+abstract class WebInstallerDocument extends WebInstallerPage {
+
+       /**
+        * @return string
+        */
+       abstract protected function getFileName();
+
+       public function execute() {
+               $text = $this->getFileContents();
+               $text = InstallDocFormatter::format( $text );
+               $this->parent->output->addWikiText( $text );
+               $this->startForm();
+               $this->endForm( false );
+       }
+
+       /**
+        * @return string
+        */
+       public function getFileContents() {
+               $file = __DIR__ . '/../../' . $this->getFileName();
+               if ( !file_exists( $file ) ) {
+                       return wfMessage( 'config-nofile', $file )->plain();
+               }
+
+               return file_get_contents( $file );
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerExistingWiki.php b/includes/installer/WebInstallerExistingWiki.php
new file mode 100644 (file)
index 0000000..ebe0ead
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerExistingWiki extends WebInstallerPage {
+
+       /**
+        * @return string
+        */
+       public function execute() {
+               // If there is no LocalSettings.php, continue to the installer welcome page
+               $vars = Installer::getExistingLocalSettings();
+               if ( !$vars ) {
+                       return 'skip';
+               }
+
+               // Check if the upgrade key supplied to the user has appeared in LocalSettings.php
+               if ( $vars['wgUpgradeKey'] !== false
+                       && $this->getVar( '_UpgradeKeySupplied' )
+                       && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey']
+               ) {
+                       // It's there, so the user is authorized
+                       $status = $this->handleExistingUpgrade( $vars );
+                       if ( $status->isOK() ) {
+                               return 'skip';
+                       } else {
+                               $this->startForm();
+                               $this->parent->showStatusBox( $status );
+                               $this->endForm( 'continue' );
+
+                               return 'output';
+                       }
+               }
+
+               // If there is no $wgUpgradeKey, tell the user to add one to LocalSettings.php
+               if ( $vars['wgUpgradeKey'] === false ) {
+                       if ( $this->getVar( 'wgUpgradeKey', false ) === false ) {
+                               $secretKey = $this->getVar( 'wgSecretKey' ); // preserve $wgSecretKey
+                               $this->parent->generateKeys();
+                               $this->setVar( 'wgSecretKey', $secretKey );
+                               $this->setVar( '_UpgradeKeySupplied', true );
+                       }
+                       $this->startForm();
+                       $this->addHTML( $this->parent->getInfoBox(
+                               wfMessage( 'config-upgrade-key-missing', "<pre dir=\"ltr\">\$wgUpgradeKey = '" .
+                                       $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )->plain()
+                       ) );
+                       $this->endForm( 'continue' );
+
+                       return 'output';
+               }
+
+               // If there is an upgrade key, but it wasn't supplied, prompt the user to enter it
+
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $key = $r->getText( 'config_wgUpgradeKey' );
+                       if ( !$key || $key !== $vars['wgUpgradeKey'] ) {
+                               $this->parent->showError( 'config-localsettings-badkey' );
+                               $this->showKeyForm();
+
+                               return 'output';
+                       }
+                       // Key was OK
+                       $status = $this->handleExistingUpgrade( $vars );
+                       if ( $status->isOK() ) {
+                               return 'continue';
+                       } else {
+                               $this->parent->showStatusBox( $status );
+                               $this->showKeyForm();
+
+                               return 'output';
+                       }
+               } else {
+                       $this->showKeyForm();
+
+                       return 'output';
+               }
+       }
+
+       /**
+        * Show the "enter key" form
+        */
+       protected function showKeyForm() {
+               $this->startForm();
+               $this->addHTML(
+                       $this->parent->getInfoBox( wfMessage( 'config-localsettings-upgrade' )->plain() ) .
+                       '<br />' .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgUpgradeKey',
+                               'label' => 'config-localsettings-key',
+                               'attribs' => array( 'autocomplete' => 'off' ),
+                       ) )
+               );
+               $this->endForm( 'continue' );
+       }
+
+       /**
+        * @param string[] $names
+        * @param mixed[] $vars
+        *
+        * @return Status
+        */
+       protected function importVariables( $names, $vars ) {
+               $status = Status::newGood();
+               foreach ( $names as $name ) {
+                       if ( !isset( $vars[$name] ) ) {
+                               $status->fatal( 'config-localsettings-incomplete', $name );
+                       }
+                       $this->setVar( $name, $vars[$name] );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Initiate an upgrade of the existing database
+        *
+        * @param mixed[] $vars Variables from LocalSettings.php
+        *
+        * @return Status
+        */
+       protected function handleExistingUpgrade( $vars ) {
+               // Check $wgDBtype
+               if ( !isset( $vars['wgDBtype'] ) ||
+                       !in_array( $vars['wgDBtype'], Installer::getDBTypes() )
+               ) {
+                       return Status::newFatal( 'config-localsettings-connection-error', '' );
+               }
+
+               // Set the relevant variables from LocalSettings.php
+               $requiredVars = array( 'wgDBtype' );
+               $status = $this->importVariables( $requiredVars, $vars );
+               $installer = $this->parent->getDBInstaller();
+               $status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               if ( isset( $vars['wgDBadminuser'] ) ) {
+                       $this->setVar( '_InstallUser', $vars['wgDBadminuser'] );
+               } else {
+                       $this->setVar( '_InstallUser', $vars['wgDBuser'] );
+               }
+               if ( isset( $vars['wgDBadminpassword'] ) ) {
+                       $this->setVar( '_InstallPassword', $vars['wgDBadminpassword'] );
+               } else {
+                       $this->setVar( '_InstallPassword', $vars['wgDBpassword'] );
+               }
+
+               // Test the database connection
+               $status = $installer->getConnection();
+               if ( !$status->isOK() ) {
+                       // Adjust the error message to explain things correctly
+                       $status->replaceMessage( 'config-connection-error',
+                               'config-localsettings-connection-error' );
+
+                       return $status;
+               }
+
+               // All good
+               $this->setVar( '_ExistingDBSettings', true );
+
+               return $status;
+       }
+
+}
+
+
diff --git a/includes/installer/WebInstallerInstall.php b/includes/installer/WebInstallerInstall.php
new file mode 100644 (file)
index 0000000..261a661
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerInstall extends WebInstallerPage {
+
+       /**
+        * @return bool Always true.
+        */
+       public function isSlow() {
+               return true;
+       }
+
+       /**
+        * @return string|bool
+        */
+       public function execute() {
+               if ( $this->getVar( '_UpgradeDone' ) ) {
+                       return 'skip';
+               } elseif ( $this->getVar( '_InstallDone' ) ) {
+                       return 'continue';
+               } elseif ( $this->parent->request->wasPosted() ) {
+                       $this->startForm();
+                       $this->addHTML( "<ul>" );
+                       $results = $this->parent->performInstallation(
+                               array( $this, 'startStage' ),
+                               array( $this, 'endStage' )
+                       );
+                       $this->addHTML( "</ul>" );
+                       // PerformInstallation bails on a fatal, so make sure the last item
+                       // completed before giving 'next.' Likewise, only provide back on failure
+                       $lastStep = end( $results );
+                       $continue = $lastStep->isOK() ? 'continue' : false;
+                       $back = $lastStep->isOK() ? false : 'back';
+                       $this->endForm( $continue, $back );
+               } else {
+                       $this->startForm();
+                       $this->addHTML( $this->parent->getInfoBox( wfMessage( 'config-install-begin' )->plain() ) );
+                       $this->endForm();
+               }
+
+               return true;
+       }
+
+       /**
+        * @param string $step
+        */
+       public function startStage( $step ) {
+               // Messages: config-install-database, config-install-tables, config-install-interwiki,
+               // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
+               $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() .
+                       wfMessage( 'ellipsis' )->escaped() );
+
+               if ( $step == 'extension-tables' ) {
+                       $this->startLiveBox();
+               }
+       }
+
+       /**
+        * @param string $step
+        * @param Status $status
+        */
+       public function endStage( $step, $status ) {
+               if ( $step == 'extension-tables' ) {
+                       $this->endLiveBox();
+               }
+               $msg = $status->isOk() ? 'config-install-step-done' : 'config-install-step-failed';
+               $html = wfMessage( 'word-separator' )->escaped() . wfMessage( $msg )->escaped();
+               if ( !$status->isOk() ) {
+                       $html = "<span class=\"error\">$html</span>";
+               }
+               $this->addHTML( $html . "</li>\n" );
+               if ( !$status->isGood() ) {
+                       $this->parent->showStatusBox( $status );
+               }
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerLanguage.php b/includes/installer/WebInstallerLanguage.php
new file mode 100644 (file)
index 0000000..ba2745c
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerLanguage extends WebInstallerPage {
+
+       /**
+        * @return string|null
+        */
+       public function execute() {
+               global $wgLang;
+               $r = $this->parent->request;
+               $userLang = $r->getVal( 'uselang' );
+               $contLang = $r->getVal( 'ContLang' );
+
+               $languages = Language::fetchLanguageNames();
+               $lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
+               if ( !$lifetime ) {
+                       $lifetime = 1440; // PHP default
+               }
+
+               if ( $r->wasPosted() ) {
+                       # Do session test
+                       if ( $this->parent->getSession( 'test' ) === null ) {
+                               $requestTime = $r->getVal( 'LanguageRequestTime' );
+                               if ( !$requestTime ) {
+                                       // The most likely explanation is that the user was knocked back
+                                       // from another page on POST due to session expiry
+                                       $msg = 'config-session-expired';
+                               } elseif ( time() - $requestTime > $lifetime ) {
+                                       $msg = 'config-session-expired';
+                               } else {
+                                       $msg = 'config-no-session';
+                               }
+                               $this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
+                       } else {
+                               if ( isset( $languages[$userLang] ) ) {
+                                       $this->setVar( '_UserLang', $userLang );
+                               }
+                               if ( isset( $languages[$contLang] ) ) {
+                                       $this->setVar( 'wgLanguageCode', $contLang );
+                               }
+
+                               return 'continue';
+                       }
+               } elseif ( $this->parent->showSessionWarning ) {
+                       # The user was knocked back from another page to the start
+                       # This probably indicates a session expiry
+                       $this->parent->showError( 'config-session-expired',
+                               $wgLang->formatTimePeriod( $lifetime ) );
+               }
+
+               $this->parent->setSession( 'test', true );
+
+               if ( !isset( $languages[$userLang] ) ) {
+                       $userLang = $this->getVar( '_UserLang', 'en' );
+               }
+               if ( !isset( $languages[$contLang] ) ) {
+                       $contLang = $this->getVar( 'wgLanguageCode', 'en' );
+               }
+               $this->startForm();
+               $s = Html::hidden( 'LanguageRequestTime', time() ) .
+                       $this->getLanguageSelector( 'uselang', 'config-your-language', $userLang,
+                               $this->parent->getHelpBox( 'config-your-language-help' ) ) .
+                       $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang,
+                               $this->parent->getHelpBox( 'config-wiki-language-help' ) );
+               $this->addHTML( $s );
+               $this->endForm( 'continue', false );
+
+               return null;
+       }
+
+       /**
+        * Get a "<select>" for selecting languages.
+        *
+        * @param string $name
+        * @param string $label
+        * @param string $selectedCode
+        * @param string $helpHtml
+        *
+        * @return string
+        */
+       public function getLanguageSelector( $name, $label, $selectedCode, $helpHtml = '' ) {
+               global $wgDummyLanguageCodes;
+
+               $output = $helpHtml;
+
+               $select = new XmlSelect( $name, $name, $selectedCode );
+               $select->setAttribute( 'tabindex', $this->parent->nextTabIndex() );
+
+               $languages = Language::fetchLanguageNames();
+               ksort( $languages );
+               foreach ( $languages as $code => $lang ) {
+                       if ( isset( $wgDummyLanguageCodes[$code] ) ) {
+                               continue;
+                       }
+                       $select->addOption( "$code - $lang", $code );
+               }
+
+               $output .= $select->getHTML();
+               return $this->parent->label( $label, $name, $output );
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerName.php b/includes/installer/WebInstallerName.php
new file mode 100644 (file)
index 0000000..7d0073c
--- /dev/null
@@ -0,0 +1,251 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerName extends WebInstallerPage {
+
+       /**
+        * @return string
+        */
+       public function execute() {
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       if ( $this->submit() ) {
+                               return 'continue';
+                       }
+               }
+
+               $this->startForm();
+
+               // Encourage people to not name their site 'MediaWiki' by blanking the
+               // field. I think that was the intent with the original $GLOBALS['wgSitename']
+               // but these two always were the same so had the effect of making the
+               // installer forget $wgSitename when navigating back to this page.
+               if ( $this->getVar( 'wgSitename' ) == 'MediaWiki' ) {
+                       $this->setVar( 'wgSitename', '' );
+               }
+
+               // Set wgMetaNamespace to something valid before we show the form.
+               // $wgMetaNamespace defaults to $wgSiteName which is 'MediaWiki'
+               $metaNS = $this->getVar( 'wgMetaNamespace' );
+               $this->setVar(
+                       'wgMetaNamespace',
+                       wfMessage( 'config-ns-other-default' )->inContentLanguage()->text()
+               );
+
+               $this->addHTML(
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgSitename',
+                               'label' => 'config-site-name',
+                               'help' => $this->parent->getHelpBox( 'config-site-name-help' )
+                       ) ) .
+                       // getRadioSet() builds a set of labeled radio buttons.
+                       // For grep: The following messages are used as the item labels:
+                       // config-ns-site-name, config-ns-generic, config-ns-other
+                       $this->parent->getRadioSet( array(
+                               'var' => '_NamespaceType',
+                               'label' => 'config-project-namespace',
+                               'itemLabelPrefix' => 'config-ns-',
+                               'values' => array( 'site-name', 'generic', 'other' ),
+                               'commonAttribs' => array( 'class' => 'enableForOther',
+                                       'rel' => 'config_wgMetaNamespace' ),
+                               'help' => $this->parent->getHelpBox( 'config-project-namespace-help' )
+                       ) ) .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgMetaNamespace',
+                               'label' => '', // @todo Needs a label?
+                               'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' )
+                       ) ) .
+                       $this->getFieldSetStart( 'config-admin-box' ) .
+                       $this->parent->getTextBox( array(
+                               'var' => '_AdminName',
+                               'label' => 'config-admin-name',
+                               'help' => $this->parent->getHelpBox( 'config-admin-help' )
+                       ) ) .
+                       $this->parent->getPasswordBox( array(
+                               'var' => '_AdminPassword',
+                               'label' => 'config-admin-password',
+                       ) ) .
+                       $this->parent->getPasswordBox( array(
+                               'var' => '_AdminPasswordConfirm',
+                               'label' => 'config-admin-password-confirm'
+                       ) ) .
+                       $this->parent->getTextBox( array(
+                               'var' => '_AdminEmail',
+                               'attribs' => array(
+                                       'dir' => 'ltr',
+                               ),
+                               'label' => 'config-admin-email',
+                               'help' => $this->parent->getHelpBox( 'config-admin-email-help' )
+                       ) ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => '_Subscribe',
+                               'label' => 'config-subscribe',
+                               'help' => $this->parent->getHelpBox( 'config-subscribe-help' )
+                       ) ) .
+                       $this->getFieldSetEnd() .
+                       $this->parent->getInfoBox( wfMessage( 'config-almost-done' )->text() ) .
+                       // getRadioSet() builds a set of labeled radio buttons.
+                       // For grep: The following messages are used as the item labels:
+                       // config-optional-continue, config-optional-skip
+                       $this->parent->getRadioSet( array(
+                               'var' => '_SkipOptional',
+                               'itemLabelPrefix' => 'config-optional-',
+                               'values' => array( 'continue', 'skip' )
+                       ) )
+               );
+
+               // Restore the default value
+               $this->setVar( 'wgMetaNamespace', $metaNS );
+
+               $this->endForm();
+
+               return 'output';
+       }
+
+       /**
+        * @return bool
+        */
+       public function submit() {
+               global $wgPasswordPolicy;
+
+               $retVal = true;
+               $this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
+                       '_AdminName', '_AdminPassword', '_AdminPasswordConfirm', '_AdminEmail',
+                       '_Subscribe', '_SkipOptional', 'wgMetaNamespace' ) );
+
+               // Validate site name
+               if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
+                       $this->parent->showError( 'config-site-name-blank' );
+                       $retVal = false;
+               }
+
+               // Fetch namespace
+               $nsType = $this->getVar( '_NamespaceType' );
+               if ( $nsType == 'site-name' ) {
+                       $name = $this->getVar( 'wgSitename' );
+                       // Sanitize for namespace
+                       // This algorithm should match the JS one in WebInstallerOutput.php
+                       $name = preg_replace( '/[\[\]\{\}|#<>%+? ]/', '_', $name );
+                       $name = str_replace( '&', '&amp;', $name );
+                       $name = preg_replace( '/__+/', '_', $name );
+                       $name = ucfirst( trim( $name, '_' ) );
+               } elseif ( $nsType == 'generic' ) {
+                       $name = wfMessage( 'config-ns-generic' )->text();
+               } else { // other
+                       $name = $this->getVar( 'wgMetaNamespace' );
+               }
+
+               // Validate namespace
+               if ( strpos( $name, ':' ) !== false ) {
+                       $good = false;
+               } else {
+                       // Title-style validation
+                       $title = Title::newFromText( $name );
+                       if ( !$title ) {
+                               $good = $nsType == 'site-name';
+                       } else {
+                               $name = $title->getDBkey();
+                               $good = true;
+                       }
+               }
+               if ( !$good ) {
+                       $this->parent->showError( 'config-ns-invalid', $name );
+                       $retVal = false;
+               }
+
+               // Make sure it won't conflict with any existing namespaces
+               global $wgContLang;
+               $nsIndex = $wgContLang->getNsIndex( $name );
+               if ( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
+                       $this->parent->showError( 'config-ns-conflict', $name );
+                       $retVal = false;
+               }
+
+               $this->setVar( 'wgMetaNamespace', $name );
+
+               // Validate username for creation
+               $name = $this->getVar( '_AdminName' );
+               if ( strval( $name ) === '' ) {
+                       $this->parent->showError( 'config-admin-name-blank' );
+                       $cname = $name;
+                       $retVal = false;
+               } else {
+                       $cname = User::getCanonicalName( $name, 'creatable' );
+                       if ( $cname === false ) {
+                               $this->parent->showError( 'config-admin-name-invalid', $name );
+                               $retVal = false;
+                       } else {
+                               $this->setVar( '_AdminName', $cname );
+                       }
+               }
+
+               // Validate password
+               $msg = false;
+               $pwd = $this->getVar( '_AdminPassword' );
+               $user = User::newFromName( $cname );
+               if ( $user ) {
+                       $upp = new UserPasswordPolicy(
+                               $wgPasswordPolicy['policies'],
+                               $wgPasswordPolicy['checks']
+                       );
+                       $status = $upp->checkUserPasswordForGroups(
+                               $user,
+                               $pwd,
+                               array( 'bureaucrat', 'sysop' )  // per Installer::createSysop()
+                       );
+                       $valid = $status->isGood() ? true : $status->getMessage();
+               } else {
+                       $valid = 'config-admin-name-invalid';
+               }
+               if ( strval( $pwd ) === '' ) {
+                       // Provide a more specific and helpful message if password field is left blank
+                       $msg = 'config-admin-password-blank';
+               } elseif ( $pwd !== $this->getVar( '_AdminPasswordConfirm' ) ) {
+                       $msg = 'config-admin-password-mismatch';
+               } elseif ( $valid !== true ) {
+                       $msg = $valid;
+               }
+               if ( $msg !== false ) {
+                       call_user_func( array( $this->parent, 'showError' ), $msg );
+                       $this->setVar( '_AdminPassword', '' );
+                       $this->setVar( '_AdminPasswordConfirm', '' );
+                       $retVal = false;
+               }
+
+               // Validate e-mail if provided
+               $email = $this->getVar( '_AdminEmail' );
+               if ( $email && !Sanitizer::validateEmail( $email ) ) {
+                       $this->parent->showError( 'config-admin-error-bademail' );
+                       $retVal = false;
+               }
+               // If they asked to subscribe to mediawiki-announce but didn't give
+               // an e-mail, show an error. Bug 29332
+               if ( !$email && $this->getVar( '_Subscribe' ) ) {
+                       $this->parent->showError( 'config-subscribe-noemail' );
+                       $retVal = false;
+               }
+
+               return $retVal;
+       }
+
+}
+
+
diff --git a/includes/installer/WebInstallerOptions.php b/includes/installer/WebInstallerOptions.php
new file mode 100644 (file)
index 0000000..30ea10e
--- /dev/null
@@ -0,0 +1,461 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerOptions extends WebInstallerPage {
+
+       /**
+        * @return string|null
+        */
+       public function execute() {
+               if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
+                       $this->submitSkins();
+                       return 'skip';
+               }
+               if ( $this->parent->request->wasPosted() ) {
+                       if ( $this->submit() ) {
+                               return 'continue';
+                       }
+               }
+
+               $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
+               $this->startForm();
+               $this->addHTML(
+                       # User Rights
+                       // getRadioSet() builds a set of labeled radio buttons.
+                       // For grep: The following messages are used as the item labels:
+                       // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
+                       $this->parent->getRadioSet( array(
+                               'var' => '_RightsProfile',
+                               'label' => 'config-profile',
+                               'itemLabelPrefix' => 'config-profile-',
+                               'values' => array_keys( $this->parent->rightsProfiles ),
+                       ) ) .
+                       $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
+
+                       # Licensing
+                       // getRadioSet() builds a set of labeled radio buttons.
+                       // For grep: The following messages are used as the item labels:
+                       // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
+                       // config-license-cc-0, config-license-pd, config-license-gfdl,
+                       // config-license-none, config-license-cc-choose
+                       $this->parent->getRadioSet( array(
+                               'var' => '_LicenseCode',
+                               'label' => 'config-license',
+                               'itemLabelPrefix' => 'config-license-',
+                               'values' => array_keys( $this->parent->licenses ),
+                               'commonAttribs' => array( 'class' => 'licenseRadio' ),
+                       ) ) .
+                       $this->getCCChooser() .
+                       $this->parent->getHelpBox( 'config-license-help' ) .
+
+                       # E-mail
+                       $this->getFieldSetStart( 'config-email-settings' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnableEmail',
+                               'label' => 'config-enable-email',
+                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ),
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-enable-email-help' ) .
+                       "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgPasswordSender',
+                               'label' => 'config-email-sender'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-sender-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnableUserEmail',
+                               'label' => 'config-email-user',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-user-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnotifUserTalk',
+                               'label' => 'config-email-usertalk',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnotifWatchlist',
+                               'label' => 'config-email-watchlist',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEmailAuthentication',
+                               'label' => 'config-email-auth',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-auth-help' ) .
+                       "</div>" .
+                       $this->getFieldSetEnd()
+               );
+
+               $skins = $this->parent->findExtensions( 'skins' );
+               $skinHtml = $this->getFieldSetStart( 'config-skins' );
+
+               $skinNames = array_map( 'strtolower', $skins );
+               $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
+
+               if ( $skins ) {
+                       $radioButtons = $this->parent->getRadioElements( array(
+                               'var' => 'wgDefaultSkin',
+                               'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
+                               'values' => $skinNames,
+                               'value' => $chosenSkinName,
+                       ) );
+
+                       foreach ( $skins as $skin ) {
+                               $skinHtml .=
+                                       '<div class="config-skins-item">' .
+                                       $this->parent->getCheckBox( array(
+                                               'var' => "skin-$skin",
+                                               'rawtext' => $skin,
+                                               'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
+                                       ) ) .
+                                       '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
+                                       '</div>';
+                       }
+               } else {
+                       $skinHtml .=
+                               $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() ) .
+                               Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
+               }
+
+               $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
+                       $this->getFieldSetEnd();
+               $this->addHTML( $skinHtml );
+
+               $extensions = $this->parent->findExtensions();
+
+               if ( $extensions ) {
+                       $extHtml = $this->getFieldSetStart( 'config-extensions' );
+
+                       foreach ( $extensions as $ext ) {
+                               $extHtml .= $this->parent->getCheckBox( array(
+                                       'var' => "ext-$ext",
+                                       'rawtext' => $ext,
+                               ) );
+                       }
+
+                       $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
+                               $this->getFieldSetEnd();
+                       $this->addHTML( $extHtml );
+               }
+
+               // Having / in paths in Windows looks funny :)
+               $this->setVar( 'wgDeletedDirectory',
+                       str_replace(
+                               '/', DIRECTORY_SEPARATOR,
+                               $this->getVar( 'wgDeletedDirectory' )
+                       )
+               );
+
+               $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
+               $this->addHTML(
+                       # Uploading
+                       $this->getFieldSetStart( 'config-upload-settings' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnableUploads',
+                               'label' => 'config-upload-enable',
+                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ),
+                               'help' => $this->parent->getHelpBox( 'config-upload-help' )
+                       ) ) .
+                       '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgDeletedDirectory',
+                               'label' => 'config-upload-deleted',
+                               'attribs' => array( 'dir' => 'ltr' ),
+                               'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
+                       ) ) .
+                       '</div>' .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgLogo',
+                               'label' => 'config-logo',
+                               'attribs' => array( 'dir' => 'ltr' ),
+                               'help' => $this->parent->getHelpBox( 'config-logo-help' )
+                       ) )
+               );
+               $this->addHTML(
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgUseInstantCommons',
+                               'label' => 'config-instantcommons',
+                               'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
+                       ) ) .
+                       $this->getFieldSetEnd()
+               );
+
+               $caches = array( 'none' );
+               $cachevalDefault = 'none';
+
+               if ( count( $this->getVar( '_Caches' ) ) ) {
+                       // A CACHE_ACCEL implementation is available
+                       $caches[] = 'accel';
+                       $cachevalDefault = 'accel';
+               }
+               $caches[] = 'memcached';
+
+               // We'll hide/show this on demand when the value changes, see config.js.
+               $cacheval = $this->getVar( '_MainCacheType' );
+               if ( !$cacheval ) {
+                       // We need to set a default here; but don't hardcode it
+                       // or we lose it every time we reload the page for validation
+                       // or going back!
+                       $cacheval = $cachevalDefault;
+               }
+               $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
+               $this->addHTML(
+                       # Advanced settings
+                       $this->getFieldSetStart( 'config-advanced-settings' ) .
+                       # Object cache settings
+                       // getRadioSet() builds a set of labeled radio buttons.
+                       // For grep: The following messages are used as the item labels:
+                       // config-cache-none, config-cache-accel, config-cache-memcached
+                       $this->parent->getRadioSet( array(
+                               'var' => '_MainCacheType',
+                               'label' => 'config-cache-options',
+                               'itemLabelPrefix' => 'config-cache-',
+                               'values' => $caches,
+                               'value' => $cacheval,
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-cache-help' ) .
+                       "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
+                       $this->parent->getTextArea( array(
+                               'var' => '_MemCachedServers',
+                               'label' => 'config-memcached-servers',
+                               'help' => $this->parent->getHelpBox( 'config-memcached-help' )
+                       ) ) .
+                       '</div>' .
+                       $this->getFieldSetEnd()
+               );
+               $this->endForm();
+
+               return null;
+       }
+
+       /**
+        * @return string
+        */
+       public function getCCPartnerUrl() {
+               $server = $this->getVar( 'wgServer' );
+               $exitUrl = $server . $this->parent->getUrl( array(
+                       'page' => 'Options',
+                       'SubmitCC' => 'indeed',
+                       'config__LicenseCode' => 'cc',
+                       'config_wgRightsUrl' => '[license_url]',
+                       'config_wgRightsText' => '[license_name]',
+                       'config_wgRightsIcon' => '[license_button]',
+               ) );
+               $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
+                       '/mw-config/config-cc.css';
+               $iframeUrl = '//creativecommons.org/license/?' .
+                       wfArrayToCgi( array(
+                               'partner' => 'MediaWiki',
+                               'exit_url' => $exitUrl,
+                               'lang' => $this->getVar( '_UserLang' ),
+                               'stylesheet' => $styleUrl,
+                       ) );
+
+               return $iframeUrl;
+       }
+
+       /**
+        * @return string
+        */
+       public function getCCChooser() {
+               $iframeAttribs = array(
+                       'class' => 'config-cc-iframe',
+                       'name' => 'config-cc-iframe',
+                       'id' => 'config-cc-iframe',
+                       'frameborder' => 0,
+                       'width' => '100%',
+                       'height' => '100%',
+               );
+               if ( $this->getVar( '_CCDone' ) ) {
+                       $iframeAttribs['src'] = $this->parent->getUrl( array( 'ShowCC' => 'yes' ) );
+               } else {
+                       $iframeAttribs['src'] = $this->getCCPartnerUrl();
+               }
+               $wrapperStyle = ( $this->getVar( '_LicenseCode' ) == 'cc-choose' ) ? '' : 'display: none';
+
+               return "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
+                       Html::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
+                       "</div>\n";
+       }
+
+       /**
+        * @return string
+        */
+       public function getCCDoneBox() {
+               $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
+               // If you change this height, also change it in config.css
+               $expandJs = str_replace( '$1', '54em', $js );
+               $reduceJs = str_replace( '$1', '70px', $js );
+
+               return '<p>' .
+                       Html::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
+                       '&#160;&#160;' .
+                       htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
+                       "</p>\n" .
+                       "<p style=\"text-align: center;\">" .
+                       Html::element( 'a',
+                               array(
+                                       'href' => $this->getCCPartnerUrl(),
+                                       'onclick' => $expandJs,
+                               ),
+                               wfMessage( 'config-cc-again' )->text()
+                       ) .
+                       "</p>\n" .
+                       "<script>\n" .
+                       # Reduce the wrapper div height
+                       htmlspecialchars( $reduceJs ) .
+                       "\n" .
+                       "</script>\n";
+       }
+
+       public function submitCC() {
+               $newValues = $this->parent->setVarsFromRequest(
+                       array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
+               if ( count( $newValues ) != 3 ) {
+                       $this->parent->showError( 'config-cc-error' );
+
+                       return;
+               }
+               $this->setVar( '_CCDone', true );
+               $this->addHTML( $this->getCCDoneBox() );
+       }
+
+       /**
+        * If the user skips this installer page, we still need to set up the default skins, but ignore
+        * everything else.
+        *
+        * @return bool
+        */
+       public function submitSkins() {
+               $skins = $this->parent->findExtensions( 'skins' );
+               $this->parent->setVar( '_Skins', $skins );
+
+               if ( $skins ) {
+                       $skinNames = array_map( 'strtolower', $skins );
+                       $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
+               }
+
+               return true;
+       }
+
+       /**
+        * @return bool
+        */
+       public function submit() {
+               $this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
+                       'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
+                       'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
+                       'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
+                       'wgUseInstantCommons', 'wgDefaultSkin' ) );
+
+               $retVal = true;
+
+               if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
+                       reset( $this->parent->rightsProfiles );
+                       $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
+               }
+
+               $code = $this->getVar( '_LicenseCode' );
+               if ( $code == 'cc-choose' ) {
+                       if ( !$this->getVar( '_CCDone' ) ) {
+                               $this->parent->showError( 'config-cc-not-chosen' );
+                               $retVal = false;
+                       }
+               } elseif ( array_key_exists( $code, $this->parent->licenses ) ) {
+                       // Messages:
+                       // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
+                       // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
+                       // config-license-cc-choose
+                       $entry = $this->parent->licenses[$code];
+                       if ( isset( $entry['text'] ) ) {
+                               $this->setVar( 'wgRightsText', $entry['text'] );
+                       } else {
+                               $this->setVar( 'wgRightsText', wfMessage( 'config-license-' . $code )->text() );
+                       }
+                       $this->setVar( 'wgRightsUrl', $entry['url'] );
+                       $this->setVar( 'wgRightsIcon', $entry['icon'] );
+               } else {
+                       $this->setVar( 'wgRightsText', '' );
+                       $this->setVar( 'wgRightsUrl', '' );
+                       $this->setVar( 'wgRightsIcon', '' );
+               }
+
+               $skinsAvailable = $this->parent->findExtensions( 'skins' );
+               $skinsToInstall = array();
+               foreach ( $skinsAvailable as $skin ) {
+                       $this->parent->setVarsFromRequest( array( "skin-$skin" ) );
+                       if ( $this->getVar( "skin-$skin" ) ) {
+                               $skinsToInstall[] = $skin;
+                       }
+               }
+               $this->parent->setVar( '_Skins', $skinsToInstall );
+
+               if ( !$skinsToInstall && $skinsAvailable ) {
+                       $this->parent->showError( 'config-skins-must-enable-some' );
+                       $retVal = false;
+               }
+               $defaultSkin = $this->getVar( 'wgDefaultSkin' );
+               $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
+               if ( $skinsToInstall && array_search( $defaultSkin, $skinsToInstallLowercase ) === false ) {
+                       $this->parent->showError( 'config-skins-must-enable-default' );
+                       $retVal = false;
+               }
+
+               $extsAvailable = $this->parent->findExtensions();
+               $extsToInstall = array();
+               foreach ( $extsAvailable as $ext ) {
+                       $this->parent->setVarsFromRequest( array( "ext-$ext" ) );
+                       if ( $this->getVar( "ext-$ext" ) ) {
+                               $extsToInstall[] = $ext;
+                       }
+               }
+               $this->parent->setVar( '_Extensions', $extsToInstall );
+
+               if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) {
+                       $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
+                       if ( !$memcServers ) {
+                               $this->parent->showError( 'config-memcache-needservers' );
+                               $retVal = false;
+                       }
+
+                       foreach ( $memcServers as $server ) {
+                               $memcParts = explode( ":", $server, 2 );
+                               if ( !isset( $memcParts[0] )
+                                       || ( !IP::isValid( $memcParts[0] )
+                                               && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
+                               ) {
+                                       $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
+                                       $retVal = false;
+                               } elseif ( !isset( $memcParts[1] ) ) {
+                                       $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
+                                       $retVal = false;
+                               } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
+                                       $this->parent->showError( 'config-memcache-badport', 1, 65535 );
+                                       $retVal = false;
+                               }
+                       }
+               }
+
+               return $retVal;
+       }
+
+}
+
index 0fcda7d..a529939 100644 (file)
@@ -205,1405 +205,3 @@ abstract class WebInstallerPage {
        }
 
 }
-
-class WebInstallerLanguage extends WebInstallerPage {
-
-       /**
-        * @return string|null
-        */
-       public function execute() {
-               global $wgLang;
-               $r = $this->parent->request;
-               $userLang = $r->getVal( 'uselang' );
-               $contLang = $r->getVal( 'ContLang' );
-
-               $languages = Language::fetchLanguageNames();
-               $lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
-               if ( !$lifetime ) {
-                       $lifetime = 1440; // PHP default
-               }
-
-               if ( $r->wasPosted() ) {
-                       # Do session test
-                       if ( $this->parent->getSession( 'test' ) === null ) {
-                               $requestTime = $r->getVal( 'LanguageRequestTime' );
-                               if ( !$requestTime ) {
-                                       // The most likely explanation is that the user was knocked back
-                                       // from another page on POST due to session expiry
-                                       $msg = 'config-session-expired';
-                               } elseif ( time() - $requestTime > $lifetime ) {
-                                       $msg = 'config-session-expired';
-                               } else {
-                                       $msg = 'config-no-session';
-                               }
-                               $this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
-                       } else {
-                               if ( isset( $languages[$userLang] ) ) {
-                                       $this->setVar( '_UserLang', $userLang );
-                               }
-                               if ( isset( $languages[$contLang] ) ) {
-                                       $this->setVar( 'wgLanguageCode', $contLang );
-                               }
-
-                               return 'continue';
-                       }
-               } elseif ( $this->parent->showSessionWarning ) {
-                       # The user was knocked back from another page to the start
-                       # This probably indicates a session expiry
-                       $this->parent->showError( 'config-session-expired',
-                               $wgLang->formatTimePeriod( $lifetime ) );
-               }
-
-               $this->parent->setSession( 'test', true );
-
-               if ( !isset( $languages[$userLang] ) ) {
-                       $userLang = $this->getVar( '_UserLang', 'en' );
-               }
-               if ( !isset( $languages[$contLang] ) ) {
-                       $contLang = $this->getVar( 'wgLanguageCode', 'en' );
-               }
-               $this->startForm();
-               $s = Html::hidden( 'LanguageRequestTime', time() ) .
-                       $this->getLanguageSelector( 'uselang', 'config-your-language', $userLang,
-                               $this->parent->getHelpBox( 'config-your-language-help' ) ) .
-                       $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang,
-                               $this->parent->getHelpBox( 'config-wiki-language-help' ) );
-               $this->addHTML( $s );
-               $this->endForm( 'continue', false );
-
-               return null;
-       }
-
-       /**
-        * Get a "<select>" for selecting languages.
-        *
-        * @param string $name
-        * @param string $label
-        * @param string $selectedCode
-        * @param string $helpHtml
-        *
-        * @return string
-        */
-       public function getLanguageSelector( $name, $label, $selectedCode, $helpHtml = '' ) {
-               global $wgDummyLanguageCodes;
-
-               $output = $helpHtml;
-
-               $select = new XmlSelect( $name, $name, $selectedCode );
-               $select->setAttribute( 'tabindex', $this->parent->nextTabIndex() );
-
-               $languages = Language::fetchLanguageNames();
-               ksort( $languages );
-               foreach ( $languages as $code => $lang ) {
-                       if ( isset( $wgDummyLanguageCodes[$code] ) ) {
-                               continue;
-                       }
-                       $select->addOption( "$code - $lang", $code );
-               }
-
-               $output .= $select->getHTML();
-               return $this->parent->label( $label, $name, $output );
-       }
-
-}
-
-class WebInstallerExistingWiki extends WebInstallerPage {
-
-       /**
-        * @return string
-        */
-       public function execute() {
-               // If there is no LocalSettings.php, continue to the installer welcome page
-               $vars = Installer::getExistingLocalSettings();
-               if ( !$vars ) {
-                       return 'skip';
-               }
-
-               // Check if the upgrade key supplied to the user has appeared in LocalSettings.php
-               if ( $vars['wgUpgradeKey'] !== false
-                       && $this->getVar( '_UpgradeKeySupplied' )
-                       && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey']
-               ) {
-                       // It's there, so the user is authorized
-                       $status = $this->handleExistingUpgrade( $vars );
-                       if ( $status->isOK() ) {
-                               return 'skip';
-                       } else {
-                               $this->startForm();
-                               $this->parent->showStatusBox( $status );
-                               $this->endForm( 'continue' );
-
-                               return 'output';
-                       }
-               }
-
-               // If there is no $wgUpgradeKey, tell the user to add one to LocalSettings.php
-               if ( $vars['wgUpgradeKey'] === false ) {
-                       if ( $this->getVar( 'wgUpgradeKey', false ) === false ) {
-                               $secretKey = $this->getVar( 'wgSecretKey' ); // preserve $wgSecretKey
-                               $this->parent->generateKeys();
-                               $this->setVar( 'wgSecretKey', $secretKey );
-                               $this->setVar( '_UpgradeKeySupplied', true );
-                       }
-                       $this->startForm();
-                       $this->addHTML( $this->parent->getInfoBox(
-                               wfMessage( 'config-upgrade-key-missing', "<pre dir=\"ltr\">\$wgUpgradeKey = '" .
-                                       $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )->plain()
-                       ) );
-                       $this->endForm( 'continue' );
-
-                       return 'output';
-               }
-
-               // If there is an upgrade key, but it wasn't supplied, prompt the user to enter it
-
-               $r = $this->parent->request;
-               if ( $r->wasPosted() ) {
-                       $key = $r->getText( 'config_wgUpgradeKey' );
-                       if ( !$key || $key !== $vars['wgUpgradeKey'] ) {
-                               $this->parent->showError( 'config-localsettings-badkey' );
-                               $this->showKeyForm();
-
-                               return 'output';
-                       }
-                       // Key was OK
-                       $status = $this->handleExistingUpgrade( $vars );
-                       if ( $status->isOK() ) {
-                               return 'continue';
-                       } else {
-                               $this->parent->showStatusBox( $status );
-                               $this->showKeyForm();
-
-                               return 'output';
-                       }
-               } else {
-                       $this->showKeyForm();
-
-                       return 'output';
-               }
-       }
-
-       /**
-        * Show the "enter key" form
-        */
-       protected function showKeyForm() {
-               $this->startForm();
-               $this->addHTML(
-                       $this->parent->getInfoBox( wfMessage( 'config-localsettings-upgrade' )->plain() ) .
-                       '<br />' .
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgUpgradeKey',
-                               'label' => 'config-localsettings-key',
-                               'attribs' => array( 'autocomplete' => 'off' ),
-                       ) )
-               );
-               $this->endForm( 'continue' );
-       }
-
-       /**
-        * @param string[] $names
-        * @param mixed[] $vars
-        *
-        * @return Status
-        */
-       protected function importVariables( $names, $vars ) {
-               $status = Status::newGood();
-               foreach ( $names as $name ) {
-                       if ( !isset( $vars[$name] ) ) {
-                               $status->fatal( 'config-localsettings-incomplete', $name );
-                       }
-                       $this->setVar( $name, $vars[$name] );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Initiate an upgrade of the existing database
-        *
-        * @param mixed[] $vars Variables from LocalSettings.php
-        *
-        * @return Status
-        */
-       protected function handleExistingUpgrade( $vars ) {
-               // Check $wgDBtype
-               if ( !isset( $vars['wgDBtype'] ) ||
-                       !in_array( $vars['wgDBtype'], Installer::getDBTypes() )
-               ) {
-                       return Status::newFatal( 'config-localsettings-connection-error', '' );
-               }
-
-               // Set the relevant variables from LocalSettings.php
-               $requiredVars = array( 'wgDBtype' );
-               $status = $this->importVariables( $requiredVars, $vars );
-               $installer = $this->parent->getDBInstaller();
-               $status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
-               if ( !$status->isOK() ) {
-                       return $status;
-               }
-
-               if ( isset( $vars['wgDBadminuser'] ) ) {
-                       $this->setVar( '_InstallUser', $vars['wgDBadminuser'] );
-               } else {
-                       $this->setVar( '_InstallUser', $vars['wgDBuser'] );
-               }
-               if ( isset( $vars['wgDBadminpassword'] ) ) {
-                       $this->setVar( '_InstallPassword', $vars['wgDBadminpassword'] );
-               } else {
-                       $this->setVar( '_InstallPassword', $vars['wgDBpassword'] );
-               }
-
-               // Test the database connection
-               $status = $installer->getConnection();
-               if ( !$status->isOK() ) {
-                       // Adjust the error message to explain things correctly
-                       $status->replaceMessage( 'config-connection-error',
-                               'config-localsettings-connection-error' );
-
-                       return $status;
-               }
-
-               // All good
-               $this->setVar( '_ExistingDBSettings', true );
-
-               return $status;
-       }
-
-}
-
-class WebInstallerWelcome extends WebInstallerPage {
-
-       /**
-        * @return string
-        */
-       public function execute() {
-               if ( $this->parent->request->wasPosted() ) {
-                       if ( $this->getVar( '_Environment' ) ) {
-                               return 'continue';
-                       }
-               }
-               $this->parent->output->addWikiText( wfMessage( 'config-welcome' )->plain() );
-               $status = $this->parent->doEnvironmentChecks();
-               if ( $status->isGood() ) {
-                       $this->parent->output->addHTML( '<span class="success-message">' .
-                               wfMessage( 'config-env-good' )->escaped() . '</span>' );
-                       $this->parent->output->addWikiText( wfMessage( 'config-copyright',
-                               SpecialVersion::getCopyrightAndAuthorList() )->plain() );
-                       $this->startForm();
-                       $this->endForm();
-               } else {
-                       $this->parent->showStatusMessage( $status );
-               }
-
-               return '';
-       }
-
-}
-
-class WebInstallerDBConnect extends WebInstallerPage {
-
-       /**
-        * @return string|null When string, "skip" or "continue"
-        */
-       public function execute() {
-               if ( $this->getVar( '_ExistingDBSettings' ) ) {
-                       return 'skip';
-               }
-
-               $r = $this->parent->request;
-               if ( $r->wasPosted() ) {
-                       $status = $this->submit();
-
-                       if ( $status->isGood() ) {
-                               $this->setVar( '_UpgradeDone', false );
-
-                               return 'continue';
-                       } else {
-                               $this->parent->showStatusBox( $status );
-                       }
-               }
-
-               $this->startForm();
-
-               $types = "<ul class=\"config-settings-block\">\n";
-               $settings = '';
-               $defaultType = $this->getVar( 'wgDBtype' );
-
-               // Messages: config-dbsupport-mysql, config-dbsupport-postgres, config-dbsupport-oracle,
-               // config-dbsupport-sqlite, config-dbsupport-mssql
-               $dbSupport = '';
-               foreach ( Installer::getDBTypes() as $type ) {
-                       $dbSupport .= wfMessage( "config-dbsupport-$type" )->plain() . "\n";
-               }
-               $this->addHTML( $this->parent->getInfoBox(
-                       wfMessage( 'config-support-info', trim( $dbSupport ) )->text() ) );
-
-               // It's possible that the library for the default DB type is not compiled in.
-               // In that case, instead select the first supported DB type in the list.
-               $compiledDBs = $this->parent->getCompiledDBs();
-               if ( !in_array( $defaultType, $compiledDBs ) ) {
-                       $defaultType = $compiledDBs[0];
-               }
-
-               foreach ( $compiledDBs as $type ) {
-                       $installer = $this->parent->getDBInstaller( $type );
-                       $types .=
-                               '<li>' .
-                               Xml::radioLabel(
-                                       $installer->getReadableName(),
-                                       'DBType',
-                                       $type,
-                                       "DBType_$type",
-                                       $type == $defaultType,
-                                       array( 'class' => 'dbRadio', 'rel' => "DB_wrapper_$type" )
-                               ) .
-                               "</li>\n";
-
-                       // Messages: config-header-mysql, config-header-postgres, config-header-oracle,
-                       // config-header-sqlite
-                       $settings .= Html::openElement(
-                                       'div',
-                                       array(
-                                               'id' => 'DB_wrapper_' . $type,
-                                               'class' => 'dbWrapper'
-                                       )
-                               ) .
-                               Html::element( 'h3', array(), wfMessage( 'config-header-' . $type )->text() ) .
-                               $installer->getConnectForm() .
-                               "</div>\n";
-               }
-
-               $types .= "</ul><br style=\"clear: left\"/>\n";
-
-               $this->addHTML( $this->parent->label( 'config-db-type', false, $types ) . $settings );
-               $this->endForm();
-
-               return null;
-       }
-
-       /**
-        * @return Status
-        */
-       public function submit() {
-               $r = $this->parent->request;
-               $type = $r->getVal( 'DBType' );
-               if ( !$type ) {
-                       return Status::newFatal( 'config-invalid-db-type' );
-               }
-               $this->setVar( 'wgDBtype', $type );
-               $installer = $this->parent->getDBInstaller( $type );
-               if ( !$installer ) {
-                       return Status::newFatal( 'config-invalid-db-type' );
-               }
-
-               return $installer->submitConnectForm();
-       }
-
-}
-
-class WebInstallerUpgrade extends WebInstallerPage {
-
-       /**
-        * @return bool Always true.
-        */
-       public function isSlow() {
-               return true;
-       }
-
-       /**
-        * @return string|null
-        */
-       public function execute() {
-               if ( $this->getVar( '_UpgradeDone' ) ) {
-                       // Allow regeneration of LocalSettings.php, unless we are working
-                       // from a pre-existing LocalSettings.php file and we want to avoid
-                       // leaking its contents
-                       if ( $this->parent->request->wasPosted() && !$this->getVar( '_ExistingDBSettings' ) ) {
-                               // Done message acknowledged
-                               return 'continue';
-                       } else {
-                               // Back button click
-                               // Show the done message again
-                               // Make them click back again if they want to do the upgrade again
-                               $this->showDoneMessage();
-
-                               return 'output';
-                       }
-               }
-
-               // wgDBtype is generally valid here because otherwise the previous page
-               // (connect) wouldn't have declared its happiness
-               $type = $this->getVar( 'wgDBtype' );
-               $installer = $this->parent->getDBInstaller( $type );
-
-               if ( !$installer->needsUpgrade() ) {
-                       return 'skip';
-               }
-
-               if ( $this->parent->request->wasPosted() ) {
-                       $installer->preUpgrade();
-
-                       $this->startLiveBox();
-                       $result = $installer->doUpgrade();
-                       $this->endLiveBox();
-
-                       if ( $result ) {
-                               // If they're going to possibly regenerate LocalSettings, we
-                               // need to create the upgrade/secret keys. Bug 26481
-                               if ( !$this->getVar( '_ExistingDBSettings' ) ) {
-                                       $this->parent->generateKeys();
-                               }
-                               $this->setVar( '_UpgradeDone', true );
-                               $this->showDoneMessage();
-
-                               return 'output';
-                       }
-               }
-
-               $this->startForm();
-               $this->addHTML( $this->parent->getInfoBox(
-                       wfMessage( 'config-can-upgrade', $GLOBALS['wgVersion'] )->plain() ) );
-               $this->endForm();
-
-               return null;
-       }
-
-       public function showDoneMessage() {
-               $this->startForm();
-               $regenerate = !$this->getVar( '_ExistingDBSettings' );
-               if ( $regenerate ) {
-                       $msg = 'config-upgrade-done';
-               } else {
-                       $msg = 'config-upgrade-done-no-regenerate';
-               }
-               $this->parent->disableLinkPopups();
-               $this->addHTML(
-                       $this->parent->getInfoBox(
-                               wfMessage( $msg,
-                                       $this->getVar( 'wgServer' ) .
-                                       $this->getVar( 'wgScriptPath' ) . '/index.php'
-                               )->plain(), 'tick-32.png'
-                       )
-               );
-               $this->parent->restoreLinkPopups();
-               $this->endForm( $regenerate ? 'regenerate' : false, false );
-       }
-
-}
-
-class WebInstallerDBSettings extends WebInstallerPage {
-
-       /**
-        * @return string|null
-        */
-       public function execute() {
-               $installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
-
-               $r = $this->parent->request;
-               if ( $r->wasPosted() ) {
-                       $status = $installer->submitSettingsForm();
-                       if ( $status === false ) {
-                               return 'skip';
-                       } elseif ( $status->isGood() ) {
-                               return 'continue';
-                       } else {
-                               $this->parent->showStatusBox( $status );
-                       }
-               }
-
-               $form = $installer->getSettingsForm();
-               if ( $form === false ) {
-                       return 'skip';
-               }
-
-               $this->startForm();
-               $this->addHTML( $form );
-               $this->endForm();
-
-               return null;
-       }
-
-}
-
-class WebInstallerName extends WebInstallerPage {
-
-       /**
-        * @return string
-        */
-       public function execute() {
-               $r = $this->parent->request;
-               if ( $r->wasPosted() ) {
-                       if ( $this->submit() ) {
-                               return 'continue';
-                       }
-               }
-
-               $this->startForm();
-
-               // Encourage people to not name their site 'MediaWiki' by blanking the
-               // field. I think that was the intent with the original $GLOBALS['wgSitename']
-               // but these two always were the same so had the effect of making the
-               // installer forget $wgSitename when navigating back to this page.
-               if ( $this->getVar( 'wgSitename' ) == 'MediaWiki' ) {
-                       $this->setVar( 'wgSitename', '' );
-               }
-
-               // Set wgMetaNamespace to something valid before we show the form.
-               // $wgMetaNamespace defaults to $wgSiteName which is 'MediaWiki'
-               $metaNS = $this->getVar( 'wgMetaNamespace' );
-               $this->setVar(
-                       'wgMetaNamespace',
-                       wfMessage( 'config-ns-other-default' )->inContentLanguage()->text()
-               );
-
-               $this->addHTML(
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgSitename',
-                               'label' => 'config-site-name',
-                               'help' => $this->parent->getHelpBox( 'config-site-name-help' )
-                       ) ) .
-                       // getRadioSet() builds a set of labeled radio buttons.
-                       // For grep: The following messages are used as the item labels:
-                       // config-ns-site-name, config-ns-generic, config-ns-other
-                       $this->parent->getRadioSet( array(
-                               'var' => '_NamespaceType',
-                               'label' => 'config-project-namespace',
-                               'itemLabelPrefix' => 'config-ns-',
-                               'values' => array( 'site-name', 'generic', 'other' ),
-                               'commonAttribs' => array( 'class' => 'enableForOther',
-                                       'rel' => 'config_wgMetaNamespace' ),
-                               'help' => $this->parent->getHelpBox( 'config-project-namespace-help' )
-                       ) ) .
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgMetaNamespace',
-                               'label' => '', // @todo Needs a label?
-                               'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' )
-                       ) ) .
-                       $this->getFieldSetStart( 'config-admin-box' ) .
-                       $this->parent->getTextBox( array(
-                               'var' => '_AdminName',
-                               'label' => 'config-admin-name',
-                               'help' => $this->parent->getHelpBox( 'config-admin-help' )
-                       ) ) .
-                       $this->parent->getPasswordBox( array(
-                               'var' => '_AdminPassword',
-                               'label' => 'config-admin-password',
-                       ) ) .
-                       $this->parent->getPasswordBox( array(
-                               'var' => '_AdminPasswordConfirm',
-                               'label' => 'config-admin-password-confirm'
-                       ) ) .
-                       $this->parent->getTextBox( array(
-                               'var' => '_AdminEmail',
-                               'attribs' => array(
-                                       'dir' => 'ltr',
-                               ),
-                               'label' => 'config-admin-email',
-                               'help' => $this->parent->getHelpBox( 'config-admin-email-help' )
-                       ) ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => '_Subscribe',
-                               'label' => 'config-subscribe',
-                               'help' => $this->parent->getHelpBox( 'config-subscribe-help' )
-                       ) ) .
-                       $this->getFieldSetEnd() .
-                       $this->parent->getInfoBox( wfMessage( 'config-almost-done' )->text() ) .
-                       // getRadioSet() builds a set of labeled radio buttons.
-                       // For grep: The following messages are used as the item labels:
-                       // config-optional-continue, config-optional-skip
-                       $this->parent->getRadioSet( array(
-                               'var' => '_SkipOptional',
-                               'itemLabelPrefix' => 'config-optional-',
-                               'values' => array( 'continue', 'skip' )
-                       ) )
-               );
-
-               // Restore the default value
-               $this->setVar( 'wgMetaNamespace', $metaNS );
-
-               $this->endForm();
-
-               return 'output';
-       }
-
-       /**
-        * @return bool
-        */
-       public function submit() {
-               global $wgPasswordPolicy;
-
-               $retVal = true;
-               $this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
-                       '_AdminName', '_AdminPassword', '_AdminPasswordConfirm', '_AdminEmail',
-                       '_Subscribe', '_SkipOptional', 'wgMetaNamespace' ) );
-
-               // Validate site name
-               if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
-                       $this->parent->showError( 'config-site-name-blank' );
-                       $retVal = false;
-               }
-
-               // Fetch namespace
-               $nsType = $this->getVar( '_NamespaceType' );
-               if ( $nsType == 'site-name' ) {
-                       $name = $this->getVar( 'wgSitename' );
-                       // Sanitize for namespace
-                       // This algorithm should match the JS one in WebInstallerOutput.php
-                       $name = preg_replace( '/[\[\]\{\}|#<>%+? ]/', '_', $name );
-                       $name = str_replace( '&', '&amp;', $name );
-                       $name = preg_replace( '/__+/', '_', $name );
-                       $name = ucfirst( trim( $name, '_' ) );
-               } elseif ( $nsType == 'generic' ) {
-                       $name = wfMessage( 'config-ns-generic' )->text();
-               } else { // other
-                       $name = $this->getVar( 'wgMetaNamespace' );
-               }
-
-               // Validate namespace
-               if ( strpos( $name, ':' ) !== false ) {
-                       $good = false;
-               } else {
-                       // Title-style validation
-                       $title = Title::newFromText( $name );
-                       if ( !$title ) {
-                               $good = $nsType == 'site-name';
-                       } else {
-                               $name = $title->getDBkey();
-                               $good = true;
-                       }
-               }
-               if ( !$good ) {
-                       $this->parent->showError( 'config-ns-invalid', $name );
-                       $retVal = false;
-               }
-
-               // Make sure it won't conflict with any existing namespaces
-               global $wgContLang;
-               $nsIndex = $wgContLang->getNsIndex( $name );
-               if ( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
-                       $this->parent->showError( 'config-ns-conflict', $name );
-                       $retVal = false;
-               }
-
-               $this->setVar( 'wgMetaNamespace', $name );
-
-               // Validate username for creation
-               $name = $this->getVar( '_AdminName' );
-               if ( strval( $name ) === '' ) {
-                       $this->parent->showError( 'config-admin-name-blank' );
-                       $cname = $name;
-                       $retVal = false;
-               } else {
-                       $cname = User::getCanonicalName( $name, 'creatable' );
-                       if ( $cname === false ) {
-                               $this->parent->showError( 'config-admin-name-invalid', $name );
-                               $retVal = false;
-                       } else {
-                               $this->setVar( '_AdminName', $cname );
-                       }
-               }
-
-               // Validate password
-               $msg = false;
-               $pwd = $this->getVar( '_AdminPassword' );
-               $user = User::newFromName( $cname );
-               if ( $user ) {
-                       $upp = new UserPasswordPolicy(
-                               $wgPasswordPolicy['policies'],
-                               $wgPasswordPolicy['checks']
-                       );
-                       $status = $upp->checkUserPasswordForGroups(
-                               $user,
-                               $pwd,
-                               array( 'bureaucrat', 'sysop' )  // per Installer::createSysop()
-                       );
-                       $valid = $status->isGood() ? true : $status->getMessage();
-               } else {
-                       $valid = 'config-admin-name-invalid';
-               }
-               if ( strval( $pwd ) === '' ) {
-                       // Provide a more specific and helpful message if password field is left blank
-                       $msg = 'config-admin-password-blank';
-               } elseif ( $pwd !== $this->getVar( '_AdminPasswordConfirm' ) ) {
-                       $msg = 'config-admin-password-mismatch';
-               } elseif ( $valid !== true ) {
-                       $msg = $valid;
-               }
-               if ( $msg !== false ) {
-                       call_user_func( array( $this->parent, 'showError' ), $msg );
-                       $this->setVar( '_AdminPassword', '' );
-                       $this->setVar( '_AdminPasswordConfirm', '' );
-                       $retVal = false;
-               }
-
-               // Validate e-mail if provided
-               $email = $this->getVar( '_AdminEmail' );
-               if ( $email && !Sanitizer::validateEmail( $email ) ) {
-                       $this->parent->showError( 'config-admin-error-bademail' );
-                       $retVal = false;
-               }
-               // If they asked to subscribe to mediawiki-announce but didn't give
-               // an e-mail, show an error. Bug 29332
-               if ( !$email && $this->getVar( '_Subscribe' ) ) {
-                       $this->parent->showError( 'config-subscribe-noemail' );
-                       $retVal = false;
-               }
-
-               return $retVal;
-       }
-
-}
-
-class WebInstallerOptions extends WebInstallerPage {
-
-       /**
-        * @return string|null
-        */
-       public function execute() {
-               if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
-                       $this->submitSkins();
-                       return 'skip';
-               }
-               if ( $this->parent->request->wasPosted() ) {
-                       if ( $this->submit() ) {
-                               return 'continue';
-                       }
-               }
-
-               $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
-               $this->startForm();
-               $this->addHTML(
-                       # User Rights
-                       // getRadioSet() builds a set of labeled radio buttons.
-                       // For grep: The following messages are used as the item labels:
-                       // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
-                       $this->parent->getRadioSet( array(
-                               'var' => '_RightsProfile',
-                               'label' => 'config-profile',
-                               'itemLabelPrefix' => 'config-profile-',
-                               'values' => array_keys( $this->parent->rightsProfiles ),
-                       ) ) .
-                       $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
-
-                       # Licensing
-                       // getRadioSet() builds a set of labeled radio buttons.
-                       // For grep: The following messages are used as the item labels:
-                       // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
-                       // config-license-cc-0, config-license-pd, config-license-gfdl,
-                       // config-license-none, config-license-cc-choose
-                       $this->parent->getRadioSet( array(
-                               'var' => '_LicenseCode',
-                               'label' => 'config-license',
-                               'itemLabelPrefix' => 'config-license-',
-                               'values' => array_keys( $this->parent->licenses ),
-                               'commonAttribs' => array( 'class' => 'licenseRadio' ),
-                       ) ) .
-                       $this->getCCChooser() .
-                       $this->parent->getHelpBox( 'config-license-help' ) .
-
-                       # E-mail
-                       $this->getFieldSetStart( 'config-email-settings' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEnableEmail',
-                               'label' => 'config-enable-email',
-                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ),
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-enable-email-help' ) .
-                       "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgPasswordSender',
-                               'label' => 'config-email-sender'
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-email-sender-help' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEnableUserEmail',
-                               'label' => 'config-email-user',
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-email-user-help' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEnotifUserTalk',
-                               'label' => 'config-email-usertalk',
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEnotifWatchlist',
-                               'label' => 'config-email-watchlist',
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEmailAuthentication',
-                               'label' => 'config-email-auth',
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-email-auth-help' ) .
-                       "</div>" .
-                       $this->getFieldSetEnd()
-               );
-
-               $skins = $this->parent->findExtensions( 'skins' );
-               $skinHtml = $this->getFieldSetStart( 'config-skins' );
-
-               $skinNames = array_map( 'strtolower', $skins );
-               $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
-
-               if ( $skins ) {
-                       $radioButtons = $this->parent->getRadioElements( array(
-                               'var' => 'wgDefaultSkin',
-                               'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
-                               'values' => $skinNames,
-                               'value' => $chosenSkinName,
-                       ) );
-
-                       foreach ( $skins as $skin ) {
-                               $skinHtml .=
-                                       '<div class="config-skins-item">' .
-                                       $this->parent->getCheckBox( array(
-                                               'var' => "skin-$skin",
-                                               'rawtext' => $skin,
-                                               'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
-                                       ) ) .
-                                       '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
-                                       '</div>';
-                       }
-               } else {
-                       $skinHtml .=
-                               $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() ) .
-                               Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
-               }
-
-               $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
-                       $this->getFieldSetEnd();
-               $this->addHTML( $skinHtml );
-
-               $extensions = $this->parent->findExtensions();
-
-               if ( $extensions ) {
-                       $extHtml = $this->getFieldSetStart( 'config-extensions' );
-
-                       foreach ( $extensions as $ext ) {
-                               $extHtml .= $this->parent->getCheckBox( array(
-                                       'var' => "ext-$ext",
-                                       'rawtext' => $ext,
-                               ) );
-                       }
-
-                       $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
-                               $this->getFieldSetEnd();
-                       $this->addHTML( $extHtml );
-               }
-
-               // Having / in paths in Windows looks funny :)
-               $this->setVar( 'wgDeletedDirectory',
-                       str_replace(
-                               '/', DIRECTORY_SEPARATOR,
-                               $this->getVar( 'wgDeletedDirectory' )
-                       )
-               );
-
-               $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
-               $this->addHTML(
-                       # Uploading
-                       $this->getFieldSetStart( 'config-upload-settings' ) .
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgEnableUploads',
-                               'label' => 'config-upload-enable',
-                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ),
-                               'help' => $this->parent->getHelpBox( 'config-upload-help' )
-                       ) ) .
-                       '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgDeletedDirectory',
-                               'label' => 'config-upload-deleted',
-                               'attribs' => array( 'dir' => 'ltr' ),
-                               'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
-                       ) ) .
-                       '</div>' .
-                       $this->parent->getTextBox( array(
-                               'var' => 'wgLogo',
-                               'label' => 'config-logo',
-                               'attribs' => array( 'dir' => 'ltr' ),
-                               'help' => $this->parent->getHelpBox( 'config-logo-help' )
-                       ) )
-               );
-               $this->addHTML(
-                       $this->parent->getCheckBox( array(
-                               'var' => 'wgUseInstantCommons',
-                               'label' => 'config-instantcommons',
-                               'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
-                       ) ) .
-                       $this->getFieldSetEnd()
-               );
-
-               $caches = array( 'none' );
-               if ( count( $this->getVar( '_Caches' ) ) ) {
-                       $caches[] = 'accel';
-               }
-               $caches[] = 'memcached';
-
-               // We'll hide/show this on demand when the value changes, see config.js.
-               $cacheval = $this->getVar( '_MainCacheType' );
-               if ( !$cacheval ) {
-                       // We need to set a default here; but don't hardcode it
-                       // or we lose it every time we reload the page for validation
-                       // or going back!
-                       $cacheval = 'none';
-               }
-               $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
-               $this->addHTML(
-                       # Advanced settings
-                       $this->getFieldSetStart( 'config-advanced-settings' ) .
-                       # Object cache settings
-                       // getRadioSet() builds a set of labeled radio buttons.
-                       // For grep: The following messages are used as the item labels:
-                       // config-cache-none, config-cache-accel, config-cache-memcached
-                       $this->parent->getRadioSet( array(
-                               'var' => '_MainCacheType',
-                               'label' => 'config-cache-options',
-                               'itemLabelPrefix' => 'config-cache-',
-                               'values' => $caches,
-                               'value' => $cacheval,
-                       ) ) .
-                       $this->parent->getHelpBox( 'config-cache-help' ) .
-                       "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
-                       $this->parent->getTextArea( array(
-                               'var' => '_MemCachedServers',
-                               'label' => 'config-memcached-servers',
-                               'help' => $this->parent->getHelpBox( 'config-memcached-help' )
-                       ) ) .
-                       '</div>' .
-                       $this->getFieldSetEnd()
-               );
-               $this->endForm();
-
-               return null;
-       }
-
-       /**
-        * @return string
-        */
-       public function getCCPartnerUrl() {
-               $server = $this->getVar( 'wgServer' );
-               $exitUrl = $server . $this->parent->getUrl( array(
-                       'page' => 'Options',
-                       'SubmitCC' => 'indeed',
-                       'config__LicenseCode' => 'cc',
-                       'config_wgRightsUrl' => '[license_url]',
-                       'config_wgRightsText' => '[license_name]',
-                       'config_wgRightsIcon' => '[license_button]',
-               ) );
-               $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
-                       '/mw-config/config-cc.css';
-               $iframeUrl = '//creativecommons.org/license/?' .
-                       wfArrayToCgi( array(
-                               'partner' => 'MediaWiki',
-                               'exit_url' => $exitUrl,
-                               'lang' => $this->getVar( '_UserLang' ),
-                               'stylesheet' => $styleUrl,
-                       ) );
-
-               return $iframeUrl;
-       }
-
-       /**
-        * @return string
-        */
-       public function getCCChooser() {
-               $iframeAttribs = array(
-                       'class' => 'config-cc-iframe',
-                       'name' => 'config-cc-iframe',
-                       'id' => 'config-cc-iframe',
-                       'frameborder' => 0,
-                       'width' => '100%',
-                       'height' => '100%',
-               );
-               if ( $this->getVar( '_CCDone' ) ) {
-                       $iframeAttribs['src'] = $this->parent->getUrl( array( 'ShowCC' => 'yes' ) );
-               } else {
-                       $iframeAttribs['src'] = $this->getCCPartnerUrl();
-               }
-               $wrapperStyle = ( $this->getVar( '_LicenseCode' ) == 'cc-choose' ) ? '' : 'display: none';
-
-               return "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
-                       Html::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
-                       "</div>\n";
-       }
-
-       /**
-        * @return string
-        */
-       public function getCCDoneBox() {
-               $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
-               // If you change this height, also change it in config.css
-               $expandJs = str_replace( '$1', '54em', $js );
-               $reduceJs = str_replace( '$1', '70px', $js );
-
-               return '<p>' .
-                       Html::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
-                       '&#160;&#160;' .
-                       htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
-                       "</p>\n" .
-                       "<p style=\"text-align: center;\">" .
-                       Html::element( 'a',
-                               array(
-                                       'href' => $this->getCCPartnerUrl(),
-                                       'onclick' => $expandJs,
-                               ),
-                               wfMessage( 'config-cc-again' )->text()
-                       ) .
-                       "</p>\n" .
-                       "<script>\n" .
-                       # Reduce the wrapper div height
-                       htmlspecialchars( $reduceJs ) .
-                       "\n" .
-                       "</script>\n";
-       }
-
-       public function submitCC() {
-               $newValues = $this->parent->setVarsFromRequest(
-                       array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
-               if ( count( $newValues ) != 3 ) {
-                       $this->parent->showError( 'config-cc-error' );
-
-                       return;
-               }
-               $this->setVar( '_CCDone', true );
-               $this->addHTML( $this->getCCDoneBox() );
-       }
-
-       /**
-        * If the user skips this installer page, we still need to set up the default skins, but ignore
-        * everything else.
-        *
-        * @return bool
-        */
-       public function submitSkins() {
-               $skins = $this->parent->findExtensions( 'skins' );
-               $this->parent->setVar( '_Skins', $skins );
-
-               if ( $skins ) {
-                       $skinNames = array_map( 'strtolower', $skins );
-                       $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
-               }
-
-               return true;
-       }
-
-       /**
-        * @return bool
-        */
-       public function submit() {
-               $this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
-                       'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
-                       'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
-                       'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
-                       'wgUseInstantCommons', 'wgDefaultSkin' ) );
-
-               $retVal = true;
-
-               if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
-                       reset( $this->parent->rightsProfiles );
-                       $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
-               }
-
-               $code = $this->getVar( '_LicenseCode' );
-               if ( $code == 'cc-choose' ) {
-                       if ( !$this->getVar( '_CCDone' ) ) {
-                               $this->parent->showError( 'config-cc-not-chosen' );
-                               $retVal = false;
-                       }
-               } elseif ( array_key_exists( $code, $this->parent->licenses ) ) {
-                       // Messages:
-                       // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
-                       // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
-                       // config-license-cc-choose
-                       $entry = $this->parent->licenses[$code];
-                       if ( isset( $entry['text'] ) ) {
-                               $this->setVar( 'wgRightsText', $entry['text'] );
-                       } else {
-                               $this->setVar( 'wgRightsText', wfMessage( 'config-license-' . $code )->text() );
-                       }
-                       $this->setVar( 'wgRightsUrl', $entry['url'] );
-                       $this->setVar( 'wgRightsIcon', $entry['icon'] );
-               } else {
-                       $this->setVar( 'wgRightsText', '' );
-                       $this->setVar( 'wgRightsUrl', '' );
-                       $this->setVar( 'wgRightsIcon', '' );
-               }
-
-               $skinsAvailable = $this->parent->findExtensions( 'skins' );
-               $skinsToInstall = array();
-               foreach ( $skinsAvailable as $skin ) {
-                       $this->parent->setVarsFromRequest( array( "skin-$skin" ) );
-                       if ( $this->getVar( "skin-$skin" ) ) {
-                               $skinsToInstall[] = $skin;
-                       }
-               }
-               $this->parent->setVar( '_Skins', $skinsToInstall );
-
-               if ( !$skinsToInstall && $skinsAvailable ) {
-                       $this->parent->showError( 'config-skins-must-enable-some' );
-                       $retVal = false;
-               }
-               $defaultSkin = $this->getVar( 'wgDefaultSkin' );
-               $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
-               if ( $skinsToInstall && array_search( $defaultSkin, $skinsToInstallLowercase ) === false ) {
-                       $this->parent->showError( 'config-skins-must-enable-default' );
-                       $retVal = false;
-               }
-
-               $extsAvailable = $this->parent->findExtensions();
-               $extsToInstall = array();
-               foreach ( $extsAvailable as $ext ) {
-                       $this->parent->setVarsFromRequest( array( "ext-$ext" ) );
-                       if ( $this->getVar( "ext-$ext" ) ) {
-                               $extsToInstall[] = $ext;
-                       }
-               }
-               $this->parent->setVar( '_Extensions', $extsToInstall );
-
-               if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) {
-                       $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
-                       if ( !$memcServers ) {
-                               $this->parent->showError( 'config-memcache-needservers' );
-                               $retVal = false;
-                       }
-
-                       foreach ( $memcServers as $server ) {
-                               $memcParts = explode( ":", $server, 2 );
-                               if ( !isset( $memcParts[0] )
-                                       || ( !IP::isValid( $memcParts[0] )
-                                               && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
-                               ) {
-                                       $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
-                                       $retVal = false;
-                               } elseif ( !isset( $memcParts[1] ) ) {
-                                       $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
-                                       $retVal = false;
-                               } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
-                                       $this->parent->showError( 'config-memcache-badport', 1, 65535 );
-                                       $retVal = false;
-                               }
-                       }
-               }
-
-               return $retVal;
-       }
-
-}
-
-class WebInstallerInstall extends WebInstallerPage {
-
-       /**
-        * @return bool Always true.
-        */
-       public function isSlow() {
-               return true;
-       }
-
-       /**
-        * @return string|bool
-        */
-       public function execute() {
-               if ( $this->getVar( '_UpgradeDone' ) ) {
-                       return 'skip';
-               } elseif ( $this->getVar( '_InstallDone' ) ) {
-                       return 'continue';
-               } elseif ( $this->parent->request->wasPosted() ) {
-                       $this->startForm();
-                       $this->addHTML( "<ul>" );
-                       $results = $this->parent->performInstallation(
-                               array( $this, 'startStage' ),
-                               array( $this, 'endStage' )
-                       );
-                       $this->addHTML( "</ul>" );
-                       // PerformInstallation bails on a fatal, so make sure the last item
-                       // completed before giving 'next.' Likewise, only provide back on failure
-                       $lastStep = end( $results );
-                       $continue = $lastStep->isOK() ? 'continue' : false;
-                       $back = $lastStep->isOK() ? false : 'back';
-                       $this->endForm( $continue, $back );
-               } else {
-                       $this->startForm();
-                       $this->addHTML( $this->parent->getInfoBox( wfMessage( 'config-install-begin' )->plain() ) );
-                       $this->endForm();
-               }
-
-               return true;
-       }
-
-       /**
-        * @param string $step
-        */
-       public function startStage( $step ) {
-               // Messages: config-install-database, config-install-tables, config-install-interwiki,
-               // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
-               $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() .
-                       wfMessage( 'ellipsis' )->escaped() );
-
-               if ( $step == 'extension-tables' ) {
-                       $this->startLiveBox();
-               }
-       }
-
-       /**
-        * @param string $step
-        * @param Status $status
-        */
-       public function endStage( $step, $status ) {
-               if ( $step == 'extension-tables' ) {
-                       $this->endLiveBox();
-               }
-               $msg = $status->isOk() ? 'config-install-step-done' : 'config-install-step-failed';
-               $html = wfMessage( 'word-separator' )->escaped() . wfMessage( $msg )->escaped();
-               if ( !$status->isOk() ) {
-                       $html = "<span class=\"error\">$html</span>";
-               }
-               $this->addHTML( $html . "</li>\n" );
-               if ( !$status->isGood() ) {
-                       $this->parent->showStatusBox( $status );
-               }
-       }
-
-}
-
-class WebInstallerComplete extends WebInstallerPage {
-
-       public function execute() {
-               // Pop up a dialog box, to make it difficult for the user to forget
-               // to download the file
-               $lsUrl = $this->getVar( 'wgServer' ) . $this->parent->getURL( array( 'localsettings' => 1 ) );
-               if ( isset( $_SERVER['HTTP_USER_AGENT'] ) &&
-                       strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false
-               ) {
-                       // JS appears to be the only method that works consistently with IE7+
-                       $this->addHtml( "\n<script>jQuery( function () { location.href = " .
-                               Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
-               } else {
-                       $this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
-               }
-
-               $this->startForm();
-               $this->parent->disableLinkPopups();
-               $this->addHTML(
-                       $this->parent->getInfoBox(
-                               wfMessage( 'config-install-done',
-                                       $lsUrl,
-                                       $this->getVar( 'wgServer' ) .
-                                       $this->getVar( 'wgScriptPath' ) . '/index.php',
-                                       '<downloadlink/>'
-                               )->plain(), 'tick-32.png'
-                       )
-               );
-               $this->addHTML( $this->parent->getInfoBox(
-                       wfMessage( 'config-extension-link' )->text() ) );
-
-               $this->parent->restoreLinkPopups();
-               $this->endForm( false, false );
-       }
-
-}
-
-class WebInstallerRestart extends WebInstallerPage {
-
-       /**
-        * @return string|null
-        */
-       public function execute() {
-               $r = $this->parent->request;
-               if ( $r->wasPosted() ) {
-                       $really = $r->getVal( 'submit-restart' );
-                       if ( $really ) {
-                               $this->parent->reset();
-                       }
-
-                       return 'continue';
-               }
-
-               $this->startForm();
-               $s = $this->parent->getWarningBox( wfMessage( 'config-help-restart' )->plain() );
-               $this->addHTML( $s );
-               $this->endForm( 'restart' );
-
-               return null;
-       }
-
-}
-
-abstract class WebInstallerDocument extends WebInstallerPage {
-
-       /**
-        * @return string
-        */
-       abstract protected function getFileName();
-
-       public function execute() {
-               $text = $this->getFileContents();
-               $text = InstallDocFormatter::format( $text );
-               $this->parent->output->addWikiText( $text );
-               $this->startForm();
-               $this->endForm( false );
-       }
-
-       /**
-        * @return string
-        */
-       public function getFileContents() {
-               $file = __DIR__ . '/../../' . $this->getFileName();
-               if ( !file_exists( $file ) ) {
-                       return wfMessage( 'config-nofile', $file )->plain();
-               }
-
-               return file_get_contents( $file );
-       }
-
-}
-
-class WebInstallerReadme extends WebInstallerDocument {
-
-       /**
-        * @return string
-        */
-       protected function getFileName() {
-               return 'README';
-       }
-
-}
-
-class WebInstallerReleaseNotes extends WebInstallerDocument {
-
-       /**
-        * @throws MWException
-        * @return string
-        */
-       protected function getFileName() {
-               global $wgVersion;
-
-               if ( !preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
-                       throw new MWException( 'Variable $wgVersion has an invalid value.' );
-               }
-
-               return 'RELEASE-NOTES-' . $result[1] . '.' . $result[2];
-       }
-
-}
-
-class WebInstallerUpgradeDoc extends WebInstallerDocument {
-
-       /**
-        * @return string
-        */
-       protected function getFileName() {
-               return 'UPGRADE';
-       }
-
-}
-
-class WebInstallerCopying extends WebInstallerDocument {
-
-       /**
-        * @return string
-        */
-       protected function getFileName() {
-               return 'COPYING';
-       }
-
-}
diff --git a/includes/installer/WebInstallerReadme.php b/includes/installer/WebInstallerReadme.php
new file mode 100644 (file)
index 0000000..36bc306
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerReadme extends WebInstallerDocument {
+
+       /**
+        * @return string
+        */
+       protected function getFileName() {
+               return 'README';
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerReleaseNotes.php b/includes/installer/WebInstallerReleaseNotes.php
new file mode 100644 (file)
index 0000000..e185876
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerReleaseNotes extends WebInstallerDocument {
+
+       /**
+        * @throws MWException
+        * @return string
+        */
+       protected function getFileName() {
+               global $wgVersion;
+
+               if ( !preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
+                       throw new MWException( 'Variable $wgVersion has an invalid value.' );
+               }
+
+               return 'RELEASE-NOTES-' . $result[1] . '.' . $result[2];
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerRestart.php b/includes/installer/WebInstallerRestart.php
new file mode 100644 (file)
index 0000000..8717cb8
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerRestart extends WebInstallerPage {
+
+       /**
+        * @return string|null
+        */
+       public function execute() {
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $really = $r->getVal( 'submit-restart' );
+                       if ( $really ) {
+                               $this->parent->reset();
+                       }
+
+                       return 'continue';
+               }
+
+               $this->startForm();
+               $s = $this->parent->getWarningBox( wfMessage( 'config-help-restart' )->plain() );
+               $this->addHTML( $s );
+               $this->endForm( 'restart' );
+
+               return null;
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerUpgrade.php b/includes/installer/WebInstallerUpgrade.php
new file mode 100644 (file)
index 0000000..7136120
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerUpgrade extends WebInstallerPage {
+
+       /**
+        * @return bool Always true.
+        */
+       public function isSlow() {
+               return true;
+       }
+
+       /**
+        * @return string|null
+        */
+       public function execute() {
+               if ( $this->getVar( '_UpgradeDone' ) ) {
+                       // Allow regeneration of LocalSettings.php, unless we are working
+                       // from a pre-existing LocalSettings.php file and we want to avoid
+                       // leaking its contents
+                       if ( $this->parent->request->wasPosted() && !$this->getVar( '_ExistingDBSettings' ) ) {
+                               // Done message acknowledged
+                               return 'continue';
+                       } else {
+                               // Back button click
+                               // Show the done message again
+                               // Make them click back again if they want to do the upgrade again
+                               $this->showDoneMessage();
+
+                               return 'output';
+                       }
+               }
+
+               // wgDBtype is generally valid here because otherwise the previous page
+               // (connect) wouldn't have declared its happiness
+               $type = $this->getVar( 'wgDBtype' );
+               $installer = $this->parent->getDBInstaller( $type );
+
+               if ( !$installer->needsUpgrade() ) {
+                       return 'skip';
+               }
+
+               if ( $this->parent->request->wasPosted() ) {
+                       $installer->preUpgrade();
+
+                       $this->startLiveBox();
+                       $result = $installer->doUpgrade();
+                       $this->endLiveBox();
+
+                       if ( $result ) {
+                               // If they're going to possibly regenerate LocalSettings, we
+                               // need to create the upgrade/secret keys. Bug 26481
+                               if ( !$this->getVar( '_ExistingDBSettings' ) ) {
+                                       $this->parent->generateKeys();
+                               }
+                               $this->setVar( '_UpgradeDone', true );
+                               $this->showDoneMessage();
+
+                               return 'output';
+                       }
+               }
+
+               $this->startForm();
+               $this->addHTML( $this->parent->getInfoBox(
+                       wfMessage( 'config-can-upgrade', $GLOBALS['wgVersion'] )->plain() ) );
+               $this->endForm();
+
+               return null;
+       }
+
+       public function showDoneMessage() {
+               $this->startForm();
+               $regenerate = !$this->getVar( '_ExistingDBSettings' );
+               if ( $regenerate ) {
+                       $msg = 'config-upgrade-done';
+               } else {
+                       $msg = 'config-upgrade-done-no-regenerate';
+               }
+               $this->parent->disableLinkPopups();
+               $this->addHTML(
+                       $this->parent->getInfoBox(
+                               wfMessage( $msg,
+                                       $this->getVar( 'wgServer' ) .
+                                       $this->getVar( 'wgScriptPath' ) . '/index.php'
+                               )->plain(), 'tick-32.png'
+                       )
+               );
+               $this->parent->restoreLinkPopups();
+               $this->endForm( $regenerate ? 'regenerate' : false, false );
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerUpgradeDoc.php b/includes/installer/WebInstallerUpgradeDoc.php
new file mode 100644 (file)
index 0000000..627ce65
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerUpgradeDoc extends WebInstallerDocument {
+
+       /**
+        * @return string
+        */
+       protected function getFileName() {
+               return 'UPGRADE';
+       }
+
+}
+
diff --git a/includes/installer/WebInstallerWelcome.php b/includes/installer/WebInstallerWelcome.php
new file mode 100644 (file)
index 0000000..e011770
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ * 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
+ * @ingroup Deployment
+ */
+
+class WebInstallerWelcome extends WebInstallerPage {
+
+       /**
+        * @return string
+        */
+       public function execute() {
+               if ( $this->parent->request->wasPosted() ) {
+                       if ( $this->getVar( '_Environment' ) ) {
+                               return 'continue';
+                       }
+               }
+               $this->parent->output->addWikiText( wfMessage( 'config-welcome' )->plain() );
+               $status = $this->parent->doEnvironmentChecks();
+               if ( $status->isGood() ) {
+                       $this->parent->output->addHTML( '<span class="success-message">' .
+                               wfMessage( 'config-env-good' )->escaped() . '</span>' );
+                       $this->parent->output->addWikiText( wfMessage( 'config-copyright',
+                               SpecialVersion::getCopyrightAndAuthorList() )->plain() );
+                       $this->startForm();
+                       $this->endForm();
+               } else {
+                       $this->parent->showStatusMessage( $status );
+               }
+
+               return '';
+       }
+
+}
+
+
index fd7dd5c..ac76621 100644 (file)
        "config-db-web-account": "Dä Zohjang zor Daatebangk för et Wiki",
        "config-db-web-help": "Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.",
        "config-db-web-account-same": "Donn dersällve Zohjang nämme, wi heh beim Opsäze.",
-       "config-db-web-create": "Donn dä Zohjang aanlääje, wann dä noch nit doh es.",
+       "config-db-web-create": "Donn dä Zohjang aanlähje, wann dä noch nit doh es.",
        "config-db-web-no-create-privs": "Dä Zohjang för et Opsäze es nit berääschtesch, ene ander Zohjan enzereeschte.\nDä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!",
        "config-mysql-engine": "De Zoot udder et Fommaat vun de Tabälle:",
        "config-mysql-innodb": "InnoDB",
index 796acb5..bb55ac6 100644 (file)
@@ -27,9 +27,11 @@ class ComposerJson {
         */
        public function getRequiredDependencies() {
                $deps = array();
-               foreach ( $this->contents['require'] as $package => $version ) {
-                       if ( $package !== "php" && strpos( $package, 'ext-' ) !== 0 ) {
-                               $deps[$package] = self::normalizeVersion( $version );
+               if ( isset( $this->contents['require'] ) ) {
+                       foreach ( $this->contents['require'] as $package => $version ) {
+                               if ( $package !== "php" && strpos( $package, 'ext-' ) !== 0 ) {
+                                       $deps[$package] = self::normalizeVersion( $version );
+                               }
                        }
                }
 
index 8427adb..db588fd 100644 (file)
 
 /**
  * Interface for log entries. Every log entry has these methods.
+ *
  * @since 1.19
  */
 interface LogEntry {
+
        /**
         * The main log type.
+        *
         * @return string
         */
        public function getType();
 
        /**
         * The log subtype.
+        *
         * @return string
         */
        public function getSubtype();
 
        /**
         * The full logtype in format maintype/subtype.
+        *
         * @return string
         */
        public function getFullType();
 
        /**
         * Get the extra parameters stored for this message.
+        *
         * @return array
         */
        public function getParameters();
 
        /**
         * Get the user for performed this action.
+        *
         * @return User
         */
        public function getPerformer();
 
        /**
         * Get the target page of this action.
+        *
         * @return Title
         */
        public function getTarget();
 
        /**
         * Get the timestamp when the action was executed.
+        *
         * @return string
         */
        public function getTimestamp();
 
        /**
         * Get the user provided comment.
+        *
         * @return string
         */
        public function getComment();
 
        /**
         * Get the access restriction.
+        *
         * @return string
         */
        public function getDeleted();
@@ -96,9 +107,11 @@ interface LogEntry {
 
 /**
  * Extends the LogEntryInterface with some basic functionality
+ *
  * @since 1.19
  */
 abstract class LogEntryBase implements LogEntry {
+
        public function getFullType() {
                return $this->getType() . '/' . $this->getSubtype();
        }
@@ -110,6 +123,7 @@ abstract class LogEntryBase implements LogEntry {
        /**
         * Whether the parameters for this log are stored in new or
         * old format.
+        *
         * @return bool
         */
        public function isLegacy() {
@@ -119,9 +133,9 @@ abstract class LogEntryBase implements LogEntry {
        /**
         * Create a blob from a parameter array
         *
+        * @since 1.26
         * @param array $params
         * @return string
-        * @since 1.26
         */
        public static function makeParamBlob( $params ) {
                return serialize( (array)$params );
@@ -130,9 +144,9 @@ abstract class LogEntryBase implements LogEntry {
        /**
         * Extract a parameter array from a blob
         *
+        * @since 1.26
         * @param string $blob
         * @return array
-        * @since 1.26
         */
        public static function extractParams( $blob ) {
                return unserialize( $blob );
@@ -141,15 +155,16 @@ abstract class LogEntryBase implements LogEntry {
 
 /**
  * This class wraps around database result row.
+ *
  * @since 1.19
  */
 class DatabaseLogEntry extends LogEntryBase {
-       // Static->
 
        /**
         * Returns array of information that is needed for querying
         * log entries. Array contains the following keys:
         * tables, fields, conds, options and join_conds
+        *
         * @return array
         */
        public static function getSelectQueryData() {
@@ -163,7 +178,7 @@ class DatabaseLogEntry extends LogEntryBase {
                );
 
                $joins = array(
-                       // IP's don't have an entry in user table
+                       // IPs don't have an entry in user table
                        'user' => array( 'LEFT JOIN', 'log_user=user_id' ),
                );
 
@@ -179,6 +194,7 @@ class DatabaseLogEntry extends LogEntryBase {
        /**
         * Constructs new LogEntry from database result row.
         * Supports rows from both logging and recentchanges table.
+        *
         * @param stdClass|array $row
         * @return DatabaseLogEntry
         */
@@ -191,17 +207,19 @@ class DatabaseLogEntry extends LogEntryBase {
                }
        }
 
-       // Non-static->
-
        /** @var stdClass Database result row. */
        protected $row;
 
        /** @var User */
        protected $performer;
 
-       /** @var bool Whether the parameters for this log entry are stored in new
-        *    or old format.
-        */
+       /** @var array Parameters for log entry */
+       protected $params;
+
+       /** @var int A rev id associated to the log entry */
+       protected $revId = null;
+
+       /** @var bool Whether the parameters for this log entry are stored in new or old format. */
        protected $legacy;
 
        protected function __construct( $row ) {
@@ -210,6 +228,7 @@ class DatabaseLogEntry extends LogEntryBase {
 
        /**
         * Returns the unique database id.
+        *
         * @return int
         */
        public function getId() {
@@ -218,23 +237,19 @@ class DatabaseLogEntry extends LogEntryBase {
 
        /**
         * Returns whatever is stored in the database field.
+        *
         * @return string
         */
        protected function getRawParameters() {
                return $this->row->log_params;
        }
 
-       // LogEntryBase->
-
        public function isLegacy() {
-               // This does the check
+               // This extracts the property
                $this->getParameters();
-
                return $this->legacy;
        }
 
-       // LogEntry->
-
        public function getType() {
                return $this->row->log_type;
        }
@@ -256,21 +271,34 @@ class DatabaseLogEntry extends LogEntryBase {
                                $this->params = LogPage::extractParams( $blob );
                                $this->legacy = true;
                        }
+
+                       if ( isset( $this->params['associated_rev_id'] ) ) {
+                               $this->revId = $this->params['associated_rev_id'];
+                               unset( $this->params['associated_rev_id'] );
+                       }
                }
 
                return $this->params;
        }
 
+       public function getAssociatedRevId() {
+               // This extracts the property
+               $this->getParameters();
+               return $this->revId;
+       }
+
        public function getPerformer() {
                if ( !$this->performer ) {
                        $userId = (int)$this->row->log_user;
-                       if ( $userId !== 0 ) { // logged-in users
+                       if ( $userId !== 0 ) {
+                               // logged-in users
                                if ( isset( $this->row->user_name ) ) {
                                        $this->performer = User::newFromRow( $this->row );
                                } else {
                                        $this->performer = User::newFromId( $userId );
                                }
-                       } else { // IP users
+                       } else {
+                               // IP users
                                $userText = $this->row->log_user_text;
                                $this->performer = User::newFromName( $userText, false );
                        }
@@ -310,7 +338,9 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
                return $this->row->rc_params;
        }
 
-       // LogEntry->
+       public function getAssociatedRevId() {
+               return $this->row->rc_this_oldid;
+       }
 
        public function getType() {
                return $this->row->rc_log_type;
@@ -357,8 +387,8 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
 }
 
 /**
- * Class for creating log entries manually, for
- * example to inject them into the database.
+ * Class for creating log entries manually, to inject them into the database.
+ *
  * @since 1.19
  */
 class ManualLogEntry extends LogEntryBase {
@@ -386,6 +416,9 @@ class ManualLogEntry extends LogEntryBase {
        /** @var string Comment for the log entry */
        protected $comment = '';
 
+       /** @var int A rev id associated to the log entry */
+       protected $revId = 0;
+
        /** @var int Deletion state of the log entry */
        protected $deleted;
 
@@ -399,7 +432,6 @@ class ManualLogEntry extends LogEntryBase {
         * Constructor.
         *
         * @since 1.19
-        *
         * @param string $type
         * @param string $subtype
         */
@@ -414,15 +446,19 @@ class ManualLogEntry extends LogEntryBase {
         * You can pass params to the log action message by prefixing the keys with
         * a number and optional type, using colons to separate the fields. The
         * numbering should start with number 4, the first three parameters are
-        * hardcoded for every message. Example:
-        * $entry->setParameters(
-        *   '4::color' => 'blue',
-        *   '5:number:count' => 3000,
-        *   'animal' => 'dog'
-        * );
+        * hardcoded for every message.
         *
-        * @since 1.19
+        * If you want to store stuff that should not be available in messages, don't
+        * prefix the array key with a number and don't use the colons.
+        *
+        * Example:
+        *   $entry->setParameters(
+        *     '4::color' => 'blue',
+        *     '5:number:count' => 3000,
+        *     'animal' => 'dog'
+        *   );
         *
+        * @since 1.19
         * @param array $parameters Associative array
         */
        public function setParameters( $parameters ) {
@@ -444,7 +480,6 @@ class ManualLogEntry extends LogEntryBase {
         * Set the user that performed the action being logged.
         *
         * @since 1.19
-        *
         * @param User $performer
         */
        public function setPerformer( User $performer ) {
@@ -455,7 +490,6 @@ class ManualLogEntry extends LogEntryBase {
         * Set the title of the object changed.
         *
         * @since 1.19
-        *
         * @param Title $target
         */
        public function setTarget( Title $target ) {
@@ -466,7 +500,6 @@ class ManualLogEntry extends LogEntryBase {
         * Set the timestamp of when the logged action took place.
         *
         * @since 1.19
-        *
         * @param string $timestamp
         */
        public function setTimestamp( $timestamp ) {
@@ -477,13 +510,25 @@ class ManualLogEntry extends LogEntryBase {
         * Set a comment associated with the action being logged.
         *
         * @since 1.19
-        *
         * @param string $comment
         */
        public function setComment( $comment ) {
                $this->comment = $comment;
        }
 
+       /**
+        * Set an associated revision id.
+        *
+        * For example, the ID of the revision that was inserted to mark a page move
+        * or protection, file upload, etc.
+        *
+        * @since 1.27
+        * @param int $revId
+        */
+       public function setAssociatedRevId( $revId ) {
+               $this->revId = $revId;
+       }
+
        /**
         * Set the 'legacy' flag
         *
@@ -495,18 +540,18 @@ class ManualLogEntry extends LogEntryBase {
        }
 
        /**
-        * TODO: document
+        * Set the 'deleted' flag.
         *
         * @since 1.19
-        *
-        * @param int $deleted
+        * @param int $deleted One of LogPage::DELETED_* bitfield constants
         */
        public function setDeleted( $deleted ) {
                $this->deleted = $deleted;
        }
 
        /**
-        * Inserts the entry into the logging table.
+        * Insert the entry into the `logging` table.
+        *
         * @param IDatabase $dbw
         * @return int ID of the log entry
         * @throws MWException
@@ -521,12 +566,22 @@ class ManualLogEntry extends LogEntryBase {
                        $this->timestamp = wfTimestampNow();
                }
 
-               # Trim spaces on user supplied text
+               // Trim spaces on user supplied text
                $comment = trim( $this->getComment() );
 
-               # Truncate for whole multibyte characters.
+               // Truncate for whole multibyte characters.
                $comment = $wgContLang->truncate( $comment, 255 );
 
+               $params = $this->getParameters();
+               $relations = $this->relations;
+
+               // Additional fields for which there's no space in the database table schema
+               $revId = $this->getAssociatedRevId();
+               if ( $revId ) {
+                       $params['associated_rev_id'] = $revId;
+                       $relations['associated_rev_id'] = $revId;
+               }
+
                $data = array(
                        'log_id' => $id,
                        'log_type' => $this->getType(),
@@ -538,7 +593,7 @@ class ManualLogEntry extends LogEntryBase {
                        'log_title' => $this->getTarget()->getDBkey(),
                        'log_page' => $this->getTarget()->getArticleID(),
                        'log_comment' => $comment,
-                       'log_params' => LogEntryBase::makeParamBlob( $this->getParameters() ),
+                       'log_params' => LogEntryBase::makeParamBlob( $params ),
                );
                if ( isset( $this->deleted ) ) {
                        $data['log_deleted'] = $this->deleted;
@@ -548,7 +603,7 @@ class ManualLogEntry extends LogEntryBase {
                $this->id = !is_null( $id ) ? $id : $dbw->insertId();
 
                $rows = array();
-               foreach ( $this->relations as $tag => $values ) {
+               foreach ( $relations as $tag => $values ) {
                        if ( !strlen( $tag ) ) {
                                throw new MWException( "Got empty log search tag." );
                        }
@@ -574,6 +629,7 @@ class ManualLogEntry extends LogEntryBase {
 
        /**
         * Get a RecentChanges object for the log entry
+        *
         * @param int $newId
         * @return RecentChange
         * @since 1.23
@@ -587,10 +643,8 @@ class ManualLogEntry extends LogEntryBase {
                $user = $this->getPerformer();
                $ip = "";
                if ( $user->isAnon() ) {
-                       /*
-                        * "MediaWiki default" and friends may have
-                        * no IP address in their name
-                        */
+                       // "MediaWiki default" and friends may have
+                       // no IP address in their name
                        if ( IP::isIPAddress( $user->getName() ) ) {
                                $ip = $user->getName();
                        }
@@ -608,12 +662,14 @@ class ManualLogEntry extends LogEntryBase {
                        $this->getComment(),
                        LogEntryBase::makeParamBlob( $this->getParameters() ),
                        $newId,
-                       $formatter->getIRCActionComment() // Used for IRC feeds
+                       $formatter->getIRCActionComment(), // Used for IRC feeds
+                       $this->getAssociatedRevId() // Used for e.g. moves and uploads
                );
        }
 
        /**
-        * Publishes the log entry.
+        * Publish the log entry.
+        *
         * @param int $newId Id of the log entry.
         * @param string $to One of: rcandudp (default), rc, udp
         */
@@ -632,9 +688,13 @@ class ManualLogEntry extends LogEntryBase {
                if ( $to === 'udp' || $to === 'rcandudp' ) {
                        $rc->notifyRCFeeds();
                }
-       }
 
-       // LogEntry->
+               // Log the autopatrol if an associated rev id was passed
+               if ( $this->getAssociatedRevId() > 0 &&
+                       $rc->getAttribute( 'rc_patrolled' ) === 1 ) {
+                       PatrolLog::record( $rc, true, $this->getPerformer() );
+               }
+       }
 
        public function getType() {
                return $this->type;
@@ -672,6 +732,14 @@ class ManualLogEntry extends LogEntryBase {
                return $this->comment;
        }
 
+       /**
+        * @since 1.27
+        * @return int
+        */
+       public function getAssociatedRevId() {
+               return $this->revId;
+       }
+
        /**
         * @since 1.25
         * @return bool
index af1f00b..22e24ae 100644 (file)
@@ -1061,14 +1061,14 @@ class Article implements Page {
         * @return bool
         */
        public function showPatrolFooter() {
-               global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
+               global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI;
 
                $outputPage = $this->getContext()->getOutput();
                $user = $this->getContext()->getUser();
                $rc = false;
 
                if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
-                       || !( $wgUseRCPatrol || $wgUseNPPatrol )
+                       || !( $wgUseRCPatrol || $wgUseNPPatrol || $wgUseFilePatrol )
                ) {
                        // Patrolling is disabled or the user isn't allowed to
                        return false;
@@ -1103,6 +1103,9 @@ class Article implements Page {
                        __METHOD__
                );
 
+               $cantPatrolNewPage = false;
+               $cantPatrolFile = false;
+
                if ( $oldestRevisionTimestamp
                        && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
                ) {
@@ -1116,7 +1119,51 @@ class Article implements Page {
                                ),
                                __METHOD__
                        );
+                       if ( $rc ) {
+                               // Use generic patrol message for new pages
+                               $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
+                       }
+               } else {
+                       $cantPatrolNewPage = true;
+               }
+
+               // Allow patrolling of latest file upload
+               if ( !$rc && $wgUseFilePatrol && $this->getTitle()->getNamespace() === NS_FILE ) {
+                       // Retrieve timestamp of most recent upload
+                       $newestUploadTimestamp = $dbr->selectField(
+                               'image',
+                               'MAX( img_timestamp )',
+                               array( 'img_name' => $this->getTitle()->getDBkey() ),
+                               __METHOD__
+                       );
+                       if ( $newestUploadTimestamp
+                               && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
+                       ) {
+                               // 6h tolerance because the RC might not be cleaned out regularly
+                               $rc = RecentChange::newFromConds(
+                                       array(
+                                               'rc_type' => RC_LOG,
+                                               'rc_log_type' => 'upload',
+                                               'rc_timestamp' => $newestUploadTimestamp,
+                                               'rc_namespace' => NS_FILE,
+                                               'rc_cur_id' => $this->getTitle()->getArticleID(),
+                                               'rc_patrolled' => 0
+                                       ),
+                                       __METHOD__,
+                                       array( 'USE INDEX' => 'rc_timestamp' )
+                               );
+                               if ( $rc ) {
+                                       // Use patrol message specific to files
+                                       $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
+                               }
+                       } else {
+                               $cantPatrolFile = true;
+                       }
                } else {
+                       $cantPatrolFile = true;
+               }
+
+               if ( $cantPatrolFile && $cantPatrolNewPage ) {
                        // Cache the information we gathered above in case we can't patrol
                        // Don't cache in case we can patrol as this could change
                        $cache->set( $key, '1' );
@@ -1156,7 +1203,7 @@ class Article implements Page {
 
                $link = Linker::linkKnown(
                        $this->getTitle(),
-                       wfMessage( 'markaspatrolledtext' )->escaped(),
+                       $markPatrolledMsg->escaped(),
                        array(),
                        array(
                                'action' => 'markpatrolled',
@@ -1174,6 +1221,17 @@ class Article implements Page {
                return true;
        }
 
+       /**
+        * Purge the cache used to check if it is worth showing the patrol footer
+        * For example, it is done during re-uploads when file patrol is used.
+        * @param int $articleID ID of the article to purge
+        * @since 1.27
+        */
+       public static function purgePatrolFooterCache( $articleID ) {
+               $cache = ObjectCache::getMainWANInstance();
+               $cache->touchCheckKey( wfMemcKey( 'unpatrollable-page', $articleID ) );
+       }
+
        /**
         * Show the error text for a missing article. For articles in the MediaWiki
         * namespace, show the default message text. To be called from Article::view().
index aac0c79..6ac25e8 100644 (file)
@@ -65,6 +65,7 @@ class ExtensionProcessor implements Processor {
                'wgNamespaceContentModels' => 'array_plus',
                'wgNamespaceProtection' => 'array_plus',
                'wgCapitalLinkOverrides' => 'array_plus',
+               'wgRateLimits' => 'array_plus_2d',
        );
 
        /**
index dbb7c7f..83f119d 100644 (file)
@@ -560,7 +560,7 @@ abstract class Skin extends ContextSource {
                        $classes .= ' catlinks-allhidden';
                }
 
-               return "<div id='catlinks' class='$classes'>{$catlinks}</div>";
+               return "<div id='catlinks' class='$classes' data-mw='interface'>{$catlinks}</div>";
        }
 
        /**
index c0e5556..2ecbafb 100644 (file)
@@ -1204,7 +1204,7 @@ class SkinTemplate extends Skin {
                        $nav_urls['print'] = array(
                                'text' => $this->msg( 'printableversion' )->text(),
                                'href' => $this->getTitle()->getLocalURL(
-                                       $request->appendQueryValue( 'printable', 'yes', true ) )
+                                       $request->appendQueryValue( 'printable', 'yes' ) )
                        );
                }
 
index db50bd8..226d633 100644 (file)
@@ -321,7 +321,8 @@ class SpecialBlock extends FormSpecialPage {
        protected function preText() {
                $this->getOutput()->addModules( array( 'mediawiki.special.block', 'mediawiki.userSuggest' ) );
 
-               $text = $this->msg( 'blockiptext' )->parse();
+               $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
+               $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
 
                $otherBlockMessages = array();
                if ( $this->target !== null ) {
index 17c224c..14d8675 100644 (file)
@@ -207,7 +207,7 @@ class SpecialExpandTemplates extends SpecialPage {
                $form
                        ->setSubmitTextMsg( 'expand_templates_ok' )
                        ->setWrapperLegendMsg( 'expandtemplates' )
-                       ->setHeaderText( $this->msg( 'expand_templates_intro' )->text() )
+                       ->setHeaderText( $this->msg( 'expand_templates_intro' )->parse() )
                        ->setSubmitCallback( array( $this, 'onSubmitInput' ) )
                        ->showAlways();
        }
index 6b7c038..53fb45e 100644 (file)
@@ -81,9 +81,20 @@ class NewFilesPager extends ReverseChronologicalPager {
         */
        protected $gallery;
 
+       /**
+        * @var bool
+        */
+       protected $showBots;
+
+       /**
+        * @var bool
+        */
+       protected $hidePatrolled;
+
        function __construct( IContextSource $context, $par = null ) {
                $this->like = $context->getRequest()->getText( 'like' );
-               $this->showbots = $context->getRequest()->getBool( 'showbots', 0 );
+               $this->showBots = $context->getRequest()->getBool( 'showbots', 0 );
+               $this->hidePatrolled = $context->getRequest()->getBool( 'hidepatrolled', 0 );
                if ( is_numeric( $par ) ) {
                        $this->setLimit( $par );
                }
@@ -95,7 +106,7 @@ class NewFilesPager extends ReverseChronologicalPager {
                $conds = $jconds = array();
                $tables = array( 'image' );
 
-               if ( !$this->showbots ) {
+               if ( !$this->showBots ) {
                        $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
 
                        if ( count( $groupsWithBotPermission ) ) {
@@ -111,6 +122,21 @@ class NewFilesPager extends ReverseChronologicalPager {
                        }
                }
 
+               if ( $this->hidePatrolled ) {
+                       $tables[] = 'recentchanges';
+                       $conds['rc_type'] = RC_LOG;
+                       $conds['rc_log_type'] = 'upload';
+                       $conds['rc_patrolled'] = 0;
+                       $jconds['recentchanges'] = array(
+                               'INNER JOIN',
+                               array(
+                                       'rc_title = img_name',
+                                       'rc_user = img_user',
+                                       'rc_timestamp = img_timestamp'
+                               )
+                       );
+               }
+
                if ( !$this->getConfig()->get( 'MiserMode' ) && $this->like !== null ) {
                        $dbr = wfGetDB( DB_SLAVE );
                        $likeObj = Title::newFromText( $this->like );
@@ -185,6 +211,11 @@ class NewFilesPager extends ReverseChronologicalPager {
                                'label-message' => 'newimages-showbots',
                                'name' => 'showbots',
                        ),
+                       'hidepatrolled' => array(
+                               'type' => 'check',
+                               'label-message' => 'newimages-hidepatrolled',
+                               'name' => 'hidepatrolled',
+                       ),
                        'limit' => array(
                                'type' => 'hidden',
                                'default' => $this->mLimit,
@@ -201,6 +232,10 @@ class NewFilesPager extends ReverseChronologicalPager {
                        unset( $fields['like'] );
                }
 
+               if ( !$this->getUser()->useFilePatrol() ) {
+                       unset( $fields['hidepatrolled'] );
+               }
+
                $context = new DerivativeContext( $this->getContext() );
                $context->setTitle( $this->getTitle() ); // Remove subpage
                $form = new HTMLForm( $fields, $context );
index ba11862..74a1322 100644 (file)
@@ -38,18 +38,27 @@ class ShortPagesPage extends QueryPage {
        }
 
        public function getQueryInfo() {
+               $tables = array( 'page' );
+               $conds = array(
+                       'page_namespace' => MWNamespace::getContentNamespaces(),
+                       'page_is_redirect' => 0
+               );
+               $joinConds = array();
+               $options = array( 'USE INDEX' => array( 'page' => 'page_redirect_namespace_len' ) );
+
+               // Allow extensions to modify the query
+               Hooks::run( 'ShortPagesQuery', array( &$tables, &$conds, &$joinConds, &$options ) );
+
                return array(
-                       'tables' => array( 'page' ),
+                       'tables' => $tables,
                        'fields' => array(
                                'namespace' => 'page_namespace',
                                'title' => 'page_title',
                                'value' => 'page_len'
                        ),
-                       'conds' => array(
-                               'page_namespace' => MWNamespace::getContentNamespaces(),
-                               'page_is_redirect' => 0
-                       ),
-                       'options' => array( 'USE INDEX' => 'page_redirect_namespace_len' )
+                       'conds' => $conds,
+                       'join_conds' => $joinConds,
+                       'options' => $options
                );
        }
 
index 3e974e3..b406bac 100644 (file)
@@ -3309,6 +3309,18 @@ class User implements IDBAccessObject {
                );
        }
 
+       /**
+        * Check whether to enable new files patrol features for this user
+        * @return bool True or false
+        */
+       public function useFilePatrol() {
+               global $wgUseRCPatrol, $wgUseFilePatrol;
+               return (
+                       ( $wgUseRCPatrol || $wgUseFilePatrol )
+                               && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
+               );
+       }
+
        /**
         * Get the WebRequest object to use with this object
         *
index 47d24dc..1b6e9d6 100644 (file)
@@ -107,11 +107,6 @@ class FakeConverter {
                return $key;
        }
 
-       /** @deprecated since 1.22 is no longer used */
-       function armourMath( $text ) {
-               return $text;
-       }
-
        function validateVariant( $variant = null ) {
                return $variant === $this->mLang->getCode() ? $variant : null;
        }
index 69f518b..cb2d24f 100644 (file)
@@ -4157,17 +4157,6 @@ class Language {
                return (bool)$this->mConverter->validateVariant( $variant );
        }
 
-       /**
-        * Put custom tags (e.g. -{ }-) around math to prevent conversion
-        *
-        * @param string $text
-        * @return string
-        * @deprecated since 1.22 is no longer used
-        */
-       public function armourMath( $text ) {
-               return $this->mConverter->armourMath( $text );
-       }
-
        /**
         * Perform output conversion on a string, and encode for safe HTML output.
         * @param string $text Text to be converted
index 78b3572..b00aa34 100644 (file)
@@ -1084,22 +1084,6 @@ class LanguageConverter {
                return true;
        }
 
-       /**
-        * Armour rendered math against conversion.
-        * Escape special chars in parsed math text. (in most cases are img elements)
-        *
-        * @param string $text Text to armour against conversion
-        * @return string Armoured text where { and } have been converted to
-        *   &#123; and &#125;
-        * @deprecated since 1.22 is no longer used
-        */
-       public function armourMath( $text ) {
-               // convert '-{' and '}-' to '-&#123;' and '&#125;-' to prevent
-               // any unwanted markup appearing in the math image tag.
-               $text = strtr( $text, array( '-{' => '-&#123;', '}-' => '&#125;-' ) );
-               return $text;
-       }
-
        /**
         * Get the cached separator pattern for ConverterRule::parseRules()
         * @return string
index f41175b..9b0254a 100644 (file)
        "passwordreset-emailtext-ip": "Dalguién (seique vusté, dende la direición IP $1)solicitó'l reaniciu de la so contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona,\no si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir\nusando la contraseña antigua.",
        "passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la so contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona, o si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir usando la contraseña antigua.",
        "passwordreset-emailelement": "Nome d'usuariu: \n$1\n\nContraseña temporal: \n$2",
-       "passwordreset-emailsentemail": "Si esta ye una direición de corréu electrónicu rexistrada pa la to cuenta, unviaráse un corréu pa reaniciar la contraseña.",
-       "passwordreset-emailsentusername": "Si hai una direición rexistrada de corréu electrónicu correspondiente a esta cuenta, unviaráse un corréu electrónicu pa reaniciar la contraseña.",
+       "passwordreset-emailsentemail": "Si esta direición de corréu electrónicu ta asociada cola to cuenta, unviaráse un corréu pa reaniciar la contraseña.",
+       "passwordreset-emailsentusername": "Si hai una direición de corréu electrónicu asociada con esti nome d'usuariu, unviaráse un corréu electrónicu pa reaniciar la contraseña.",
        "passwordreset-emailsent-capture": "Unvióse un corréu electrónicu pa reaniciar la contraseña, que s'amuesa abaxo.",
        "passwordreset-emailerror-capture": "Unvióse un corréu electrónicu pa reaniciar la contraseña, que s'amuesa abaxo, pero falló l'unviu {{GENDER:$2|al usuariu|a la usuaria}}: $1",
        "changeemail": "Camudar o desaniciar la dirección de corréu electrónicu",
        "upload-form-label-select-file": "Seleiciona un ficheru",
        "upload-form-label-infoform-title": "Detalles",
        "upload-form-label-infoform-name": "Nome",
+       "upload-form-label-infoform-name-tooltip": "Un títulu descriptivu únicu pal ficheru, que sirvirá para da-y nome al ficheru. Se pue usar llinguax normal con espacios. Nun amiestes la estensión del ficheru.",
        "upload-form-label-infoform-description": "Descripción",
+       "upload-form-label-infoform-description-tooltip": "Describe de mou curtiu cualquier cosa notable de la obra.\nPa una semeya, cuenta les principales coses qu'apaecen, la ocasión o'l sitiu.",
        "upload-form-label-usage-title": "Usu",
        "upload-form-label-usage-filename": "Nome del ficheru",
        "foreign-structured-upload-form-label-own-work": "Esti ye'l mio propiu trabayu",
        "export-download": "Guardar como archivu",
        "export-templates": "Inxerir plantíes",
        "export-pagelinks": "Incluyir páxines enllazaes fasta una profundidá de:",
+       "export-manual": "Amestar páxines a mano:",
        "allmessages": "Tolos mensaxes del sistema",
        "allmessagesname": "Nome",
        "allmessagesdefault": "Testu predetermináu",
        "expand_templates_preview": "Vista previa",
        "expand_templates_preview_fail_html": "<em>Como {{SITENAME}} tien activáu el códigu HTML puru y hebo una perda de datos de la sesión, la vista previa ta tapecida como precaución escontra ataques de JavaScript.</em>\n\n<strong>Si esti ye un intentu llexítimu d'accesu a la vista previa, vuelvi a intentalo.</strong>\nSi inda nun funciona, intenta [[Special:UserLogout|salir]] y volver a entrar na to cuenta.",
        "expand_templates_preview_fail_html_anon": "<em>Como {{SITENAME}} tien activáu el códigu HTML puru y nun aniciasti sesión, la vista previa ta tapecida como precaución escontra ataques de JavaScript.</em>\n\n<strong>Si esti ye un intentu llexítimu d'accesu a la vista previa, intenta [[Special:UserLogin|entrar]] y vuelvi a intentalo.</strong>",
+       "expand_templates_input_missing": "Fai falta dar daqué de testu d'entrada.",
        "pagelanguage": "Selector de llingua de la páxina",
        "pagelang-name": "Páxina",
        "pagelang-language": "Llingua",
        "pagelang-use-default": "Usar la llingua predeterminada",
        "pagelang-select-lang": "Escoyer llingua",
+       "pagelang-submit": "Unviar",
        "right-pagelang": "Cambiar la llingua de la páxina",
        "action-pagelang": "cambiar la llingua de la páxina",
        "log-name-pagelang": "Rexistru de cambios de llingua",
        "mediastatistics": "Estadístiques de multimedia",
        "mediastatistics-summary": "Estadístiques sobro los tipos de ficheros xubíos. Esto sólo incluye la versión más nueva d'un ficheru. Escluyense les versiones antigües o desaniciaes de los ficheros.",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%)",
+       "mediastatistics-bytespertype": "Tamañu total del ficheru pa esta sección: {{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%).",
+       "mediastatistics-allbytes": "Tamañu total del ficheru pa tolos ficheros: {{PLURAL:$1|$1 byte|$1 bytes}} ($2).",
        "mediastatistics-table-mimetype": "Tipu MIME",
        "mediastatistics-table-extensions": "Estensiones posibles",
        "mediastatistics-table-count": "Númberu de ficheros",
        "mediastatistics-header-text": "Testual",
        "mediastatistics-header-executable": "Executables",
        "mediastatistics-header-archive": "Formatos comprimíos",
+       "mediastatistics-header-total": "Tolos ficheros",
        "json-warn-trailing-comma": "$1 {{PLURAL:$1|coma al final desanicióse|comes al final desaniciáronse}} de JSON",
        "json-error-unknown": "Hebo un problema col JSON. Error: $1",
        "json-error-depth": "Pasóse de la fondura máxima de la pila",
index f408386..a212a0e 100644 (file)
        "databasenotlocked": "وئریلن‌لر بازاسی باغلانماییب.",
        "lockedbyandtime": "({{Gender: $1 | $1}} طرفین‌دن  $2 $3 اعتبار ایله)",
        "move-page": "$1 داشینیر",
-       "move-page-legend": "صحیفه‌نین آدینی دییش",
+       "move-page-legend": "صفحه‌نین آدینی دَییش",
        "movepagetext": "آشاغی‌داکی فورمدان ایستیفاده ائتمک، صحیفه‌نین آدینی، بوتون تاریخچه‌سینی ده کؤچورمک‌له، یئنی باشلیغا دییشه‌جک.\nاسکی باشلیق یئنی باشلیغا یول‌لاندیریلاجاق‌دیر.\nاسکی صحیفه‌یه اولان یول‌لاندیرماقلاری، اوتوماتیک گونجل‌له‌یه بیلرسینیز.\nبو سئچیمی ائتمه‌دیگینیز حالدا، [[Special:DoubleRedirects|تکرارلانان]] و یا [[Special:BrokenRedirects|قیریق یول‌لاندیرمالاری]] یوخلاماغی یاددان چیخارمایین.\nباغلانتیلاری اویغون یئره یول‌لاندیرماسیندان آرخایین اولماق، سیزین مسئولیتینیزده‌دیر.\n\nنظره آلین کی، هدف باشلیق آلتیندا بیر صحیفه مؤوجود اولسا، یئردییشمه '''باش توتمایاجاق'''، مگر بوکی او سونراکی صحیفه یول‌لاندیرما اولا و اؤنجه دَییشمه گئچمیشی ده اولمایا. بو او دئمک‌دیر کی، سهواً آدینی دییشدیگینیز صحیفه‌لری گئری قایتارا بیلمک اولار، بونونلا یاناشی آرتیق مؤوجود اولان صحیفه‌نین اوزرینه باشقا صحیفه یازا بیلمزسینیز.\n\n'''خبردارلیق!'''\nبو یئردییشمه مشهور صحیفه اوچون اساس‌لی و گؤزلنیلمز اولا بیلر؛ اونا گؤره ده بو دییشیک‌لیگی یئرینه یئتیرمزدن اول، بونون مومکون نتیجه‌لرینی باشا دوشدوگونوزدن آرخایین اولون.",
-       "movepagetext-noredirectfixer": "آشاغی‌داکی فورمو دول‌دورماق بیر صحیفنی یئنی‌دن آدلاندیریر، بوتون کئچمیشینی یئنی آدا داشیییر.\nکؤهنه مؤوزو یئنی باشلیغا بیر ایستیقامتلندیرمه صحیفه‌سی اولار.\n[[Special:DoubleRedirects|جوت]] یا دا [[Special:BrokenRedirects|نوزوک ایستیقامتلندیرمه‌لر]] صحیفه‌لرینی ایداره ائدین.\nعلاقه‌لرین گئتمه‌لری لازیم اولان یئرلره گئتدیک‌لرینی عمین اولماق سیزین سوروملولوغونوزدا‌دیر.\n\nیئنی باش‌لیقدا مؤوجود بیر صحیفه وارسا، بوش یا دا بیر ایستیقامتلندیرمه اولمادیقجا و دییشیک‌لیک کئچمیشی اولمادیغی تق‌دیرده، سهیف 'تاشینمایاجاکتیر.\nبو بو معنانی وئرر، بیر صحیفنی اشتباه ائتسه‌نیز صحیفنی کؤهنه آدییلا یئنی‌دن آدلان‌دیرا بیلریک، بو مؤوجود صحیفه‌نین اوزرینه یازماز.\n\n' 'خبردارلیق!'\nبو مشهور بیر صحیفه اوچون تأثیرلی و گؤزلنیلمز بیر دییشیک‌لیک اولا بیلر؛\nخاهیش ائدیریک راتیفیکاسیا ائتمه‌دن اول بونون نتیجه‌لرینی آنلادیغینیزدان امین اولون.",
+       "movepagetext-noredirectfixer": "آشاغی‌داکی فوْرمو دوْلدورماق بیر صفحه‌نی یئنی‌دن آدلاندیریر، بۆتون گئچمیشینی یئنی آدا داشیییر.\nکؤهنه موضوعو یئنی باشلیغا بیر یوْللاندیرما صفحه‌سی اوْلار.\n[[Special:DoubleRedirects|ایکی‌قات]] یوْخسا [[Special:BrokenRedirects|خطالی یوْللاندیرمالار]] صفحه‌لرینی یوْخلایین.\nباغلانتی‌لارین گئتمه‌لری گرک اولان یئرلره گئتدیک‌لرینی آرخایین اوْلماق سیزین مسعولیتینیزدیر.\n\nیئنی باشلیقدا مؤوجود بیر صفحه وارسا، بوْش یوْخسا بیر یوْللاندیرما اوْلمادیقجا و دییشیک‌لیک گئچمیشی اوْلمازسا، صفحه داشینمایاجاق.\nبۇ یانی، بیر صفحه‌نی اشتباه ائتسه‌نیز صفحه‌نی کؤهنه آدییلا یئنی‌دن آدلاندیرا بیلرسینیز، آنجاق بیر صفحه‌نی قاباقجادان مووجود اوْلان صفحه‌یه یوْللاندیرا بیلمزسینیز.\n\n' 'تذکور!'\nچوْخ گؤروشلو صفحه‌لرین یوْللاندیرماسی گؤزله‌نیلمز اثرلری اوْلا بیلر. لوطفا یوْللاندیرمادان قاباق، ایشینیزین نتیجه‌سیندن آرخایین اوْلون.",
        "movepagetalktext": "اویغون دانیشیق صحیفه‌سی آوتوماتیک حرکت ائده‌جک 'گر:'\n* بوش اولمایان دانیشیق صحیفه‌سی یئنی آدلا آرتیق مؤوجوددورسا، و یا\n* سیز بایراغی آشاغی‌دان گؤتورسه‌نیز.\n\nهمین حال‌لاردا ، احتیاج یارانارسا سیز صحیفه‌لری الله بیرلش‌دیرمک مجبوریتینده قالاجاقسینیز",
        "moveuserpage-warning": "' 'خبردارلیق:' بیر ایستیفاده‌چی صحیفه‌سینی داشیماق اوزرسینیز. خاهیش ائدیریک یالنیز صحیفه‌نین تاشیناجاغینا، آنجاق ایستیفاده‌چی‌نین یئنی‌دن آدلاندیریلمایاجاغینا دقت ائدین.",
        "movenologintext": "صحیفه‌نین آدینی دییشیک‌لیک اوچون قئیدیات‌لی و [[Special:UserLogin|سیستئمه]] داخیل اولمانیز لازیم‌دیر.",
        "cant-move-user-page": "ایستیفاده‌چی صحیفه‌لری‌نین آدینی دییشه بیلمزسینیز (باش‌لیق‌لاردان باشقا).",
        "cant-move-to-user-page": "بیر صحیفنی، بیر ایستیفاده‌چی صحیفه‌سینه داشیماغا ایجازه وئریلمیر (بیر ایستیفاده‌چی آلتسایفاسی خاریجینده).",
        "newtitle": "یئنی باش‌لیق",
-       "move-watch": "بو صحیفنی ایزله",
-       "movepagebtn": "صحیفه‌نین آدینی دییش",
+       "move-watch": "بۇ صفحه‌نی ایزله",
+       "movepagebtn": "صفحه‌نین آدینی دَییش",
        "pagemovedsub": "یئردییشمه ائدیلمیش‌دیر",
        "movepage-moved": "'\"$1\" صحیفه‌سی \"$2\" صحیفه‌سینه یئرلشدیریلمیشدیر",
        "movepage-moved-redirect": "یؤنلندیرمه یارادیلدی.",
        "movepage-moved-noredirect": "یؤنلندیرمه‌نین یارادیلماسینین قارشییس آلیندی.",
        "articleexists": "بو آددا صحیفه آرتیق مؤوجوددور و یا سیزین سئچدیگینیز آد اویغون دئییل.\nزحمت اولماسا باشقا آد سئچین.",
        "cantmove-titleprotected": "بیر صحیفنی بو مؤوقئیه داشییا بیلمز، چونکی یئنی موضونون یارادیلماسی قورونور",
-       "movetalk": "بو صحیفه‌نین دانیشیق صحیفه‌سی‌نین ده آدینی دییش‌دیر.",
+       "movetalk": "بۇ صفحه‌نین دانیشیق صفحه‌سی‌نین ده آدینی دَییشدیر.",
        "move-subpages": "یاریم صحیفه‌لری کؤچور ($1-ا قدر)",
        "move-talk-subpages": "دانیشیق صحیفه‌لری‌نین آلت صحیفه‌لرینی کؤچور ($1-ا قدر)",
        "movepage-page-exists": "$1 مادده‌سی اونسوز دا وار اولماقدا‌دیر، و آوتوماتیک اولا‌راق یئنی‌دن یازیلا بیلمز.",
index c245745..edf10c8 100644 (file)
        "export-download": "Захаваць як файл",
        "export-templates": "Разам з шаблёнамі",
        "export-pagelinks": "Уключыць зьвязаныя старонкі да глыбіні:",
+       "export-manual": "Дадаць старонкі ўручную:",
        "allmessages": "Сыстэмныя паведамленьні",
        "allmessagesname": "Назва",
        "allmessagesdefault": "Тэкст па змоўчаньні",
        "exif-compression-4": "CCITT Група 4 факсымільнае кадаваньне",
        "exif-copyrighted-true": "Ахоўваецца аўтарскім правам",
        "exif-copyrighted-false": "Статус аўтарскіх правоў ня вызначаны",
+       "exif-photometricinterpretation-1": "Чорны і белы (чорны — 0)",
        "exif-unknowndate": "Невядомая дата",
        "exif-orientation-1": "Звычайная",
        "exif-orientation-2": "Адлюстраваная па гарызанталі",
        "tags-deactivate": "адключыць",
        "tags-hitcount": "$1 {{PLURAL:$1|зьмена|зьмены|зьменаў}}",
        "tags-manage-no-permission": "Вы ня маеце правоў на зьмену метак.",
+       "tags-manage-blocked": "Вы ня можаце мяняць меткі, калі заблякаваныя.",
        "tags-create-heading": "Стварэньне новай меткі",
        "tags-create-explanation": "Па змоўчаньні, наваствораныя меткі будуць даступныя для выкарыстаньня ўдзельнікамі і робатамі.",
        "tags-create-tag-name": "Назва меткі:",
        "tags-deactivate-not-allowed": "Немагчыма дэактываваць метку «$1».",
        "tags-deactivate-submit": "Адключыць",
        "tags-apply-no-permission": "Вы ня маеце права прымяняць меткі да вашых рэдагаваньняў.",
+       "tags-apply-blocked": "Вы ня можаце мяняць меткі да вашых зьменаў, калі заблякаваныя.",
        "tags-apply-not-allowed-one": "Метка «$1» ня можа быць прызначаная ўручную.",
        "tags-apply-not-allowed-multi": "{{PLURAL:$2|Наступную метку|Наступныя меткі}} нельга дадаваць уручную: $1",
        "tags-update-no-permission": "Вы ня маеце права на дадаваньне ці выдаленьне метак зьменаў для асобных вэрсіяў ці запісаў журналаў.",
index fa96f36..50b3ffb 100644 (file)
        "sig_tip": "Вашият подпис заедно с времева отметка",
        "hr_tip": "Хоризонтална линия (използвайте пестеливо)",
        "summary": "Резюме:",
-       "subject": "Тема/заглавие:",
+       "subject": "Ð\97аглавие:",
        "minoredit": "Това е малка промяна",
        "watchthis": "Наблюдаване на страницата",
        "savearticle": "Съхраняване",
        "anonpreviewwarning": "Внимание: Не сте влезли в системата. Ако съхраните редакцията си, тя ще бъде записана в историята на страницата с вашият IP-адрес.",
        "missingsummary": "'''Напомняне:''' Не е въведено кратко описание на промените. При повторно натискане на бутона „Съхраняване“, редакцията ще бъде съхранена без резюме.",
        "missingcommenttext": "По-долу въведете вашето съобщение.",
-       "missingcommentheader": "'''Напомняне:''' Не е въведено заглавие на коментара.\nПри повторно натискане на \"{{int:savearticle}}\", редакцията ще бъде записана без такова.",
+       "missingcommentheader": "<strong>Напомняне:</strong> Не е въведено заглавие на коментара.\nПри повторно натискане на \"{{int:savearticle}}\", редакцията ще бъде записана без такова.",
        "summary-preview": "Предварителен преглед на резюмето:",
        "subject-preview": "Предварителен преглед на заглавието:",
+       "previewerrortext": "Възникна грешка при опита за преглед на промените.",
        "blockedtitle": "Потребителят е блокиран",
        "blockedtext": "'''Вашето потребителско име (или IP-адрес) беше блокирано.'''\n\nБлокирането е извършено от $1. Посочената причина е: ''$2''\n\n*Начало на блокирането: $8\n*Край на блокирането: $6\n*Блокирането се отнася за: $7\n\nМожете да се свържете с $1 или с някой от останалите [[{{MediaWiki:Grouppage-sysop}}|администратори]], за да обсъдите блокирането.\n\nМожете да използвате услугата „Пращане писмо на потребител“ само ако не ви е забранена употребата й и ако сте посочили валидна електронна поща в [[Special:Preferences|настройките]] си.\n\nВашият IP адрес е $3, а номерът на блокирането е $5. Включвайте едно от двете или и двете във всяко запитване, което правите.",
        "autoblockedtext": "IP-адресът ви беше блокиран автоматично, защото е бил използван от друг потребител, който е бил блокиран от $1.\nПосочената причина е:\n\n:''$2''\n\n* Начало на блокирането: $8\n* Край на блокирането: $6\n* Блокирането се отнася за: $7\n\nМожете да се свържете с $1 или с някой от останалите [[{{MediaWiki:Grouppage-sysop}}|администратори]], за да обсъдите блокирането.\n\nМожете да използвате услугата „Пращане писмо на потребител“ само ако не ви е забранена употребата й и ако сте посочили валидна електронна поща в [[Special:Preferences|настройките]] си.\n\nТекущият ви IP-адрес е $3, а номерът на блокирането ви е $5. Включвайте ги във всяко питане, което правите.",
        "copyrightwarning": "Обърнете внимание, че всички приноси към {{SITENAME}} се публикуват при условията на $2 (за подробности вижте $1).\nАко не сте съгласни вашата писмена работа да бъде променяна и разпространявана без ограничения, не я публикувайте.<br />\n\nСъщо потвърждавате, че '''вие''' сте написали материала или сте използвали '''свободни ресурси''' — <em>обществено достояние</em> или друг свободен източник.\nАко сте ползвали чужди материали, за които имате разрешение, непременно посочете източника.\n\n<div style=\"font-variant:small-caps\">'''Не публикувайте произведения с авторски права без разрешение!'''</div>",
        "copyrightwarning2": "Обърнете внимание, че всички приноси към {{SITENAME}} могат да бъдат редактирани, променяни или премахвани от останалите сътрудници.\nАко не сте съгласни вашата писмена работа да бъде променяна без ограничения, не я публикувайте.<br />\nСъщо потвърждавате, че '''вие''' сте написали материала или сте използвали '''свободни ресурси''' — <em>обществено достояние</em> или друг свободен източник (за подробности вижте $1).\nАко сте ползвали чужди материали, за които имате разрешение, непременно посочете източника.\n\n<div style=\"font-variant:small-caps\">'''Не публикувайте произведения с авторски права без разрешение!'''</div>",
        "longpageerror": "'''ГРЕШКА: Изпратеният текст е с големина {{PLURAL:$1|един килобайт|$1 килобайта}}, което надвишава позволения максимум от {{PLURAL:$2|един килобайт|$2 килобайта}}.'''\nПоради тази причина той не може да бъде съхранен.",
-       "readonlywarning": "'''ВНИМАНИЕ: Базата от данни беше затворена за поддръжка, затова в момента промените няма да могат да бъдат съхранени.'''\n\nАко желаете, можете да съхраните страницата като текстов файл и да се опитате да я публикувате по-късно.\n\nАдминистраторът, който е затворил базата от данни, е посочил следната причина: $1",
+       "readonlywarning": "<strong>ВНИМАНИЕ: Базата от данни беше затворена за поддръжка, затова в момента промените няма да могат да бъдат съхранени.</strong>\n\nАко желаете, можете да съхраните страницата като текстов файл и да се опитате да я публикувате по-късно.\n\nСистемният администратор, който е затворил базата от данни, е посочил следната причина: $1",
        "protectedpagewarning": "'''Внимание: Страницата е защитена и само потребители със статут на администратори могат да я редактират.'''\nЗа справка по-долу е показан последният запис от дневниците.",
        "semiprotectedpagewarning": "'''Забележка:''' Тази страница е защитена и само регистрирани потребители могат да я редактират.\nЗа справка по-долу е показан последният запис от дневниците.",
-       "cascadeprotectedwarning": "'''Внимание:''' Страницата е защитена, като само потребители с администраторски права могат да я редактират. Тя е включена в {{PLURAL:$1|следната страница|следните страници}} с каскадна защита:",
+       "cascadeprotectedwarning": "<strong>Внимание:</strong> Страницата е защитена, като само потребители с администраторски права могат да я редактират. Тя е включена в {{PLURAL:$1|следната страница|следните страници}} с каскадна защита:",
        "titleprotectedwarning": "'''Внимание: Тази страница е защитена и са необходими [[Special:ListGroupRights|специални права]], за да бъде създадена.'''\nЗа справка по-долу е показан последният запис от дневниците.",
        "templatesused": "{{PLURAL:$1|Шаблон, използван|Шаблони, използвани}} на страницата:",
        "templatesusedpreview": "{{PLURAL:$1|Шаблон, използван|Шаблони, използвани}} в предварителния преглед:",
        "columns": "Колони:",
        "searchresultshead": "Търсене",
        "stub-threshold": "Праг за форматиране на <a href=\"#\" class=\"stub\">препратки към мъничета</a>:",
+       "stub-threshold-sample-link": "пример",
        "stub-threshold-disabled": "Изключено",
        "recentchangesdays": "Брой дни в последни промени:",
        "recentchangesdays-max": "(най-много $1 {{PLURAL:$1|ден|дни}})",
        "upload-misc-error-text": "Неизвестна грешка при качване. Убедете се, че адресът е верен и опитайте отново. Ако отново имате проблем, обърнете се към [[Special:ListUsers/sysop|администратор]].",
        "upload-too-many-redirects": "Адресът съдържа твърде много пренасочвания",
        "upload-http-error": "Възникна HTTP грешка: $1",
+       "upload-dialog-button-cancel": "Отказване",
+       "upload-dialog-button-done": "Готово",
+       "upload-dialog-button-save": "Съхраняване",
+       "upload-dialog-button-upload": "Качване",
+       "upload-form-label-select-file": "Избиране на файл",
+       "upload-form-label-infoform-title": "Подробности",
+       "upload-form-label-infoform-name": "Име",
+       "upload-form-label-infoform-description": "Описание",
+       "upload-form-label-usage-title": "Използване",
+       "upload-form-label-usage-filename": "Име на файл",
+       "foreign-structured-upload-form-label-infoform-categories": "Категории",
+       "foreign-structured-upload-form-label-infoform-date": "Дата",
        "foreign-structured-upload-form-3-label-yes": "Да",
        "foreign-structured-upload-form-3-label-no": "Не",
        "backend-fail-notexists": "Файлът $1 не съществува.",
        "wlshowlast": "Показване на последните $1 часа $2 дни",
        "watchlistall2": "всички",
        "watchlist-hide": "Скриване",
+       "watchlist-submit": "Показване",
        "wlshowtime": "Период от време за показване:",
+       "wlshowhideminor": "малки промени",
+       "wlshowhidebots": "ботове",
+       "wlshowhideliu": "регистрирани потребители",
+       "wlshowhideanons": "анонимни потребители",
+       "wlshowhidepatr": "проверени редакции",
+       "wlshowhidemine": "моите редакции",
        "watchlist-options": "Опции на списъка за наблюдение",
        "watching": "Наблюдение…",
        "unwatching": "Спиране на наблюдение…",
        "deletepage": "Изтриване",
        "confirm": "Потвърждаване",
        "excontent": "съдържанието беше: „$1“",
-       "excontentauthor": "съдържанието беше: „$1“ (като единственият автор беше [[Special:Contributions/$2|$2]])",
+       "excontentauthor": "съдържанието беше: „$1“, като единственият автор беше \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|беседа]])",
        "exbeforeblank": "премахнато преди това съдържание: „$1“",
        "delete-confirm": "Изтриване на „$1“",
        "delete-legend": "Изтриване",
-       "historywarning": "'''Внимание:''' Страницата, която възнамерявате да изтриете, има история с приблизително $1 {{PLURAL:$1|редакция|редакции}}:",
+       "historywarning": "\"Внимание:</strong>  Страницата, която възнамерявате да изтриете, има история с приблизително $1 {{PLURAL:$1|редакция|редакции}}:",
        "historyaction-submit": "Показване",
        "confirmdeletetext": "На път сте безвъзвратно да изтриете страница или файл, заедно с цялата прилежаща редакционна история, от базата от данни.\nПотвърдете, че искате това, разбирате последствията и правите това в съответствие с [[{{MediaWiki:Policy-url}}|линията на поведение]].",
        "actioncomplete": "Действието беше изпълнено",
        "rollback-success": "Отменени редакции на $1; възвръщане към последната версия на $2.",
        "sessionfailure-title": "Прекъсната сесия",
        "sessionfailure": "Изглежда има проблем със сесията ви; действието беше отказано като предпазна мярка срещу крадене на сесията. Натиснете бутона за връщане на браузъра, презаредете страницата, от която сте дошли, и опитайте отново.",
+       "changecontentmodel-title-label": "Заглавие на страницата",
+       "changecontentmodel-reason-label": "Причина:",
        "protectlogpage": "Дневник на защитата",
        "protectlogtext": "Списък на промените в защитата за страницата.\nМожете да прегледате и [[Special:ProtectedPages|списъка на текущо защитените страници]].",
        "protectedarticle": "защити „[[$1]]“",
        "movenotallowedfile": "Нямате права да премествате файлове.",
        "cant-move-user-page": "Нямате нужните права на достъп, за да местите потребителски страници (можете да местите само подстраници).",
        "cant-move-to-user-page": "Нямате нужните права на достъп, за да извършвате преместване на страници върху потребителски страници (можете да местите само върху подстраници от потребителското пространство).",
-       "newtitle": "Ð\9aÑ\8aм Ð½ово заглавие:",
+       "newtitle": "Ð\9dово заглавие:",
        "move-watch": "Наблюдаване на страницата",
        "movepagebtn": "Преместване",
        "pagemovedsub": "Преместването беше успешно",
        "version-libraries": "Инсталирани библиотеки",
        "version-libraries-library": "Библиотека",
        "version-libraries-version": "Версия",
+       "version-libraries-description": "Описание",
+       "version-libraries-authors": "Автори",
        "redirect-submit": "Отваряне",
        "redirect-value": "Стойност:",
        "redirect-user": "Потребителски номер",
        "special-characters-title-minus": "знак минус",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
+       "mw-widgets-titleinput-description-new-page": "страницата все още не съществува",
+       "mw-widgets-titleinput-description-redirect": "пренасочване към $1",
        "api-error-blacklisted": "Моля, изберете различно, описателно заглавие."
 }
index 83bf032..959ef9f 100644 (file)
@@ -5,7 +5,8 @@
                        "Ibrahim khashrowdi",
                        "Rachitrali",
                        "Mjbmr",
-                       "Macofe"
+                       "Macofe",
+                       "Hosseinblue"
                ]
        },
        "tog-underline": "لینکانی جهلگا خط کشیتین",
        "allmessages-prefix": "فیلتر کورتین بئ اساس پدوند:",
        "allmessages-language": "زبان:",
        "allmessages-filter-submit": "برا",
-       "allmessages-filter-translate": "ترجمه",
+       "allmessages-filter-translate": "چاوواشەکِردن زوون",
        "thumbnail-more": "ٹُوه کورتین",
        "filemissing": "فایل وجود نداریت",
        "thumbnail_error": "خطا بئ ناحُنی ئی جۆڑ کورتین ئی وختا: $1",
index b4dabe0..2833ee9 100644 (file)
        "viewpagelogs": "Pogledaj zapisnike ove stranice",
        "nohistory": "Ne postoji historija izmjena za ovu stranicu.",
        "currentrev": "Trenutna verzija",
-       "currentrev-asof": "Trenutna verzija na dan $1",
+       "currentrev-asof": "Trenutna verzija na dan $2 u $3",
        "revisionasof": "Verzija od $1",
        "revision-info": "Izmjena od $1 od {{GENDER:$6|$2}}$7",
        "previousrevision": "← Starija izmjena",
index 05e6589..2ead049 100644 (file)
        "unblock": "Benutzer freigeben",
        "blockip": "IP-Adresse/{{GENDER:$1|Benutzer|Benutzerin}} sperren",
        "blockip-legend": "IP-Adresse/Benutzer sperren",
-       "blockiptext": "Mit diesem Formular sperrst du eine IP-Adresse oder einen Benutzernamen, so dass von dort keine Änderungen mehr vorgenommen werden können.\nDies sollte nur erfolgen, um Vandalismus zu verhindern und in Übereinstimmung mit den [[{{MediaWiki:Policy-url}}|Richtlinien]].\nBitte gib den Grund für die Sperre an.",
+       "blockiptext": "Mit diesem Formular sperrst du eine IP-Adresse oder einen Benutzernamen, so dass von dort keine Änderungen mehr vorgenommen werden können.\nDies sollte nur erfolgen, um Vandalismus zu verhindern und in Übereinstimmung mit den [[{{MediaWiki:Policy-url}}|Richtlinien]].\nBitte gib den Grund für die Sperre an.\nDu kannst IP-Bereiche mit der [https://de.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]-Syntax sperren; der größte erlaubte Bereich ist /$1 für IPv4 und /$2 für IPv6.",
        "ipaddressorusername": "IP-Adresse oder Benutzername:",
        "ipbexpiry": "Sperrdauer:",
        "ipbreason": "Grund:",
index c4c0e96..f2370d6 100644 (file)
        "intentionallyblankpage": "Ena pel bi zanayişî weng mendo.",
        "external_image_whitelist": "  #no satır zey xo verde/raverde<pre>\n#parçeyê ifadeya rêzbiyayeyani (têna zerreyê ıney de // ) u çıtayo/çiyo zi mende cêr de têare kerê.\n#ney URL ya (hotlink) resmê teberi de hemcıta benî.\n#Ê yê ke hemcıt (eşleşmek-hemçift) biyê zey resımi asenî, eqsê hal de zi zey gıreyê resmi aseno.\nsatır ê ke pê ney # # destpêkenê zey mışore/mıjore muamele vineno.\n#herfa gırd û qıci ferq nêkeno\n\n#parçeyê ifadeya rêzbiyayeyani bıerzê serê ney satıri. no satır zey xo verde/raverde </pre>",
        "tags": "Etiketê vurnayîşê raştî",
-       "tag-filter": "Avrêcê [[Special:Tags|Etiketi]]:",
+       "tag-filter": "Parzûnê [[Special:Tags|etiketi]]:",
        "tag-filter-submit": "Avrêc",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiket|Etiketi}}]]: $2)",
        "tags-title": "Etiketan",
index 3a4857b..b436f90 100644 (file)
        "unblock-summary": "",
        "blockip": "Block {{GENDER:$1|user}}",
        "blockip-legend": "Block user",
-       "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).",
+       "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).\nYou can block IP ranges using the [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax; the largest allowed range is /$1 for IPv4 and /$2 for IPv6.",
        "ipaddressorusername": "IP address or username:",
        "ipbexpiry": "Expiry:",
        "ipbreason": "Reason:",
        "markaspatrolleddiff": "Mark as patrolled",
        "markaspatrolledlink": "[$1]",
        "markaspatrolledtext": "Mark this page as patrolled",
+       "markaspatrolledtext-file": "Mark this file version as patrolled",
        "markedaspatrolled": "Marked as patrolled",
        "markedaspatrolledtext": "The selected revision of [[:$1]] has been marked as patrolled.",
        "rcpatroldisabled": "Recent changes patrol disabled",
        "newimages-legend": "Filter",
        "newimages-label": "Filename (or a part of it):",
        "newimages-showbots": "Show uploads by bots",
+       "newimages-hidepatrolled": "Hide patrolled uploads",
        "noimages": "Nothing to see.",
        "ilsubmit": "Search",
        "bydate": "by date",
index 8890867..be375b0 100644 (file)
        "undelete_short": "احیای {{PLURAL:$1|یک ویرایش|$1 ویرایش}}",
        "viewdeleted_short": "نمایش {{PLURAL:$1|یک ویرایش حذف‌شده|$1 ویرایش حذف‌شده}}",
        "protect": "محافظت",
-       "protect_change": "تغییر",
+       "protect_change": "گؤەڕانن/تغییر",
        "protectthispage": "محافظت از این صفحه",
-       "unprotect": "تغییر محافظت",
+       "unprotect": "گؤەڕانن/تغییر محافظت",
        "unprotectthispage": "تغییر محافظت این صفحه",
        "newpage": "صفحهٔ تازه",
        "talkpage": "بحث دربارهٔ این صفحه",
        "php-mail-error-unknown": "خطای ناشناخته در تابع  mail()‎ پی‌اچ‌پی",
        "user-mail-no-addy": "تلاش برای ارسال ایمیل بدون آدرس ایمیل.",
        "user-mail-no-body": "سعی کردید نامه‌ای الکترونیکی با محتوایی بسیار کوتاه و یا خالی بفرستید.",
-       "changepassword": "تغییر گذرواژه",
+       "changepassword": "گؤەڕانن/تغییر رمز",
        "resetpass_announce": "شما باید برای پایان ورود به سامانه، گذرواژهٔ جدیدی را تنظیم کنید.",
        "resetpass_text": "<!-- اینجا متن اضافه کنید -->",
        "resetpass_header": "تغییر گذرواژهٔ حساب کاربری",
        "revdelete-hide-comment": "خلاصهٔ ویرایش",
        "revdelete-hide-user": "نام کاربری/نشانی آی‌پی",
        "revdelete-hide-restricted": "فرونشانی اطلاعات برای مدیران به همراه دیگران",
-       "revdelete-radio-same": "(بدون تغییر)",
+       "revdelete-radio-same": "(بدون گؤەڕانن/تغییر)",
        "revdelete-radio-set": "پنهان",
        "revdelete-radio-unset": "نمایان",
        "revdelete-suppress": "از دسترسی مدیران به داده نیز مانند سایر کاربران جلوگیری به عمل آید.",
        "revdelete-failure": "'''پیدایی نسخه‌ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد با موفقیت انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
-       "revdel-restore": "تغییر پیدایی",
+       "revdel-restore": "گؤەڕانن/تغییر پیدایی",
        "pagehist": "تاریخچهٔ صفحه",
        "deletedhist": "تاریخچهٔ حذف‌شده",
        "revdelete-hide-current": "خطا در پنهان کردن مورد مورخ $2 ساعت $1: این نسخه، نسخهٔ اخیر است و قابل پنهان کردن نیست.",
        "allmessagesnotsupportedDB": "این صفحه نمی‌تواند استفاده شود به این دلیل که <bdi>'''$wgUseDatabaseMessages'''</bdi> غیرفعال شده‌است.",
        "allmessages-filter-legend": "پالایه",
        "allmessages-filter": "پالودن بر اساس وضعیت شخصی‌سازی:",
-       "allmessages-filter-unmodified": "تغییر نیافته",
+       "allmessages-filter-unmodified": "نەگؤەڕیائە/تغییرنیافتە",
        "allmessages-filter-all": "همه",
        "allmessages-filter-modified": "تغییر یافته",
        "allmessages-prefix": "پالودن بر اساس پسوند:",
index e490b21..e07228d 100644 (file)
        "unblock": "שחרור משתמש",
        "blockip": "חסימת {{GENDER:$1|משתמש|משתמשת}}",
        "blockip-legend": "חסימת משתמש",
-       "blockiptext": "השתמשו בטופס שלהלן כדי לחסום את הרשאות הכתיבה מכתובת IP או משתמש מסוימים.\nחסימות כאלה צריכות להתבצע רק כדי למנוע השחתה, ובהתאם ל[[{{MediaWiki:Policy-url}}|נהלים]].\nאנא מלאו את הסיבה הפרטנית לחסימה להלן (לדוגמה, באמצעות ציון דפים מסוימים שהשחית המשתמש).",
+       "blockiptext": "השתמשו בטופס שלהלן כדי לחסום את הרשאות הכתיבה מכתובת IP או משתמש מסוימים.\nחסימות כאלה צריכות להתבצע רק כדי למנוע השחתה, ובהתאם ל[[{{MediaWiki:Policy-url}}|נהלים]].\nאנא מלאו את הסיבה הפרטנית לחסימה להלן (לדוגמה, באמצעות ציון דפים מסוימים שהשחית המשתמש).\nבאפשרותך לחסום טווחי IP באמצעות תחביר [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]; הטווח הגדול ביותר שמורשה הוא <span dir=\"ltr\">/$1</span> עבור IPv4 ו־<span dir=\"ltr\">/$2</span> עבור IPv6.",
        "ipaddressorusername": "כתובת IP או שם משתמש:",
        "ipbexpiry": "פקיעה:",
        "ipbreason": "סיבה:",
index 008e61f..19062c2 100644 (file)
        "wlshowlast": "Prikaži posljednjih $1 sati $2 dana",
        "watchlistall2": "sve",
        "watchlist-hide": "Sakrij",
+       "watchlist-submit": "Prikaži",
        "wlshowtime": "Prikaži posljednjih:",
        "wlshowhideminor": "manje promjene",
        "wlshowhidebots": "botove",
index ff584d5..d71203c 100644 (file)
        "whatlinkshere-hidelinks": "linkek $1",
        "whatlinkshere-hideimages": "fájlhivatkozások $1",
        "whatlinkshere-filters": "Elemek szűrése",
+       "whatlinkshere-submit": "Indítás",
        "autoblockid": "$1. autoblokk",
        "block": "Felhasználó blokkolása",
        "unblock": "Felhasználó blokkolásának feloldása",
index 54d78c3..8ef32b7 100644 (file)
        "laggedslavemode": "<strong>Ballaag:</strong> Ti panid ket mabalin a saan nga aglaon kadagiti kinaudi a panagpabaro.",
        "readonly": "Narikepan ti database",
        "enterlockreason": "Agikabil ti rason para iti pannakarikep, mangiraman ti maysa a karkulo no kaanonto a malukatan",
-       "readonlytext": "Ti database ket agdama a narikpan kadagiti baro a panagikabil ken panagbaliw, mabalin a gapu dagiti kadawyan a pagsimpa, kalpasanna a normalto nga agsubli.\n\nTi administrador a nangrikep ket nangited iti daytoy a palawag: $1",
+       "readonlytext": "Ti database ket agdama a narikpan kadagiti baro a panagikabil ken panagbaliw, mabalin a gapu dagiti kadawyan a pagsimpa, kalpasanna a normalto nga agsubli.\n\nTi administrador ti sistema a nangrikep ket nangited iti daytoy a palawag: $1",
        "missing-article": "Ti database ket saan a nakabiruk ti testo ti panid a mabirukanna koma, a nanaganan ti \"$1\" $2.\n\nDaytoy ket kadawyan a gapuanan babaen ti sumaganad a baak a paggiddiatan wenno silpo ti pakasaritaan ti maysa panid a dati a naikkat.\n\nNo saan a kasta, mabalin a nakasarakka ti parikut ti sopwer.\n\nPangngaasi nga ipadamagmo kadagiti [[Special:ListUsers/sysop|administrador]], isuratmo ti pakaammo dayta nga URL.",
        "missingarticle-rev": "(rebision#: $1)",
        "missingarticle-diff": "(Dip: $1, $2)",
        "mypreferencesprotected": "Awan pammalubosmo nga agurnos kadagiti kakaykayatam.",
        "ns-specialprotected": "Saan a mabalin nga urnosen dagiti espesial a panid.",
        "titleprotected": "Daytoy a titulo ket nasalakniban manipud iti pannakapartuat babaen ni [[User:$1|$1]].\nTi naited a rason ket \"<em>$2</em>\".",
-       "filereadonlyerror": "Di nabaliwan ti papeles ti \"$1\" gapu ta ti repositorio ti papeles ti \"$2\" ket mabasa laeng a moda.\n\nTi administrador a nangserra ket nangited iti daytoy a panagilawlawag: \"$3\".",
+       "filereadonlyerror": "Di nabaliwan ti papeles ti \"$1\" gapu ta ti repositorio ti papeles ti \"$2\" ket mabasa laeng a moda.\n\nTi administrador ti sistema a nangserra ket nangited iti daytoy a panagilawlawag: \"$3\".",
        "invalidtitle-knownnamespace": "Imbalido a titulo iti nagan ti espasio \"$2\" ken teksto \"$3\"",
        "invalidtitle-unknownnamespace": "Imbalido a titulo iti di ammo a nagan ti espasio a bilang $1 ken teksto \"$2\"",
        "exception-nologin": "Saan a nakastrek",
        "passwordreset-emailtext-ip": "Adda (baka sika, ti naggapuan ti IP a pagtaengan $1) a nagkiddaw ti maysa a panangisaad manen ti kontrasenias para iti {{SITNAME}} ($4) . {{PLURAL:$3|Ti |Dagiti}} sumaganad a pakabilangan ti agar-aramat ket\nmainaig iti daytoy nga esurat a pagtaengan:\n\n$2\n\n{{PLURAL:$3|Daytoy temporario a kontrasenias|Dagitoy temporario a kontrasenias}} ket agpaso {{PLURAL:$5|iti maysa nga aldaw|kadagiti $5 nga aldaw}}.\nSumrekka koman tapno agpilika ti baro a kontraseniasmo tattan. No adda met sabali a nagaramid daytoy a \npanagkiddaw, wenno malagipmo ti dati a kontraseniasmo, ket saanmo a kayaten a sukatan, saanmo nga ikaskaso daytoy a mensahe ken \nagtuloyka nga agusar ti daan a kontrasenias.",
        "passwordreset-emailtext-user": "Daytoy nga agar-aramat $1 iti {{SITENAME}} ket nagkiddaw ti maysa a panangisaad manen ti bukod a kontrasenias para iti {{SITENAME}}\n($4) . {{PLURAL:$3|Ti|Dagiti}} sumaganad a pakabilangan ti agar-aramat ket\nmainaig iti daytoy nga esurat a pagtaengan:\n\n$2\n\n{{PLURAL:$3|Daytoy temporario a kontrasenias|Dagitoy temporario a kontrasenias}} ket agpaso {{PLURAL:$5|iti maysa nga aldaw|kadagiti $5 nga aldaw}}.\nSumrekka koman tapno agpilika ti baro a kontraseniasmo tattan. No adda met sabali a nagaramid daytoy a \npanagkiddaw, wenno malagipmo ti dati a kontraseniasmo, ken saanmo a kayaten a sukatan, saanmo nga ikaskaso daytoy a mensahe ken \nagtuloykan nga agusar ti daan a kontraseniasmo.",
        "passwordreset-emailelement": "Nagan ti agar-aramat: \n$1\n\nTemporario a kontrasenias: \n$2",
-       "passwordreset-emailsent": "No daytoy ket nairehistro nga adres ti esurat para iti pakabilangam, maipatulodto ti maysa nga esurat iti panangisaad manen ti kontrasenias.",
+       "passwordreset-emailsentemail": "No daytoy nga adres ti esurat ket mainaig iti pakabilangam, maipatulodto ti maysa nga esurat iti panangisaad manen ti kontrasenias.",
        "passwordreset-emailsent-capture": "Ti maysa nga esurat ti panangisaad manen ti kontrasenias ket naipatuloden, a naipakita dita baba.",
        "passwordreset-emailerror-capture": "Naaramid ti maysa nga esurat a panangisaad manen ti kontrasenias, a napaikita dita baba, ngem ti panangitulod kenni {{GENDER:$2|agar-aramat}} ket napaay: $1",
        "changeemail": "Sukatan wenno ikkaten ti adres ti esurat",
        "copyrightwarning2": "Pangngaasi a laglagipen nga amin a kontribusion iti {{SITENAME}} ket mabalin a maurnos, mabaliwan, wenno ikkaten dagiti sabali a kontributor.\nNo dimo kayat a ti sinuratmo ket maurnos nga awanan-asi ken maiwaras nga awan sungsungbatan kenka, saanmon nga ited ditoy.<br />\nIkarkarim pay kadakami a bukodmo a sinurat daytoy, wenno kinopia manipud iti publiko a dominio wenno ti kapadpadana a nawaya a nagtaudan. (kitaen ti $1 para kadagiti salaysay).\n<strong>Saan a mangited ti nakarbengan ti kopia nga obra no awan iti pammalubos!</strong>",
        "editpage-cannot-use-custom-model": "Saan a mabaliwan ti modelo ti linaon iti daytoy a panid.",
        "longpageerror": "<strong>Biddut: Ti teksto nga intedmo ket {{PLURAL:$1|maysa a kilobyte|$1 kilkilobyte}} ti katiddogna, nga at-atiddog ngem ti kangatuan iti  {{PLURAL:$2|maysa a kilobyte|$2 kilkilobyte}}.</strong>\nSaan a mabalin a maidulin.",
-       "readonlywarning": "<strong>Ballaag: Narikepan ti database tapno mataripato, isu a saanmo a mabalin nga idulin dagita inurnosmo tattan.</strong>\nMabalinmo ti agkopia ken agipegket ti testom iti papeles ti testo ken idulinmo daytoy intono madamdama.\n\nTi administrador a nangrikep ket nangited iti daytoy a palawag: $1",
+       "readonlywarning": "<strong>Ballaag: Narikepan ti database tapno mataripato, isu a saanmo a mabalin nga idulin dagita inurnosmo tattan.</strong>\nMabalinmo ti agkopia ken agipegket ti testom iti papeles ti testo ken idulinmo daytoy intono madamdama.\n\nTi administrador ti sistema a nangrikep ket nangited iti daytoy a palawag: $1",
        "protectedpagewarning": "<strong>Ballaag: Daytoy a panid ket nasalakniban tapno dagiti laeng agar-aramat nga addaan iti gundaway nga administrador ti makaurnos ditoy.</strong>\nTi naudi a naikabil iti listaan ket naited dita baba para iti reperensia:",
        "semiprotectedpagewarning": "<strong>Nota:</strong> Nasalakniban daytoy a panid tapno dagiti laeng nakarehistro nga agar-aramat ti makaurnos ditoy.\nTi naudi a naikabil iti listaan ket naited dita baba para iti reperensia:",
        "cascadeprotectedwarning": "<strong>Ballaag:</strong> Daytoy a panid ket nasalakniban tapno dagiti laeng agar-aramat nga addaan iti gundaway nga administrador ti makaurnos gapu ta nailak-am {{PLURAL:$1|iti sumaganad a panid|kadagiti sumaganad a panid}} a nasalakniban iti sariap:",
        "permissionserrors": "Biddut ti pammalubos",
        "permissionserrorstext": "Awan ti pammalubosmo nga agaramid iti dayta, gapu ti sumaganad {{PLURAL:$1|a rason|a rasrason}}:",
        "permissionserrorstext-withaction": "Awan ti pammalubosmo nga $2, gapu ti sumaganad a {{PLURAL:$1|rason|rasrason}}:",
-       "contentmodelediterror": "Saanmo a maurnos daytoy a rebision gapu ta ti modelo ti linaon ket <code>$1</code>, ken ti agdama a linaon ti panid ket <code>$2</code>.",
+       "contentmodelediterror": "Saanmo a maurnos daytoy a rebision gapu ta ti modelo ti linaon ket <code>$1</code>, a maigiddiat manipud iti agdama a modelo ti linaon ti panid ti <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Ballaag: Agparpartuatka manen ti dati a naikkat a panid.</strong>\n\nUsigem koma no maitutop ti agtuloy nga agurnos iti daytoy a panid.\nTi listaan ti pannakaikkat ken pannakaiyalis para iti daytoy a panid ket naited ditoy para iti pakainugotan:",
        "moveddeleted-notice": "Naikkaten daytoy a panid.\nTi listaan ti pannakaikkat ken pannakaiyalis para iti panid ket naited dita baba para iti reperensia.",
        "moveddeleted-notice-recent": "Pasensian, daytoy a panid ket kaik-ikkat idi (iti kaunegan dagiti 24 nga oras).\nTi listaan ti pannakaikkat ken pannakaiyalis para iti panid ket naited dita baba para iti reperensia.",
        "prefs-help-prefershttps": "Daytoy a kakaykayatan ket mapakabaelanto iti sumaruno nga iseserrekmo.",
        "prefswarning-warning": "Nagaramikka kadagiti panagbalbaliw kadagiti kakaykayatam a saan pay a naidulin.\nNo panawan daytoy a panid a saan nga agpindut iti \"$1\" dagiti kakaykayatam ket saanto a mapabaro.",
        "prefs-tabs-navigation-hint": "Pakaammo: Mabalinmo nga usaren dagiti kanigid ken kanawan a tekla ti pana tapno madaliasat ti baetan dagiti etiketa iti listaan dagiti etiketa.",
-       "email-address-validity-valid": "Ti esurat a pagtaengan ket kasla umisu",
-       "email-address-validity-invalid": "Ikabil ti umisu nga esurat a pagtaengan",
        "userrights": "Panagtaripato kadagiti karbengan ti agar-aramat",
        "userrights-lookup-user": "Agtaripato kadagiti grupo ti agar-aramat",
        "userrights-user-editname": "Mangiserrek iti nagan ti agar-aramat:",
        "recentchanges-label-plusminus": "Ti panagbaliw ti kadakkel ti panid babaen ti bilang dagiti byte",
        "recentchanges-legend-heading": "'''Leyenda:'''",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (kitaen pay ti [[Special:NewPages|listaan ti baro a pampanid]])",
+       "recentchanges-submit": "Ipakita",
        "rcnotefrom": "Dita baba ket {{PLURAL:$5|ti sinukatan|dagiti sinukatan}} manipud idi <strong>$3, $4</strong> (aginggana iti <strong>$1</strong> a naipakita).",
        "rclistfrom": "Ipakita dagiti kabarbaro a sinukatan a mangrugi manipud idi $2, $3",
        "rcshowhideminor": "$1 dagiti bassit a panagurnos",
        "foreign-structured-upload-form-label-own-work": "Daytoy ket bukodko nga obra",
        "foreign-structured-upload-form-label-infoform-categories": "Katkategoria",
        "foreign-structured-upload-form-label-infoform-date": "Petsa",
+       "foreign-structured-upload-form-3-label-yes": "Wen",
+       "foreign-structured-upload-form-3-label-no": "Saan",
        "backend-fail-stream": "Saan a maipan ti papeles $1.",
        "backend-fail-backup": "Saan a makaidulin ti kapada ti papeles ti $1.",
        "backend-fail-notexists": "Awan ti papeles ti $1.",
        "mostrevisions": "Dagiti panid a kaaduan kadagiti rebision",
        "prefixindex": "Amin a pampanid nga addaan iti pasakbay",
        "prefixindex-namespace": "Amin a pampanid nga addaan iti pasaruno (nagan ti espasio ti $1)",
+       "prefixindex-submit": "Ipakita",
        "prefixindex-strip": "Ikkaten ti pasakbay iti listaan",
        "shortpages": "Dagiti ababa a panid",
        "longpages": "Dagiti atiddog a panid",
        "protectedpages-performer": "Nangsalaknib nga agar-aramat",
        "protectedpages-params": "Dagiti parametro ti panagsalaknib",
        "protectedpages-reason": "Rason",
+       "protectedpages-submit": "Ipakita dagiti panid",
        "protectedpages-unknown-timestamp": "Di ammo",
        "protectedpages-unknown-performer": "Di ammo nga agar-aramat",
        "protectedtitles": "Dagiti nasalakniban a titulo",
        "protectedtitles-summary": "Daytoy a panid ket ilistana dagiti titulo nga agdama a nasalakniban manipud ti pannakapartuat. Para iti listaan dagiti adda a panid a nasalakniban, kitaen ti  [[{{#special:ProtectedPages}}|{{int:protectedpages}}]]..",
        "protectedtitlesempty": "Awan dagiti titulo nga agdama a nasalakniban kadagitoy a parametro.",
+       "protectedtitles-submit": "Ipakita dagiti titulo",
        "listusers": "Listaan ti agar-aramat",
        "listusers-editsonly": "Ipakita laeng dagiti agar-aramat nga addaan kadagiti inurnos",
        "listusers-creationsort": "Ilasin babaen ti petsa a pannakapartuat",
        "usereditcount": "$1 {{PLURAL:$1|nga inurnos|kadagiti inurnos}}",
        "usercreated": "{{GENDER:$3|Pinartuat}} idi $1, $2",
        "newpages": "Baro a pampanid",
+       "newpages-submit": "Ipakita",
        "newpages-username": "Nagan ti agar-aramat:",
        "ancientpages": "Dagiti kadaanan a panid",
        "move": "Iyalis",
        "specialloguserlabel": "Nangitungpal:",
        "speciallogtitlelabel": "Puntaan (titulo wenno {{ns:user}}:nagan ti agar-aramat para iti agar-aramat):",
        "log": "Dagiti listaan",
+       "logeventslist-submit": "Ipakita",
        "all-logs-page": "Amin a listaan a publiko",
        "alllogstext": "Naikaykaysa a panagiparang kadagiti amin a magun-od a listaan iti {{SITENAME}}.\nMapabassitmo ti panagkita babaen ti panagpili ti kita ti listaan, ti nagan ti agar-aramat (sensitibo ti kadakkel ti letra), wenno ti naapektaran a panid (sensitibo pay ti kadakkel ti letra).",
        "logempty": "Awan dagiti maipada a banag iti listaan.",
        "cachedspecial-viewing-cached-ts": "Kitkitaem ti maysa a naidulin a bersion iti daytoy a panid, a mabalin daytoy a saan a kompleto nga agpayso.",
        "cachedspecial-refresh-now": "Kitaen ti kinaudian.",
        "categories": "Katkategoria",
+       "categories-submit": "Ipakita",
        "categoriespagetext": "Ti sumaganad a {{PLURAL:$1|kategoria ket aglaon|katkategoria ket aglaon}} kadagiti panid wenno midia.\n[[Special:UnusedCategories|Dagiti saan a nausar a kategoria]] ket saan a maiparang ditoy.\nKitaen met [[Special:WantedCategories|dagiti makiddaw a kategoria]].",
        "categoriesfrom": "Ipakita dagiti kategoria a mangrugi iti:",
        "special-categories-sort-count": "ilasin babaen ti bilang",
        "activeusers-hidebots": "Ilemmeng dagiti bot",
        "activeusers-hidesysops": "Ilemmeng dagiti administrador",
        "activeusers-noresult": "Awan ti nasarakan nga agar-aramat.",
+       "activeusers-submit": "Ipakita dagiti aktibo nga agar-aramat",
        "listgrouprights": "Dagiti karbengan ti grupo ti agar-aramat",
        "listgrouprights-summary": "Dagiti sumaganad a listaan ti grupo ti agar-aramat a naipalawag iti daytoy a wiki, a nairaman dagiti mainaig a karbengan ti panagserrekda.\nAdda pay mabalin nga [[{{MediaWiki:Listgrouprights-helppage}}|adu a pakaammo]] a maipanggep kadagiti kabukbuodan a karbengan.",
        "listgrouprights-key": "Leyenda: \n* <span class=\"listgrouprights-granted\">Naited a karbengan</span> \n* <span class=\"listgrouprights-revoked\">Naukas a karbengan</span>",
        "wlshowlast": "Ipakita dagiti naudi a $1 nga or-oras $2 nga al-aldaw",
        "watchlistall2": "amin",
        "watchlist-hide": "Ilemmeng",
-       "wlshowtime": "Ipakita ti naudi:",
+       "watchlist-submit": "Ipakita",
+       "wlshowtime": "Ipakita a paset ti panawen:",
        "wlshowhideminor": "dagiti bassit a panagurnos",
        "wlshowhidebots": "dagiti bot",
        "wlshowhideliu": "dagiti nakarehistro nga agar-aramat",
        "wlshowhideanons": "dagiti di ammo nga agar-aramat",
        "wlshowhidepatr": "dagiti napatrulian a panagurnos",
        "wlshowhidemine": "dagiti inurnosko",
+       "wlshowhidecategorization": "pannakaikategoria ti panid",
        "watchlist-options": "Dagiti pagpilian ti listaan a bambantayan",
        "watching": "Bambantayan...",
        "unwatching": "Saanen a bantayan...",
        "delete-confirm": "Ikkaten ti \"$1\"",
        "delete-legend": "Ikkaten",
        "historywarning": "<strong>Ballaag:</strong> Ti panid a kayatmo nga ikkaten ket adda pakasaritaanna iti $1 {{PLURAL:$1|a rebision|kadagiti rebision}}:",
+       "historyaction-submit": "Ipakita",
        "confirmdeletetext": "Mangrugrugika a mangikkat iti maysa a panid a kakuyog amin ti pakasaritaanna.\nPangngaasi a pasingkedam a naikeddeng a kayatmo nga aramiden daytoy, a maawatam ti pagbnagan ti panangikkatmo, ken aramidem daytoy a kas maiyannugot iti [[{{MediaWiki:Policy-url}}|annuroten]].",
        "actioncomplete": "Nalpasen ti aramid",
        "actionfailed": "Napaay ti aramid",
        "whatlinkshere-hidelinks": "$1 dagiti silpo",
        "whatlinkshere-hideimages": "$1 dagiti silpo ti papeles",
        "whatlinkshere-filters": "Dagiti sagat",
+       "whatlinkshere-submit": "Inkan",
        "autoblockid": "Auto a panagserra #$1",
        "block": "Seraan ti agar-aramat",
        "unblock": "Ikkaten ti serra ti agar-aramat",
        "move-page-legend": "Iyalis ti panid",
        "movepagetext": "Ti panagusar ti porma dita baba, ket mangnagan manen ti panid, a mangiyalis amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin a baw-ing a panid iti baro a titulo.\nMapabarom a kas automatiko dagiti baw-ing a nakatudo dita kasisigud a titulo.\nNo agpilika a saanmo a kayat, siguraduem a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRenbbengmo ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addan sigud a panid iti baro a titulo, malaksid no ti kinaudi ket maysa a baw-ing ken awan ti napalabas a pakasaritaan ti panag-urnos. \nKayat a sawen daytoy a mabalinmo a suktan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saan mo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Ballaag!</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim a pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
        "movepagetext-noredirectfixer": "Ti panagusar ti kinabuklan dita baba, ket panaganan ti panid, iyalisna amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin baw-ing a panid idiay baro a titulo.\nPasaruduam a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRebbengem ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addan sigud a panid iti baro a titulo, malaksid no awan linaonna wenno no maysa a baw-ing a panid ken awan ti panagbaliw iti pakasaritaan ti napalabas. \nKayat a sawen daytoy a mabalinmo a suktan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saanmo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Ballaag!</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim ta pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
-       "movepagetalktext": "Ti mainaig a tungtungan ti panid ket automatikonto a maiyalis a karamanna <strong>malaksid:</strong>\n*Ti addan ti awan linaon a tungtungan ti panid babaen ti baro a nagan, wenno\n*No ikkatem ti kur-itna ti kahon iti baba.\n\nKadagitoy a kaso, masapul nga iyalis wenno manual nga itiponmo ti panid no kayatmo.",
+       "movepagetalktext": "No kur-item daytoy a kahon, automatikonto a maiyalis ti mainaig a tungtungan a panid, malaksid no addanto idiay iti adda linaon a tungtungan a panid.\n\nIti daytoy a kaso, masapul nga iyalis wenno manual nga itiponmo ti panid no kayatmo.",
        "moveuserpage-warning": "<strong>Ballaag:</strong> Mangrugrugika nga agiyalis ti panid ti agar-aramat. Pangngaasi a laglapipen a ti panid ket isu laeng ti maiyalis ken ti agar-aramat ket <em>saanto</em> a managanan.",
        "movecategorypage-warning": "<strong>Ballaag:</strong> Mangiyal-aliskan iti panid ti kategoria. Pangngaasi a laglagipen a ti maiyalisto laeng ket ti panid ken ti aniaman a pampanid iti daan a kategoria ket <em>saanto</em> a maikategoria iti baro.",
        "movenologintext": "Masapul a nakarehistroka nga agar-aramat ken [[Special:UserLogin|nakastrek]] tapno makaiyalis iti panid.",
        "export-download": "Idulin a kas papeles",
        "export-templates": "Mangiraman kadagiti plantilia",
        "export-pagelinks": "Mangiraman kadagiti nakasilpo a panid iti kauneg iti:",
+       "export-manual": "Manual nga inayon dagiti panid:",
        "allmessages": "Dagiti mensahe ti sistema",
        "allmessagesname": "Nagan",
        "allmessagesdefault": "Kasisigud a testo ti mensahe",
        "exif-compression-1": "Saan a napespes",
        "exif-copyrighted-true": "Nakarbengan ti kopia",
        "exif-copyrighted-false": "Saan a naisaad ti kasasaad ti karbengan ti kopia",
+       "exif-photometricinterpretation-1": "Nangisit ken puraw (Ti nangisit ket 0)",
        "exif-unknowndate": "Di ammo a petsa",
        "exif-orientation-1": "Kadawyan",
        "exif-orientation-2": "Horisontal a binaliktad",
        "pagelang-language": "Pagsasao",
        "pagelang-use-default": "Usaren ti kasisigud a pagsasao",
        "pagelang-select-lang": "Agpili iti pagsasao",
+       "pagelang-submit": "Ited",
        "right-pagelang": "Baliwan ti pagsasao ti panid",
        "action-pagelang": "baliwan ti pagsasao ti panid",
        "log-name-pagelang": "Listaan ti panagbaliw ti pagsasao",
        "mediastatistics-header-video": "Dagiti video",
        "mediastatistics-header-office": "Opisina",
        "mediastatistics-header-text": "Tekstual",
+       "mediastatistics-header-total": "Amin a papeles",
        "json-error-unknown": "Adda idi parikut ti JSON. Biddut: $1",
        "json-error-state-mismatch": "Imbalido wenno nadadael a JSON",
        "json-error-syntax": "Biddut ti sintaksis",
index 3225cbd..94700cc 100644 (file)
        "mediastatistics": "メディア統計",
        "mediastatistics-summary": "アップロードされたファイルの種類に関する統計です。これはファイルの最新バージョンのみを含みます。以前のまたは削除されたバージョンについては除外されています。",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 バイト}} ($2; $3%)",
-       "mediastatistics-bytespertype": "このセクションの総ファイルサイズは $1 バイトです。",
-       "mediastatistics-allbytes": "全ファイルの総ファイルサイズは $1 バイトです。",
+       "mediastatistics-bytespertype": "このセクションの総ファイルサイズは {{PLURAL:$1|$1 バイト}} ($2、$3%) です。",
+       "mediastatistics-allbytes": "全ファイルの総ファイルサイズは {{PLURAL:$1|$1 バイト}} ($2) です。",
        "mediastatistics-table-mimetype": "MIMEタイプ",
        "mediastatistics-table-extensions": "取りうる拡張子",
        "mediastatistics-table-count": "ファイル数",
index 5571ae5..fbab2fc 100644 (file)
        "cachedspecial-viewing-cached-ts": "თქვენ ნახულობთ ამ გვერდის ქეშირებულ ვერსიას, რომელიც შესაძლოა მნიშვნელოვნად განსხვავდებოდეს მიმდინარე ვერსისაგან.",
        "cachedspecial-refresh-now": "ბოლო ვერსიის ხილვა.",
        "categories": "კატეგორიები",
+       "categories-submit": "ჩვენება",
        "categoriespagetext": "შემდეგი {{PLURAL:$1|კატეგორია შეიცავს|კატეგორია შეიცავს}} გვერდს ან მედიას.\n[[Special:UnusedCategories|გამოუყენებელი კატეგორიები]] აქ ნაჩვენები არ არის.\nიხ. ასევე [[Special:WantedCategories|მოთხოვნილი კატეგორიები]].",
        "categoriesfrom": "აჩვენეთ კატეგორიები, რომლებიც იწყება:",
        "special-categories-sort-count": "დაალაგეთ რაოდენობის მიხედვით",
        "delete-confirm": "„$1“-ის წაშლა",
        "delete-legend": "წაშლა",
        "historywarning": "'''ყურადღება:''' გვერდს, რომლის წაშლასაც აპირებთ, აქვს დიდი ისტორია: ($1)",
+       "historyaction-submit": "ჩვენება",
        "confirmdeletetext": "თქვენ მოითხოვეთ გვერდისა (ან ფაილისა) და მონაცემთა ბაზიდან მისი ისტორიის წაშლა.\nგთხოვთ დაადასტუროთ, რომ მართლაც აპირებთ ამის გაკეთებას და გესმით თქვენი ქმედებების ფასი.\nასევე გადაამოწმეთ, თუ ასრულებთ ამას [[{{MediaWiki:Policy-url}}|წესებიდან გამომდინარე]].",
        "actioncomplete": "მოქმედება შესრულებულია",
        "actionfailed": "მოქმედება ვერ განხორციელდა",
index e99b740..5c7ed50 100644 (file)
        "emailccme": "Scheck mer en Kopie vun dä E-Mail.",
        "emailccsubject": "En Kopie vun Dinger E-Mail aan $1: $2",
        "emailsent": "De <i lang=\"en\">e-mail</i> es ongerwähs",
-       "emailsenttext": "Ding E-Mail es jetz lossjescheck woode.",
+       "emailsenttext": "Ding <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"„de eläktrohnesche Poß“\">e-mail</i> es jäz loßßjeschek woode.",
        "emailuserfooter": "Heh di <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"„de eläktrohnesche Poß“\">e-mail</i> hät {{GENDER:$1|dä|et|dä Metmaacher|di Metmaacherėn|dät}} „$1“ an {{GENDER:$2|dä|et|dä Metmaacher|di Metmaacherėn|dät}} „$2“ jescheck, un doför {{GRAMMAR:en dative|{{SITENAME}}}} dat „{{int:emailuser}}“ jebruch.",
        "usermessage-summary": "En Nohreesch vum Wiki afjelivvert.",
        "usermessage-editor": "Name vum Metmaacher för de Täxte un Nohreshte vum Wiki ze beärbeide",
        "thumbnail_error_remote": "Ene Fähler es em $1 opjevalle:\n$2",
        "djvu_page_error": "De DjVu-Sgg es ußerhallef",
        "djvu_no_xml": "De XML-Date för di DjVu-Datei kunnte mer nit afrofe",
-       "thumbnail-temp-create": "Mer kunnte kein Zweschedattei für Minnibeldscher aanlääje.",
+       "thumbnail-temp-create": "Mer kunnte kein Zweschedattei für Minnibeldscher aanlähje.",
        "thumbnail-dest-create": "Mer kunnte kein Minnibeldscher faßhallde, woh se hen sulle.",
        "thumbnail_invalid_params": "Ene Parameter för et Breefmarke-Belldsche (<i lang=\"en\">thumbnail</i>) Maache wohr nit en Odenung",
        "thumbnail_toobigimagearea": "Datteij met mih wi $1",
index 07241bd..86a2f3e 100644 (file)
        "tog-hideminor": "Celare recensiones minores in indice nuper mutatorum",
        "tog-hidepatrolled": "Redactiones censae inter nuper mutatas celandae",
        "tog-newpageshidepatrolled": "Paginae censae inter nouissime creatas celandae",
-       "tog-extendwatchlist": "Extendere indicem paginarum observatarum ut omnes emendationes monstrentur, non solum emendationes recentissimae",
+       "tog-extendwatchlist": "In indice paginarum observatarum omnes, non solum recentissimas mutationes ostendere",
        "tog-usenewrc": "Indice nuper mutatarum excelsa uti",
        "tog-numberheadings": "Subtituli numeris adornandi",
        "tog-showtoolbar": "Affigere trabem redigentem",
        "tog-editondblclick": "Percussus duplex redactionem hortetur",
        "tog-editsectiononrightclick": "Paginarum segmenta dextero percussu in titulis redigenda",
-       "tog-watchcreations": "Paginas quas creo et fasciculos quos impono in paginarum observatarum indicem addere",
-       "tog-watchdefault": "Paginas et fasciculos quos recenseo in paginarum observatarum indicem addere",
-       "tog-watchmoves": "Paginas et fasciculos quos moveo in paginarum observatarum indicem addere",
+       "tog-watchcreations": "Paginas, quas creavero, et fasciculos, quos imposuero, observare",
+       "tog-watchdefault": "Paginas et fasciculos, quos recensuero, observare",
+       "tog-watchmoves": "Paginas et fasciculos, quos movero, observare",
        "tog-watchdeletion": "Paginas et fasciculos quos deleo in paginarum observatarum indicem addere",
        "tog-minordefault": "Notare omnes recensiones quasi minores",
        "tog-previewontop": "Monstrare praevisum ante capsam recensiti, non post ipsam",
        "tog-fancysig": "Subscriptio vicitext (sine nexu automatico)",
        "tog-uselivepreview": "Praevisum viventem adhibere",
        "tog-forceeditsummary": "Si recensionem non summatim descripsero, me roga si continuare velim",
-       "tog-watchlisthideown": "Celare recensiones meas in paginarum observatarum indice",
-       "tog-watchlisthidebots": "Celare recensiones automatarias in paginarum observatarum indice",
-       "tog-watchlisthideminor": "Celare recensiones minores in paginarum observatarum indice",
-       "tog-watchlisthideliu": "Celare recensiones usorum notorum in paginarum observatarum indice",
-       "tog-watchlisthideanons": "Celare recensiones usorum ignotorum in paginarum observatarum indice",
-       "tog-watchlisthidepatrolled": "Recensiones vigilatae paginas custoditas celare",
+       "tog-watchlisthideown": "Recensiones meas in paginarum observatarum indice supprimere",
+       "tog-watchlisthidebots": "Recensiones per automaton factas in paginarum observatarum indice supprimere",
+       "tog-watchlisthideminor": "Minores recensiones in paginarum observatarum indice supprimere",
+       "tog-watchlisthideliu": "Recensiones ab usoribus notis factas in paginarum observatarum indice supprimere",
+       "tog-watchlistreloadautomatically": "Quamprimum aliquis selectus mutatus erit, indicem paginarum observatarum reficere (JavaScript necesse est)",
+       "tog-watchlisthideanons": "Recensiones ab usoribus ignotis factas in paginarum observatarum indice supprimere",
+       "tog-watchlisthidepatrolled": "Recensiones custoditas supprimere",
        "tog-ccmeonemails": "Transcriptiones earum, quas ad alios usores misero litteras, mihi ipsi mittantur",
        "tog-diffonly": "Nihil nisi differentiam in pagina factam ostendatur",
        "tog-showhiddencats": "Categorias celatas monstrare",
        "last": "prox",
        "page_first": "prim",
        "page_last": "ult",
-       "histlegend": "Selige pro dissimilitudine: indica emendationes in botones radiales et \"intrare\" in claviatura vel \"comparatio\" imprime ut conferas.<br />\nTitulus: '''({{int:cur}})''' = dissimilis ab emendatione novissima,\n'''({{int:last}})''' = dissimilis ab emendatione proxima, '''{{int:minoreditletter}}''' = recensio minor.",
+       "histlegend": "Ad seligendas differentias nota diversarum redactionum bullas et agi iube!<br />\nLegenda: '''({{int:cur}})''' = differentiam monstrabit inter hanc et novissimam redactionem,\n'''({{int:last}})''' = differentiam monstrabit inter hanc et superiorem redactionem,\n'''({{int:minoreditletter}})''' = recensio minor.",
        "history-fieldset-title": "Quaerere in paginae historia",
        "history-show-deleted": "Solum recensiones deletas monstrare",
        "histfirst": "veterrima",
        "revertmerge": "Inconfundere",
        "history-title": "Historia paginae \"$1\"",
        "lineno": "Linea $1:",
-       "compareselectedversions": "Conferre emendationes selectas",
+       "compareselectedversions": "Redactiones selectas conferre",
        "showhideselectedversions": "Monstrare/celare emendationes selectas",
        "editundo": "abrogare",
        "diff-empty": "(eadem)",
        "email": "Litterae electronicae",
        "prefs-help-realname": "Nomen verum non necesse est.\nSi vis id dare, opera tua tibi ascribentur.",
        "prefs-help-email": "Non necesse est inscriptionem electronicam dare. Qua tamen data tessera tibi tribui poterit nova, si prioris oblitus eris.",
-       "prefs-help-email-others": "Si vis, sinit etiam aliis tecum loqui per tuam paginam usoris vel disputationis, nisi te reveles.",
+       "prefs-help-email-others": "Praeterea, si libeat, aliis concedas tibi nuntia per nexum in pagina vel disputatione tua positum mittere electronicas.\nInscriptio tua electronica usoribus tibi scribentis non erit visibilis.",
        "prefs-help-email-required": "Inscriptio electronica necesse est.",
        "prefs-info": "Generalia",
        "prefs-i18n": "Sermonis delectus",
        "prefs-signature": "Subscriptio",
        "prefs-preview": "Praevisum",
-       "prefs-advancedwatchlist": "Praeferentiae monstrare",
+       "prefs-advancedwatchlist": "Indicis modi speciales",
        "prefs-displayrc": "Praeferentiae vultus",
        "prefs-displaywatchlist": "Praeferentiae vultus",
        "prefs-diffs": "Differentiae",
        "movethispage": "Movere hanc paginam",
        "notargettitle": "Nullus scopus",
        "notargettext": "Paginam aut usorem non notavisti.",
-       "pager-newer-n": "{{PLURAL:$1|novior 1|noviores $1}}",
-       "pager-older-n": "{{PLURAL:$1|senior 1|seniores $1}}",
+       "pager-newer-n": "{{PLURAL:$1|recentiorem 1|recentiores $1}}",
+       "pager-older-n": "{{PLURAL:$1|superiorem 1|superiores $1}}",
        "suppress": "Censura",
        "booksources": "Librorum fontes",
        "booksources-search-legend": "Fontes impressas quaerere",
        "contributions-title": "Conlationes usoris $1",
        "mycontris": "Conlationes",
        "anoncontribs": "Conlationes",
-       "contribsub2": "Pro $1 ($2)",
+       "contribsub2": "factae ab usore \"$1\" ($2)",
        "nocontribs": "Nullae mutationes inventae sunt ex his indiciis.",
        "uctop": "(vertex)",
        "month": "Ab mense (et prior):",
        "tooltip-save": "Servare mutationes tuas",
        "tooltip-preview": "Praevidere mutationes tuas, quaesumus hoc utere antequam servas!",
        "tooltip-diff": "Monstrare mutationes textui tuas",
-       "tooltip-compareselectedversions": "Inspicere, quantum contenta inter versiones selectas distent",
+       "tooltip-compareselectedversions": "Inspicere, quatenus contenta redactionum selectarum inter se distent",
        "tooltip-watch": "Addere hanc paginam tuis paginis observatis",
        "tooltip-recreate": "Recreare hanc paginam etiamsi deleta est",
        "tooltip-upload": "Incipere imponere",
index b454bb8..bc25dfc 100644 (file)
        "undelete_short": "احیای {{PLURAL:$1|یإ دةسکاری|$1 دةسکاری}}",
        "viewdeleted_short": "نمایش {{PLURAL:$1|یک ویرایش حذف‌شده|$1 ویرایش حذف‌شده}}",
        "protect": "پروژۀ",
-       "protect_change": "تغییر/آلِشت",
+       "protect_change": "گؤەڕانن/تغییر",
        "protectthispage": "محافظت إژ ئئ وةڵگة",
-       "unprotect": "تغییر محافظت",
+       "unprotect": "گؤەڕانن/تغییر محافظت",
        "unprotectthispage": "تغییر محافظت ئئ وةڵگة",
        "newpage": "وةڵگة  تازۀ",
        "talkpage": "گةپ دةربارة ئئ وةڵگة",
        "nstab-main": "وةڵگة/پەڕە",
        "nstab-user": "وۀلگۀ کاربۀر",
        "nstab-media": "وةڵگة رسانه",
-       "nstab-special": "Ù\88Û\80Ù\84Ú¯Û\80/Ù¾Û\80رÛ\80 Ù\88Û\8cÚ\98Û\80",
+       "nstab-special": "Ù\88Û\95ÚµÚ¯Û\95(Ù¾Û\95Ú\95Û\95\88Û\8cÚ\98Û\95",
        "nstab-project": "وۀلگۀ پروژۀ",
        "nstab-image": "فایل",
        "nstab-mediawiki": "پیغام",
        "nav-login-createaccount": " إ نؤم هةتن سیستم/ حساوو کاربةری سازین",
        "userlogin": " إ نؤم هةتن سیستم/ حساوو کاربةری سازین",
        "userloginnocreate": "نؤم هۀتن سیستم",
-       "logout": "دةرچئن/خروج",
-       "userlogout": "دةرچئن/خروج",
+       "logout": "دەرچێن|خروج",
+       "userlogout": "دەرچێن|خروج",
        "notloggedin": "وارد سیستم نؤینۀ",
        "userlogin-noaccount": "حساوو کاربۀری نرین؟",
        "userlogin-joinproject": "{{SITENAME}}نام نؤیسی کۀن",
        "pt-login": "نؤم هۀتن.",
        "pt-login-button": "نؤم هۀتن سیستم",
        "pt-createaccount": "حساووئ أرا ووژتان بِسازِن",
-       "pt-userlogout": "دةرچئن/خروج",
+       "pt-userlogout": "دەرچێن|خروج",
        "php-mail-error-unknown": "خطای ناشناخته در تابع  mail()‎ پی‌اچ‌پی",
        "user-mail-no-addy": "تلاش برای ارسال ایمیل بدون آدرس ایمیل.",
        "user-mail-no-body": "سعی کردید ایمیلی با محتوای بی‌دلیل کوتاه و یا خالی بفرستید.",
        "changepassword": "تغییردائن رمز",
        "resetpass_announce": "شما باید برای پایان ورود به سامانه، گذرواژهٔ جدیدی را تنظیم کنید.",
-       "resetpass_header": "تغییر گذرواژهٔ حساب کاربری",
+       "resetpass_header": "گؤەڕانن/تغییر رمز حساب کاربری",
        "oldpassword": "گذرواژهٔ پیشین:",
        "newpassword": "گذرواژهٔ تازه:",
        "retypenew": "گذرواژهٔ تازه را دوباره وارد کنید",
        "changeemail-newemail-help": "برای حذف ایمیل باید این بخش را خالی رها کنید در نتیجه امکان بازگردانی گذرواژه و دریافت ایمیل از ویکی برای شما مقدور نخواهد بود.",
        "changeemail-none": "(هؤیچ کام)",
        "changeemail-password": "گذرواژهٔ {{SITENAME}} هؤمة:",
-       "changeemail-submit": "تغییر ایمیل",
+       "changeemail-submit": "گؤەڕانن/تغییر ایمیل",
        "changeemail-throttled": "شما به مراتب برای ورود تلاش کرده‌اید.\nلطفاً پیش از آنکه دوباره تلاش کنید $1 صبر کنید.",
        "changeemail-nochange": "لطفاً رایانامهٔ جدید و متفاوتی وارد کنید.",
        "resettokens": "بازنشانی شناساننده‌ها",
        "revdelete-hide-comment": "خلاصة دةسکاری",
        "revdelete-hide-user": "نام کاربری/نشانی آی‌پی",
        "revdelete-hide-restricted": "فرونشانی اطلاعات برای مدیران به همراه دیگران",
-       "revdelete-radio-same": "(بدون تغییر)",
+       "revdelete-radio-same": "(بدون گؤەڕانن/تغییر )",
        "revdelete-radio-set": "آشاریا/پنهان",
        "revdelete-radio-unset": "دیارۀ-نمایان",
        "revdelete-suppress": "از دسترسی مدیران به داده نیز مانند سایر کاربران جلوگیری به عمل آید.",
        "revdelete-failure": "'''پیدایی ورژن ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد با موفقیت انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
-       "revdel-restore": "تغییر پیدایی",
+       "revdel-restore": "گؤەڕانن/تغییر پیدایی",
        "pagehist": "تاریخ وةڵگة",
        "deletedhist": "تاریخچهٔ پاک شده",
        "revdelete-hide-current": "خطا در پنهان کردن مورد مورخ $2 ساعت $1: این نسخه، ورژن اخیر است و قابل پنهان کردن نیست.",
        "sessionfailure-title": "خطای نشست کاربری",
        "sessionfailure": "به نظر می‌رسد مشکلی در مورد نشست کاربری شما وجود دارد؛\nعمل درخواست شده در اقدامی پیشگیرانه در برابر ربوده‌شدن اطلاعات نشست کاربری، لغو شد.\nلطفاً دکمهٔ «بازگشت» را در مرورگر خود بفشارید و صفحه‌ای که از آن به اینجا رسیده‌اید را دوباره فراخوانی کنید، سپس مجدداً سعی کنید.",
        "changecontentmodel": "ویرایش نمونه محتوای یک صفحه",
-       "changecontentmodel-legend": "تغییر نوع محتوی",
+       "changecontentmodel-legend": "گؤەڕانن/تغییر نوع محتوی",
        "changecontentmodel-title-label": "عنوان وةڵگة",
        "changecontentmodel-model-label": "نمونه محتوای جدید",
        "changecontentmodel-reason-label": ":دةلیل",
-       "changecontentmodel-success-title": "نمونه محتوی تغییر یافت",
+       "changecontentmodel-success-title": "نمونه محتوی گؤەڕیا/تغییر یافت",
        "changecontentmodel-success-text": "نوع محتوی [[:$1]]  تغییر یافت",
        "changecontentmodel-cannot-convert": "محتوی در [[:$1]] نمی‌تواند به گونه‌ای از $2 تبدیل شود.",
        "changecontentmodel-nodirectediting": "نمونه محتوی $1 امکان ویرایش مستقیم را پشتیبانی نمی‌کند",
        "modifiedarticleprotection": "وضعیت محافظت «[[$1]]» را تغییر داد",
        "unprotectedarticle": "صفحهٔ «[[$1]]» را از محافظت بیرون آورد",
        "movedarticleprotection": "تنظیمات محافظت را از «[[$2]]» به «[[$1]]» منتقل کرد",
-       "protect-title": "تغییر وضعیت محافظت «$1»",
+       "protect-title": "گؤەڕانن/تغییر وضعیت محافظت «$1»",
        "protect-title-notallowed": "مشاهده سطح حفاظت  \" $1 \"",
        "prot_1movedto2": "[[$1]] به [[$2]] منتقل بی",
        "protect-badnamespace-title": "فضای نام بدون محافظت",
        "allmessagesnotsupportedDB": "این صفحه نمی‌تواند استفاده شود به این دلیل که <bdi>'''$wgUseDatabaseMessages'''</bdi> غیرفعال شده‌است.",
        "allmessages-filter-legend": "پالانۀل/فیلترۀل",
        "allmessages-filter": "پالودن بر اساس وضعیت شخصی‌سازی:",
-       "allmessages-filter-unmodified": "تغییر نیافته",
+       "allmessages-filter-unmodified": "نگؤەڕیائە/تغییرنیافته",
        "allmessages-filter-all": "کۆل",
-       "allmessages-filter-modified": "تغییر یافته",
+       "allmessages-filter-modified": "گؤەڕیائە/تغییریافته",
        "allmessages-prefix": "پالودن بر اساس پسوند:",
        "allmessages-language": ":زوون",
        "allmessages-filter-submit": "بِچۆ",
-       "allmessages-filter-translate": "Ú\86اÙ\88Ù\88اشÛ\80گر زوون",
+       "allmessages-filter-translate": "Ú\86اÙ\88Ù\88اشÛ\95Ú©Ù\90ردÙ\86 زوون",
        "thumbnail-more": "کۀلنگ کِردن",
        "filemissing": "پرونده وجود ندارد",
        "thumbnail_error": "خطا در ایجاد بندانگشتی: $1",
        "tooltip-pt-mycontris": "فهرست مشارکت‌های شما",
        "tooltip-pt-anoncontribs": "لیست دةسکاریةل دؤرس بی/سازریا إژ ئئ آدرس ای پی",
        "tooltip-pt-login": "توصیه مۀکیم بونإ نام سامانه ، هۀرچۀند اجباری نیۀ",
-       "tooltip-pt-logout": "دةرچئن/خروج",
+       "tooltip-pt-logout": "دەرچێن|خروج",
        "tooltip-pt-createaccount": "مکئس تانۀ مۀکیم حساووئ بسازن و بونإ سامانۀ؛ هرچۀند حساوو کاربری سازین دل .بخوایۀ",
        "tooltip-ca-talk": "گۀپ/قسۀ دۀربارۀ بنچۀک/محتوا وۀلگۀ",
        "tooltip-ca-edit": "ئئ وۀلگۀ دۀسکاری کۀن",
        "tooltip-ca-viewsource": ".ئئ وۀلگۀ محافظۀت بیۀ\nمۀتؤنین  متن مبدأ/بنچۀک بؤینین",
        "tooltip-ca-history": "ورژن دؤمائن/پئش ئئ وۀلگۀ",
        "tooltip-ca-protect": "محافظت إژ ئئ وةڵگة",
-       "tooltip-ca-unprotect": "تغییر محافظت ئئ وةڵگة",
+       "tooltip-ca-unprotect": "گؤەڕانن/تغییر محافظت ئئ وةڵگة",
        "tooltip-ca-delete": "حةذف ئئ وةڵگة",
        "tooltip-ca-undelete": "بازگرداندن نسخه‌های صفحهٔ حذف‌شده",
        "tooltip-ca-move": "جاوواز کردن ئئ وۀلگۀ",
        "pageinfo-hidden-categories": "{{PLURAL:$1| ردهٔ|ردهٔ}} پنهان ( $1 )",
        "pageinfo-templates": "{{PLURAL:$1|الگو|الگوها}} استفاده‌شده ($1)",
        "pageinfo-transclusions": "{{PLURAL:$1|صفحهٔ|صفحه‌های}} تراگنجانش‌شده در ($1)",
-       "pageinfo-toolboxlink": "اطÙ\84اعات Ù\88Û\80Ù\84Ú¯Û\80/Ù¾Û\80رÛ\80",
+       "pageinfo-toolboxlink": "اطÙ\84اعات Ù\88Û\95ÚµÚ¯Û\95(Ù¾Û\95Ú\95Û\95)",
        "pageinfo-redirectsto": "تغییرمسیر به",
        "pageinfo-redirectsto-info": "زانستةنیةل",
        "pageinfo-contentpage": "شمرده شده به عنوان صفحهٔ محتوایی",
        "exif-serialnumber": "شماره سریال دوربین",
        "exif-cameraownername": "صاحب دوربین",
        "exif-label": "برچسب",
-       "exif-datetimemetadata": "تاریخ آخرین تغییر فراداده",
+       "exif-datetimemetadata": "تاریخ آخرین گؤەڕانن/تغییر فراداده",
        "exif-nickname": "نام غیررسمی تصویر",
        "exif-rating": "امتیاز (از 5)",
        "exif-rightscertificate": "گواهینامه مدیریت حقوق",
        "blankpage": "وةڵگة خالی",
        "intentionallyblankpage": "این وةڵگة به طور عمدی خالی گذاشته شده است.",
        "external_image_whitelist": " #این سطر را همان‌گونه که هست رها کنید<pre>\n#عبارت‌های باقاعده (regex) را در زیر قرار دهید (فقط بخشی که بین // قرار می‌گیرد)\n#آن‌ها با نشانی اینترنتی تصاویر خارجی پیوند داده شده تطبیق داده می‌شوند\n#مواردی که مطابق باشند به صورت تصویر نمایش می‌یابند، و در غیر این صورت تنها یک پیوند به تصویر نمایش می‌یابد\n#سطرهایی که با # آغاز شوند به عنوان توضیحات در نظر گرفته می‌شوند\n#این سطرها به کوچکی و بزرگی حروف حساس هستند\n\n#عبارت‌های باقاعده (regex)  را زیر این سطر قرار دهید. این سطر را همان‌گونه که هست رها کنید</pre>",
-       "tags": "برچسب‌های تغییر مجاز",
+       "tags": "برچسب‌های گؤەڕانن/تغییر مجاز",
        "tag-filter": ":فیلتر کۀ[[Special:Tags|برچسب‌ۀل]]",
        "tag-filter-submit": "پالانۀل/فیلترۀل",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|بۀرچۀسب|بۀرچۀسبۀل}}]]:$2)",
        "pagelang-use-default": "استفاده إژ زوون پئش فرض",
        "pagelang-select-lang": "زوون انتخاب کۀ",
        "pagelang-submit": "تائید کردن",
-       "right-pagelang": "تغییر وةڵگة زوون",
-       "action-pagelang": "تغییر زوون وةڵگة",
+       "right-pagelang": "گؤەڕانن/تغییر وةڵگة زوون",
+       "action-pagelang": "گؤەڕانن/تغییر زوون وةڵگة",
        "log-name-pagelang": "تغییر سیاههٔ زبان",
        "log-description-pagelang": "ای پهرستنومه در بلگه زونا آلشت گرته.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2| تغییریافت}} زبان صفحه برای  $3  از  $4  به  $5 .",
        "special-characters-title-minus": "علامت منفی",
        "mw-widgets-dateinput-no-date": "هیچ داده‌ای انتخاب نشده",
        "mw-widgets-titleinput-description-new-page": "ئئ وةڵگة هنوز/حالی وجود نِئرێ",
-       "mw-widgets-titleinput-description-redirect": "تغییر مسیر به $1",
+       "mw-widgets-titleinput-description-redirect": "گؤەڕانن/تغییر مسیر به $1",
        "api-error-blacklisted": "لطفاً یک عنوان توصیفی متفاوت انتخاب کنید."
 }
index e0cc91c..f20007b 100644 (file)
        "passwordreset-emailtext-ip": "Некој (веројатно вие, од IP-адресата $1) побара измена на вашата\nлозинка за {{SITENAME}} ($4). Оваа е-поштенска адреса е наведена во\n{{PLURAL:$3|следнава корисничка сметка|следниве кориснички сметки}}:\n\n$2\n\n{{PLURAL:$3|Оваа привремена лозинка ќе истече|Овие привремени лозинки ќе истечат}} во рок од {{PLURAL:$5|еден ден|$5 дена}}.\nСега треба да се најавите и да внесете нова лозинка. Ако ова барање го\nпоставил некој друг, или пак во меѓувреме сте се сетиле на лозинката, и не сакате\nда ја менувате, тогаш слободно занемарете ја поракава и продолжете да ја користите старата.",
        "passwordreset-emailtext-user": "Корисникот $1 на {{SITENAME}} побара измена на вашата лозинка на {{SITENAME}}\n($4). Оваа е-поштенска адреса е наведена во {{PLURAL:$3|следнава корисничка сметка|следниве кориснички сметки}}:\n\n$2\n\n{{PLURAL:$3|Оваа привремена лозинка ќе истече|Овие привремени лозинки ќе истечат}} во рок од {{PLURAL:$5|еден ден|$5 дена}}.\nСега треба да се најавите и да внесете нова лозинка. Ако ова барање го\nпоставил некој друг, или пак во меѓувреме сте се сетиле на лозинката, и не сакате\nда ја менувате, тогаш слободно занемарете ја поракава и продолжете да ја користите старата.",
        "passwordreset-emailelement": "Корисничко име: \n$1\n\nПривремена лозинка: \n$2",
-       "passwordreset-emailsentemail": "Ð\90ко Ð¾Ð²Ð° Ðµ Ñ\80егиÑ\81Ñ\82Ñ\80иÑ\80анаÑ\82а Ðµ-поÑ\88Ñ\82а Ð·Ð° вашата сметка, тогаш ќе ви биде испратено писмо за задавање на нова лозинка.",
-       "passwordreset-emailsentusername": "Ако има соодветна регистрирана е-пошта, тогаш ќе ви биде испратена порака за промена на лозинката.",
+       "passwordreset-emailsentemail": "Ð\90ко Ð¾Ð²Ð° Ðµ Ñ\80егиÑ\81Ñ\82Ñ\80иÑ\80анаÑ\82а Ðµ-поÑ\88Ñ\82а Ð¿Ð¾Ð²Ñ\80зана Ñ\81о вашата сметка, тогаш ќе ви биде испратено писмо за задавање на нова лозинка.",
+       "passwordreset-emailsentusername": "Ако има соодветна регистрирана е-пошта поврзана со ова корисничко име, тогаш ќе ви биде испратена порака за промена на лозинката.",
        "passwordreset-emailsent-capture": "Испратено е писмо за измена на лозинката (прикажано подолу).",
        "passwordreset-emailerror-capture": "Создадено е писмо за измена на лозинката (прикажано подолу), но не успеав да го испратам на {{GENDER:$2|корисникот}}: $1",
        "changeemail": "Смени или отстрани е-пошта",
        "upload-form-label-select-file": "Одберете податотека",
        "upload-form-label-infoform-title": "Подробно",
        "upload-form-label-infoform-name": "Назив",
+       "upload-form-label-infoform-name-tooltip": "Краток и единствен наслов на податотеката, кој ќе служи како нејзин назив. Можете да користите прост јазик со меѓупростор, но не пишувајте ја податотечната наставка.",
        "upload-form-label-infoform-description": "Опис",
+       "upload-form-label-infoform-description-tooltip": "Накратко опишете го сето она што е значајно за делото. Ако е фотографија, споменете ги главните нешта што се прикажани на неа, настанот или местото.",
        "upload-form-label-usage-title": "Употреба",
        "upload-form-label-usage-filename": "Назив на податотеката",
        "foreign-structured-upload-form-label-own-work": "Ова е мое дело",
        "export-download": "Зачувај како податотека",
        "export-templates": "Вклучи и шаблони",
        "export-pagelinks": "Вклучи поврзани страници до длабочина од:",
+       "export-manual": "Додајте страници рачно:",
        "allmessages": "Системски пораки",
        "allmessagesname": "Име",
        "allmessagesdefault": "Текст по основно",
        "expand_templates_preview": "Преглед",
        "expand_templates_preview_fail_html": "<em>Бидејќи {{SITENAME}} има овозможено сиров HTML и се јави губиток на седнички податоци, прегледот е скриен како мерка на претпазливост против напади со JavaScript.</em>\n\n<strong>Ако ова е е легитимен обид за преглед, тогаш обидете се повторно.</strong>\nАко не работи и тогаш, [[Special:UserLogout|одјавете се]] и повторно најавете се.",
        "expand_templates_preview_fail_html_anon": "<em>Бидејќи {{SITENAME}} има овозможено сиров HTML, а вие не сте најавени, прегледот е скриен како мерка на претпазливост против напади со JavaScript.</em>\n\n<strong>Ако ова е е легитимен обид за преглед, тогаш обидете се повторно.</strong>\nАко не работи и тогаш, [[Special:UserLogout|одјавете се]] и повторно најавете се.",
+       "expand_templates_input_missing": "Треба да внесете некаков текст.",
        "pagelanguage": "Изборник за јазик на страницата",
        "pagelang-name": "Страница",
        "pagelang-language": "Јазик",
        "mediastatistics": "Статистики за слики и снимки",
        "mediastatistics-summary": "Статистики за подигнати типови податотеки. Се зема предвид само последната верзија на податотеката. Старите и избришаните верзии не се бројат.",
        "mediastatistics-nbytes": "{{PLURAL:$1|Еден бајт|$1 бајти}} ($2; $3%)",
-       "mediastatistics-bytespertype": "Вкупен обем на пасусот: $1 бајти.",
-       "mediastatistics-allbytes": "Вкупен обем на сите податотеки: $1 бајти.",
+       "mediastatistics-bytespertype": "Вкупен обем на пасусот: {{PLURAL:$1|$1 бајт|$1 бајти}} ($2; $3%).",
+       "mediastatistics-allbytes": "Вкупен обем на сите податотеки: {{PLURAL:$1|$1 бајт|$1 бајти}} ($2).",
        "mediastatistics-table-mimetype": "MIME-тип",
        "mediastatistics-table-extensions": "Можни додатоци",
        "mediastatistics-table-count": "Број на податотеки",
index 615de51..9694320 100644 (file)
        "searcharticle": "Vàje",
        "history": "Verziune 'e primma",
        "history_short": "Cronologgia",
-       "updatedmarker": "cagnamiénte 'e mija urdema visita",
+       "updatedmarker": "cagnamiénte 'e ll'urdema visita d' 'a mia",
        "printableversion": "Verzione pe' stampa",
        "permalink": "Jonta permanente",
        "print": "Stampà",
        "userpage-userdoesnotexist": "'O cunto utente \"<nowiki>$1</nowiki>\" nun è riggistrato. Cuntrolla ca si buò overo crià o cagnà sta paggena.",
        "userpage-userdoesnotexist-view": "'O cunto utente \"$1\" nun è riggistrato.",
        "blocked-notice-logextract": "St'utente è bloccato mò.\nL'urdemo elemento d' 'o riggistro 'e blocche è ripurtato ccà abbascio p'avé nu riferimento:",
-       "clearyourcache": "'''Nota:''' aroppo sarvate putisse necessità 'e pulezzà 'a caché d' 'o navigatóre pe' vedé 'e cagnamiente. \n*'''Firefox / Safari''': sprémme 'o buttóne maiuscole e ffà clic ncopp'a ''Recarreca'', o pure spremme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' ncopp'a Mac)\n*'''Google Chrome''': spremme ''Ctrl-Shift-R'' (''⌘-Shift-R'' ncopp'a nu Mac)\n*'''Internet Explorer''': spremme 'o buttóne ''Ctrl'' pe' tramente ca faie click ncopp'a ''Refresh'', o pure spremmere ''Ctrl-F5''\n*'''Opera''': abbacanta tutt' 'a cache addò menu ''Strumiente → Preferenze''",
+       "clearyourcache": "'''Nota:''' aroppo sarvate putisse necessità 'e pulezzà 'a caché d' 'o navigatóre pe' vedé 'e cagnamiente. \n*'''Firefox / Safari''': sprémme 'o buttóne maiuscole e ffà clic ncopp'a ''Recarreca'', o pure spremme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' ncopp'a Mac)\n*'''Google Chrome''': spremme ''Ctrl-Shift-R'' (''⌘-Shift-R'' ncopp'a nu Mac)\n*'''Internet Explorer''': spremme 'o buttóne ''Ctrl'' pe' tramente ca faie click ncopp'a ''Refresh'', o pure spremmere ''Ctrl-F5''\n*'''Opera''': sbacanta tutt' 'a cache addò menu ''Strumiente → Preferenze''",
        "usercssyoucanpreview": "'''Cunziglio:''' spremme 'o buttone 'Vide anteprimma' pe' pruvà 'o CSS nuovo apprimma d' 'o sarvà.",
        "userjsyoucanpreview": "'''Cunziglio:''' spremme 'o buttone 'Vide anteprimma' pe' pruvà 'o JavaScript nuovo apprimma d' 'o sarvà.",
        "usercsspreview": "'''Arricuordate ca chest'è sulamente n'anteprimma p' 'o CSS perzunale. 'E cagnamiente nun so' state ancora sarvate!'''",
        "watchlistedit-clear-done": "L'elenco 'e paggene cuntrullate vuosto è stat'abbacantato.",
        "watchlistedit-clear-removed": "{{PLURAL:$1|nu titolo è stato luvato|$1 titule so' state luvate}}:",
        "watchlistedit-too-many": "Ce stanno troppe paggene 'a veré ccà.",
-       "watchlisttools-clear": "Abbacanta l'elenco 'e paggene cuntrullate",
+       "watchlisttools-clear": "Sbacanta l'elenco 'e paggene cuntrullate",
        "watchlisttools-view": "Vide 'e cagnamiente mpurtante",
        "watchlisttools-edit": "Vide e cagna l'elenco 'e paggene cuntrullate",
        "watchlisttools-raw": "Cagna l'elenco 'e paggene cuntrullate ncruro",
index a24db3a..0bb4726 100644 (file)
        "revdelete-uname-unhid": "brukarnamn gjort synleg",
        "revdelete-restricted": "la til avgrensingar for administratorar",
        "revdelete-unrestricted": "fjerna avgrensingar for administratorar",
+       "logentry-block-block": "$1 {{GENDER:$2|blokkerte}} {{GENDER:$4|$3}} for $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|endra}} blokkeringsinnstillingar for {{GENDER:$4|$3}} med opphøyrstid $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|flytte}} sida $3 til $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|flytte}} sida $3 til $4 utan å lata etter ei omdirigering",
index 7c7355d..b7556e0 100644 (file)
@@ -87,7 +87,8 @@
                        "Rhcastilhos",
                        "Claudio Emanuel Weiler",
                        "Almondega",
-                       "Eduardo Addad de Oliveira"
+                       "Eduardo Addad de Oliveira",
+                       "Raphaelras"
                ]
        },
        "tog-underline": "Sublinhar links:",
        "contributions": "Contribuições {{GENDER:$1|do usuário|da usuária|do(a) usuário(a)}}",
        "contributions-title": "Contribuições {{GENDER:$1|do usuário|da usuária|do(a) usuário(a)}} $1",
        "mycontris": "Contribuições",
+       "anoncontribs": "Contribuições",
        "contribsub2": "Para {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "A conta de usuário \"$1\" não está registrada.",
        "nocontribs": "Não foram encontradas mudanças com este critério.",
index b0cfb61..66449f8 100644 (file)
        "unblock-summary": "{{doc-specialpagesummary|unblock}}",
        "blockip": "Used as the text of a link in the sidebar toolbox. Clicking this link takes you to [[Special:Block]], with a relevant username or IP address (e.g. \"Username\" on [[User talk:Username]], [[Special:Contributions/Username]], etc.) already filled in.\n\nParameters:\n* $1 - username, for GENDER support\n{{Identical|Block user}}",
        "blockip-legend": "Legend/Header for the fieldset around the input form of [[Special:Block]].\n\n{{Identical|Block user}}",
-       "blockiptext": "Used in the {{msg-mw|Blockip}} form in [[Special:Block]].\n\nRefers to {{msg-mw|Policy-url}}.\n\nThis message may follow the message {{msg-mw|Ipb-otherblocks-header}} and other block messages.\n\nSee also:\n* {{msg-mw|Unblockiptext}}",
+       "blockiptext": "Used in the {{msg-mw|Blockip}} form in [[Special:Block]].\n\nRefers to {{msg-mw|Policy-url}}.\n\nThis message may follow the message {{msg-mw|Ipb-otherblocks-header}} and other block messages.\n\nParameters:\n* $1 - CIDR suffix of the largest allowed IPv4 block (as an integer)\n* $2 - CIDR suffix of the largest allowed IPv6 block (as an integer)\n\nSee also:\n* {{msg-mw|Unblockiptext}}",
        "ipaddressorusername": "{{Identical|IP address or username}}",
        "ipbexpiry": "{{Identical|Expiry}}",
        "ipbreason": "Label of the block reason dropdown in [[Special:BlockIP]] and the unblock reason textfield in [{{fullurl:Special:IPBlockList|action=unblock}} Special:IPBlockList?action=unblock].\n\n{{Identical|Reason}}",
        "markaspatrolleddiff": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolledtext}}\n{{Identical|Mark as patrolled}}",
        "markaspatrolledlink": "{{notranslate}}\nParameters:\n* $1 - link which has text {{msg-mw|Markaspatrolledtext}}",
        "markaspatrolledtext": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolleddiff}}",
+       "markaspatrolledtext-file": "Same as markaspatrolledtext, but for files (new versions included) instead of pages.",
        "markedaspatrolled": "Used as title of the message {{msg-mw|Markedaspatrolledtext}}, when marking a change as patrolled.\n{{Related|Markedaspatrolled}}",
        "markedaspatrolledtext": "Used when marking a change as patrolled.\n\nThe title for this message is {{msg-mw|Markedaspatrolled}}.\n\nParameters:\n* $1 - page title\n{{Related|Markedaspatrolled}}",
        "rcpatroldisabled": "Used as title of the error message {{msg-mw|Rcpatroldisabledtext}}, when marking a change as patrolled.\n{{Related|Markedaspatrolled}}",
        "newimages-legend": "Caption of the fieldset for the filter on [[Special:NewImages]]\n\n{{Identical|Filter}}",
        "newimages-label": "Caption of the filter editbox on [[Special:NewImages]]",
        "newimages-showbots": "Used as label for a checkbox. When checked, [[Special:NewImages]] will also display uploads by users in the bots group.",
+       "newimages-hidepatrolled": "Used as label for a checkbox. When checked, [[Special:NewImages]] will not display patrolled uploads.",
        "noimages": "This is shown on the special page [[Special:NewImages]], when there aren't any recently uploaded files.",
        "ilsubmit": "Used as label for input box in the MIMESearch form on [[Special:MIMESearch]].\n\nSee also:\n* {{msg-mw|Mimesearch|page title}}\n* {{msg-mw|Mimetype|label for input box}}\n{{Identical|Search}}",
        "bydate": "{{Identical|Date}}",
index 10056c6..080d35e 100644 (file)
@@ -86,7 +86,8 @@
                        "Nzeemin",
                        "INS Pirat",
                        "Краснорядцева Елена",
-                       "Frhdkazan"
+                       "Frhdkazan",
+                       "Ядерный Трамвай"
                ]
        },
        "tog-underline": "Подчёркивание ссылок:",
        "showhideselectedversions": "Показать/скрыть выбранные версии",
        "editundo": "отменить",
        "diff-empty": "(нет различий)",
-       "diff-multi-sameuser": "(не {{PLURAL:$1|показана одна промежуточная версия|показаны $1 промежуточные версии|показано $1 промежуточных версий}} этого же участника)",
+       "diff-multi-sameuser": "(не {{PLURAL:$1|показана $1 промежуточная версия|показаны $1 промежуточные версии|показано $1 промежуточных версий}} этого же участника)",
        "diff-multi-otherusers": "(не {{PLURAL:$1|показана $1 промежуточная версия|показаны $1 промежуточные версии|показано $1 промежуточных версий}} {{PLURAL:$2|$2 участника|$2 участников}})",
        "diff-multi-manyusers": "({{PLURAL:$1|не показана $1 промежуточная версия, сделанная|не показаны $1 промежуточных версий, сделанных|не показаны $1 промежуточные версии, сделанные}} более чем {{PLURAL:$2|$2 участником|$2 участниками}})",
        "difference-missing-revision": "Не {{PLURAL:$2|1=найдена|найдены}} {{PLURAL:$2|$2 версия|$2 версий|$2 версии|1=одна из версий}} для этого сравнения ($1).\n\nТакое обычно случается при переходе по устаревшей ссылке сравнения версий для страницы, которая была удалена.\nПодробности могут быть в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале удалений].",
        "upload-form-label-select-file": "Выбрать файл",
        "upload-form-label-infoform-title": "Подробности",
        "upload-form-label-infoform-name": "Имя",
+       "upload-form-label-infoform-name-tooltip": "Уникальный описательный заголовок для файла, который будет сохранён как его название. Можете использовать простой язык и пробелы. Не указывайте расширение.",
        "upload-form-label-infoform-description": "Описание",
+       "upload-form-label-infoform-description-tooltip": "Коротко опишите всё самое важное об этом произведении. Для фото — укажите, что главное изображено, обстоятельства съёмки или место.",
        "upload-form-label-usage-title": "Использование",
        "upload-form-label-usage-filename": "Имя файла",
        "foreign-structured-upload-form-label-own-work": "Это моя собственная работа",
        "expand_templates_preview": "Предпросмотр",
        "expand_templates_preview_fail_html": "<em>Поскольку на сайте {{SITENAME}} с включенным «сырым» HTML произошла потеря данных сессии, предварительный просмотр скрыт в качестве меры предосторожности против JavaScript-атак.</em>\n\n<strong>Если это была правомерная попытка предварительного просмотра, пожалуйста, попробуйте ещё раз.</strong>\nЕсли у вас по-прежнему не получается, попробуйте [[Special:UserLogout|завершить сеанс работы]] и авторизоваться ещё раз.",
        "expand_templates_preview_fail_html_anon": "<em>Поскольку на сайте {{SITENAME}} включен «сырой» HTML, а вы не авторизовались, предварительный просмотр скрыт в качестве меры предосторожности против JavaScript-атак.</em>\n\n<strong>Если это правомерная попытка предварительного просмотра, пожалуйста, [[Special:UserLogin|войдите]] и попробуйте ещё раз.",
+       "expand_templates_input_missing": "Вы должны вставить хоть какой-то текст.",
        "pagelanguage": "Выбор языка страницы",
        "pagelang-name": "Страница",
        "pagelang-language": "Язык",
index f9b4b1a..76344ed 100644 (file)
        "perfcachedts": "Бу мәгълүматлар кэштан алынган, ул соңгы тапкыр $1 яңартылды. Кэшта иң күбе {{PLURAL:$4|язма}} саклана",
        "querypage-no-updates": "Хәзер бу битне яңартып булмый. Монда күрсәтелгән мәгълүматлар кабул ителмәячәк.",
        "viewsource": "Карау",
-       "viewsource-title": "$1 Ð±Ð¸Ñ\82енең Ñ\8fÑ\85ма Ñ\82екÑ\81Ñ\82ын карау",
+       "viewsource-title": "$1 Ð±Ð¸Ñ\82енең Ñ\87Ñ\8bганагын карау",
        "actionthrottled": "Тизлек киметелгән",
        "actionthrottledtext": "Спамга каршы көрәш өчен, аз вакыт эчендә бу гамәлне еш куллану тыелган һәм СЕз бирелгән вакытны бетергәнсез инде. Зинһар, соңарак кабатлагыз.",
        "protectedpagetext": "Бу бит үзгәртүләрдән һәм башка төрле гамәлләрдән якланган.",
        "logout": "Чыгу",
        "userlogout": "Чыгу",
        "notloggedin": "Сез хисап язмагызга кермәгәнсез",
-       "userlogin-noaccount": "Ð\90ккаÑ\83нÑ\82 юкмы?",
-       "userlogin-joinproject": "Проектка керү",
+       "userlogin-noaccount": "ХиÑ\81ап Ñ\8fзмагÑ\8bз юкмы?",
+       "userlogin-joinproject": "{{SITENAME}} проектына керү",
        "nologin": "Кулланучы исемең юкмы? '''$1'''",
        "nologinlink": "Хисап язмасы төзү",
        "createaccount": "Яңа кулланучыны теркәү",
        "enhancedrc-history": "тарих",
        "recentchanges": "Соңгы үзгәртүләр",
        "recentchanges-legend": "Соңгы үзгәртүләр көйләүләре",
-       "recentchanges-summary": "Ð\91Ñ\83 Ð±Ð¸Ñ\82Ñ\82Ó\99 {{grammar:genitive|{{SITENAME}}}} Ð¿Ñ\80оекÑ\82Ñ\8bнÑ\8bÒ£ Ñ\81оңгÑ\8b Ò¯Ð·Ð³Ó\99Ñ\80Ñ\82үлÓ\99Ñ\80е ÐºÒ¯Ñ\80Ñ\81Ó\99Ñ\82елÓ\99.",
+       "recentchanges-summary": "ТөÑ\80ле Ð±Ð¸Ñ\82лÓ\99Ñ\80дÓ\99 Ñ\8dÑ\88лÓ\99нгÓ\99н Ñ\81оңгÑ\8b Ò¯Ð·Ð³Ó\99Ñ\80Ñ\82үлÓ\99Ñ\80 Ð¸Ñ\81емлеге.",
        "recentchanges-feed-description": "Бу агымда соңгы үзгәртүләрне күзәтү.",
        "recentchanges-label-newpage": "Бу үзгәртү белән яңа бит төзелгән",
        "recentchanges-label-minor": "Бу кече үзгәртү",
        "recentchanges-label-unpatrolled": "Үзгәртүне әлегә тикшермәгәннәр",
        "recentchanges-label-plusminus": "Битнең зурлыгы шуның кадәрле байтка үзгәрде",
        "recentchanges-legend-heading": "'''Легенда:&nbsp;'''",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|яңа бит]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (шулай ук [[Special:NewPages|яңа битләр исемлеген]] карагыз)",
        "rcnotefrom": "Астарак <strong>$3, $4</strong> өчен {{PLURAL:$5|үзгәртүләр күрсәтелгән}} (<strong>$1</strong> артык түгел).",
        "rclistfrom": "$3 $2 башлап яңа үзгәртүләрне күрсәт",
        "rcshowhideminor": "кече үзгәртүләрне $1",
-       "rcshowhideminor-show": "күÑ\80Ñ\81Ó\99Ñ\82",
+       "rcshowhideminor-show": "Ð\9aÒ¯Ñ\80Ñ\81Ó\99Ñ\82Ò¯",
        "rcshowhideminor-hide": "Яшер",
        "rcshowhidebots": "ботларны $1",
        "rcshowhidebots-show": "Күрсәт",
-       "rcshowhidebots-hide": "яшер",
+       "rcshowhidebots-hide": "Яшер",
        "rcshowhideliu": "теркәлгән кулланучыларны $1",
        "rcshowhideliu-show": "күрсәт",
        "rcshowhideliu-hide": "Яшер",
        "rcshowhideanons": "кермәгән кулланучыларны $1",
-       "rcshowhideanons-show": "күÑ\80Ñ\81Ó\99Ñ\82",
+       "rcshowhideanons-show": "Ð\9aÒ¯Ñ\80Ñ\81Ó\99Ñ\82Ò¯",
        "rcshowhideanons-hide": "Яшер",
        "rcshowhidepatr": "тикшерелгән үзгәртүләрне $1",
        "rcshowhidepatr-hide": "яшер",
        "rcshowhidemine": "минем үзгәртүләремне $1",
-       "rcshowhidemine-show": "күÑ\80Ñ\81Ó\99Ñ\82",
+       "rcshowhidemine-show": "Ð\9aÒ¯Ñ\80Ñ\81Ó\99Ñ\82Ò¯",
        "rcshowhidemine-hide": "Яшер",
        "rclinks": "Соңгы $2 көн эчендә соңгы $1 үзгәртүне күрсәт<br />$3",
        "diff": "аерма",
        "whatlinkshere-prev": "{{PLURAL:$1|1=алдагы}} $1",
        "whatlinkshere-next": "{{PLURAL:$1|1=киләсе}} $1",
        "whatlinkshere-links": "← сылтамалар",
-       "whatlinkshere-hideredirs": "юнәлтүләрне $1",
-       "whatlinkshere-hidetrans": "кертүләрне $1",
-       "whatlinkshere-hidelinks": "сылтамаларны $1",
+       "whatlinkshere-hideredirs": "Юнәлтүләрне $1",
+       "whatlinkshere-hidetrans": "Ð\9aертүләрне $1",
+       "whatlinkshere-hidelinks": "Сылтамаларны $1",
        "whatlinkshere-hideimages": "$1 файл сылтамалары",
        "whatlinkshere-filters": "Фильтрлар",
        "blockip": "{{GENDER:$1|Кулланучыны}} тыю",
        "tooltip-pt-mytalk": "Бәхәс битегез",
        "tooltip-pt-preferences": "Көйләнмәләрегез",
        "tooltip-pt-watchlist": "Сез күзәтелгән төзәтмәле битләр исемлеге",
-       "tooltip-pt-mycontris": "Сезнең ÐºÐµÑ\80Ñ\82еменгезне Ð¸Ñ\81емлеге",
+       "tooltip-pt-mycontris": "Сезнең ÐºÐµÑ\80Ñ\82емегез",
        "tooltip-pt-login": "Сез хисап язмасы төзи алыр идегез, әмма бу мәҗбүри түгел.",
        "tooltip-pt-logout": "Чыгу",
        "tooltip-pt-createaccount": "Сезгә аккаунт ясарга һәм системага керергә киңәш итәбез, әмма бу мәҗбүри түгел.",
        "tooltip-t-recentchangeslinked": "Бу биттән сылтаган битләрдә ахыргы үзгәртүләр",
        "tooltip-feed-rss": "Бу бит өчен RSS трансляциясе",
        "tooltip-feed-atom": "Бу бит өчен Atom трансляциясе",
-       "tooltip-t-contributions": "Ð\9aÑ\83лланÑ\83Ñ\87Ñ\8b ÐºÐµÑ\80Ñ\82еменең Ð¸Ñ\81емлегене карау",
+       "tooltip-t-contributions": "Ð\91Ñ\83 ÐºÑ\83лланÑ\83Ñ\87Ñ\8bнÑ\8bÒ£ ÐºÐµÑ\80Ñ\82ем Ð¸Ñ\81емлеген карау",
        "tooltip-t-emailuser": "Бу кулланучыга хат җибәрү",
        "tooltip-t-upload": "Файлларны йөкләү",
        "tooltip-t-specialpages": "Барлык махсус битләр исемлеге",
        "specialpages-group-users": "Кулланучылар һәм аларның хокуклары",
        "specialpages-group-highuse": "Еш кулланылучы битләр",
        "specialpages-group-pages": "Битләр исемлеге",
-       "specialpages-group-pagetools": "Бит өчен җиһазлар",
+       "specialpages-group-pagetools": "Бит өчен кораллар",
        "specialpages-group-wiki": "Мәгълүмат һәм җиһазлар",
        "specialpages-group-redirects": "Күчерелүче махсус битләр",
        "specialpages-group-spam": "Спамга каршы кораллар",
        "revdelete-uname-unhid": "кулланучының исеме ачылган",
        "revdelete-restricted": "чикләүләр идарәчеләргә дә кулланыла",
        "revdelete-unrestricted": "чикләүләр идарәчеләр өчен бетерелгән",
-       "logentry-move-move": "$1 $3 сәхифәсен $4 {{GENDER:$2|итеп күчерде}}",
+       "logentry-move-move": "$1  $3 битенең исемен {{GENDER:$2| үзгәртте}}. Яңа исеме: $4",
        "logentry-move-move-noredirect": "$1 юнәлтү калдырмыйча $3 сәхифәсен $4 итеп күчерде",
        "logentry-move-move_redir": "$1 юнәлтү аша $3 сәхифәсен $4 итеп күчерде",
        "logentry-move-move_redir-noredirect": "$1 юнәлтү аша, юнәлтү калдырмыйча $3 сәхифәсен $4 итеп күчерде",
index e10c91e..445856f 100644 (file)
        "nstab-template": "قېلىپ",
        "nstab-help": "ياردەم بەت",
        "nstab-category": "تۈر",
+       "mainpage-nstab": "باش بەت",
        "nosuchaction": "بۇنداق مەشغۇلات يوق",
        "nosuchactiontext": "بۇ مەشغۇلات بېكىتكەن URL ئىناۋەتسىز.\n\nURL نى خاتا كىرگۈزۈپ قالدىڭىز ياكى خاتا ئۇلانمىغا ئەگەشتىڭىز.\n\n {{SITENAME}} بېكەت يۇمشاق دېتالىنىڭ خاتالىقى بولۇشى مۇمكىن.",
        "nosuchspecialpage": "بۇنىڭغا ئوخشاش ئالاھىدە بەت يوق",
        "createaccountreason": "سەۋەب:",
        "createacct-reason": "سەۋەبى",
        "createacct-reason-ph": "نېمىشقا باشقا ھېسابات قۇرماقچى بولدىڭىز",
-       "createacct-captcha": "بىخەتەرلىك تەكشۈرۈشى",
-       "createacct-imgcaptcha-ph": "ئۈستىدە كۆرگىنىڭىزنى كىرگۈزۈڭ",
        "createacct-submit": "ھېساباتىڭىزنى قۇرۇڭ",
        "createacct-another-submit": "باشقا ھېسابات قۇرىمەن",
        "createacct-benefit-heading": "{{SITENAME}} سىزگە ئوخشاش كىشىلەر تەرىپىدىن قۇرۇلغان.",
        "loginlanguagelabel": "تىل: $1",
        "suspicious-userlogout": "تىزىمدىن چىقىش ئىلتىماسىڭىز رەت قىلىندى، چۈنكى ئۇ بەلكىم بۇزۇلغان توركۆرگۈ ياكى غەملەك ۋاكالەتچىسى يوللىغان بولۇشى مۇمكىن.",
        "createacct-another-realname-tip": "ھەقىقىي ئىسمىڭىز ئىختىيارى.\nئەگەر تەمىنلەشنى تاللىسىڭىز، ئۇ سىزنىڭ تۆھپىڭىزنىڭ ئىمزاسى بولىدۇ.",
+       "pt-login": "تىزىمغا كىرىڭ",
+       "pt-createaccount": "ھېسابات قۇر",
        "php-mail-error-unknown": "PHP نىڭ mail() فونكسىيەسىدىكى يوچۇن خاتالىق",
        "user-mail-no-addy": "ئېلخەت ئادرېسسىز خەت يوللاشنى سىنىدى.",
        "user-mail-no-body": "بوش ياكى مەزمۇنى قىسقا مۇۋاپىق بولمىغان تورخەت ئەۋەتىشنى سىنىدى.",
        "passwordreset-emailtext-ip": "باشقىلار (بەلكىم سىز، IP ئادرېسى $1) {{SITENAME}} ($4) دىكى پارولنى قايتا بېكىتىشنى ئىلتىماس قىلدى. تۆۋەندىكى ئىشلەتكۈچىنىڭ {{PLURAL:$3|ھېسابات|ھېسابات}}ى مۇشۇ ئېلخەتكە باغلانغان:\n\n$2\n\n{{PLURAL:$3|بۇ ۋاقىتلىق پارول|بۇ ۋاقىتلىق پارول}} {{PLURAL:$5|بىر كۈن|$5 كۈن}}دە ۋاقتى ئۆتىدۇ. ئەگەر بۇ مەشغۇلاتنى سىز ئىلتىماس قىلغان بولسىڭىز، دەرھال تىزىمغا كىرىپ يېڭى پارولدىن بىرنى تاللاڭ.\nسىز بەلگىلىگەن يېڭى پارول  {{PLURAL:$5|كۈن|$5 كۈن}}دە ۋاقتى توشىدۇ. ئەگەر باشقىلار ئىلتىماس قىلغان بولسا ياكى ئۆزىڭىز بەلگىلىگەن پارول ئېسىڭىزگە كېلىپ ئۇنى ئۆزگەرتمىسىڭىز، \nبۇ ئۇچۇرغا پەرۋا قىلماي ئۆزىڭىزنىڭ كونا پارولىنى ئىشلىتىۋېرىڭ.",
        "passwordreset-emailtext-user": "{{SITENAME}} دىكى ئىشلەتكۈچى $1 بېكەت {{SITENAME}} ($4) دىكى پارولىڭىزنى قايتا بېكىتىشنى ئىلتىماس قىلدى .\nتۆۋەندىكى ئىشلەتكۈچىنىڭ {{PLURAL:$3|ھېسابات|ھېسابات}}($4)ى مۇشۇ ئېلخەتكە باغلانغان:\n\n$2\n\n{{PLURAL:$3|بۇ ۋاقىتلىق پارول|بۇ ۋاقىتلىق پارول}} {{PLURAL:$5|بىر كۈن|$5 كۈن}}دە ۋاقتى ئۆتىدۇ. ئەگەر بۇ مەشغۇلاتنى سىز ئىلتىماس قىلغان بولسىڭىز، دەرھال تىزىمغا كىرىپ يېڭى پارولدىن بىرنى تاللاڭ.\nسىز بەلگىلىگەن يېڭى پارول {{PLURAL:$5|كۈن|$5 كۈن}}دە ۋاقتى توشىدۇ. ئەگەر باشقىلار ئىلتىماس قىلغان بولسا ياكى ئۆزىڭىز بەلگىلىگەن پارول ئېسىڭىزگە كېلىپ ئۇنى ئۆزگەرتمىسىڭىز، \nبۇ ئۇچۇرغا پەرۋا قىلماي ئۆزىڭىزنىڭ كونا پارولىنى ئىشلىتىۋېرىڭ.",
        "passwordreset-emailelement": "ئىشلەتكۈچى نامى: \n$1\n\nۋاقىتلىق پارول: \n$2",
-       "passwordreset-emailsent": "پارولنى قايتا بېكىتىش ئېلخېتى يوللاندى.",
+       "passwordreset-emailsentemail": "پارولنى قايتا بېكىتىش ئېلخېتى يوللاندى.",
        "passwordreset-emailsent-capture": "پارولنى قايتا بېكىتىش ئېلخېتى يوللاندى، تۆۋەندە كۆرسىتىلىدۇ.",
        "passwordreset-emailerror-capture": "ھاسىل قىلىنغان پارولنى قايتا بېكىتىش ئېلخېتى تۆۋەندە كۆرسىتىلگەندەك ئەمما ئۇنى {{GENDER:$2|ئىشلەتكۈچى}}گە يوللىيالمىدى: $1",
        "changeemail": "ئېلخەت ئادرېس ئۆزگەرت",
-       "changeemail-text": "بۇ جەدۋەل تاماملانسا ئېلخەت ئادرېسىڭىزنى ئۆزگەرتىدۇ. سىز ئىم كىرگۈزۈپ بۇ ئۆزگەرتىشنى جەزملەيسىز.",
+       "changeemail-header": "ھېساباتنىڭ ئېلخەت ئادرېسىنى ئۆزگەرت",
        "changeemail-no-info": "سىز تىزىمغا كىرگەندىن كېيىن بىۋاسىتە بۇ بەتكە كىرىشىڭىز لازىم.",
        "changeemail-oldemail": "نۆۋەتتىكى ئېلخەت ئادرېسى:",
        "changeemail-newemail": "يېڭى ئېلخەت ئادرېسى:",
        "prefs-tokenwatchlist": "ئاچقۇچ",
        "prefs-diffs": "پەرقلەر",
        "prefs-help-prefershttps": "بۇ سەپلەك، سىز قايتا تىزىمغا كىرگەندە ئىشلەيدۇ.",
-       "email-address-validity-valid": "ئېلخەت ئادرېسى ئىناۋەتلىك",
-       "email-address-validity-invalid": "ئىناۋەتلىك ئېلخەت ئادرېسىدىن بىرنى كىرگۈزۈڭ",
        "userrights": "ئىشلەتكۈچى ھوقۇقى باشقۇرۇش",
        "userrights-lookup-user": "ئىشلەتكۈچى گۇرۇپپىسى باشقۇرۇش",
        "userrights-user-editname": "ئىشلەتكۈچى ئاتى كىرگۈزۈڭ:",
        "wlheader-showupdated": "سىز ئالدىنقى قېتىم كۆرگەندىن كېيىن ئۆزگەرتىلگەن بەتلەر '''توم''' كۆرۈنىدۇ",
        "wlnote": "تۆۋەندىكىسى يېقىنقى {{PLURAL:$2|سائەت}} ئىچىدىكى ئاخىرقى '{{PLURAL:$1| قېتىملىق}}  ئۆزگەرتىش، $3 $4 گىچە.",
        "wlshowlast": "يېقىنقى $1 سائەت $2 كۈن  نىڭ ئۆزگەرتىشىنى كۆرسەت",
+       "watchlistall2": "ھەممىسى",
        "watchlist-options": "كۆزەت تىزىملىك تاللانما",
        "watching": "كۆزەت قىلىۋاتىدۇ…",
        "unwatching": "كۆزەت قىلمايۋاتىدۇ…",
        "movenosubpage": "بۇ بەتنىڭ تارماق بېتى يوق",
        "movereason": "سەۋەب:",
        "revertmove": "قايتۇر",
-       "delete_and_move": "ئۆچۈرۈپ يۆتكە",
        "delete_and_move_text": "== ئۆچۈرۈش زۆرۈر ==\nنىشان بەت \"[[:$1]]\" مەۋجۇد.\nيۆتكەشكە قولاي بولۇشى ئۈچۈن بۇ بەتنى ئۆچۈرەمسىز؟",
        "delete_and_move_confirm": "ھەئە، بۇ بەتنى ئۆچۈر",
        "delete_and_move_reason": " \"[[$1]]\" يۆتكەشكە قولاي بولۇشى ئۈچۈن ئۆچۈرۈۋېتىلدى",
        "tooltip-pt-mycontris": "تۆھپە تىزىملىكىڭىز",
        "tooltip-pt-login": "تىزىمغا كىرىشىڭىزنى تەۋسىيە قىلىمىز ئەمما مەجبۇرىي ئەمەس",
        "tooltip-pt-logout": "تىزىمدىن چىق",
+       "tooltip-pt-createaccount": "ھېساباتتىن بىرنى قۇرۇپ تىزىمغا كىرىشىڭىزنى تەۋسىيە قىلىمىز، ئەمما بۇ مەجبۇرىي ئەمەس.",
        "tooltip-ca-talk": "بەت مەزمۇنى ھەققىدىكى مۇنازىرە",
        "tooltip-ca-edit": "بۇ بەتنى تەھرىرلىيەلەيسىز.\nساقلاشتىن ئىلگىرى ئالدىن كۆزەت كۇنۇپكىسىنى ئىشلىتىڭ",
        "tooltip-ca-addsection": "يېڭى بىر مۇنازىرە باشلاڭ",
index e90812d..291920b 100644 (file)
@@ -109,6 +109,9 @@ abstract class Maintenance {
         */
        private $mDb = null;
 
+       /** @var float UNIX timestamp */
+       private $lastSlaveWait = 0.0;
+
        /**
         * Used when creating separate schema files.
         * @var resource
@@ -1178,21 +1181,27 @@ abstract class Maintenance {
        }
 
        /**
-        * Commit a transcation on a DB
+        * Commit the transcation on a DB handle and wait for slaves to catch up
         *
         * This method makes it clear that commit() is called from a maintenance script,
         * which has outermost scope. This is safe, unlike $dbw->commit() called in other places.
         *
         * @param IDatabase $dbw
         * @param string $fname Caller name
+        * @return bool Whether the slave wait succeeded
         * @since 1.27
         */
        protected function commitTransaction( IDatabase $dbw, $fname ) {
                $dbw->commit( $fname );
+
+               $ok = wfWaitForSlaves( $this->lastSlaveWait, false, '*', 30 );
+               $this->lastSlaveWait = microtime( true );
+
+               return $ok;
        }
 
        /**
-        * Rollback a transcation on a DB
+        * Rollback the transcation on a DB handle
         *
         * This method makes it clear that rollback() is called from a maintenance script,
         * which has outermost scope. This is safe, unlike $dbw->rollback() called in other places.
index 608605c..2de2e22 100644 (file)
@@ -116,6 +116,13 @@ class ConvertExtensionToRegistration extends Maintenance {
                        }
                }
 
+               // check, if the extension requires composer libraries
+               if ( $this->needsComposerAutoloader( dirname( $this->getArg( 0 ) ) ) ) {
+                       // set the load composer autoloader automatically property
+                       $this->output( "Detected composer dependencies, setting 'load_composer_autoloader' to true.\n" );
+                       $this->json['load_composer_autoloader'] = true;
+               }
+
                // Move some keys to the top
                $out = array();
                foreach ( $this->promote as $key ) {
@@ -246,6 +253,19 @@ class ConvertExtensionToRegistration extends Maintenance {
                        $this->json['ResourceFileModulePaths'] = $defaults;
                }
        }
+
+       protected function needsComposerAutoloader( $path ) {
+               $path .= '/composer.json';
+               if ( file_exists( $path ) ) {
+                       // assume, that the composer.json file is in the root of the extension path
+                       $composerJson = new ComposerJson( $path );
+                       // check, if there are some dependencies in the require section
+                       if ( $composerJson->getRequiredDependencies() ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
 }
 
 $maintClass = 'ConvertExtensionToRegistration';
index 6b5792a..18737a4 100644 (file)
@@ -95,13 +95,10 @@ TEXT;
                $this->reporting = !$this->hasOption( 'quiet' );
 
                if ( $this->hasOption( 'pagelist' ) ) {
-                       $olddir = getcwd();
-                       chdir( $originalDir );
-                       $pages = file( $this->getOption( 'quiet' ) );
-                       chdir( $olddir );
+                       $filename = $this->getOption( 'pagelist' );
+                       $pages = file( $filename );
                        if ( $pages === false ) {
-                               echo "Unable to open file {$options['pagelist']}\n";
-                               die( 1 );
+                               $this->fatalError( "Unable to open file {$filename}\n" );
                        }
                        $pages = array_map( 'trim', $pages );
                        $this->pages = array_filter( $pages, create_function( '$x', 'return $x !== "";' ) );
diff --git a/maintenance/waitForSlave.php b/maintenance/waitForSlave.php
deleted file mode 100644 (file)
index 50665ef..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-/**
- * Wait for the slaves to catch up to the master position.
- *
- * 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
- * @ingroup Maintenance
- * @see wfWaitForSlaves()
- */
-
-require_once __DIR__ . '/Maintenance.php';
-
-/**
- * Maintenance script to wait for the slaves to catch up to the master position.
- *
- * @ingroup Maintenance
- */
-class WaitForSlave extends Maintenance {
-       public function execute() {
-               wfWaitForSlaves();
-       }
-}
-
-$maintClass = "WaitForSlave";
-require_once RUN_MAINTENANCE_IF_MAIN;
index 84df938..74eaac5 100644 (file)
@@ -15,9 +15,9 @@
     "grunt-jscs": "2.5.0",
     "grunt-jsonlint": "1.0.7",
     "grunt-karma": "0.12.1",
-    "karma": "0.13.10",
-    "karma-chrome-launcher": "0.2.0",
-    "karma-firefox-launcher": "0.1.6",
+    "karma": "0.13.19",
+    "karma-chrome-launcher": "0.2.2",
+    "karma-firefox-launcher": "0.1.7",
     "karma-qunit": "0.1.5",
     "qunitjs": "1.18.0"
   }
index afa7201..519e39b 100644 (file)
@@ -5,8 +5,8 @@
  */
 ( function ( $, mw ) {
 
-// Cached access key prefix for used browser
-var cachedAccessKeyPrefix,
+// Cached access key modifiers for used browser
+var cachedAccessKeyModifiers,
 
        // Whether to use 'test-' instead of correct prefix (used for testing)
        useTestPrefix = false,
@@ -16,37 +16,39 @@ var cachedAccessKeyPrefix,
        labelable = 'button, input, textarea, keygen, meter, output, progress, select';
 
 /**
- * Get the prefix for the access key for browsers that don't support accessKeyLabel.
+ * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
  *
+ * The result is dependant on the ua paramater or the current platform.
  * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
+ * Valid key values that are returned can be: ctrl, alt, option, shift, esc
  *
  * @private
  * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
- * @return {string} Access key prefix
+ * @return {Array} Array with 0 or more of the string values: ctrl, option, alt, shift, esc
  */
-function getAccessKeyPrefix( ua ) {
+function getAccessKeyModifiers( ua ) {
        // use cached prefix if possible
-       if ( !ua && cachedAccessKeyPrefix ) {
-               return cachedAccessKeyPrefix;
+       if ( !ua && cachedAccessKeyModifiers ) {
+               return cachedAccessKeyModifiers;
        }
 
        var profile = $.client.profile( ua ),
-               accessKeyPrefix = 'alt-';
+               accessKeyModifiers = [ 'alt' ];
 
        // Classic Opera on any platform
        if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
-               accessKeyPrefix = 'shift-esc-';
+               accessKeyModifiers = [ 'shift', 'esc' ];
 
        // Chrome and modern Opera on any platform
        } else if ( profile.name === 'chrome' || profile.name === 'opera' ) {
-               accessKeyPrefix = (
+               accessKeyModifiers = (
                        profile.platform === 'mac'
                                // Chrome on Mac
-                               ? 'ctrl-option-'
+                               ? [ 'ctrl', 'option' ]
                                // Chrome on Windows or Linux
                                // (both alt- and alt-shift work, but alt with E, D, F etc does not
                                // work since they are browser shortcuts)
-                               : 'alt-shift-'
+                               : [ 'alt', 'shift' ]
                );
 
        // Non-Windows Safari with webkit_version > 526
@@ -54,7 +56,7 @@ function getAccessKeyPrefix( ua ) {
                && profile.name === 'safari'
                && profile.layoutVersion > 526
        ) {
-               accessKeyPrefix = 'ctrl-alt-';
+               accessKeyModifiers = [ 'ctrl', 'alt' ];
 
        // Safari/Konqueror on any platform, or any browser on Mac
        // (but not Safari on Windows)
@@ -63,27 +65,27 @@ function getAccessKeyPrefix( ua ) {
                || profile.platform === 'mac'
                || profile.name === 'konqueror' )
        ) {
-               accessKeyPrefix = 'ctrl-';
+               accessKeyModifiers = [ 'ctrl' ];
 
        // Firefox/Iceweasel 2.x and later
        } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' )
                && profile.versionBase > '1'
        ) {
-               accessKeyPrefix = 'alt-shift-';
+               accessKeyModifiers = [ 'alt', 'shift' ];
        }
 
-       // cache prefix
+       // cache modifiers
        if ( !ua ) {
-               cachedAccessKeyPrefix = accessKeyPrefix;
+               cachedAccessKeyModifiers = accessKeyModifiers;
        }
-       return accessKeyPrefix;
+       return accessKeyModifiers;
 }
 
 /**
  * Get the access key label for an element.
  *
  * Will use native accessKeyLabel if available (currently only in Firefox 8+),
- * falls back to #getAccessKeyPrefix.
+ * falls back to #getAccessKeyModifiers.
  *
  * @private
  * @param {HTMLElement} element Element to get the label for
@@ -99,7 +101,7 @@ function getAccessKeyLabel( element ) {
        if ( !useTestPrefix && element.accessKeyLabel ) {
                return element.accessKeyLabel;
        }
-       return ( useTestPrefix ? 'test-' : getAccessKeyPrefix() ) + element.accessKey;
+       return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
 }
 
 /**
@@ -175,12 +177,30 @@ $.fn.updateTooltipAccessKeys = function () {
 };
 
 /**
- * Exposed for testing.
+ * getAccessKeyModifiers
+ *
+ * @method updateTooltipAccessKeys_getAccessKeyModifiers
+ * @inheritdoc #getAccessKeyModifiers
+ */
+$.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
+
+/**
+ * getAccessKeyLabel
+ *
+ * @method updateTooltipAccessKeys_getAccessKeyLabel
+ * @inheritdoc #getAccessKeyLabel
+ */
+$.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
+
+/**
+ * getAccessKeyPrefix
  *
  * @method updateTooltipAccessKeys_getAccessKeyPrefix
- * @inheritdoc #getAccessKeyPrefix
+ * @deprecated 1.27 Use #getAccessKeyModifiers
  */
-$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = getAccessKeyPrefix;
+$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
+       return getAccessKeyModifiers( ua ).join( '-' ) + '-';
+};
 
 /**
  * Switch test mode on and off.
index 013d34f..1d4d0e9 100644 (file)
                                        );
                                }
                                if ( response.parse.categorieshtml ) {
-                                       $( '#catlinks' ).replaceWith( response.parse.categorieshtml[ '*' ] );
+                                       $content = $( $.parseHTML( response.parse.categorieshtml[ '*' ] ) );
+                                       mw.hook( 'wikipage.categories' ).fire( $content );
+                                       $( '.catlinks[data-mw="interface"]' ).replaceWith( $content );
                                }
                                if ( response.parse.templates ) {
                                        newList = [];
index 69655a6..68fb2aa 100644 (file)
@@ -1,52 +1,54 @@
-/*global OO*/
+/*global OO */
 ( function ( mw, $ ) {
        /**
-        * This is a factory for MessagePoster objects, which allows a pluggable to way to script leaving a
-        * talk page message.
+        * Factory for MessagePoster objects. This provides a pluggable to way to script the action
+        * of adding a message to someone's talk page.
         *
         * @class mw.messagePoster.factory
         * @singleton
         */
-       function MwMessagePosterFactory() {
+       function MessagePosterFactory() {
                this.contentModelToClass = {};
        }
 
-       OO.initClass( MwMessagePosterFactory );
+       OO.initClass( MessagePosterFactory );
 
        // Note: This registration scheme is currently not compatible with LQT, since that doesn't
-       // have its own content model, just islqttalkpage.  LQT pages will be passed to the wikitext
+       // have its own content model, just islqttalkpage. LQT pages will be passed to the wikitext
        // MessagePoster.
        /**
-        * Registers a MessagePoster subclass for a given content model.
+        * Register a MessagePoster subclass for a given content model.
         *
         * @param {string} contentModel Content model of pages this MessagePoster can post to
-        * @param {Function} messagePosterConstructor Constructor for MessagePoster
+        * @param {Function} constructor Constructor of a MessagePoster subclass
         */
-       MwMessagePosterFactory.prototype.register = function ( contentModel, messagePosterConstructor ) {
+       MessagePosterFactory.prototype.register = function ( contentModel, constructor ) {
                if ( this.contentModelToClass[ contentModel ] !== undefined ) {
-                       throw new Error( 'The content model \'' + contentModel + '\' is already registered.' );
+                       throw new Error( 'Content model "' + contentModel + '" is already registered' );
                }
 
-               this.contentModelToClass[ contentModel ] = messagePosterConstructor;
+               this.contentModelToClass[ contentModel ] = constructor;
        };
 
        /**
-        * Unregisters a given content model
-        * This is exposed for testing and should not normally be needed.
+        * Unregister a given content model.
+        * This is exposed for testing and should not normally be used.
         *
         * @param {string} contentModel Content model to unregister
         */
-       MwMessagePosterFactory.prototype.unregister = function ( contentModel ) {
+       MessagePosterFactory.prototype.unregister = function ( contentModel ) {
                delete this.contentModelToClass[ contentModel ];
        };
 
        /**
-        * Creates a MessagePoster, given a title.  A promise for this is returned.
-        * This works by determining the content model, then loading the corresponding
-        * module (which will register the MessagePoster class), and finally constructing it.
+        * Create a MessagePoster for given a title.
         *
-        * This does not require the message and should be called as soon as possible, so it does the
-        * API and ResourceLoader requests in the background.
+        * A promise for this is returned. It works by determining the content model, then loading
+        * the corresponding module (which registers the MessagePoster class), and finally constructing
+        * an object for the given title.
+        *
+        * This does not require the message and should be called as soon as possible, so that the
+        * API and ResourceLoader requests run in the background.
         *
         * @param {mw.Title} title Title that will be posted to
         * @param {string} [apiUrl] api.php URL if the title is on another wiki
         *   - error Error explanation
         *   - details Further error details
         */
-       MwMessagePosterFactory.prototype.create = function ( title, apiUrl ) {
-               var pageId, page, contentModel, moduleName, api,
-                       factory = this;
-
-               if ( apiUrl ) {
-                       api = new mw.ForeignApi( apiUrl );
-               } else {
-                       api = new mw.Api();
-               }
+       MessagePosterFactory.prototype.create = function ( title, apiUrl ) {
+               var factory = this,
+                       api = apiUrl ? new mw.ForeignApi( apiUrl ) : new mw.Api();
 
                return api.get( {
                        action: 'query',
                        prop: 'info',
                        indexpageids: true,
                        titles: title.getPrefixedDb()
-               } ).then( function ( result ) {
-                       if ( result.query.pageids && result.query.pageids.length > 0 ) {
-                               pageId = result.query.pageids[ 0 ];
-                               page = result.query.pages[ pageId ];
-
-                               contentModel = page.contentmodel;
-                               moduleName = 'mediawiki.messagePoster.' + contentModel;
-                               return mw.loader.using( moduleName ).then( function () {
-                                       return factory.createForContentModel(
-                                               contentModel,
-                                               title,
-                                               api
-                                       );
-                               }, function () {
-                                       return $.Deferred().reject( 'failed-to-load-module', 'Failed to load the \'' + moduleName + '\' module' );
-                               } );
-                       } else {
+               } ).then( function ( data ) {
+                       var pageId, page, contentModel, moduleName;
+                       if ( !data.query.pageids[ 0 ] ) {
                                return $.Deferred().reject( 'unexpected-response', 'Unexpected API response' );
                        }
-               }, function ( errorCode, details ) {
-                       return $.Deferred().reject( 'content-model-query-failed', errorCode, details );
-               } ).promise();
+                       pageId = data.query.pageids[ 0 ];
+                       page = data.query.pages[ pageId ];
+
+                       contentModel = page.contentmodel;
+                       moduleName = 'mediawiki.messagePoster.' + contentModel;
+                       return mw.loader.using( moduleName ).then( function () {
+                               return factory.createForContentModel(
+                                       contentModel,
+                                       title,
+                                       api
+                               );
+                       }, function () {
+                               return $.Deferred().reject( 'failed-to-load-module', 'Failed to load "' + moduleName + '"' );
+                       } );
+               }, function ( error, details ) {
+                       return $.Deferred().reject( 'content-model-query-failed', error, details );
+               } );
        };
 
        /**
         * Creates a MessagePoster instance, given a title and content model
         *
         * @private
-        *
         * @param {string} contentModel Content model of title
         * @param {mw.Title} title Title being posted to
         * @param {mw.Api} api mw.Api instance that the instance should use
         * @return {mw.messagePoster.MessagePoster}
         *
         */
-       MwMessagePosterFactory.prototype.createForContentModel = function ( contentModel, title, api ) {
+       MessagePosterFactory.prototype.createForContentModel = function ( contentModel, title, api ) {
                return new this.contentModelToClass[ contentModel ]( title, api );
        };
 
        mw.messagePoster = {
-               factory: new MwMessagePosterFactory()
+               factory: new MessagePosterFactory()
        };
 }( mediaWiki, jQuery ) );
index ddf7f2b..4806097 100644 (file)
@@ -43,7 +43,7 @@
                mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this )
                        .done( function () {
                                // Point the CategorySelector to the right wiki
-                               this.upload.apiPromise.done( function ( api ) {
+                               this.upload.getApi().done( function ( api ) {
                                        // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
                                        if ( api.apiUrl ) {
                                                // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
index 61fb59f..aa08b6c 100644 (file)
         * or to local uploads if no foreign target is configured.
         */
 
+       /**
+        * @inheritdoc
+        */
+       ForeignUpload.prototype.getApi = function () {
+               return this.apiPromise;
+       };
+
        /**
         * Override from mw.Upload to make sure the API info is found and allowed
         */
index 26eabc7..1cd9101 100644 (file)
         */
        mw.Upload.BookletLayout.prototype.initialize = function () {
                var
-                       apiPromise,
                        booklet = this,
                        deferred = $.Deferred();
 
                this.upload = this.createUpload();
                this.setPage( 'upload' );
 
-               apiPromise = this.upload.apiPromise || $.Deferred().resolve( this.upload.api );
-               apiPromise.done( function ( api ) {
+               this.upload.getApi().done( function ( api ) {
                        // If the user can't upload anything, don't give them the option to.
                        api.getUserInfo().done( function ( userInfo ) {
                                if ( userInfo.rights.indexOf( 'upload' ) === -1 ) {
index d80b4eb..8a74ffc 100644 (file)
 
        UP = Upload.prototype;
 
+       /**
+        * Get the mw.Api instance used by this Upload object.
+        *
+        * @return {jQuery.Promise}
+        * @return {Function} return.done
+        * @return {mw.Api} return.done.api
+        */
+       UP.getApi = function () {
+               return $.Deferred().resolve( this.api ).promise();
+       };
+
        /**
         * Set the text of the file page, to be created on file upload.
         *
index 514a3dd..d444923 100644 (file)
@@ -20,7 +20,7 @@
        function humanSize( bytes ) {
                if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; }
                var i = 0,
-                       units = [ '', ' kB', ' MB', ' GB', ' TB', ' PB' ];
+                       units = [ '', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB' ];
 
                for ( ; bytes >= 1024; bytes /= 1024 ) { i++; }
                // Maintain one decimal for kB and above, but don't
index 9505bdd..9b3458b 100644 (file)
@@ -36,7 +36,7 @@
 
        // Things outside the wikipage content
        $( function () {
-               var $nodes;
+               var $nodes, $oouiNodes;
 
                if ( !supportsPlaceholder ) {
                        // Exclude content to avoid hitting it twice for the (first) wikipage content
                $nodes.updateTooltipAccessKeys();
 
                // Infuse OOUI widgets, if any are present
-               $nodes = $( '[data-ooui]' );
-               if ( $nodes.length ) {
+               $oouiNodes = $( '[data-ooui]' );
+               if ( $oouiNodes.length ) {
                        // FIXME: We should only load the widgets that are being infused
                        mw.loader.using( [ 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget' ] ).done( function () {
-                               $nodes.each( function () {
+                               $oouiNodes.each( function () {
                                        OO.ui.infuse( this );
                                } );
                        } );
                }
 
+               $nodes = $( '.catlinks[data-mw="interface"]' );
+               if ( $nodes.length ) {
+                       /**
+                        * Fired when categories are being added to the DOM
+                        *
+                        * It is encouraged to fire it before the main DOM is changed (when $content
+                        * is still detached).  However, this order is not defined either way, so you
+                        * should only rely on $content itself.
+                        *
+                        * This includes the ready event on a page load (including post-edit loads)
+                        * and when content has been previewed with LivePreview.
+                        *
+                        * @event wikipage_categories
+                        * @member mw.hook
+                        * @param {jQuery} $content The most appropriate element containing the content,
+                        *   such as .catlinks
+                        */
+                       mw.hook( 'wikipage.categories' ).fire( $nodes );
+               }
        } );
 
 }( mediaWiki, jQuery ) );
index 1a10f83..0f51a35 100644 (file)
@@ -31,8 +31,8 @@ function isCompatible( ua ) {
 
        // Browsers with outdated or limited JavaScript engines get the no-JS experience
        return !(
-               // Internet Explorer < 8
-               ( ua.indexOf( 'MSIE' ) !== -1 && parseFloat( ua.split( 'MSIE' )[ 1 ] ) < 8 ) ||
+               // Internet Explorer < 9
+               ( ua.indexOf( 'MSIE' ) !== -1 && parseFloat( ua.split( 'MSIE' )[ 1 ] ) < 9 ) ||
                // Firefox < 3
                ( ua.indexOf( 'Firefox/' ) !== -1 && parseFloat( ua.split( 'Firefox/' )[ 1 ] ) < 3 ) ||
                // Opera < 12
index b91a5bc..6a6d447 100644 (file)
@@ -962,7 +962,7 @@ class ParserTest {
                        'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
                        'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
                        'site_stats', 'ipblocks', 'image', 'oldimage',
-                       'recentchanges', 'watchlist', 'interwiki', 'logging',
+                       'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
                        'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
                        'archive', 'user_groups', 'page_props', 'category'
                );
diff --git a/tests/phpunit/data/gitinfo/extension/gitinfo.json b/tests/phpunit/data/gitinfo/extension/gitinfo.json
new file mode 100644 (file)
index 0000000..8cf21bd
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "head": "refs/heads/master",
+    "headSHA1": "0123456789abcdef0123456789abcdef01234567",
+    "headCommitDate": "1070884800",
+    "branch": "master",
+    "remoteURL": "https://gerrit.wikimedia.org/r/mediawiki/core"
+}
index c3539d0..9f4a01c 100644 (file)
@@ -9,18 +9,23 @@ class GitInfoTest extends MediaWikiTestCase {
                $this->setMwGlobals( 'wgGitInfoCacheDirectory', __DIR__ . '/../data/gitinfo' );
        }
 
-       public function testValidJsonData() {
-               $dir = $GLOBALS['IP'] . DIRECTORY_SEPARATOR . 'testValidJsonData';
-               $fixture = new GitInfo( $dir );
-
-               $this->assertTrue( $fixture->cacheIsComplete() );
-               $this->assertEquals( 'refs/heads/master', $fixture->getHead() );
+       protected function assertValidGitInfo( GitInfo $gitInfo ) {
+               $this->assertTrue( $gitInfo->cacheIsComplete() );
+               $this->assertEquals( 'refs/heads/master', $gitInfo->getHead() );
                $this->assertEquals( '0123456789abcdef0123456789abcdef01234567',
-                       $fixture->getHeadSHA1() );
-               $this->assertEquals( '1070884800', $fixture->getHeadCommitDate() );
-               $this->assertEquals( 'master', $fixture->getCurrentBranch() );
+                       $gitInfo->getHeadSHA1() );
+               $this->assertEquals( '1070884800', $gitInfo->getHeadCommitDate() );
+               $this->assertEquals( 'master', $gitInfo->getCurrentBranch() );
                $this->assertContains( '0123456789abcdef0123456789abcdef01234567',
-                       $fixture->getHeadViewUrl() );
+                       $gitInfo->getHeadViewUrl() );
+
+       }
+
+       public function testValidJsonData() {
+               global $IP;
+
+               $this->assertValidGitInfo( new GitInfo( "$IP/testValidJsonData") );
+               $this->assertValidGitInfo( new GitInfo( __DIR__ . "/../data/gitinfo/extension" ) );
        }
 
        public function testMissingJsonData() {
diff --git a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php
deleted file mode 100644 (file)
index 1433b89..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-/**
- * Integration test that checks import success and
- * LinkCache integration.
- *
- * @group medium
- * @group Database
- *
- * @author mwjames
- */
-class ImportLinkCacheIntegrationTest extends MediaWikiTestCase {
-
-       private $importStreamSource;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $file = dirname( __DIR__ ) . '/data/import/ImportLinkCacheIntegrationTest.xml';
-
-               $this->importStreamSource = ImportStreamSource::newFromFile( $file );
-
-               if ( !$this->importStreamSource->isGood() ) {
-                       throw new Exception( "Import source for {$file} failed" );
-               }
-       }
-
-       public function testImportForImportSource() {
-
-               $this->doImport( $this->importStreamSource );
-
-               // Imported title
-               $loremIpsum = Title::newFromText( 'Lorem ipsum' );
-
-               $this->assertSame(
-                       $loremIpsum->getArticleID(),
-                       $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
-               );
-
-               $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' );
-
-               $this->assertSame(
-                       $categoryLoremIpsum->getArticleID(),
-                       $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
-               );
-
-               $page = new WikiPage( $loremIpsum );
-               $page->doDeleteArticle( 'import test: delete page' );
-
-               $page = new WikiPage( $categoryLoremIpsum );
-               $page->doDeleteArticle( 'import test: delete page' );
-       }
-
-       /**
-        * @depends testImportForImportSource
-        */
-       public function testReImportForImportSource() {
-
-               $this->doImport( $this->importStreamSource );
-
-               // ReImported title
-               $loremIpsum = Title::newFromText( 'Lorem ipsum' );
-
-               $this->assertSame(
-                       $loremIpsum->getArticleID(),
-                       $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
-               );
-
-               $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' );
-
-               $this->assertSame(
-                       $categoryLoremIpsum->getArticleID(),
-                       $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
-               );
-       }
-
-       private function doImport( $importStreamSource ) {
-
-               $importer = new WikiImporter(
-                       $importStreamSource->value,
-                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
-               );
-               $importer->setDebug( true );
-
-               $reporter = new ImportReporter(
-                       $importer,
-                       false,
-                       '',
-                       false
-               );
-
-               $reporter->setContext( new RequestContext() );
-               $reporter->open();
-               $exception = false;
-
-               try {
-                       $importer->doImport();
-               } catch ( Exception $e ) {
-                       $exception = $e;
-               }
-
-               $result = $reporter->close();
-
-               $this->assertFalse(
-                       $exception
-               );
-
-               $this->assertTrue(
-                       $result->isGood()
-               );
-       }
-
-}
diff --git a/tests/phpunit/includes/ImportTest.php b/tests/phpunit/includes/ImportTest.php
deleted file mode 100644 (file)
index 9c22430..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-<?php
-
-/**
- * Test class for Import methods.
- *
- * @group Database
- *
- * @author Sebastian Brückner < sebastian.brueckner@student.hpi.uni-potsdam.de >
- */
-class ImportTest extends MediaWikiLangTestCase {
-
-       private function getDataSource( $xml ) {
-               return new ImportStringSource( $xml );
-       }
-
-       /**
-        * @covers WikiImporter::handlePage
-        * @dataProvider getRedirectXML
-        * @param string $xml
-        * @param string|null $redirectTitle
-        */
-       public function testHandlePageContainsRedirect( $xml, $redirectTitle ) {
-               $source = $this->getDataSource( $xml );
-
-               $redirect = null;
-               $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount,
-                       $sRevCount, $pageInfo ) use ( &$redirect ) {
-                       if ( array_key_exists( 'redirect', $pageInfo ) ) {
-                               $redirect = $pageInfo['redirect'];
-                       }
-               };
-
-               $importer = new WikiImporter(
-                       $source,
-                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
-               );
-               $importer->setPageOutCallback( $callback );
-               $importer->doImport();
-
-               $this->assertEquals( $redirectTitle, $redirect );
-       }
-
-       public function getRedirectXML() {
-               // @codingStandardsIgnoreStart Generic.Files.LineLength
-               return array(
-                       array(
-                               <<< EOF
-<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
-       <page>
-               <title>Test</title>
-               <ns>0</ns>
-               <id>21</id>
-               <redirect title="Test22"/>
-               <revision>
-                       <id>20</id>
-                       <timestamp>2014-05-27T10:00:00Z</timestamp>
-                       <contributor>
-                               <username>Admin</username>
-                               <id>10</id>
-                       </contributor>
-                       <comment>Admin moved page [[Test]] to [[Test22]]</comment>
-                       <model>wikitext</model>
-                       <format>text/x-wiki</format>
-                       <text xml:space="preserve" bytes="20">#REDIRECT [[Test22]]</text>
-                       <sha1>tq456o9x3abm7r9ozi6km8yrbbc56o6</sha1>
-               </revision>
-       </page>
-</mediawiki>
-EOF
-                       ,
-                               'Test22'
-                       ),
-                       array(
-                               <<< EOF
-<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.9/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.9/ http://www.mediawiki.org/xml/export-0.9.xsd" version="0.9" xml:lang="en">
-       <page>
-               <title>Test</title>
-               <ns>0</ns>
-               <id>42</id>
-               <revision>
-                       <id>421</id>
-                       <timestamp>2014-05-27T11:00:00Z</timestamp>
-                       <contributor>
-                               <username>Admin</username>
-                               <id>10</id>
-                       </contributor>
-                       <text xml:space="preserve" bytes="4">Abcd</text>
-                       <sha1>n7uomjq96szt60fy5w3x7ahf7q8m8rh</sha1>
-                       <model>wikitext</model>
-                       <format>text/x-wiki</format>
-               </revision>
-       </page>
-</mediawiki>
-EOF
-                       ,
-                               null
-                       ),
-               );
-               // @codingStandardsIgnoreEnd
-       }
-
-       /**
-        * @covers WikiImporter::handleSiteInfo
-        * @dataProvider getSiteInfoXML
-        * @param string $xml
-        * @param array|null $namespaces
-        */
-       public function testSiteInfoContainsNamespaces( $xml, $namespaces ) {
-               $source = $this->getDataSource( $xml );
-
-               $importNamespaces = null;
-               $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) {
-                       $importNamespaces = $siteinfo['_namespaces'];
-               };
-
-               $importer = new WikiImporter(
-                       $source,
-                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
-               );
-               $importer->setSiteInfoCallback( $callback );
-               $importer->doImport();
-
-               $this->assertEquals( $importNamespaces, $namespaces );
-       }
-
-       public function getSiteInfoXML() {
-               // @codingStandardsIgnoreStart Generic.Files.LineLength
-               return array(
-                       array(
-                               <<< EOF
-<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
-  <siteinfo>
-    <namespaces>
-      <namespace key="-2" case="first-letter">Media</namespace>
-      <namespace key="-1" case="first-letter">Special</namespace>
-      <namespace key="0" case="first-letter" />
-      <namespace key="1" case="first-letter">Talk</namespace>
-      <namespace key="2" case="first-letter">User</namespace>
-      <namespace key="3" case="first-letter">User talk</namespace>
-      <namespace key="100" case="first-letter">Portal</namespace>
-      <namespace key="101" case="first-letter">Portal talk</namespace>
-    </namespaces>
-  </siteinfo>
-</mediawiki>
-EOF
-                       ,
-                               array(
-                                       '-2' => 'Media',
-                                       '-1' => 'Special',
-                                       '0' => '',
-                                       '1' => 'Talk',
-                                       '2' => 'User',
-                                       '3' => 'User talk',
-                                       '100' => 'Portal',
-                                       '101' => 'Portal talk',
-                               )
-                       ),
-               );
-               // @codingStandardsIgnoreEnd
-       }
-
-}
diff --git a/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/import/ImportLinkCacheIntegrationTest.php
new file mode 100644 (file)
index 0000000..5e3c626
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Integration test that checks import success and
+ * LinkCache integration.
+ *
+ * @group medium
+ * @group Database
+ *
+ * @author mwjames
+ */
+class ImportLinkCacheIntegrationTest extends MediaWikiTestCase {
+
+       private $importStreamSource;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $file = dirname( __DIR__ ) . '/../data/import/ImportLinkCacheIntegrationTest.xml';
+
+               $this->importStreamSource = ImportStreamSource::newFromFile( $file );
+
+               if ( !$this->importStreamSource->isGood() ) {
+                       throw new Exception( "Import source for {$file} failed" );
+               }
+       }
+
+       public function testImportForImportSource() {
+
+               $this->doImport( $this->importStreamSource );
+
+               // Imported title
+               $loremIpsum = Title::newFromText( 'Lorem ipsum' );
+
+               $this->assertSame(
+                       $loremIpsum->getArticleID(),
+                       $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
+               );
+
+               $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' );
+
+               $this->assertSame(
+                       $categoryLoremIpsum->getArticleID(),
+                       $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
+               );
+
+               $page = new WikiPage( $loremIpsum );
+               $page->doDeleteArticle( 'import test: delete page' );
+
+               $page = new WikiPage( $categoryLoremIpsum );
+               $page->doDeleteArticle( 'import test: delete page' );
+       }
+
+       /**
+        * @depends testImportForImportSource
+        */
+       public function testReImportForImportSource() {
+
+               $this->doImport( $this->importStreamSource );
+
+               // ReImported title
+               $loremIpsum = Title::newFromText( 'Lorem ipsum' );
+
+               $this->assertSame(
+                       $loremIpsum->getArticleID(),
+                       $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
+               );
+
+               $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' );
+
+               $this->assertSame(
+                       $categoryLoremIpsum->getArticleID(),
+                       $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE )
+               );
+       }
+
+       private function doImport( $importStreamSource ) {
+
+               $importer = new WikiImporter(
+                       $importStreamSource->value,
+                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+               $importer->setDebug( true );
+
+               $reporter = new ImportReporter(
+                       $importer,
+                       false,
+                       '',
+                       false
+               );
+
+               $reporter->setContext( new RequestContext() );
+               $reporter->open();
+               $exception = false;
+
+               try {
+                       $importer->doImport();
+               } catch ( Exception $e ) {
+                       $exception = $e;
+               }
+
+               $result = $reporter->close();
+
+               $this->assertFalse(
+                       $exception
+               );
+
+               $this->assertTrue(
+                       $result->isGood()
+               );
+       }
+
+}
diff --git a/tests/phpunit/includes/import/ImportTest.php b/tests/phpunit/includes/import/ImportTest.php
new file mode 100644 (file)
index 0000000..f4aac23
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * Test class for Import methods.
+ *
+ * @group Database
+ *
+ * @author Sebastian Brückner < sebastian.brueckner@student.hpi.uni-potsdam.de >
+ */
+class ImportTest extends MediaWikiLangTestCase {
+
+       private function getDataSource( $xml ) {
+               return new ImportStringSource( $xml );
+       }
+
+       /**
+        * @covers WikiImporter
+        * @dataProvider getUnknownTagsXML
+        * @param string $xml
+        * @param string $text
+        * @param string $title
+        */
+       public function testUnknownXMLTags( $xml, $text, $title ) {
+               $source = $this->getDataSource( $xml );
+
+               $importer = new WikiImporter(
+                       $source,
+                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+
+               $importer->doImport();
+               $title = Title::newFromText( $title );
+               $this->assertTrue( $title->exists() );
+
+               $this->assertEquals( WikiPage::factory( $title )->getContent()->getNativeData(), $text );
+       }
+
+       public function getUnknownTagsXML() {
+               // @codingStandardsIgnoreStart Generic.Files.LineLength
+               return array(
+                       array(
+                               <<< EOF
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
+  <page unknown="123" dontknow="533">
+    <title>TestImportPage</title>
+    <unknowntag>Should be ignored</unknowntag>
+    <ns>0</ns>
+    <id unknown="123" dontknow="533">14</id>
+    <revision>
+      <id unknown="123" dontknow="533">15</id>
+      <unknowntag>Should be ignored</unknowntag>
+      <timestamp>2016-01-03T11:18:43Z</timestamp>
+      <contributor>
+        <unknowntag>Should be ignored</unknowntag>
+        <username unknown="123" dontknow="533">Admin</username>
+        <id>1</id>
+      </contributor>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
+      <text xml:space="preserve" bytes="0">noitazinagro tseb eht si ikiWaideM</text>
+      <sha1>phoiac9h4m842xq45sp7s6u21eteeq1</sha1>
+      <unknowntag>Should be ignored</unknowntag>
+    </revision>
+  </page>
+  <unknowntag>Should be ignored</unknowntag>
+</mediawiki>
+EOF
+                               ,
+                               'noitazinagro tseb eht si ikiWaideM',
+                               'TestImportPage'
+                       )
+               );
+               // @codingStandardsIgnoreEnd
+       }
+
+       /**
+        * @covers WikiImporter::handlePage
+        * @dataProvider getRedirectXML
+        * @param string $xml
+        * @param string|null $redirectTitle
+        */
+       public function testHandlePageContainsRedirect( $xml, $redirectTitle ) {
+               $source = $this->getDataSource( $xml );
+
+               $redirect = null;
+               $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount,
+                       $sRevCount, $pageInfo ) use ( &$redirect ) {
+                       if ( array_key_exists( 'redirect', $pageInfo ) ) {
+                               $redirect = $pageInfo['redirect'];
+                       }
+               };
+
+               $importer = new WikiImporter(
+                       $source,
+                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+               $importer->setPageOutCallback( $callback );
+               $importer->doImport();
+
+               $this->assertEquals( $redirectTitle, $redirect );
+       }
+
+       public function getRedirectXML() {
+               // @codingStandardsIgnoreStart Generic.Files.LineLength
+               return array(
+                       array(
+                               <<< EOF
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
+       <page>
+               <title>Test</title>
+               <ns>0</ns>
+               <id>21</id>
+               <redirect title="Test22"/>
+               <revision>
+                       <id>20</id>
+                       <timestamp>2014-05-27T10:00:00Z</timestamp>
+                       <contributor>
+                               <username>Admin</username>
+                               <id>10</id>
+                       </contributor>
+                       <comment>Admin moved page [[Test]] to [[Test22]]</comment>
+                       <model>wikitext</model>
+                       <format>text/x-wiki</format>
+                       <text xml:space="preserve" bytes="20">#REDIRECT [[Test22]]</text>
+                       <sha1>tq456o9x3abm7r9ozi6km8yrbbc56o6</sha1>
+               </revision>
+       </page>
+</mediawiki>
+EOF
+                       ,
+                               'Test22'
+                       ),
+                       array(
+                               <<< EOF
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.9/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.9/ http://www.mediawiki.org/xml/export-0.9.xsd" version="0.9" xml:lang="en">
+       <page>
+               <title>Test</title>
+               <ns>0</ns>
+               <id>42</id>
+               <revision>
+                       <id>421</id>
+                       <timestamp>2014-05-27T11:00:00Z</timestamp>
+                       <contributor>
+                               <username>Admin</username>
+                               <id>10</id>
+                       </contributor>
+                       <text xml:space="preserve" bytes="4">Abcd</text>
+                       <sha1>n7uomjq96szt60fy5w3x7ahf7q8m8rh</sha1>
+                       <model>wikitext</model>
+                       <format>text/x-wiki</format>
+               </revision>
+       </page>
+</mediawiki>
+EOF
+                       ,
+                               null
+                       ),
+               );
+               // @codingStandardsIgnoreEnd
+       }
+
+       /**
+        * @covers WikiImporter::handleSiteInfo
+        * @dataProvider getSiteInfoXML
+        * @param string $xml
+        * @param array|null $namespaces
+        */
+       public function testSiteInfoContainsNamespaces( $xml, $namespaces ) {
+               $source = $this->getDataSource( $xml );
+
+               $importNamespaces = null;
+               $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) {
+                       $importNamespaces = $siteinfo['_namespaces'];
+               };
+
+               $importer = new WikiImporter(
+                       $source,
+                       ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+               $importer->setSiteInfoCallback( $callback );
+               $importer->doImport();
+
+               $this->assertEquals( $importNamespaces, $namespaces );
+       }
+
+       public function getSiteInfoXML() {
+               // @codingStandardsIgnoreStart Generic.Files.LineLength
+               return array(
+                       array(
+                               <<< EOF
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en">
+  <siteinfo>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+    </namespaces>
+  </siteinfo>
+</mediawiki>
+EOF
+                       ,
+                               array(
+                                       '-2' => 'Media',
+                                       '-1' => 'Special',
+                                       '0' => '',
+                                       '1' => 'Talk',
+                                       '2' => 'User',
+                                       '3' => 'User talk',
+                                       '100' => 'Portal',
+                                       '101' => 'Portal talk',
+                               )
+                       ),
+               );
+               // @codingStandardsIgnoreEnd
+       }
+
+}
index 288b527..b3c4bee 100644 (file)
@@ -21,7 +21,7 @@
                        function () {
                                mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor );
                        },
-                       new RegExp( 'The content model \'' + TEST_MODEL + '\' is already registered.' ),
+                       new RegExp( 'Content model "' + TEST_MODEL + '" is already registered' ),
                        'Throws exception is same model is registered a second time'
                );
        } );
index d3c4748..d3f528c 100644 (file)
@@ -21,8 +21,7 @@
                        'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 OPR/15.0.1147.153',
                        'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36 OPR/16.0.1196.62',
                        'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36 OPR/23.0.1522.75',
-                       // Internet Explorer 8+
-                       'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)',
+                       // Internet Explorer 9+
                        'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)',
                        'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
                        'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko',
                        'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17'
                ],
                gradeC: [
-                       // Internet Explorer < 8
+                       // Internet Explorer < 9
                        'Mozilla/2.0 (compatible; MSIE 3.03; Windows 3.1)',
                        'Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)',
                        'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98;)',
                        'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
                        'Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)',
                        'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)',
+                       'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)',
                        // Firefox < 3
                        'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.2) Gecko/20060308 Firefox/1.5.0.2',
                        'Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1.1) Gecko/20070311 Firefox/2.0.0.1',