Merge "mediawiki.api: Transform arrays with join('|') in query parameters"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 30 Oct 2014 21:09:23 +0000 (21:09 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 30 Oct 2014 21:09:23 +0000 (21:09 +0000)
185 files changed:
INSTALL
RELEASE-NOTES-1.25
api.php
composer.json
docs/hooks.txt
docs/mwlogger.txt
includes/AutoLoader.php
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/MovePage.php
includes/PHPVersionError.php
includes/Title.php
includes/api/ApiBlock.php
includes/api/ApiClearHasMsg.php
includes/api/ApiComparePages.php
includes/api/ApiCreateAccount.php
includes/api/ApiDelete.php
includes/api/ApiEditPage.php
includes/api/ApiEmailUser.php
includes/api/ApiExpandTemplates.php
includes/api/ApiFeedContributions.php
includes/api/ApiFeedRecentChanges.php
includes/api/ApiFeedWatchlist.php
includes/api/ApiFileRevert.php
includes/api/ApiFormatBase.php
includes/api/ApiHelp.php
includes/api/ApiImageRotate.php
includes/api/ApiImport.php
includes/api/ApiLogin.php
includes/api/ApiLogout.php
includes/api/ApiMain.php
includes/api/ApiMove.php
includes/api/ApiOpenSearch.php
includes/api/ApiOptions.php
includes/api/ApiPageSet.php
includes/api/ApiParamInfo.php
includes/api/ApiParse.php
includes/api/ApiPatrol.php
includes/api/ApiProtect.php
includes/api/ApiPurge.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllCategories.php
includes/api/ApiQueryAllImages.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllMessages.php
includes/api/ApiQueryAllPages.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategories.php
includes/api/ApiQueryCategoryInfo.php
includes/api/ApiQueryCategoryMembers.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryDuplicateFiles.php
includes/api/ApiQueryExtLinksUsage.php
includes/api/ApiQueryExternalLinks.php
includes/api/ApiQueryFileRepoInfo.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryIWBacklinks.php
includes/api/ApiQueryIWLinks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryImages.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLangBacklinks.php
includes/api/ApiQueryLangLinks.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryPagePropNames.php
includes/api/ApiQueryPageProps.php
includes/api/ApiQueryPagesWithProp.php
includes/api/ApiQueryPrefixSearch.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryQueryPage.php
includes/api/ApiQueryRandom.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQuerySearch.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryStashImageInfo.php
includes/api/ApiQueryTags.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiQueryWatchlistRaw.php
includes/api/ApiRevisionDelete.php
includes/api/ApiRollback.php
includes/api/ApiRsd.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiUnblock.php
includes/api/ApiUndelete.php
includes/api/ApiUpload.php
includes/api/ApiUserrights.php
includes/api/ApiWatch.php
includes/api/i18n/be-tarask.json [new file with mode: 0644]
includes/api/i18n/fa.json
includes/api/i18n/fr.json
includes/api/i18n/hu.json [new file with mode: 0644]
includes/api/i18n/lv.json [new file with mode: 0644]
includes/api/i18n/mk.json
includes/api/i18n/nl.json
includes/db/ORMTable.php
includes/debug/MWDebug.php
includes/debug/logger/legacy/Logger.php [new file with mode: 0644]
includes/debug/logger/legacy/Spi.php [new file with mode: 0644]
includes/debug/logger/monolog/Handler.php
includes/filerepo/file/LocalFile.php
includes/installer/i18n/nl.json
includes/limit.sh [changed mode: 0644->0755]
includes/password/MWOldPassword.php
includes/password/ParameterizedPassword.php
includes/profiler/Profiler.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderModule.php
includes/specials/SpecialImport.php
includes/specials/SpecialLinkSearch.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialUpload.php
includes/upload/UploadStash.php
index.php
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/de.json
languages/i18n/en.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/gsw.json
languages/i18n/he.json
languages/i18n/inh.json
languages/i18n/ko.json
languages/i18n/nl.json
languages/i18n/qqq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
languages/i18n/th.json
languages/i18n/zea.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
load.php
maintenance/Maintenance.php
maintenance/cleanupCaps.php
maintenance/dev/includes/php.sh [changed mode: 0644->0755]
maintenance/dev/includes/require-php.sh [changed mode: 0644->0755]
maintenance/jsduck/categories.json
maintenance/moveBatch.php
maintenance/postgres/compare_schemas.pl [changed mode: 0644->0755]
maintenance/postgres/mediawiki_mysql2postgres.pl [changed mode: 0644->0755]
maintenance/storage/make-blobs [changed mode: 0644->0755]
maintenance/update.php
mw-config/index.php
resources/Resources.php
resources/lib/oojs-ui/oojs-ui-apex.css
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-apex.svg.css
resources/lib/oojs-ui/oojs-ui-mediawiki.css
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css
resources/lib/oojs-ui/oojs-ui.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
resources/src/mediawiki.action/templates/postEdit.html [new file with mode: 0644]
resources/src/mediawiki.api/mediawiki.api.js
resources/src/mediawiki.skinning/elements.css [changed mode: 0755->0644]
resources/src/mediawiki.special/mediawiki.special.upload.js
resources/src/mediawiki.special/templates/thumbnail.html [new file with mode: 0644]
resources/src/mediawiki/mediawiki.confirmCloseWindow.js
resources/src/mediawiki/mediawiki.feedback.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.template.js [new file with mode: 0644]
resources/src/mediawiki/templates/dialog.html [new file with mode: 0644]
tests/frontend/Gruntfile.js
tests/phpunit/includes/GlobalFunctions/GlobalTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/TestUser.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
tests/phpunit/includes/resourceloader/templates/template.html [new file with mode: 0644]
tests/phpunit/includes/resourceloader/templates/template2.html [new file with mode: 0644]
tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars [new file with mode: 0644]
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js [new file with mode: 0644]

diff --git a/INSTALL b/INSTALL
index e8731a1..70d8d53 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki
 "in-place", as long as you have the necessary prerequisites available.
 
 Required software:
-* Web server with PHP 5.3.2 or higher.
+* Web server with PHP 5.3.3 or higher.
 * A SQL server, the following types are supported
 ** MySQL 5.0.2 or higher
 ** PostgreSQL 8.3 or higher
index 28a3958..7137d57 100644 (file)
@@ -42,12 +42,24 @@ production.
     https://www.mediawiki.org/wiki/Manual:Skinning#Page_status_indicators
 * Edit tokens may now be time-limited: passing a maximum age to
   User::matchEditToken will reject any older tokens.
+* The debug logging internals have been overhauled, and are now using the
+  PSR-3 interfaces.
 
 === Bug fixes in 1.25 ===
 * (bug 71003) No additional code will be generated to try to load CSS-embedded
   SVG images in Internet Explorer 6 and 7, as they don't support them anyway.
 * (bug 67021) On Special:BookSources, corrected validation of ISBNs (both
   10- and 13-digit forms) containing "X".
+* Page moving was refactored into a MovePage class. As part of that:
+** The AbortMove hook was removed.
+** MovePageIsValidMove is for extensions to specify whether a page
+   cannot be moved for technical reasons, and should not be overriden.
+** MovePageCheckPermissions is for checking whether the given user is
+   allowed to make the move.
+** Title::moveNoAuth() was deprecated. Use the MovePage class instead.
+** Title::moveTo() was deprecated. Use the MovePage class instead.
+** Title::isValidMoveOperation() broken down into MovePage::isValidMove()
+   and MovePage::checkPermissions().
 
 === Action API changes in 1.25 ===
 * (bug 65403) XML tag highlighting is now only performed for formats
@@ -147,7 +159,7 @@ changes to languages because of Bugzilla reports.
 
 == Compatibility ==
 
-MediaWiki 1.25 requires PHP 5.3.2 or later. There is experimental support for
+MediaWiki 1.25 requires PHP 5.3.3 or later. There is experimental support for
 HHVM 3.3.0.
 
 MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
diff --git a/api.php b/api.php
index 7974f15..74ee775 100644 (file)
--- a/api.php
+++ b/api.php
@@ -34,7 +34,7 @@
 define( 'MW_API', true );
 
 // Bail if PHP is too low
-if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
+if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.3' ) < 0 ) {
        // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
        require dirname( __FILE__ ) . '/includes/PHPVersionError.php';
        wfPHPVersionError( 'api.php' );
index 3b18933..851ad41 100644 (file)
@@ -16,7 +16,7 @@
                "wiki": "https://www.mediawiki.org/"
        },
        "require": {
-               "php": ">=5.3.2",
+               "php": ">=5.3.3",
                "psr/log": "1.0.0"
        },
        "require-dev": {
index b71c347..52eeab8 100644 (file)
@@ -260,13 +260,6 @@ $password: the password being submitted, not yet checked for validity
           a machine API rather than the HTML user interface.
 &$msg: the message identifier for abort reason (new in 1.18, not available before 1.18)
 
-'AbortMove': Allows to abort moving an article (title).
-$old: old title
-$nt: new title
-$user: user who is doing the move
-$err: error message
-$reason: the reason for the move (added in 1.13)
-
 'AbortNewAccount': Return false to cancel explicit account creation.
 $user: the User object about to be created (read-only, incomplete)
 &$msg: out parameter: HTML to display on abort
@@ -1835,6 +1828,18 @@ $db: The database object to be queried.
 &$opts: Options for the query.
 &$join_conds: Join conditions for the query.
 
+'MovePageCheckPermissions': Specify whether the user is allowed to move the page.
+$oldTitle: Title object of the current (old) location
+$newTitle: Title object of the new location
+$user: User making the move
+$reason: string of the reason provided by the user
+$status: Status object to pass error messages to
+
+'MovePageIsValidMove': Specify whether a page can be moved for technical reasons.
+$oldTitle: Title object of the current (old) location
+$newTitle: Title object of the new location
+$status: Status object to pass error messages to
+
 'BaseTemplateToolbox': Called by BaseTemplate when building the $toolbox array
 and returning it for the skin to output. You can add items to the toolbox while
 still letting the skin make final decisions on skin-specific markup conventions
index 5a3e249..aab9599 100644 (file)
@@ -24,6 +24,14 @@ instance. Alternately the MWLogger::registerProvider method can be called
 to inject an MWLoggerSpi instance into MWLogger and bypass the use of this
 configuration variable.
 
+The MWLoggerLegacySpi class implements a service provider to generate
+MWLoggerLegacyLogger instances. The MWLoggerLegacyLogger class implements the
+PSR-3 logger interface and provides output and configuration equivalent to the
+historic logging output of wfDebug, wfDebugLog, wfLogDBError and wfErrorLog.
+The MWLoggerLegacySpi class is the default service provider configured in
+DefaultSettings.php. It's usage should be transparent for users who are not
+ready or do not wish to switch to a alternate logging platform.
+
 The MWLoggerMonologSpi class implements a service provider to generate
 MWLogger instances that use the Monolog [1] logging library. See the PHP docs
 (or source) for MWLoggerMonologSpi for details on the configuration of this
@@ -39,6 +47,11 @@ a more feature rich logging configuration.
 : Service provider interface for MWLogger factories
 ; MWLoggerNullSpi
 : MWLoggerSpi for creating instances that discard all log events
+; MWLoggerLegacySpi
+: Service provider for creating MWLoggerLegacyLogger instances
+; MWLoggerLegacyLogger
+: PSR-3 logger that mimics the historical output and configuration of wfDebug,
+  wfErrorLog and other global logging functions.
 ; MWLoggerMonologSpi
 : MWLoggerSpi for creating instances backed by the monolog logging library
 ; MwLoggerMonologHandler
index 09bc5c0..0895873 100644 (file)
@@ -464,6 +464,8 @@ $wgAutoloadLocalClasses = array(
        # includes/debug
        'MWDebug' => 'includes/debug/MWDebug.php',
        'MWLogger' => 'includes/debug/logger/Logger.php',
+       'MWLoggerLegacyLogger' => 'includes/debug/logger/legacy/Logger.php',
+       'MWLoggerLegacySpi' => 'includes/debug/logger/legacy/Spi.php',
        'MWLoggerMonologHandler' => 'includes/debug/logger/monolog/Handler.php',
        'MWLoggerMonologProcessor' => 'includes/debug/logger/monolog/Processor.php',
        'MWLoggerMonologSpi' => 'includes/debug/logger/monolog/Spi.php',
index 6b937ea..cfc8438 100644 (file)
@@ -5233,7 +5233,7 @@ $wgDebugLogGroups = array();
  * @see MwLogger
  */
 $wgMWLoggerDefaultSpi = array(
-       'class' => 'MWLoggerNullSpi',
+       'class' => 'MWLoggerLegacySpi',
 );
 
 /**
index 11388e8..4ef731b 100644 (file)
@@ -30,7 +30,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 /**
  * Compatibility functions
  *
- * We support PHP 5.3.2 and up.
+ * We support PHP 5.3.3 and up.
  * Re-implementations of newer functions or functions in non-standard
  * PHP extensions may be included here.
  */
@@ -959,7 +959,7 @@ function wfMatchesDomainList( $url, $domains ) {
  *     - false: same as 'log'
  */
 function wfDebug( $text, $dest = 'all' ) {
-       global $wgDebugLogFile, $wgDebugRawPage, $wgDebugLogPrefix;
+       global $wgDebugRawPage, $wgDebugLogPrefix;
 
        if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
                return;
@@ -974,6 +974,7 @@ function wfDebug( $text, $dest = 'all' ) {
 
        $timer = wfDebugTimer();
        if ( $timer !== '' ) {
+               // Prepend elapsed request time and real memory usage to each line
                $text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 );
        }
 
@@ -981,13 +982,13 @@ function wfDebug( $text, $dest = 'all' ) {
                MWDebug::debugMsg( $text );
        }
 
-       if ( $wgDebugLogFile != '' ) {
-               # Strip unprintables; they can switch terminal modes when binary data
-               # gets dumped, which is pretty annoying.
-               $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
-               $text = $wgDebugLogPrefix . $text;
-               wfErrorLog( $text, $wgDebugLogFile );
+       $ctx = array();
+       if ( $wgDebugLogPrefix !== '' ) {
+               $ctx['prefix'] = $wgDebugLogPrefix;
        }
+
+       $logger = MWLogger::getInstance( 'wfDebug' );
+       $logger->debug( rtrim( $text, "\n" ), $ctx );
 }
 
 /**
@@ -1068,8 +1069,6 @@ function wfDebugMem( $exact = false ) {
 function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
        global $wgDebugLogGroups;
 
-       $text = trim( $text ) . "\n";
-
        // Turn $dest into a string if it's a boolean (for b/c)
        if ( $dest === true ) {
                $dest = 'all';
@@ -1077,34 +1076,16 @@ function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
                $dest = 'private';
        }
 
-       if ( !isset( $wgDebugLogGroups[$logGroup] ) ) {
-               if ( $dest !== 'private' ) {
-                       wfDebug( "[$logGroup] $text", $dest );
-               }
-               return;
-       }
+       $text = trim( $text );
 
        if ( $dest === 'all' ) {
-               MWDebug::debugMsg( "[$logGroup] $text" );
-       }
-
-       $logConfig = $wgDebugLogGroups[$logGroup];
-       if ( $logConfig === false ) {
-               return;
-       }
-       if ( is_array( $logConfig ) ) {
-               if ( isset( $logConfig['sample'] ) && mt_rand( 1, $logConfig['sample'] ) !== 1 ) {
-                       return;
-               }
-               $destination = $logConfig['destination'];
-       } else {
-               $destination = strval( $logConfig );
+               MWDebug::debugMsg( "[{$logGroup}] {$text}\n" );
        }
 
-       $time = wfTimestamp( TS_DB );
-       $wiki = wfWikiID();
-       $host = wfHostname();
-       wfErrorLog( "$time $host $wiki: $text", $destination );
+       $logger = MWLogger::getInstance( $logGroup );
+       $logger->debug( $text, array(
+               'private' => ( $dest === 'private' ),
+       ) );
 }
 
 /**
@@ -1113,30 +1094,8 @@ function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
  * @param string $text Database error message.
  */
 function wfLogDBError( $text ) {
-       global $wgDBerrorLog, $wgDBerrorLogTZ;
-       static $logDBErrorTimeZoneObject = null;
-
-       if ( $wgDBerrorLog ) {
-               $host = wfHostname();
-               $wiki = wfWikiID();
-
-               if ( $wgDBerrorLogTZ && !$logDBErrorTimeZoneObject ) {
-                       $logDBErrorTimeZoneObject = new DateTimeZone( $wgDBerrorLogTZ );
-               }
-
-               // Workaround for https://bugs.php.net/bug.php?id=52063
-               // Can be removed when min PHP > 5.3.2
-               if ( $logDBErrorTimeZoneObject === null ) {
-                       $d = date_create( "now" );
-               } else {
-                       $d = date_create( "now", $logDBErrorTimeZoneObject );
-               }
-
-               $date = $d->format( 'D M j G:i:s T Y' );
-
-               $text = "$date\t$host\t$wiki\t" . trim( $text ) . "\n";
-               wfErrorLog( $text, $wgDBerrorLog );
-       }
+       $logger = MWLogger::getInstance( 'wfLogDBError' );
+       $logger->error( trim( $text ) );
 }
 
 /**
@@ -1194,58 +1153,10 @@ function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
  * @throws MWException
  */
 function wfErrorLog( $text, $file ) {
-       if ( substr( $file, 0, 4 ) == 'udp:' ) {
-               # Needs the sockets extension
-               if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
-                       // IPv6 bracketed host
-                       $host = $m[2];
-                       $port = intval( $m[3] );
-                       $prefix = isset( $m[4] ) ? $m[4] : false;
-                       $domain = AF_INET6;
-               } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
-                       $host = $m[2];
-                       if ( !IP::isIPv4( $host ) ) {
-                               $host = gethostbyname( $host );
-                       }
-                       $port = intval( $m[3] );
-                       $prefix = isset( $m[4] ) ? $m[4] : false;
-                       $domain = AF_INET;
-               } else {
-                       throw new MWException( __METHOD__ . ': Invalid UDP specification' );
-               }
-
-               // Clean it up for the multiplexer
-               if ( strval( $prefix ) !== '' ) {
-                       $text = preg_replace( '/^/m', $prefix . ' ', $text );
-
-                       // Limit to 64KB
-                       if ( strlen( $text ) > 65506 ) {
-                               $text = substr( $text, 0, 65506 );
-                       }
-
-                       if ( substr( $text, -1 ) != "\n" ) {
-                               $text .= "\n";
-                       }
-               } elseif ( strlen( $text ) > 65507 ) {
-                       $text = substr( $text, 0, 65507 );
-               }
-
-               $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
-               if ( !$sock ) {
-                       return;
-               }
-
-               socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
-               socket_close( $sock );
-       } else {
-               wfSuppressWarnings();
-               $exists = file_exists( $file );
-               $size = $exists ? filesize( $file ) : false;
-               if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
-                       file_put_contents( $file, $text, FILE_APPEND );
-               }
-               wfRestoreWarnings();
-       }
+       $logger = MWLogger::getInstance( 'wfErrorLog' );
+       $logger->info( trim( $text ), array(
+               'destination' => $file,
+       ) );
 }
 
 /**
@@ -3798,7 +3709,7 @@ function wfWaitForSlaves(
        // Figure out which clusters need to be checked
        $lbs = array();
        if ( $cluster === '*' ) {
-               wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( &$lbs ) {
+               wfGetLBFactory()->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) {
                        $lbs[] = $lb;
                } );
        } elseif ( $cluster !== false ) {
@@ -3818,7 +3729,9 @@ function wfWaitForSlaves(
                        if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
                                continue; // assume no writes done
                        }
-                       $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
+                       // Use the empty string to not trigger selectDB() since the connection
+                       // may have been to a server that does not have a DB for the current wiki.
+                       $dbw = $lb->getConnection( DB_MASTER, array(), '' );
                        if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
                                continue; // no writes since the last wait
                        }
index 79095e9..7bad3f9 100644 (file)
@@ -42,6 +42,52 @@ class MovePage {
                $this->newTitle = $newTitle;
        }
 
+       public function checkPermissions( User $user, $reason ) {
+               $status = new Status();
+
+               $errors = wfMergeErrorArrays(
+                       $this->oldTitle->getUserPermissionsErrors( 'move', $user ),
+                       $this->oldTitle->getUserPermissionsErrors( 'edit', $user ),
+                       $this->newTitle->getUserPermissionsErrors( 'move-target', $user ),
+                       $this->newTitle->getUserPermissionsErrors( 'edit', $user )
+               );
+
+               // Convert into a Status object
+               if ( $errors ) {
+                       foreach ( $errors as $error ) {
+                               call_user_func_array( array( $status, 'fatal' ), $error );
+                       }
+               }
+
+               if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+                       // This is kind of lame, won't display nice
+                       $status->fatal( 'spamprotectiontext' );
+               }
+
+               # The move is allowed only if (1) the target doesn't exist, or
+               # (2) the target is a redirect to the source, and has no history
+               # (so we can undo bad moves right after they're done).
+
+               if ( $this->newTitle->getArticleID() ) { # Target exists; check for validity
+                       if ( !$this->isValidMoveTarget() ) {
+                               $status->fatal( 'articleexists' );
+                       }
+               } else {
+                       $tp = $this->newTitle->getTitleProtection();
+                       if ( $tp !== false ) {
+                               if ( !$user->isAllowed( $tp['permission'] ) ) {
+                                       $status->fatal( 'cantmove-titleprotected' );
+                               }
+                       }
+               }
+
+               wfRunHooks( 'MovePageCheckPermissions',
+                       array( $this->oldTitle, $this->newTitle, $user, $reason, $status )
+               );
+
+               return $status;
+       }
+
        /**
         * Does various sanity checks that the move is
         * valid. Only things based on the two titles
@@ -99,6 +145,9 @@ class MovePage {
                        $status->fatal( 'nonfile-cannot-move-to-file' );
                }
 
+               // Hook for extensions to say a title can't be moved for technical reasons
+               wfRunHooks( 'MovePageIsValidMove', array( $this->oldTitle, $this->newTitle, $status ) );
+
                return $status;
        }
 
@@ -126,6 +175,53 @@ class MovePage {
                return $status;
        }
 
+       /**
+        * Checks if $this can be moved to a given Title
+        * - Selects for update, so don't call it unless you mean business
+        *
+        * @since 1.25
+        * @return bool
+        */
+       protected function isValidMoveTarget() {
+               # Is it an existing file?
+               if ( $this->newTitle->inNamespace( NS_FILE ) ) {
+                       $file = wfLocalFile( $this->newTitle );
+                       if ( $file->exists() ) {
+                               wfDebug( __METHOD__ . ": file exists\n" );
+                               return false;
+                       }
+               }
+               # Is it a redirect with no history?
+               if ( !$this->newTitle->isSingleRevRedirect() ) {
+                       wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
+                       return false;
+               }
+               # Get the article text
+               $rev = Revision::newFromTitle( $this->newTitle, false, Revision::READ_LATEST );
+               if ( !is_object( $rev ) ) {
+                       return false;
+               }
+               $content = $rev->getContent();
+               # Does the redirect point to the source?
+               # Or is it a broken self-redirect, usually caused by namespace collisions?
+               $redirTitle = $content ? $content->getRedirectTarget() : null;
+
+               if ( $redirTitle ) {
+                       if ( $redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() &&
+                               $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey() ) {
+                               wfDebug( __METHOD__ . ": redirect points to other page\n" );
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       # Fail safe (not a redirect after all. strange.)
+                       wfDebug( __METHOD__ . ": failsafe: database says " . $this->newTitle->getPrefixedDBkey() .
+                               " is a redirect, but it doesn't contain a valid redirect.\n" );
+                       return false;
+               }
+       }
+
        /**
         * @param User $user
         * @param string $reason
@@ -135,6 +231,8 @@ class MovePage {
        public function move( User $user, $reason, $createRedirect ) {
                global $wgCategoryCollation;
 
+               wfRunHooks( 'TitleMove', array( $this->oldTitle, $this->newTitle, $user ) );
+
                // If it is a file, move it first.
                // It is done before all other moving stuff is done because it's hard to revert.
                $dbw = wfGetDB( DB_MASTER );
@@ -274,7 +372,6 @@ class MovePage {
 
                wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
                return Status::newGood();
-
        }
 
        /**
index 5af818f..b061990 100644 (file)
@@ -40,7 +40,7 @@
  */
 function wfPHPVersionError( $type ) {
        $mwVersion = '1.25';
-       $minimumVersionPHP = '5.3.2';
+       $minimumVersionPHP = '5.3.3';
 
        $phpVersion = PHP_VERSION;
        $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
index 0efc94e..b97d36a 100644 (file)
@@ -2232,19 +2232,13 @@ class Title {
                } elseif ( $action == 'create' ) {
                        $title_protection = $this->getTitleProtection();
                        if ( $title_protection ) {
-                               if ( $title_protection['pt_create_perm'] == 'sysop' ) {
-                                       $title_protection['pt_create_perm'] = 'editprotected'; // B/C
-                               }
-                               if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
-                                       $title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
-                               }
-                               if ( $title_protection['pt_create_perm'] == ''
-                                       || !$user->isAllowed( $title_protection['pt_create_perm'] )
+                               if ( $title_protection['permission'] == ''
+                                       || !$user->isAllowed( $title_protection['permission'] )
                                ) {
                                        $errors[] = array(
                                                'titleprotected',
-                                               User::whoIs( $title_protection['pt_user'] ),
-                                               $title_protection['pt_reason']
+                                               User::whoIs( $title_protection['user'] ),
+                                               $title_protection['reason']
                                        );
                                }
                        }
@@ -2536,7 +2530,7 @@ class Title {
         * @return array|bool An associative array representing any existent title
         *   protection, or false if there's none.
         */
-       private function getTitleProtection() {
+       public function getTitleProtection() {
                // Can't protect pages in special namespaces
                if ( $this->getNamespace() < 0 ) {
                        return false;
@@ -2551,13 +2545,27 @@ class Title {
                        $dbr = wfGetDB( DB_SLAVE );
                        $res = $dbr->select(
                                'protected_titles',
-                               array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
+                               array(
+                                       'user' => 'pt_user',
+                                       'reason' => 'pt_reason',
+                                       'expiry' => 'pt_expiry',
+                                       'permission' => 'pt_create_perm'
+                               ),
                                array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
                                __METHOD__
                        );
 
                        // fetchRow returns false if there are no rows.
-                       $this->mTitleProtection = $dbr->fetchRow( $res );
+                       $row = $dbr->fetchRow( $res );
+                       if ( $row ) {
+                               if ( $row['permission'] == 'sysop' ) {
+                                       $row['permission'] = 'editprotected'; // B/C
+                               }
+                               if ( $row['permission'] == 'autoconfirmed' ) {
+                                       $row['permission'] = 'editsemiprotected'; // B/C
+                               }
+                       }
+                       $this->mTitleProtection = $row;
                }
                return $this->mTitleProtection;
        }
@@ -2978,12 +2986,12 @@ class Title {
 
                                if ( $title_protection ) {
                                        $now = wfTimestampNow();
-                                       $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
+                                       $expiry = $wgContLang->formatExpiry( $title_protection['expiry'], TS_MW );
 
                                        if ( !$expiry || $expiry > $now ) {
                                                // Apply the restrictions
                                                $this->mRestrictionsExpiry['create'] = $expiry;
-                                               $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
+                                               $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
                                        } else { // Get rid of the old restrictions
                                                Title::purgeExpiredRestrictions();
                                                $this->mTitleProtection = false;
@@ -3579,10 +3587,12 @@ class Title {
        /**
         * Move this page without authentication
         *
+        * @deprecated since 1.25 use MovePage class instead
         * @param Title $nt The new page Title
         * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
         */
        public function moveNoAuth( &$nt ) {
+               wfDeprecated( __METHOD__, '1.25' );
                return $this->moveTo( $nt, false );
        }
 
@@ -3590,10 +3600,9 @@ class Title {
         * Check whether a given move operation would be valid.
         * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
         *
-        * @todo finish moving this into MovePage
+        * @deprecated since 1.25, use MovePage's methods instead
         * @param Title $nt The new title
-        * @param bool $auth Indicates whether $wgUser's permissions
-        *  should be checked
+        * @param bool $auth Ignored
         * @param string $reason Is the log summary of the move, used for spam checking
         * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
         */
@@ -3607,54 +3616,12 @@ class Title {
                }
 
                $mp = new MovePage( $this, $nt );
-               $errors = $mp->isValidMove()->getErrorsArray();
-
-               $newid = $nt->getArticleID();
-
-               if ( $auth ) {
-                       $errors = wfMergeErrorArrays( $errors,
-                               $this->getUserPermissionsErrors( 'move', $wgUser ),
-                               $this->getUserPermissionsErrors( 'edit', $wgUser ),
-                               $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
-                               $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
-               }
-
-               $match = EditPage::matchSummarySpamRegex( $reason );
-               if ( $match !== false ) {
-                       // This is kind of lame, won't display nice
-                       $errors[] = array( 'spamprotectiontext' );
-               }
-
-               $err = null;
-               if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
-                       $errors[] = array( 'hookaborted', $err );
-               }
-
-               # The move is allowed only if (1) the target doesn't exist, or
-               # (2) the target is a redirect to the source, and has no history
-               # (so we can undo bad moves right after they're done).
+               $errors = wfMergeErrorArrays(
+                       $mp->isValidMove()->getErrorsArray(),
+                       $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
+               );
 
-               if ( 0 != $newid ) { # Target exists; check for validity
-                       if ( !$this->isValidMoveTarget( $nt ) ) {
-                               $errors[] = array( 'articleexists' );
-                       }
-               } else {
-                       $tp = $nt->getTitleProtection();
-                       $right = $tp['pt_create_perm'];
-                       if ( $right == 'sysop' ) {
-                               $right = 'editprotected'; // B/C
-                       }
-                       if ( $right == 'autoconfirmed' ) {
-                               $right = 'editsemiprotected'; // B/C
-                       }
-                       if ( $tp && !$wgUser->isAllowed( $right ) ) {
-                               $errors[] = array( 'cantmove-titleprotected' );
-                       }
-               }
-               if ( empty( $errors ) ) {
-                       return true;
-               }
-               return $errors;
+               return $errors ? : true;
        }
 
        /**
@@ -3679,7 +3646,7 @@ class Title {
        /**
         * Move a title to a new location
         *
-        * @todo Deprecate this in favor of MovePage
+        * @deprecated since 1.25, use the MovePage class instead
         * @param Title $nt The new title
         * @param bool $auth Indicates whether $wgUser's permissions
         *  should be checked
@@ -3701,8 +3668,6 @@ class Title {
                        $createRedirect = true;
                }
 
-               wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
-
                $mp = new MovePage( $this, $nt );
                $status = $mp->move( $wgUser, $reason, $createRedirect );
                if ( $status->isOK() ) {
@@ -3838,7 +3803,7 @@ class Title {
         * Checks if $this can be moved to a given Title
         * - Selects for update, so don't call it unless you mean business
         *
-        * @todo move to MovePage
+        * @deprecated since 1.25, use MovePage's methods instead
         * @param Title $nt The new title to check
         * @return bool
         */
index 44565a8..dea43ba 100644 (file)
@@ -169,7 +169,7 @@ class ApiBlock extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
                                => 'apihelp-block-example-ip-simple',
index 8667a67..eb471ae 100644 (file)
@@ -42,7 +42,7 @@ class ApiClearHasMsg extends ApiBase {
                return false;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=clearhasmsg'
                                => 'apihelp-clearhasmsg-example-1',
index a2f33ea..ce256a6 100644 (file)
@@ -126,7 +126,7 @@ class ApiComparePages extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=compare&fromrev=1&torev=2'
                                => 'apihelp-compare-example-1',
index cfb6dab..89f8481 100644 (file)
@@ -200,7 +200,7 @@ class ApiCreateAccount extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=createaccount&name=testuser&password=test123'
                                => 'apihelp-createaccount-example-pass',
index e455f71..d8b5718 100644 (file)
@@ -214,7 +214,7 @@ class ApiDelete extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=delete&title=Main%20Page&token=123ABC'
                                => 'apihelp-delete-example-simple',
index f2bb273..269b016 100644 (file)
@@ -565,7 +565,7 @@ class ApiEditPage extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=edit&title=Test&summary=test%20summary&' .
                                'text=article%20content&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
index 911f454..15eb475 100644 (file)
@@ -106,7 +106,7 @@ class ApiEmailUser extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=emailuser&target=WikiSysop&text=Content&token=123ABC'
                                => 'apihelp-emailuser-example-email',
index 6e49d3c..eea10e9 100644 (file)
@@ -159,7 +159,7 @@ class ApiExpandTemplates extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=expandtemplates&text={{Project:Sandbox}}'
                                => 'apihelp-expandtemplates-example-simple',
index 20f4d3d..ced5f0c 100644 (file)
@@ -199,7 +199,7 @@ class ApiFeedContributions extends ApiBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=feedcontributions&user=Example'
                                => 'apihelp-feedcontributions-example-simple',
index 0d2fca6..d452bbd 100644 (file)
@@ -171,7 +171,7 @@ class ApiFeedRecentChanges extends ApiBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=feedrecentchanges'
                                => 'apihelp-feedrecentchanges-example-simple',
index 84ee228..561ff3b 100644 (file)
@@ -249,7 +249,7 @@ class ApiFeedWatchlist extends ApiBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=feedwatchlist'
                                => 'apihelp-feedwatchlist-example-default',
index 02147fa..61966e5 100644 (file)
@@ -139,7 +139,7 @@ class ApiFileRevert extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=filerevert&filename=Wiki.png&comment=Revert&' .
                                'archivename=20110305152740!Wiki.png&token=123ABC'
index 62705ef..913b8eb 100644 (file)
@@ -164,8 +164,8 @@ abstract class ApiFormatBase extends ApiBase {
                        $out->setPageTitle( $context->msg( 'api-format-title' ) );
 
                        $header = $context->msg( 'api-format-prettyprint-header' )
-                          ->params( $format, strtolower( $format ) )
-                          ->parseAsBlock();
+                               ->params( $format, strtolower( $format ) )
+                               ->parseAsBlock();
                        $out->addHTML(
                                Html::rawElement( 'div', array( 'class' => 'api-pretty-header' ),
                                        ApiHelp::fixHelpLinks( $header )
@@ -207,7 +207,7 @@ abstract class ApiFormatBase extends ApiBase {
                return $this->mBuffer;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
                                => array( 'apihelp-format-example-generic', $this->getFormat() )
index 88a3df2..fdde4bd 100644 (file)
@@ -528,11 +528,11 @@ class ApiHelp extends ApiBase {
                                                        ->parse();
                                        }
 
-                                       if ( !$description && !$info ) {
-                                               $description[] = self::wrap(
+                                       if ( !array_filter( $description ) ) {
+                                               $description = array( self::wrap(
                                                        $context->msg( 'api-help-param-no-description' ),
                                                        'apihelp-empty'
-                                               );
+                                               ) );
                                        }
 
                                        // Add "deprecated" flag
@@ -650,7 +650,7 @@ class ApiHelp extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=help'
                                => 'apihelp-help-example-main',
index 6aa9c22..aba6921 100644 (file)
@@ -199,7 +199,7 @@ class ApiImageRotate extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=imagerotate&titles=File:Example.jpg&rotation=90&token=123ABC'
                                => 'apihelp-imagerotate-example-simple',
index 8b7802c..b2febde 100644 (file)
@@ -120,7 +120,7 @@ class ApiImport extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&' .
                                'namespace=100&fullhistory=&token=123ABC'
index eae8ebc..cc4dee4 100644 (file)
@@ -184,7 +184,7 @@ class ApiLogin extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=login&lgname=user&lgpassword=password'
                                => 'apihelp-login-example-gettoken',
index bfdad34..bf81723 100644 (file)
@@ -46,7 +46,7 @@ class ApiLogout extends ApiBase {
                return false;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=logout'
                                => 'apihelp-logout-example-logout',
index bf26eee..f7588a3 100644 (file)
@@ -310,7 +310,7 @@ class ApiMain extends ApiBase {
                        // then there's an appropriate Vary header set by whatever set
                        // their non-default language.
                        wfDebug( __METHOD__ . ": downgrading cache mode 'public' to " .
-                          "'anon-public-user-private' due to uselang=user\n" );
+                               "'anon-public-user-private' due to uselang=user\n" );
                        $mode = 'anon-public-user-private';
                }
 
@@ -1157,7 +1157,7 @@ class ApiMain extends ApiBase {
        }
 
        /** @see ApiBase::getExamplesMessages() */
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=help'
                                => 'apihelp-help-example-main',
index db0fde3..c7f40c7 100644 (file)
@@ -72,9 +72,9 @@ class ApiMove extends ApiBase {
 
                // Move the page
                $toTitleExists = $toTitle->exists();
-               $retval = $fromTitle->moveTo( $toTitle, true, $params['reason'], !$params['noredirect'] );
-               if ( $retval !== true ) {
-                       $this->dieUsageMsg( reset( $retval ) );
+               $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'] );
+               if ( !$status->isOK() ) {
+                       $this->dieStatus( $status );
                }
 
                $r = array(
@@ -99,8 +99,8 @@ class ApiMove extends ApiBase {
                // Move the talk page
                if ( $params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
                        $toTalkExists = $toTalk->exists();
-                       $retval = $fromTalk->moveTo( $toTalk, true, $params['reason'], !$params['noredirect'] );
-                       if ( $retval === true ) {
+                       $status = $this->movePage( $fromTalk, $toTalk, $params['reason'], !$params['noredirect'] );
+                       if ( $status->isOK() ) {
                                $r['talkfrom'] = $fromTalk->getPrefixedText();
                                $r['talkto'] = $toTalk->getPrefixedText();
                                if ( $toTalkExists ) {
@@ -108,9 +108,9 @@ class ApiMove extends ApiBase {
                                }
                        } else {
                                // We're not gonna dieUsage() on failure, since we already changed something
-                               $parsed = $this->parseMsg( reset( $retval ) );
-                               $r['talkmove-error-code'] = $parsed['code'];
-                               $r['talkmove-error-info'] = $parsed['info'];
+                               $error = $this->getErrorFromStatus( $status );
+                               $r['talkmove-error-code'] = $error[0];
+                               $r['talkmove-error-info'] = $error[1];
                        }
                }
 
@@ -147,6 +147,28 @@ class ApiMove extends ApiBase {
                $result->addValue( null, $this->getModuleName(), $r );
        }
 
+       /**
+        * @param Title $from
+        * @param Title $to
+        * @param string $reason
+        * @param bool $createRedirect
+        * @return Status
+        */
+       protected function movePage( Title $from, Title $to, $reason, $createRedirect ) {
+               $mp = new MovePage( $from, $to );
+               $valid = $mp->isValidMove();
+               if ( !$valid->isOK() ) {
+                       return $valid;
+               }
+
+               $permStatus = $mp->checkPermissions( $this->getUser(), $reason );
+               if ( !$permStatus->isOK() ) {
+                       return $permStatus;
+               }
+
+               return $mp->move( $this->getUser(), $reason, $createRedirect );
+       }
+
        /**
         * @param Title $fromTitle
         * @param Title $toTitle
@@ -224,7 +246,7 @@ class ApiMove extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
                                'reason=Misspelled%20title&movetalk=&noredirect='
index 4c72677..8fa495c 100644 (file)
@@ -91,7 +91,7 @@ class ApiOpenSearch extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=opensearch&search=Te'
                                => 'apihelp-opensearch-example-te',
index c804563..8ef0629 100644 (file)
@@ -161,7 +161,7 @@ class ApiOptions extends ApiBase {
                return 'https://www.mediawiki.org/wiki/API:Options';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=options&reset=&token=123ABC'
                                => 'apihelp-options-example-reset',
index ab705b2..db5eb52 100644 (file)
@@ -669,6 +669,10 @@ class ApiPageSet extends ApiBase {
 
        /**
         * Populate this PageSet from a rowset returned from the database
+        *
+        * Note that the query result must include the columns returned by
+        * $this->getPageTableFields().
+        *
         * @param DatabaseBase $db
         * @param ResultWrapper $queryResult Query result object
         */
index d07907f..07670f6 100644 (file)
@@ -385,7 +385,7 @@ class ApiParamInfo extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=paraminfo&modules=parse|phpfm|query+allpages|query+siteinfo'
                                => 'apihelp-paraminfo-example-1',
index 007c620..2bf1677 100644 (file)
@@ -721,7 +721,7 @@ class ApiParse extends ApiBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=parse&page=Project:Sandbox'
                                => 'apihelp-parse-example-page',
index 01bc568..3684461 100644 (file)
@@ -90,7 +90,7 @@ class ApiPatrol extends ApiBase {
                return 'patrol';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=patrol&token=123ABC&rcid=230672766'
                                => 'apihelp-patrol-example-rcid',
index f5786e8..ae7d42b 100644 (file)
@@ -179,7 +179,7 @@ class ApiProtect extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=protect&title=Main%20Page&token=123ABC&' .
                                'protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never'
index a69a0d5..ec55137 100644 (file)
@@ -144,7 +144,7 @@ class ApiPurge extends ApiBase {
                return $result;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=purge&titles=Main_Page|API'
                                => 'apihelp-purge-example-simple',
index cf63a7d..a091663 100644 (file)
@@ -607,7 +607,7 @@ class ApiQuery extends ApiBase {
                return true;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=revisions&meta=siteinfo&' .
                                'titles=Main%20Page&rvprop=user|comment&continue='
index 36c2088..672c234 100644 (file)
@@ -191,7 +191,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=allcategories&acprop=size'
                                => 'apihelp-query+allcategories-example-size',
index 95ad6ef..725b782 100644 (file)
@@ -376,7 +376,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
 
        private $propertyFilter = array( 'archivename', 'thumbmime', 'uploadwarning' );
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=allimages&aifrom=B'
                                => 'apihelp-query+allimages-example-B',
index 075d199..a70d019 100644 (file)
@@ -280,7 +280,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
                return $allowedParams;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                $p = $this->getModulePrefix();
                $name = $this->getModuleName();
                $path = $this->getModulePath();
index 7e0ceff..98552ba 100644 (file)
@@ -235,7 +235,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&meta=allmessages&amprefix=ipb-'
                                => 'apihelp-query+allmessages-example-ipb',
index d7d71b3..a85c9c9 100644 (file)
@@ -299,7 +299,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=allpages&apfrom=B'
                                => 'apihelp-query+allpages-example-B',
index d9a173d..1c3f9fb 100644 (file)
@@ -362,7 +362,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=allusers&aufrom=Y'
                                => 'apihelp-query+allusers-example-Y',
index a0786b0..5e17a5c 100644 (file)
@@ -550,7 +550,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                return $retval;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                static $examples = array(
                        'backlinks' => array(
                                'action=query&list=backlinks&bltitle=Main%20Page'
index 7804dbf..b4752ae 100644 (file)
@@ -389,7 +389,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                $settings = self::$settings[$this->getModuleName()];
                $name = $this->getModuleName();
                $path = $this->getModulePath();
index 159b1c8..5c44173 100644 (file)
@@ -341,7 +341,7 @@ class ApiQueryBlocks extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=blocks'
                                => 'apihelp-query+blocks-example-simple',
index 7518dad..fcfddd6 100644 (file)
@@ -218,7 +218,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=categories&titles=Albert%20Einstein'
                                => 'apihelp-query+categories-example-simple',
index 8f9b229..5c67ebf 100644 (file)
@@ -109,7 +109,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar'
                                => 'apihelp-query+categoryinfo-example-simple',
index 97c292e..a6fc223 100644 (file)
@@ -379,7 +379,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=categorymembers&cmtitle=Category:Physics'
                                => 'apihelp-query+categorymembers-example-simple',
index b31b14b..7e76db2 100644 (file)
@@ -242,7 +242,7 @@ class ApiQueryContributors extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=contributors&titles=Main_Page'
                                => 'apihelp-query+contributors-example-simple',
index 9d34724..4a5f5fd 100644 (file)
@@ -495,7 +495,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&' .
                                'drprop=user|comment|content'
index 763c306..010f8d5 100644 (file)
@@ -181,7 +181,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles'
                                => 'apihelp-query+duplicatefiles-example-simple',
index 9836352..e77355b 100644 (file)
@@ -216,7 +216,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
                }
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=exturlusage&euquery=www.mediawiki.org'
                                => 'apihelp-query+exturlusage-example-simple',
index b9a4263..6ddb6c8 100644 (file)
@@ -126,7 +126,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=extlinks&titles=Main%20Page'
                                => 'apihelp-query+extlinks-example-simple',
index cb5af2f..39c5902 100644 (file)
@@ -89,7 +89,7 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
                ) ) );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&meta=filerepoinfo&friprop=apiurl|name|displayname'
                                => 'apihelp-query+filerepoinfo-example-simple',
index 415cb3d..6b92603 100644 (file)
@@ -288,7 +288,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=filearchive'
                                => 'apihelp-query+filearchive-example-simple',
index 23f6477..a2af124 100644 (file)
@@ -197,7 +197,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks'
                                => 'apihelp-query+iwbacklinks-example-simple',
index fb398dd..c1208cb 100644 (file)
@@ -179,7 +179,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=iwlinks&titles=Main%20Page'
                                => 'apihelp-query+iwlinks-example-simple',
index 18da1e6..cfd06f1 100644 (file)
@@ -749,7 +749,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo'
                                => 'apihelp-query+imageinfo-example-simple',
index e23ce45..029d945 100644 (file)
@@ -162,7 +162,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=images&titles=Main%20Page'
                                => 'apihelp-query+images-example-simple',
index 3a924b0..7ecb429 100644 (file)
@@ -821,7 +821,7 @@ class ApiQueryInfo extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=info&titles=Main%20Page'
                                => 'apihelp-query+info-example-simple',
index a3a285b..b41b4b7 100644 (file)
@@ -196,7 +196,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=langbacklinks&lbltitle=Test&lbllang=fr'
                                => 'apihelp-query+langbacklinks-example-simple',
index 2b555d3..2d03347 100644 (file)
@@ -175,7 +175,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=langlinks&titles=Main%20Page&redirects='
                                => 'apihelp-query+langlinks-example-simple',
index d654550..3bd3714 100644 (file)
@@ -207,7 +207,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                $name = $this->getModuleName();
                $path = $this->getModulePath();
 
index eb5ca4f..917332b 100644 (file)
@@ -551,7 +551,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                return $ret;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=logevents'
                                => 'apihelp-query+logevents-example-simple',
index e4a5002..026f061 100644 (file)
@@ -96,7 +96,7 @@ class ApiQueryPagePropNames extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=pagepropnames'
                                => 'apihelp-query+pagepropnames-example-simple',
index 130b829..269afb1 100644 (file)
@@ -134,7 +134,7 @@ class ApiQueryPageProps extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=pageprops&titles=Category:Foo'
                                => 'apihelp-query+pageprops-example-simple',
index 1e9bc4d..6ffe0ae 100644 (file)
@@ -160,7 +160,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value'
                                => 'apihelp-query+pageswithprop-example-simple',
index 2694067..3c90acc 100644 (file)
@@ -100,7 +100,7 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                        );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=prefixsearch&pssearch=meaning'
                                => 'apihelp-query+prefixsearch-example-simple',
index 098bfc7..f1e6d01 100644 (file)
@@ -224,7 +224,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=protectedtitles'
                                => 'apihelp-query+protectedtitles-example-simple',
index 2da5777..74586bb 100644 (file)
@@ -158,7 +158,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=querypage&qppage=Ancientpages'
                                => 'apihelp-query+querypage-example-ancientpages',
index f4061e7..282f498 100644 (file)
@@ -165,7 +165,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=random&rnnamespace=0&rnlimit=2'
                                => 'apihelp-query+random-example-simple',
index a620a9f..e20380e 100644 (file)
@@ -703,7 +703,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=recentchanges'
                                => 'apihelp-query+recentchanges-example-simple',
index 27050ce..f5ad9d0 100644 (file)
@@ -775,7 +775,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=revisions&titles=API|Main%20Page&' .
                                'rvprop=timestamp|user|comment|content'
index 7d07f57..cc9f9aa 100644 (file)
@@ -331,7 +331,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                return $params;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=search&srsearch=meaning'
                                => 'apihelp-query+search-example-simple',
index 068418d..18bbc5a 100644 (file)
@@ -834,7 +834,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
                                => 'apihelp-query+siteinfo-example-simple',
index 21b58af..be6f669 100644 (file)
@@ -113,7 +113,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567'
                                => 'apihelp-query+stashimageinfo-example-simple',
index d1e6e28..f3b2652 100644 (file)
@@ -158,7 +158,7 @@ class ApiQueryTags extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=tags&tgprop=displayname|description|hitcount'
                                => 'apihelp-query+tags-example-simple',
index 10d62cb..41f7ee7 100644 (file)
@@ -524,7 +524,7 @@ class ApiQueryContributions extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=usercontribs&ucuser=Example'
                                => 'apihelp-query+usercontribs-example-user',
index f9eb677..fed5a33 100644 (file)
@@ -256,7 +256,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&meta=userinfo'
                                => 'apihelp-query+userinfo-example-simple',
index 6d14523..db8cc1c 100644 (file)
@@ -314,7 +314,7 @@ class ApiQueryUsers extends ApiQueryBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
                                => 'apihelp-query+users-example-simple',
index bb6e59a..4059ff8 100644 (file)
@@ -519,7 +519,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=watchlist'
                                => 'apihelp-query+watchlist-example-simple',
index 71ce27f..ae3596d 100644 (file)
@@ -175,7 +175,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
                );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=query&list=watchlistraw'
                                => 'apihelp-query+watchlistraw-example-simple',
index 5107455..783a39b 100644 (file)
@@ -203,7 +203,7 @@ class ApiRevisionDelete extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=revisiondelete&target=Main%20Page&type=revision&ids=12345&' .
                                'hide=content&token=123ABC'
index 471fb43..02e62a0 100644 (file)
@@ -190,7 +190,7 @@ class ApiRollback extends ApiBase {
                return $this->mTitleObj;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=rollback&title=Main%20Page&user=Example&token=123ABC' =>
                                'apihelp-rollback-example-simple',
index d32b0dd..91f3266 100644 (file)
@@ -51,7 +51,7 @@ class ApiRsd extends ApiBase {
                return new ApiFormatXmlRsd( $this->getMain(), 'xml' );
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=rsd'
                                => 'apihelp-rsd-example-simple',
index ed3ea15..5d37e20 100644 (file)
@@ -230,7 +230,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
                return $result;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=setnotificationtimestamp&entirewatchlist=&token=123ABC'
                                => 'apihelp-setnotificationtimestamp-example-all',
index fb41839..1af83ba 100644 (file)
@@ -97,7 +97,7 @@ class ApiUnblock extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=unblock&id=105'
                                => 'apihelp-unblock-example-id',
index 39b63f4..943ae8e 100644 (file)
@@ -121,7 +121,7 @@ class ApiUndelete extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=undelete&title=Main%20Page&token=123ABC&reason=Restoring%20main%20page'
                                => 'apihelp-undelete-example-page',
index 8cf53d7..9ddadcb 100644 (file)
@@ -64,7 +64,7 @@ class ApiUpload extends ApiBase {
                                $this->dieUsage( 'No upload module set', 'nomodule' );
                        }
                } catch ( UploadStashException $e ) { // XXX: don't spam exception log
-                       $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
+                       $this->handleStashException( $e );
                }
 
                // First check permission to upload
@@ -112,7 +112,7 @@ class ApiUpload extends ApiBase {
                                $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
                        }
                } catch ( UploadStashException $e ) { // XXX: don't spam exception log
-                       $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
+                       $this->handleStashException( $e );
                }
 
                $this->getResult()->addValue( null, $this->getModuleName(), $result );
@@ -159,6 +159,8 @@ class ApiUpload extends ApiBase {
                        if ( $warnings && count( $warnings ) > 0 ) {
                                $result['warnings'] = $warnings;
                        }
+               } catch ( UploadStashException $e ) {
+                       $this->handleStashException( $e );
                } catch ( MWException $e ) {
                        $this->dieUsage( $e->getMessage(), 'stashfailed' );
                }
@@ -180,6 +182,8 @@ class ApiUpload extends ApiBase {
                try {
                        $result['filekey'] = $this->performStash();
                        $result['sessionkey'] = $result['filekey']; // backwards compatibility
+               } catch ( UploadStashException $e ) {
+                       $this->handleStashException( $e );
                } catch ( MWException $e ) {
                        $result['warnings']['stashfailed'] = $e->getMessage();
                }
@@ -205,6 +209,8 @@ class ApiUpload extends ApiBase {
                if ( $this->mParams['offset'] == 0 ) {
                        try {
                                $filekey = $this->performStash();
+                       } catch ( UploadStashException $e ) {
+                               $this->handleStashException( $e );
                        } catch ( MWException $e ) {
                                // FIXME: Error handling here is wrong/different from rest of this
                                $this->dieUsage( $e->getMessage(), 'stashfailed' );
@@ -282,7 +288,8 @@ class ApiUpload extends ApiBase {
                } catch ( MWException $e ) {
                        $message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
                        wfDebug( __METHOD__ . ' ' . $message . "\n" );
-                       throw new MWException( $message );
+                       $className = get_class( $e );
+                       throw new $className( $message );
                }
 
                return $fileKey;
@@ -576,6 +583,41 @@ class ApiUpload extends ApiBase {
                return $warnings;
        }
 
+       /**
+        * Handles a stash exception, giving a useful error to the user.
+        * @param Exception $e The exception we encountered.
+        */
+       protected function handleStashException( $e ) {
+               $exceptionType = get_class( $e );
+
+               switch ( $exceptionType ) {
+                       case 'UploadStashFileNotFoundException':
+                               $this->dieUsage( 'Could not find the file in the stash: ' . $e->getMessage(), 'stashedfilenotfound' );
+                               break;
+                       case 'UploadStashBadPathException':
+                               $this->dieUsage( 'File key of improper format or otherwise invalid: ' . $e->getMessage(), 'stashpathinvalid' );
+                               break;
+                       case 'UploadStashFileException':
+                               $this->dieUsage( 'Could not store upload in the stash: ' . $e->getMessage(), 'stashfilestorage' );
+                               break;
+                       case 'UploadStashZeroLengthFileException':
+                               $this->dieUsage( 'File is of zero length, and could not be stored in the stash: ' . $e->getMessage(), 'stashzerolength' );
+                               break;
+                       case 'UploadStashNotLoggedInException':
+                               $this->dieUsage( 'Not logged in: ' . $e->getMessage(), 'stashnotloggedin' );
+                               break;
+                       case 'UploadStashWrongOwnerException':
+                               $this->dieUsage( 'Wrong owner: ' . $e->getMessage(), 'stashwrongowner' );
+                               break;
+                       case 'UploadStashNoSuchKeyException':
+                               $this->dieUsage( 'No such filekey: ' . $e->getMessage(), 'stashnosuchfilekey' );
+                               break;
+                       default:
+                               $this->dieUsage( $exceptionType . ": " . $e->getMessage(), 'stasherror' );
+                               break;
+               }
+       }
+
        /**
         * Perform the actual upload. Returns a suitable result array on success;
         * dies on failure.
@@ -736,7 +778,7 @@ class ApiUpload extends ApiBase {
                return 'csrf';
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=upload&filename=Wiki.png' .
                                '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
index 7e93c3e..cf8ed5a 100644 (file)
@@ -135,7 +135,7 @@ class ApiUserrights extends ApiBase {
                return $this->getUrUser( $params )->getName();
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
                                => 'apihelp-userrights-example-user',
index 3ba06e1..09638f3 100644 (file)
@@ -172,7 +172,7 @@ class ApiWatch extends ApiBase {
                return $result;
        }
 
-       public function getExamplesMessages() {
+       protected function getExamplesMessages() {
                return array(
                        'action=watch&titles=Main_Page&token=123ABC'
                                => 'apihelp-watch-example-watch',
diff --git a/includes/api/i18n/be-tarask.json b/includes/api/i18n/be-tarask.json
new file mode 100644 (file)
index 0000000..09d2086
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Red Winged Duck"
+               ]
+       },
+       "apihelp-main-param-action": "Дзеяньне для выкананьня."
+}
index 5d5aca4..fbfe7d4 100644 (file)
@@ -65,6 +65,7 @@
        "apihelp-edit-param-nocreate": "رها کردن خطا در صورتی که صفحه وجود ندارد.",
        "apihelp-edit-param-watch": "افزودن صفحه به فهرست پی‌گیری شما",
        "apihelp-edit-param-unwatch": "حذف صفحه از فهرست پی‌گیری شما",
+       "apihelp-edit-param-prependtext": "این متن را به ابتدای صفحه اضافه کنید. $1text را لغو می‌کند.",
        "apihelp-edit-param-redirect": "اصلاح خودکار تغییرمسیرها.",
        "apihelp-edit-example-edit": "ویرایش صفحه",
        "apihelp-emailuser-description": "ایمیل به کاربر",
@@ -84,6 +85,8 @@
        "apihelp-feedcontributions-param-newonly": "فقط نمایش ویرایش‌هایی که تولید‌های صفحه هستند.",
        "apihelp-feedcontributions-param-showsizediff": "نمایش تفاوت حجم تغییرات بین نسخه‌ها.",
        "apihelp-feedrecentchanges-param-feedformat": "فرمت خوراک.",
+       "apihelp-feedrecentchanges-param-namespace": "فضای نام برای محدودکردن نتایج به.",
+       "apihelp-feedrecentchanges-param-invert": "همهٔ فضاهای نام به جز انتخاب‌شده‌ها.",
        "apihelp-feedrecentchanges-param-days": "روز برای محدود کردن نتایج.",
        "apihelp-feedrecentchanges-param-limit": "حداکثر تعداد نتایج خروجی.",
        "apihelp-feedrecentchanges-param-from": "نمایش تغییرات پس از آن.",
        "apihelp-move-param-movesubpages": "انتقال زیر صفحه‌ها اگر امکان‌پذیر است.",
        "apihelp-move-param-noredirect": "عدم ساخت تغییرمسیر.",
        "apihelp-move-param-ignorewarnings": "چشم‌پوشی از همهٔ هشدارها.",
+       "apihelp-opensearch-param-search": "جستجوی رشته.",
+       "apihelp-opensearch-param-limit": "حداکثر تعداد نتایج برای بازگرداندن.",
        "apihelp-opensearch-param-namespace": "فضاهای نامی برای جستجو",
        "apihelp-opensearch-param-format": "فرمت خروجی.",
+       "apihelp-opensearch-example-te": "یافتن صفحه‌هایی که با «ته» آغاز می‌شوند",
        "apihelp-options-example-reset": "بازنشانی همه تنظیمات.",
        "apihelp-parse-example-page": "تجزیه یک صفحه.",
        "apihelp-parse-example-text": "تجزیه متن ویکی.",
        "apihelp-protect-param-reason": "دلیل برای (عدم) حفاظت.",
        "apihelp-protect-example-protect": "محافظت از صفحه",
        "apihelp-purge-param-forcelinkupdate": "به‌روزرسانی جداول پیوندها.",
+       "apihelp-query+allpages-param-filterredir": "صفحه‌هایی که باید فهرست شوند.",
+       "apihelp-query+allpages-param-minsize": "محدودکردن به صفحه‌هایی که همراه دست کم این تعداد بایت است.",
+       "apihelp-query+allredirects-param-limit": "تعداد آیتم‌ها برای بازگرداندن.",
+       "apihelp-query+categorymembers-description": "فهرست‌کردن همهٔ صفحه‌ها در یک ردهٔ مشخص‌شده.",
+       "apihelp-query+categorymembers-param-startsortkey": "جایش از $1starthexsortkey استفاده کنید.",
+       "apihelp-query+imageinfo-param-urlheight": "مشابه $1urlwidth.",
+       "apihelp-query+info-description": "دریافت اطلاعات سادهٔ صفحه.",
+       "apihelp-query+iwbacklinks-param-prefix": "پیشوند میان‌ویکی.",
+       "apihelp-query+iwbacklinks-param-title": "پیوند میان‌ویکی برای جستجو. باید همراه $1blprefix استفاده شود.",
+       "apihelp-query+iwbacklinks-param-limit": "تعداد صفحه‌ها برای بازگرداندن.",
+       "apihelp-query+linkshere-param-limit": "تعداد برای بازگرداندن.",
+       "apihelp-query+logevents-description": "دریافت رویدادها از سیاهه‌ها.",
+       "apihelp-query+protectedtitles-param-namespace": "فقط عنوان‌ها در این فضاهای نام را فهرست کنید.",
+       "apihelp-query+protectedtitles-param-level": "فقط عنوان‌ها در این سطح‌های حفاظت را فهرست کنید.",
+       "apihelp-query+protectedtitles-param-limit": "تعداد صفحه‌ها برای بازگرداندن.",
+       "apihelp-query+protectedtitles-param-start": "آغاز فهرست‌کردن از این برچسب زمانی حفاظت.",
+       "apihelp-query+protectedtitles-param-end": "متوقف‌کردن فهرست‌کردن در این برچسب زمانی حفاظت.",
+       "apihelp-query+random-param-namespace": "بازگرداندن صفحه‌های فقط در این فضاهای نام.",
+       "apihelp-query+random-param-limit": "محدود کنید چه تعداد صفحه بازگردانده خواهد شد.",
+       "apihelp-query+random-param-redirect": "یک تغییرمسیر تصادفی جای یک صفحه تصادفی بارگیری کنید.",
+       "apihelp-query+random-example-simple": "بازگرداندن تو صفحهٔ تصادفی از فضای نام اصلی",
+       "apihelp-query+random-example-generator": "بازگرداندن اطلاعات صفحه دربارهٔ دو صفحهٔ تصادفی از فضای نام اصلی",
+       "apihelp-query+recentchanges-param-start": "برچسب زمانی برای آغاز شمارش از.",
+       "apihelp-query+recentchanges-param-end": "برچسب زمانی برای پایان شمارش.",
+       "apihelp-query+redirects-param-limit": "تعداد تغییرمسیرها برای بازگرداندن.",
        "apihelp-upload-param-ignorewarnings": "چشم‌پوشی از همهٔ هشدارها.",
        "apihelp-userrights-param-user": "نام کاربری.",
        "api-help-param-deprecated": "توصیه.",
index a6167df..2040094 100644 (file)
        "apihelp-query+allfileusages-param-to": "Le titre du fichier auquel arrêter l’énumération.",
        "apihelp-query+allfileusages-param-prefix": "Rechercher tous les titres de fichier qui commencent par cette valeur.",
        "apihelp-query+allfileusages-param-unique": "Afficher uniquement les titres de fichier distincts. Impossible à utiliser avec $1prop=ids.\nQuand utilisé comme générateur, produit les pages cibles au lieu des sources.",
+       "apihelp-query+allfileusages-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page utilisatrice (impossible à utiliser avec $1unique).\n;title:Ajoute le titre du fichier.",
        "apihelp-query+allfileusages-param-limit": "Combien d’éléments renvoyer au total.",
        "apihelp-query+allfileusages-param-dir": "La direction dans laquelle lister.",
        "apihelp-query+allfileusages-example-B": "Lister les titres de fichier, y compris les manquants, avec les ids de page d’où ils proviennent, en commençant à B",
        "apihelp-query+allimages-param-to": "Le titre de l’image auquel arrêter l’énumération. Ne peut être utilisé qu’avec $1sort=name.",
        "apihelp-query+allimages-param-start": "L’horodatage depuis lequel énumérer. Ne peut être utilisé qu’avec $1sort=timestamp.",
        "apihelp-query+allimages-param-end": "L’horodatage de fin de l’énumération. Ne peut être utilisé qu’avec $1sort=timestamp.",
+       "apihelp-query+allimages-param-prop": "Quelle information obtenir sur l’image :\n;timestamp:Ajoute l’horodatage de la version téléchargée.\n;user:Ajoute l’utilisateur qui a téléchargé la version de l’image.\n;userid:Ajoute l’ID de l’utilisateur qui a téléchargé la version de l’image.\n;comment:Commentaire sur la version.\n;parsedcomment:Analyser le commentaire de la version.\n;canonicaltitle:Ajoute le titre canonique du fichier image.\n;url:Fournit l’URL vers l’image et la page de description.\n;size:Ajoute la taille de l’image en octets et sa hauteur et largeur, et le numéro de page (si applicable).\n;dimensions:Alias de la taille.\n;sha1:Ajoute le hachage SHA-1 de l’image.\n;mime:Ajoute le type MIME de l’image.\n;mediatype:Ajoute le type de média de l’image.\n;metadata:Liste les métadonnées Exif de la version de l’image.\n;commonmetadata:Liste les métadonnées génériques du format de fichier pour la version de l’image.\n;extmetadata:Liste les métadonnées mises en forme regroupées depuis différentes sources. Les résultats sont au format HTML.\n;bitdepth:Ajoute la profondeur de couleur de la version.",
+       "apihelp-query+allimages-param-prefix": "Rechercher tous les titres d’image commençant par cette valeur. Utilisable uniquement avec $1sort=name.",
+       "apihelp-query+allimages-param-minsize": "Restreindre aux images avec au moins ce nombre d’octets.",
+       "apihelp-query+allimages-param-maxsize": "Restreindre aux images avec au plus ce nombre d’octets.",
+       "apihelp-query+allimages-param-sha1": "Hachage SHA1 de l’image. Écrase $1sha1base36.",
+       "apihelp-query+allimages-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MédiaWiki).",
+       "apihelp-query+allimages-param-user": "Renvoyer seulement les fichiers téléchargés par cet utilisateur. Utilisable uniquement avec $1sort=timestamp. Impossible à utiliser avec $1filterbots.",
+       "apihelp-query+allimages-param-filterbots": "Comment filtrer les fichiers téléchargés par des robots. Peut être utilisé uniquement avec $1sort=timestamp. Impossible à utiliser avec $1user.",
+       "apihelp-query+allimages-param-mime": "Quel type MIME rechercher, par ex. image/jpeg.",
+       "apihelp-query+allimages-param-limit": "Combien d’images renvoyer au total.",
+       "apihelp-query+allimages-example-B": "Afficher une liste des fichiers commençant par la lettre « B »",
        "apihelp-query+alllinks-param-namespace": "L’espace de noms à énumérer.",
        "apihelp-query+alllinks-param-limit": "Combien d’éléments renvoyer au total.",
        "apihelp-query+alllinks-param-dir": "La direction dans laquelle lister.",
diff --git a/includes/api/i18n/hu.json b/includes/api/i18n/hu.json
new file mode 100644 (file)
index 0000000..cdf9057
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Csega"
+               ]
+       },
+       "apihelp-userrights-param-userid": "Felhasználói azonosító."
+}
diff --git a/includes/api/i18n/lv.json b/includes/api/i18n/lv.json
new file mode 100644 (file)
index 0000000..b24e5f6
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Papuass"
+               ]
+       },
+       "apihelp-userrights-param-userid": "Lietotāja ID:"
+}
index dbcd87c..6ef1d31 100644 (file)
        "apihelp-import-param-namespace": "За меѓујазични увози: увези во овој именски простор.",
        "apihelp-import-param-rootpage": "Увези како потстраница на страницава.",
        "apihelp-import-example-import": "Увези [[meta:Help:Parserfunctions]] во именскиот простор 100 со целата историја.",
+       "apihelp-login-description": "Најавете се и добијте колачиња за заверка.\n\nВо случај кога ќе се најавите успешно, потребните колачиња ќе се придодадат кон заглавијата на HTTP-одѕивот. Во случај да не успеете да се најавите, понатамошните обиди може да се ограничат за да се ограничат нападите со автоматизирано погодување на лозинката.",
        "apihelp-login-param-name": "Корисничко име.",
        "apihelp-login-param-password": "Лозинка.",
        "apihelp-login-param-domain": "Домен (незадолжително).",
        "apihelp-opensearch-param-format": "Формат на изводот.",
        "apihelp-opensearch-example-te": "Најди страници што почнуваат со „Те“",
        "apihelp-options-param-reset": "Ги враќа поставките по основно.",
+       "apihelp-options-param-resetkinds": "Писок на типови можности за повраток кога е зададена можноста „$1reset“.",
+       "apihelp-options-param-change": "Список на промени во форматот name=value (на пр. skin=vector). Вредностите не треба да содржат исправени црти. Ако не зададете вредност (дури ни знак за равенство), на пр., можност|другаможност|..., ќе биде зададена вредноста на можноста по основно.",
+       "apihelp-options-param-optionname": "Назив на можноста што треба да ѝ се зададе на вредноста дадена од „$1optionvalue“.",
+       "apihelp-options-param-optionvalue": "Вредноста на можноста укажана од „$1optionnam“. Може да содржи исправени црти.",
        "apihelp-options-example-reset": "Врати ги сите поставки по основно",
        "apihelp-options-example-change": "Смени ги поставките „skinЗ“ и „hideminor“",
        "apihelp-options-example-complex": "Врати ги сите нагодувања по основно, а потоа задај ги „skin“ и „nickname“",
        "apihelp-paraminfo-description": "Набави информации за прилошки (API) модули.",
        "apihelp-paraminfo-param-modules": "Список на називи на модули (вредности на параметрите action= и format=, или пак „main“). Може да се укажат подмодули со „+“.",
        "apihelp-paraminfo-param-helpformat": "Формат на помошните низи.",
+       "apihelp-paraminfo-param-querymodules": "Список на називи на модули за барања (вредност на параметарот prop=, meta= или list=). Користете го „$1modules=query+foo“ наместо „$1querymodules=foo“.",
+       "apihelp-paraminfo-param-mainmodule": "Добави информации и за главниот (врховен) модул. Користете го „$1modules=main“ наместо тоа.",
        "apihelp-parse-param-summary": "Опис за расчленување.",
+       "apihelp-parse-param-preview": "Расчлени во прегледен режим.",
        "apihelp-parse-param-sectionpreview": "Расчлени во прегледен режим на поднасловот (го овозможува и прегледниот режим).",
        "apihelp-parse-param-disabletoc": "Изземи го преглед на содржината во изводеот.",
        "apihelp-parse-param-contentformat": "Формат на серијализацијата на содржината во вносниот текст. Важи само кога се користи со $1text.",
        "apihelp-parse-example-text": "Расчлени викитекст.",
        "apihelp-parse-example-texttitle": "Расчлени страница, укажувајќи го насловот на страницата.",
        "apihelp-parse-example-summary": "Расчлени опис.",
+       "apihelp-patrol-description": "Испатролирај страница или ревизија.",
+       "apihelp-patrol-param-rcid": "Назнака на спорешните промени за патролирање.",
+       "apihelp-patrol-param-revid": "Назнака на преработката за патролирање.",
+       "apihelp-patrol-example-rcid": "Испатролирај скорешна промена",
+       "apihelp-patrol-example-revid": "Патролирај праработка",
+       "apihelp-protect-description": "Смени го степенот на заштита на страница.",
+       "apihelp-protect-param-title": "Наслов на страница што се (од)заштитува. Не може да се користи заедно со $1pageid.",
+       "apihelp-protect-param-pageid": "Назнака на страница што се (од)заштитува. Не може да се користи заедно со $1title.",
        "apihelp-protect-param-reason": "Причиина за (од)заштитување",
        "apihelp-protect-example-protect": "Заштити страница",
+       "apihelp-purge-example-simple": "Превчитај ги „Главна страница“ и „Прилог“",
+       "apihelp-query-param-list": "Кои списоци да се набават.",
+       "apihelp-query-param-meta": "Кои метаподатоци да се набават.",
        "apihelp-query+backlinks-example-simple": "Прикажи врски до [[Главна страница|Главната страница]]",
        "apihelp-query+backlinks-example-generator": "Дава информации за страниците што водат до [[Главна страница|Главната страница]]",
        "apihelp-query+blocks-description": "Список на сите блокирани корисници и IP-адреси",
        "apihelp-query+blocks-param-end": "На кој датум и време да запре набројувањето.",
        "apihelp-query+blocks-param-ids": "Список на назнаки на блоковите за испис (незадолжително)",
        "apihelp-query+blocks-param-users": "Список на корисници што ќе се пребаруваат (незадолжително)",
+       "apihelp-query+imageinfo-param-urlheight": "Слично на $1urlwidth.",
        "apihelp-format-example-generic": "Форматирај го резултатот од барањето во $1-формат",
        "apihelp-dbg-description": "Давај го изводот во PHP-форматот var_export().",
        "apihelp-dbgfm-description": "Давај го изводот во PHP-форматот var_export() (подобрен испис во HTML).",
index e0e6497..1dabdb9 100644 (file)
@@ -2,7 +2,8 @@
        "@metadata": {
                "authors": [
                        "Siebrand",
-                       "Sjoerddebruin"
+                       "Sjoerddebruin",
+                       "Robin0van0der0vliet"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [https://www.mediawiki.org/wiki/API:Main_page Documentatie]\n* [https://www.mediawiki.org/wiki/API:FAQ FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-maillijst]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-aankondigingen]\n* [https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts Bugs & verzoeken]\n</div>\n<strong>Status:</strong> Alle funties die op deze pagina worden weergegeven horen te werken. Aan de API wordt actief gewerkt, en deze kan gewijzigd worden. Abonneer u op  de [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ e-maillijst mediawiki-api-announce] voor meldingen over aanpassingen.\n\n<strong>Foutieve verzoeken:</strong> als de API foutieve verzoeken ontvangt, wordt er geantwoord met een HTTP-header met de sleutel \"MediaWiki-API-Error\" en daarna worden de waarde van de header en de foutcode op dezelfde waarde ingesteld. Zie https://www.mediawiki.org/wiki/API:Errors_and_warnings voor meer informatie.",
        "apihelp-main-param-maxlag": "De maximale vertraging kan gebruikt worden als MediaWiki is geïnstalleerd op een databasecluster die gebruik maakt van replicatie. Om te voorkomen dat handelingen nog meer databasereplicatievertraging veroorzaken, kan deze parameter er voor zorgen dat de client wacht totdat de replicatievertraging lager is dan de aangegeven waarde. In het geval van buitensporige vertraging, wordt de foutcode \"maxlag\" teruggegeven met een bericht als \"Waiting for $host: $lag seconds lagged\".<br />Zie https://www.mediawiki.org/wiki/Manual:Maxlag_parameter voor mee informatie.",
        "apihelp-main-param-smaxage": "Stelt de header \"s-maxage\" in op het aangegeven aantal seocnden. Foutmeldingen komen nooit in de cache.",
        "apihelp-main-param-maxage": "Stelt de header \"max-age\" in op het aangegeven aantal seocnden. Foutmeldingen komen nooit in de cache.",
+       "apihelp-block-description": "Gebruiker blokkeren.",
+       "apihelp-block-param-reason": "Reden voor blokkade.",
+       "apihelp-edit-example-edit": "Pagina bewerken",
+       "apihelp-emailuser-description": "Gebruiker e-mailen.",
+       "apihelp-emailuser-param-subject": "Onderwerp header.",
+       "apihelp-emailuser-param-text": "Mailbody",
+       "apihelp-expandtemplates-param-title": "Titel van de pagina.",
+       "apihelp-feedcontributions-param-year": "Van jaar (en eerder).",
+       "apihelp-feedcontributions-param-month": "Van maand (en eerder).",
+       "apihelp-login-param-name": "Gebruikersnaam.",
+       "apihelp-login-param-password": "Wachtwoord.",
+       "apihelp-login-param-domain": "Domein (optioneel).",
+       "apihelp-login-example-login": "Aanmelden",
+       "apihelp-move-description": "Pagina hernoemen.",
        "api-help-parameters": "{{PLURAL:$1|Parameter|Parameters}}:",
        "api-help-param-deprecated": "Verouderd.",
        "api-help-param-default": "Standaard: $1",
index 1868073..b22df39 100644 (file)
@@ -800,7 +800,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
        private function stripFieldPrefix( array $fieldNames ) {
                $start = strlen( $this->fieldPrefix );
 
-               return array_map( function( $fieldName ) use ( $start ) {
+               return array_map( function ( $fieldName ) use ( $start ) {
                        return substr( $fieldName, $start );
                }, $fieldNames );
        }
index c2f2223..ffc6b3b 100644 (file)
@@ -46,7 +46,7 @@ class MWDebug {
        protected static $debug = array();
 
        /**
-        * SQL statements of the databses queries.
+        * SQL statements of the database queries.
         *
         * @var array $query
         */
diff --git a/includes/debug/logger/legacy/Logger.php b/includes/debug/logger/legacy/Logger.php
new file mode 100644 (file)
index 0000000..5823d51
--- /dev/null
@@ -0,0 +1,318 @@
+<?php
+/**
+ * @section LICENSE
+ * 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
+ */
+
+/**
+ * PSR-3 logger that mimics the historic implementation of MediaWiki's
+ * wfErrorLog logging implementation.
+ *
+ * This logger is configured by the following global configuration variables:
+ * - `$wgDebugLogFile`
+ * - `$wgDebugLogGroups`
+ * - `$wgDBerrorLog`
+ * - `$wgDBerrorLogTZ`
+ *
+ * See documentation in DefaultSettings.php for detailed explanations of each
+ * variable.
+ *
+ * @see MWLogger
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class MWLoggerLegacyLogger extends \Psr\Log\AbstractLogger {
+
+       /**
+        * @var string $channel
+        */
+       protected $channel;
+
+
+       /**
+        * @param string $channel
+        */
+       public function __construct( $channel ) {
+               $this->channel = $channel;
+       }
+
+       /**
+        * Logs with an arbitrary level.
+        *
+        * @param string|int $level
+        * @param string $message
+        * @param array $context
+        */
+       public function log( $level, $message, array $context = array() ) {
+               if ( self::shouldEmit( $this->channel, $message, $context ) ) {
+                       $text = self::format( $this->channel, $message, $context );
+                       $destination = self::destination( $this->channel, $message, $context );
+                       self::emit( $text, $destination );
+               }
+       }
+
+
+       /**
+        * Determine if the given message should be emitted or not.
+        *
+        * @param string $channel
+        * @param string $message
+        * @param array $context
+        * @return bool True if message should be sent to disk/network, false
+        * otherwise
+        */
+       protected static function shouldEmit( $channel, $message, $context ) {
+               global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+
+               if ( $channel === 'wfLogDBError' ) {
+                       // wfLogDBError messages are emitted if a database log location is
+                       // specfied.
+                       $shouldEmit = (bool)$wgDBerrorLog;
+
+               } elseif ( $channel === 'wfErrorLog' ) {
+                       // All messages on the wfErrorLog channel should be emitted.
+                       $shouldEmit = true;
+
+               } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
+                       $logConfig = $wgDebugLogGroups[$channel];
+
+                       if ( is_array( $logConfig ) && isset( $logConfig['sample'] ) ) {
+                               // Emit randomly with a 1 in 'sample' chance for each message.
+                               $shouldEmit = mt_rand( 1, $logConfig['sample'] ) === 1;
+
+                       } else {
+                               // Emit unless the config value is explictly false.
+                               $shouldEmit = $logConfig !== false;
+                       }
+
+               } elseif ( isset( $context['private'] ) && $context['private'] ) {
+                       // Don't emit if the message didn't match previous checks based on the
+                       // channel and the event is marked as private. This check discards
+                       // messages sent via wfDebugLog() with dest == 'private' and no explicit
+                       // wgDebugLogGroups configuration.
+                       $shouldEmit = false;
+               } else {
+                       // Default return value is the the same as the historic wfDebug
+                       // method: emit if $wgDebugLogFile has been set.
+                       $shouldEmit = $wgDebugLogFile != '';
+               }
+
+               return $shouldEmit;
+       }
+
+
+       /**
+        * Format a message.
+        *
+        * Messages to the 'wfDebug', 'wfLogDBError' and 'wfErrorLog' channels
+        * receive special fomatting to mimic the historic output of the functions
+        * of the same name. All other channel values are formatted based on the
+        * historic output of the `wfDebugLog()` global function.
+        *
+        * @param string $channel
+        * @param string $message
+        * @param array $context
+        * @return string
+        */
+       protected static function format( $channel, $message, $context ) {
+               global $wgDebugLogGroups;
+
+               if ( $channel === 'wfDebug' ) {
+                       $text = self::formatWfDebug( $channel, $message, $context );
+
+               } elseif ( $channel === 'wfLogDBError' ) {
+                       $text = self::formatWfLogDBError( $channel, $message, $context );
+
+               } elseif ( $channel === 'wfErrorLog' ) {
+                       $text = "{$message}\n";
+
+               } elseif ( !isset( $wgDebugLogGroups[$channel] ) ) {
+                       $text = self::formatWfDebug(
+                               $channel, "[{$channel}] {$message}", $context );
+
+               } else {
+                       // Default formatting is wfDebugLog's historic style
+                       $time = wfTimestamp( TS_DB );
+                       $wiki = wfWikiID();
+                       $host = wfHostname();
+                       $text = "{$time} {$host} {$wiki}: {$message}\n";
+               }
+               return $text;
+       }
+
+
+       /**
+        * Format a message as `wfDebug()` would have formatted it.
+        *
+        * @param string $channel
+        * @param string $message
+        * @param array $context
+        * @return string
+        */
+       protected static function formatWfDebug( $channel, $message, $context ) {
+               $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $message );
+               if ( isset( $context['prefix'] ) ) {
+                       $text = "{$context['prefix']}{$text}";
+               }
+               return "{$text}\n";
+       }
+
+
+       /**
+        * Format a message as `wfLogDBError()` would have formatted it.
+        *
+        * @param string $channel
+        * @param string $message
+        * @param array $context
+        * @return string
+        */
+       protected static function formatWfLogDBError( $channel, $message, $context ) {
+               global $wgDBerrorLogTZ;
+               static $cachedTimezone = null;
+
+               if ( $wgDBerrorLogTZ && !$cachedTimezone ) {
+                       $cachedTimezone = new DateTimeZone( $wgDBerrorLogTZ );
+               }
+
+               // Workaround for https://bugs.php.net/bug.php?id=52063
+               // Can be removed when min PHP > 5.3.6
+               if ( $cachedTimezone === null ) {
+                       $d = date_create( 'now' );
+               } else {
+                       $d = date_create( 'now', $cachedTimezone );
+               }
+               $date = $d->format( 'D M j G:i:s T Y' );
+
+               $host = wfHostname();
+               $wiki = wfWikiID();
+
+               $text = "{$date}\t{$host}\t{$wiki}\t{$message}\n";
+               return $text;
+       }
+
+
+       /**
+        * Select the appropriate log output destination for the given log event.
+        *
+        * If the event context contains 'destination'
+        *
+        * @param string $channel
+        * @param string $message
+        * @param array $context
+        * @return string
+        */
+       protected static function destination( $channel, $message, $context ) {
+               global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+
+               // Default destination is the debug log file as historically used by
+               // the wfDebug function.
+               $destination = $wgDebugLogFile;
+
+               if ( isset( $context['destination'] ) ) {
+                       // Use destination explicitly provided in context
+                       $destination = $context['destination'];
+
+               } elseif ( $channel === 'wfDebug' ) {
+                       $destination = $wgDebugLogFile;
+
+               } elseif ( $channel === 'wfLogDBError' ) {
+                       $destination = $wgDBerrorLog;
+
+               } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
+                       $logConfig = $wgDebugLogGroups[$channel];
+
+                       if ( is_array( $logConfig ) ) {
+                               $destination = $logConfig['destination'];
+                       } else {
+                               $destination = strval( $logConfig );
+                       }
+               }
+
+               return $destination;
+       }
+
+
+       /**
+       * Log to a file without getting "file size exceeded" signals.
+       *
+       * Can also log to UDP with the syntax udp://host:port/prefix. This will send
+       * lines to the specified port, prefixed by the specified prefix and a space.
+       *
+       * @param string $text
+       * @param string $file Filename
+       * @throws MWException
+       */
+       public static function emit( $text, $file ) {
+               if ( substr( $file, 0, 4 ) == 'udp:' ) {
+                       # Needs the sockets extension
+                       if ( preg_match( '!^udp:(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
+                               // IPv6 bracketed host
+                               $host = $m[1];
+                               $port = intval( $m[2] );
+                               $prefix = isset( $m[3] ) ? $m[3] : false;
+                               $domain = AF_INET6;
+                       } elseif ( preg_match( '!^udp:(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
+                               $host = $m[1];
+                               if ( !IP::isIPv4( $host ) ) {
+                                       $host = gethostbyname( $host );
+                               }
+                               $port = intval( $m[2] );
+                               $prefix = isset( $m[3] ) ? $m[3] : false;
+                               $domain = AF_INET;
+                       } else {
+                               throw new MWException( __METHOD__ . ': Invalid UDP specification' );
+                       }
+
+                       // Clean it up for the multiplexer
+                       if ( strval( $prefix ) !== '' ) {
+                               $text = preg_replace( '/^/m', $prefix . ' ', $text );
+
+                               // Limit to 64KB
+                               if ( strlen( $text ) > 65506 ) {
+                                       $text = substr( $text, 0, 65506 );
+                               }
+
+                               if ( substr( $text, -1 ) != "\n" ) {
+                                       $text .= "\n";
+                               }
+                       } elseif ( strlen( $text ) > 65507 ) {
+                               $text = substr( $text, 0, 65507 );
+                       }
+
+                       $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
+                       if ( !$sock ) {
+                               return;
+                       }
+
+                       socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
+                       socket_close( $sock );
+               } else {
+                       wfSuppressWarnings();
+                       $exists = file_exists( $file );
+                       $size = $exists ? filesize( $file ) : false;
+                       if ( !$exists ||
+                               ( $size !== false && $size + strlen( $text ) < 0x7fffffff )
+                       ) {
+                               file_put_contents( $file, $text, FILE_APPEND );
+                       }
+                       wfRestoreWarnings();
+               }
+       }
+
+}
diff --git a/includes/debug/logger/legacy/Spi.php b/includes/debug/logger/legacy/Spi.php
new file mode 100644 (file)
index 0000000..a3d34fa
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @section LICENSE
+ * 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
+ */
+
+/**
+ * MWLogger service provider that creates MWLoggerLegacyLogger instances.
+ *
+ * Usage:
+ * @code
+ * $wgMWLoggerDefaultSpi = array(
+ *   'class' => 'MWLoggerLegacySpi',
+ * );
+ * @endcode
+ *
+ * @see MWLogger
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class MWLoggerLegacySpi implements MWLoggerSpi {
+
+       /**
+        * @var array $singletons
+        */
+       protected $singletons = array();
+
+
+       /**
+        * Get a logger instance.
+        *
+        * @param string $channel Logging channel
+        * @return MWLogger Logger instance
+        */
+       public function getLogger( $channel ) {
+               if ( !isset( $this->singletons[$channel] ) ) {
+                       $this->singletons[$channel] = new MWLoggerLegacyLogger( $channel );
+               }
+               return $this->singletons[$channel];
+       }
+
+}
index 1472459..02ab309 100644 (file)
@@ -168,7 +168,7 @@ class MWLoggerMonologHandler extends \Monolog\Handler\AbstractProcessingHandler
                        $this->openSink();
                }
 
-               $text = (string) $record['formatted'];
+               $text = (string)$record['formatted'];
                if ( $this->useUdp() ) {
 
                        // Clean it up for the multiplexer
index 08c9afb..9f14669 100644 (file)
@@ -1303,9 +1303,11 @@ class LocalFile extends File {
                );
                if ( $dbw->affectedRows() == 0 ) {
                        if ( $allowTimeKludge ) {
-                               # Use FOR UPDATE to ignore any transaction snapshotting
+                               # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
                                $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
-                                       array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+                                       array( 'img_name' => $this->getName() ),
+                                       __METHOD__,
+                                       array( 'LOCK IN SHARE MODE' ) );
                                $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
                                # Avoid a timestamp that is not newer than the last version
                                # TODO: the image/oldimage tables should be like page/revision with an ID field
index f3a9fa6..9cf7006 100644 (file)
@@ -54,7 +54,7 @@
        "config-restart": "Ja, opnieuw starten",
        "config-welcome": "=== Controle omgeving ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nLever deze gegevens dan ook aan indien u support vraagt bij de installatie.",
        "config-copyright": "=== Auteursrechten en voorwaarden ===\n\n$1\n\nDit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.\n\nDit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.\nZie de GNU General Public License voor meer informatie.\n\nSamen met dit programma hoort u een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
-       "config-sidebar": "* [//www.mediawiki.org MediaWiki thuispagina]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veel gestelde vragen] (Engelstalig)\n----\n* <doclink href=Readme>Leesmij</doclink> (Engelstalig)\n* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)\n* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)\n* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)",
+       "config-sidebar": "* [//www.mediawiki.org MediaWiki thuispagina]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen] (Engelstalig)\n----\n* <doclink href=Readme>Leesmij</doclink> (Engelstalig)\n* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)\n* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)\n* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)",
        "config-env-good": "De omgeving is gecontroleerd.\nU kunt MediaWiki installeren.",
        "config-env-bad": "De omgeving is gecontroleerd.\nU kunt MediaWiki niet installeren.",
        "config-env-php": "PHP $1 is op dit moment geïnstalleerd.",
old mode 100644 (file)
new mode 100755 (executable)
index 0ba407f..afa5cac 100644 (file)
@@ -38,7 +38,7 @@ class MWOldPassword extends ParameterizedPassword {
        public function crypt( $plaintext ) {
                global $wgPasswordSalt;
 
-               if ( $wgPasswordSalt && count( $this->args ) == 1 ) {
+               if ( $wgPasswordSalt && count( $this->args ) === 1 ) {
                        $this->hash = md5( $this->args[0] . '-' . md5( $plaintext ) );
                } else {
                        $this->args = array();
index 4d6e415..187f895 100644 (file)
@@ -83,10 +83,14 @@ abstract class ParameterizedPassword extends Password {
        }
 
        public function toString() {
-               return
-                       ':' . $this->config['type'] . ':' .
-                       implode( $this->getDelimiter(), array_merge( $this->params, $this->args ) ) .
-                       $this->getDelimiter() . $this->hash;
+               $str = ':' . $this->config['type'] . ':';
+
+               if ( count( $this->params ) || count( $this->args ) ) {
+                       $str .= implode( $this->getDelimiter(), array_merge( $this->params, $this->args ) );
+                       $str .= $this->getDelimiter();
+               }
+
+               return $str . $this->hash;
        }
 
        /**
index 418b5d4..aaf899f 100644 (file)
@@ -143,14 +143,10 @@ abstract class Profiler {
                        if ( is_array( $wgProfiler ) ) {
                                if ( !isset( $wgProfiler['class'] ) ) {
                                        $class = 'ProfilerStub';
-                               } elseif ( $wgProfiler['class'] === 'Profiler' ) {
-                                       $class = 'ProfilerStub'; // b/c; don't explode
                                } else {
                                        $class = $wgProfiler['class'];
                                }
                                self::$__instance = new $class( $wgProfiler );
-                       } elseif ( $wgProfiler instanceof Profiler ) {
-                               self::$__instance = $wgProfiler; // back-compat
                        } else {
                                self::$__instance = new ProfilerStub( array() );
                        }
index 57deb00..eecb936 100644 (file)
@@ -974,12 +974,20 @@ class ResourceLoader {
                                        case 'messages':
                                                $out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
                                                break;
+                                       case 'templates':
+                                               $out .= Xml::encodeJsCall(
+                                                       'mw.templates.set',
+                                                       array( $name, (object)$module->getTemplates() ),
+                                                       ResourceLoader::inDebugMode()
+                                               );
+                                               break;
                                        default:
                                                $out .= self::makeLoaderImplementScript(
                                                        $name,
                                                        $scripts,
                                                        $styles,
-                                                       new XmlJsCode( $messagesBlob )
+                                                       new XmlJsCode( $messagesBlob ),
+                                                       $module->getTemplates()
                                                );
                                                break;
                                }
@@ -1044,15 +1052,20 @@ class ResourceLoader {
         * @param mixed $messages List of messages associated with this module. May either be an
         *   associative array mapping message key to value, or a JSON-encoded message blob containing
         *   the same data, wrapped in an XmlJsCode object.
+        * @param array $templates Keys are name of templates and values are the source of
+        *   the template.
         * @throws MWException
         * @return string
         */
-       public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+       public static function makeLoaderImplementScript( $name, $scripts, $styles,
+               $messages, $templates
+       ) {
                if ( is_string( $scripts ) ) {
                        $scripts = new XmlJsCode( "function ( $, jQuery ) {\n{$scripts}\n}" );
                } elseif ( !is_array( $scripts ) ) {
                        throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
                }
+
                return Xml::encodeJsCall(
                        'mw.loader.implement',
                        array(
@@ -1064,7 +1077,8 @@ class ResourceLoader {
                                // PHP/json_encode() consider empty arrays to be numerical arrays and
                                // output javascript "[]" instead of "{}". This fixes that.
                                (object)$styles,
-                               (object)$messages
+                               (object)$messages,
+                               (object)$templates,
                        ),
                        ResourceLoader::inDebugMode()
                );
index 7bbc9bb..bcef149 100644 (file)
@@ -34,6 +34,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
        /** @var string Remote base path, see __construct() */
        protected $remoteBasePath = '';
 
+       /** @var array Saves a list of the templates named by the modules. */
+       protected $templates = array();
+
        /**
         * @var array List of paths to JavaScript files to always include
         * @par Usage:
@@ -199,6 +202,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
         *         'loaderScripts' => [file path string or array of file path strings],
         *         // Modules which must be loaded before this module
         *         'dependencies' => [module name string or array of module name strings],
+        *         'templates' => array(
+        *             [template alias with file.ext] => [file path to a template file],
+        *         ),
         *         // Styles to always load
         *         'styles' => [file path string or array of file path strings],
         *         // Styles to include in specific skin contexts
@@ -223,6 +229,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                $localBasePath = null,
                $remoteBasePath = null
        ) {
+               // Flag to decide whether to automagically add the mediawiki.template module
+               $hasTemplates = false;
                // localBasePath and remoteBasePath both have unbelievably long fallback chains
                // and need to be handled separately.
                list( $this->localBasePath, $this->remoteBasePath ) =
@@ -238,6 +246,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                                case 'styles':
                                        $this->{$member} = (array)$option;
                                        break;
+                               case 'templates':
+                                       $hasTemplates = true;
+                                       $this->{$member} = (array)$option;
+                                       break;
                                // Collated lists of file paths
                                case 'languageScripts':
                                case 'skinScripts':
@@ -281,6 +293,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                                        break;
                        }
                }
+               if ( $hasTemplates ) {
+                       $this->dependencies[] = 'mediawiki.template';
+               }
        }
 
        /**
@@ -535,6 +550,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                $files = array_merge(
                        $files,
                        $this->scripts,
+                       $this->templates,
                        $context->getDebug() ? $this->debugScripts : array(),
                        $this->getLanguageScripts( $context->getLanguage() ),
                        self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
@@ -590,6 +606,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                        'dependencies',
                        'messages',
                        'targets',
+                       'templates',
                        'group',
                        'position',
                        'skipFunction',
@@ -959,4 +976,30 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
        protected function getLessCompiler( ResourceLoaderContext $context = null ) {
                return ResourceLoader::getLessCompiler( $this->getConfig() );
        }
+
+       /**
+        * Takes named templates by the module and returns an array mapping.
+        *
+        * @return array of templates mapping template alias to content
+        */
+       public function getTemplates() {
+               $templates = array();
+
+               foreach ( $this->templates as $alias => $templatePath ) {
+                       // Alias is optional
+                       if ( is_int( $alias ) ) {
+                               $alias = $templatePath;
+                       }
+                       $localPath = $this->getLocalPath( $templatePath );
+                       if ( file_exists( $localPath ) ) {
+                               $content = file_get_contents( $localPath );
+                               $templates[$alias] = $content;
+                       } else {
+                               $msg = __METHOD__ . ": template file not found: \"$localPath\"";
+                               wfDebugLog( 'resourceloader', $msg );
+                               throw new MWException( $msg );
+                       }
+               }
+               return $templates;
+       }
 }
index 45eb70f..4c49fae 100644 (file)
@@ -134,6 +134,16 @@ abstract class ResourceLoaderModule {
                return '';
        }
 
+       /**
+        * Takes named templates by the module and returns an array mapping.
+        *
+        * @return array of templates mapping template alias to content
+        */
+       public function getTemplates() {
+               // Stub, override expected.
+               return array();
+       }
+
        /**
         * @return Config
         * @since 1.24
index 3d762aa..05bb607 100644 (file)
@@ -251,7 +251,7 @@ class SpecialImport extends SpecialPage {
                                        Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
                                        "</td>
                                        <td class='mw-input'>" .
-                                       Xml::input( 'log-comment', 50, 
+                                       Xml::input( 'log-comment', 50,
                                                ( $this->sourceName == 'upload' ? $this->logcomment : '' ),
                                                array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' .
                                        "</td>
index 37edc0f..2d6213a 100644 (file)
@@ -251,6 +251,25 @@ class LinkSearchPage extends QueryPage {
                return $retval;
        }
 
+       /**
+        * Pre-fill the link cache
+        *
+        * @param DatabaseBase $db
+        * @param ResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               if ( $res->numRows() > 0 ) {
+                       $linkBatch = new LinkBatch();
+
+                       foreach ( $res as $row ) {
+                               $linkBatch->add( $row->namespace, $row->title );
+                       }
+
+                       $res->seek( 0 );
+                       $linkBatch->execute();
+               }
+       }
+
        /**
         * @param Skin $skin
         * @param object $result Result row
index ec9593f..4cbf584 100644 (file)
@@ -549,10 +549,22 @@ class MovePageForm extends UnlistedSpecialPage {
                }
 
                # Do the actual move.
-               $error = $ot->moveTo( $nt, true, $this->reason, $createRedirect );
-               if ( $error !== true ) {
-                       $this->showForm( $error );
+               $mp = new MovePage( $ot, $nt );
+               $valid = $mp->isValidMove();
+               if ( !$valid->isOK() ) {
+                       $this->showForm( $valid->getErrorsArray() );
+                       return;
+               }
+
+               $permStatus = $mp->checkPermissions( $user, $this->reason );
+               if ( !$permStatus->isOK() ) {
+                       $this->showForm( $permStatus->getErrorsArray() );
+                       return;
+               }
 
+               $status = $mp->move( $user, $this->reason, $createRedirect );
+               if ( !$status->isOK() ) {
+                       $this->showForm( $status->getErrorsArray() );
                        return;
                }
 
index 5b34297..ad1f051 100644 (file)
@@ -255,7 +255,7 @@ class SpecialNewpages extends IncludableSpecialPage {
                // The form should be visible on each request (inclusive requests with submitted forms), so
                // return always false here.
                $htmlForm->setSubmitCallback(
-                       function() {
+                       function () {
                                return false;
                        }
                );
index 51dd7bd..77ad9aa 100644 (file)
@@ -746,7 +746,7 @@ class SpecialUpload extends SpecialPage {
         *
         * @todo What about non-BitmapHandler handled files?
         */
-       static public function rotationEnabled() {
+       public static function rotationEnabled() {
                $bitmapHandler = new BitmapHandler();
                return $bitmapHandler->autoRotateEnabled();
        }
@@ -903,7 +903,7 @@ class UploadForm extends HTMLForm {
                                        $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
                                )->parse() .
                                        $this->msg( 'word-separator' )->escaped() .
-                                       $this->msg( 'upload_source_url' )->escaped(),
+                                       $this->msg( 'upload_source_url' )->parse(),
                                'checked' => $selectedSourceType == 'url',
                        );
                }
index 7d80b44..52ce4d3 100644 (file)
@@ -151,6 +151,7 @@ class UploadStash {
 
                if ( !$this->files[$key]->exists() ) {
                        wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" );
+                       // @todo Is this not an UploadStashFileNotFoundException case?
                        throw new UploadStashBadPathException( "path doesn't exist" );
                }
 
index b35967b..c32bd3a 100644 (file)
--- a/index.php
+++ b/index.php
@@ -34,7 +34,7 @@
 # has structures (try/catch, foo()->bar(), etc etc) which throw parse errors in
 # PHP 4. Setup.php and ObjectCache.php have structures invalid in PHP 5.0 and
 # 5.1, respectively.
-if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
+if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.3' ) < 0 ) {
        // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
        require dirname( __FILE__ ) . '/includes/PHPVersionError.php';
        wfPHPVersionError( 'index.php' );
index 912ae86..ab26b65 100644 (file)
        "viewyourtext": "Vostè pot veure i copiar la font de ' ' les modificacions ' ' d'aquesta pàgina:",
        "protectedinterface": "Aquesta pàgina proporciona el text de la interfície del software d'aquest wiki i està protegida per evitar els abusos.\nPer afegir o canviar les traduccions per a tots els wikis, feu servir [//translatewiki.net/ translatewiki.net], el projecte de localització de MediaWiki.",
        "editinginterface": "'''Avís:''' Esteu editant una pàgina que conté cadenes de text per a la interfície d'aquest programari. Tingueu en compte que els canvis que es fan a aquesta pàgina afecten a l'aparença de la interfície d'altres usuaris. Per afegir o modificar traduccions a totes les wikis, plantegeu-vos utilitzar la [//translatewiki.net/ translatewiki.net], el projecte de localització de MediaWiki.",
+       "translateinterface": "Per afegir o canviar traduccions per a tots els wikis, utilitzeu [//translatewiki.net/ translatewiki.net], el projecte de localització de MediaWiki.",
        "cascadeprotected": "Aquesta pàgina està protegida i no es pot modificar perquè està inclosa en {{PLURAL:$1|la següent pàgina, que té|les següents pàgines, que tenen}} activada l'opció de «protecció en cascada»:\n$2",
        "namespaceprotected": "No teniu permís per a modificar pàgines en l'espai de noms '''$1'''.",
        "customcssprotected": "No teniu permisos per editar la pàgina CSS perquè conté els paràmetres personals d'un altre usuari.",
index fc2629b..15f778d 100644 (file)
        "content-model-text": "цхьалхе йоза",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
+       "duplicate-args-category": "Кепийн дехарашкахь юх юха аргументаш йолу агӀонаш",
        "expensive-parserfunction-warning": "'''Тидам бе!''' Ресурсийн функцийн дехарш сов даьлла агӀонаш .\n\nДукху хилла ца деза {{PLURAL:$2|$2 дехар|$2 дехарш|1=цхьана дехар}}, хӀинца $1 {{PLURAL:$1|дехар}} ду.",
        "expensive-parserfunction-category": "Ресурсийн функцийн дехарш сов даьлла агӀонаш",
        "post-expand-template-inclusion-warning": "ДӀахьедар: юкъа тоьхна кепашан жамӀан барам тӀех бокха бу. Цхьайолу кепаш юкъа тухур яц.",
        "search-result-category-size": "$1 {{PLURAL:$1|юкъаяр}} ($2 {{PLURAL:$2|1=бухара категори|бухара категореш}}, $3 {{PLURAL:$3|1=файл|файлаш}}).",
        "search-redirect": "(дlасахьажийна $1)",
        "search-section": "(дакъа $1)",
+       "search-category": "(категори $1)",
        "search-file-match": "(файлан чулацаме тера хилар)",
        "search-suggest": "Хила мега ахьа лоьхарг: $1",
        "search-interwiki-caption": "Гергара проекташ",
-       "search-interwiki-default": "$1 хилам.:",
+       "search-interwiki-default": "$1 хилам:",
        "search-interwiki-more": "(кхин)",
        "search-relatedarticle": "ХӀоттаделларг",
        "searchrelated": "хlоттаделларг",
        "searchall": "массо",
-       "showingresults": "Лахахьа {{PLURAL:$1|гойта|гойту|гойту}} <strong>$1</strong> {{PLURAL:$1|хилам|хиламаш|хиламаш}}, дlаболало кху № <strong>$2</strong>.",
+       "showingresults": "Лахахьа {{PLURAL:$1|гойту}} <strong>$1</strong> {{PLURAL:$1|хилам}}, дӀаболало кху № <strong>$2</strong>.",
        "showingresultsinrange": "Лахахь гайтина {{PLURAL:$1|<strong>1</strong> хилам}} диапазонехь <strong>$2</strong> тӀера <strong>$3</strong> кхаччалц.",
        "search-showingresults": "{{PLURAL:$4|Хилам <strong>$1</strong> <strong>$3</strong> нах}}",
        "search-nonefound": "Дехаре терра цхьа хӀума ца карийна.",
        "brokenredirects-delete": "дӀаяккха",
        "withoutinterwiki": "Юкъарвики-хьажоргаш йоцу агӀонаш",
        "withoutinterwiki-summary": "Лахара агӀонийн юкъарвики-хьажоргаш яц:",
+       "withoutinterwiki-legend": "ТӀетоьхна элпаш",
        "withoutinterwiki-submit": "Гайта",
        "fewestrevisions": "ЧӀогӀа кӀезиг версеш йолу агӀонаш",
        "nbytes": "$1 {{PLURAL:$1|байт}}",
        "pager-newer-n": "{{PLURAL:$1|алсамо керла 1|алсамо керланаш $1}}",
        "pager-older-n": "{{PLURAL:$1|алсамо шира 1|алсамо ширниш $1}}",
        "suppress": "Хьулдар",
+       "apihelp": "API гӀо",
+       "apihelp-no-such-module": "Модуль «$1» цакарий.",
        "booksources": "Жайнан хьосташ",
        "booksources-search-legend": "Жайнех лаьцна хаам лахар",
        "booksources-search": "Лаха",
        "locknoconfirm": "Ахьа бакъдеш йолу меттиге билгало йилина яц.",
        "lockdbsuccesssub": "Хаамийн базан блоктоьхна",
        "unlockdbsuccesssub": "Хаамийн базан тӀера блокдӀаяьккхина",
+       "lockedbyandtime": "($1 $2 $3)",
        "move-page": "$1 — цӀе хийцар",
        "move-page-legend": "ЦӀe хийца яр",
        "movepagetext": "Бухахь йолу форманца агӀон цӀе хийцало. Цул совнах цуьна хийцаман тептар кхоьчу метте доккха. Хьалхалера цӀарахь хиръю керла кхоьллина агӀонан хьажораг.\n\nХьовсалаш [[Special:DoubleRedirects|шалха]] а [[Special:BrokenRedirects|йохна хьажоргаш]] юй техь аьлла.\n\nШу жоьпехь ду хьажоргаш нийса некъ гойтуш хиларан.\n\nТидам бе хьалхалера агӀон цӀе ‘’’хийцалур яц’’’ иштта цӀе йолу агӀо йолуш елахь. Юкъардаккхар: йолуш йолу агӀо кхоьчухьа хьажораг елахь, я еса елахь а, цуьна хийцаме истори яцахь а.\n\nИ бохург ду шун агӀонан цӀе юха а хьалха хилларгчунтӀе хийца йиш ю, амма йолуш йолу агӀо дӀаяккха йиш яц.\n\n'''ДӀАХЬЕДАР!'''\n\nЦӀе хийцар бахьнехь гӀаръяьлла агӀонашна дукха дагахь боцу хийцамаш хила тарло. Цундела цӀе хийцале шеш хила тарлучу тӀехьонашах кхета аьлла тешна хила.",
        "movenotallowedfile": "Хьан файлийн цӀераш хийца бакъо яц.",
        "cant-move-user-page": "Хьан бакъо яц декъашхойн коьрта агӀонийн цӀераш хийца.",
        "cant-move-to-user-page": "Хьан бакъо яц агӀона цӀе декъашхочун агӀон тӀе хийца (бухара агӀон тӀе хийца мега).",
+       "cant-move-category-page": "Хьан категорийн цӀераш хийца бакъо яц.",
+       "cant-move-to-category-page": "Хьан категорийн цӀераш хийца бакъо яц.",
        "newtitle": "Керла цӀе",
        "move-watch": "Латайé хӀара агӀо тергаме могӀанан юкъахь",
        "movepagebtn": "АгӀон цӀе хийца",
        "nextdiff": "Тlяхьа догlа нисдинарг →",
        "imagemaxsize": "Суьртан бараман доза тохар:<br />''(Файла агӀона куц дийцар)''",
        "thumbsize": "Жима дина суьртан барам:",
-       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|агlо|агlонаш|агlонаш}}",
+       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|агӀо}}",
        "file-info": "файлан барам: $1, MIME-тайп: $2",
        "file-info-size": "$1 × $2 пиксель, файлан барам: $3, MIME-тайп: $4",
        "file-info-size-pages": "$1 × $2 пиксель, файлан барам: $3, MIME-тайп: $4, $5 {{PLURAL:$5|1=агӀо|агӀонаш}}",
        "confirm-unwatch-button": "ХӀаъ",
        "confirm-unwatch-top": "ДӀаяккха хӀара агӀо хьай тергаме могӀанан юкъар?",
        "comma-separator": ",&#32;",
+       "quotation-marks": "«$1»",
        "imgmultipageprev": "← хьалхара агlо",
        "imgmultipagenext": "тlаьхьара агlо →",
        "imgmultigo": "ДехьагӀо!",
        "imgmultigoto": "АгӀончу $1 гӀо",
        "img-lang-default": "(Ӏад битарца болу мотт)",
+       "img-lang-info": "Гайта хӀара сурт $1 $2 маттахь",
        "img-lang-go": "Кхочушдé",
+       "ascending_abbrev": "гар",
+       "descending_abbrev": "йина",
        "table_pager_next": "Тlаьхьа йогlу агlо",
        "table_pager_prev": "Хьалха йоьду агlо",
        "table_pager_first": "Дуьххьаралера агlо",
        "watchlistedit-raw-explain": "Лахахь гойтуш ю хьа тергаме могӀанийн юкъахь йолу агӀонаш. Хьан йиш ю могӀан хийцам ба, оьцу чура цӀераш тӀетухуш а дӀайохкуш а.\nХийцамаш бина баьлчи тӀетаӀе кнопка «{{int:Watchlistedit-raw-submit}}».\nХьа кхин йиш ю [[Special:EditWatchlist|лело стандартни тадар]].",
        "watchlistedit-raw-titles": "ДӀаяздарш:",
        "watchlistedit-raw-submit": "МогӀам Ӏалашбар",
+       "watchlistedit-raw-done": "Хьан тергаман могӀам Ӏалашбина",
        "watchlistedit-raw-added": "{{PLURAL:$1|ТӀетоьхна}} $1 {{PLURAL:$1|дӀаяздар|дӀаяздарш}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|ДӀаяьккхина|ДӀаяьхна}} $1 {{PLURAL:$1|дӀаяздар|дӀаяздарш}}:",
        "watchlistedit-clear-title": "Тергаман могӀам дӀацӀанбар",
        "watchlisttools-edit": "Хьажа/нисбé могӀам",
        "watchlisttools-raw": "Йоза санна тае",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|дийцаре]])",
+       "unknown_extension_tag": "«$1» шордаран ца йоьвзу тег",
        "version": "Верси MediaWiki",
        "version-extensions": "ДӀахӀоттийна шордарш",
        "version-skins": "ДӀахӀоттийна кечяран темаш",
        "version-software-version": "Верси",
        "version-entrypoints": "ЧугӀо адресин тӀадамаш",
        "version-entrypoints-header-entrypoint": "Яздаран тӀадам",
+       "version-entrypoints-header-url": "URL",
        "version-entrypoints-articlepath": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgArticlePath АгӀона тӀе некъ]",
        "version-entrypoints-scriptpath": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgScriptPath Скриптан тӀе некъ]",
        "redirect": "Декъашхочун файлан тӀера дӀасхьажор",
        "fileduplicatesearch-result-1": "«$1» файлах тера хӀума яц.",
        "fileduplicatesearch-noresults": "ЦӀе «$1» йолуш файл цакарий.",
        "specialpages": "Леррина агӀонаш",
+       "specialpages-note-top": "Легенда",
        "specialpages-note": "* Гуттарлера белха агlонаш.\n* <strong class=\"mw-specialpagerestricted\">Кlеззиг таронаш йолу леррина агlонаш.</strong>",
        "specialpages-group-maintenance": "Жамlаш гlирса хьашташ кхочушдар",
        "specialpages-group-other": "Кхин белхан агӀонаш",
index 71c0722..cacfe0f 100644 (file)
        "api-error-stashfailed": "Interner Fehler: Der Server konnte keine temporäre Datei speichern.",
        "api-error-publishfailed": "Interner Fehler: Der Server konnte die temporäre Datei nicht veröffentlichen.",
        "api-error-stasherror": "Beim Hochladen der Datei gab es einen Fehler.",
+       "api-error-stashedfilenotfound": "Die vorab gespeicherte Datei wurde beim Versuch, sie vom Speicher hochzuladen, nicht gefunden.",
+       "api-error-stashpathinvalid": "Der Pfad, unter dem die gespeicherte Datei gefunden werden sollte, war ungültig.",
+       "api-error-stashfilestorage": "Beim Speichern der Datei in den Speicher gab es einen Fehler.",
+       "api-error-stashzerolength": "Der Server konnte die Datei nicht speichern, da sie eine Länge von Null hat.",
+       "api-error-stashnotloggedin": "Du musst angemeldet sein, um Dateien in den Hochladespeicher zu speichern.",
+       "api-error-stashwrongowner": "Die Datei, auf die du im Speicher zugreifen möchtest, gehört nicht dir.",
+       "api-error-stashnosuchfilekey": "Der Dateischlüssel, auf den du im Speicher zugreifen möchtest, ist nicht vorhanden.",
        "api-error-timeout": "Der Server hat nicht innerhalb der erwarteten Zeit reagiert.",
        "api-error-unclassified": "Ein unbekannter Fehler ist aufgetreten.",
        "api-error-unknown-code": "Unbekannter Fehler: „$1“",
index 1c6890c..341f626 100644 (file)
        "api-error-stashfailed": "Internal error: Server failed to store temporary file.",
        "api-error-publishfailed": "Internal error: Server failed to publish temporary file.",
        "api-error-stasherror": "There was an error while uploading the file to stash.",
+       "api-error-stashedfilenotfound": "The stashed file was not found when attempting to upload it from the stash.",
+       "api-error-stashpathinvalid": "The path at which the stashed file should have been found was invalid.",
+       "api-error-stashfilestorage": "There was an error while storing the file in the stash.",
+       "api-error-stashzerolength": "The server could not stash the file, because it had zero length.",
+       "api-error-stashnotloggedin": "You must be logged in to save files in the upload stash.",
+       "api-error-stashwrongowner": "The file you were attempting to access in the stash does not belong to you.",
+       "api-error-stashnosuchfilekey": "The file key you were attempting to access in the stash does not exist.",
        "api-error-timeout": "The server did not respond within the expected time.",
        "api-error-unclassified": "An unknown error occurred.",
        "api-error-unknown-code": "Unknown error: \"$1\".",
index cdeb08c..294d230 100644 (file)
        "revdelete-restricted": "administratzaileentzako mugak ezarri dira",
        "revdelete-unrestricted": "administratzaileentzako mugak kendu dira",
        "logentry-move-move": "$1 {{GENDER:$2|wikilariak}} «$3» orria «$4» izenera aldatu du",
-       "logentry-move-move-noredirect": "$1 {{GENDER:$2|wikilariak}} $3 orria $4 izenera aldatu du, birzuzenketarik utzi gabe",
+       "logentry-move-move-noredirect": "$1 {{GENDER:$2|wikilariak}} «$3» orria «$4» izenera aldatu du, birzuzenketarik utzi gabe",
        "logentry-move-move_redir": "$1 {{GENDER:$2|wikilariak}} «$3» orria «$4» izenera aldatu du, birzuzenketaren gainetik",
        "logentry-move-move_redir-noredirect": "$1 {{GENDER:wikilariak}} «$3» orria «$4» izenera aldatu du, birbideratze bat gainidatzita, birbideratzerik utzi gabe",
        "logentry-patrol-patrol": "$1(e)k $3 orrialdearen $4 berrikuzpena patruilatutzat {{GENDER:$2|markatu}} du",
index 49abd11..883ed0d 100644 (file)
        "showhideselectedversions": "تغییر پدیداری نسخه‌های انتخاب‌شده",
        "editundo": "خنثی‌سازی",
        "diff-empty": "(بدون تفاوت)",
-       "diff-multi-sameuser": "({{PLURAL:$1|یک نسخهٔ میانی|$1 نسخهٔ میانی}} توسط کاربر مشابهی که نشان داده نشده)",
+       "diff-multi-sameuser": "({{PLURAL:$1|یک نسخهٔ میانی|$1 نسخهٔ میانی}}ِ همین کاربر نمایش داده نشده است)",
        "diff-multi-otherusers": "({{PLURAL:$1|یک نسخهٔ متوسط|$1 نسخه‌های متوسط}} توسط {{PLURAL:$2|کاربر دیگری|$2 کاربران}} نشان داده نشده)",
        "diff-multi-manyusers": "({{PLURAL:$1|یک|$1}} ویرایش میانی توسط بیش از {{PLURAL:$2|یک|$2}} کاربر نشان داده نشده‌است)",
        "difference-missing-revision": "{{PLURAL:$2|یک ویرایش|$2 ویرایش}}  از تفاوت نسخه‌ها ($1) {{PLURAL:$2|یافت|یافت}}  نشد.\n\nمعمولاً در اثر پیوند به تاریخچهٔ به‌روز نشدهٔ صفحهٔ حذف شده است.\nمی‌توانید جزئیات بیشتر را در [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاههٔ حذف] بیابید.",
index 3c2bdda..57c018c 100644 (file)
@@ -35,6 +35,7 @@
        "tog-watchdefault": "Sälber gändereti Syte un Dateie automatisch beobachte",
        "tog-watchmoves": "Sälber verschobeni Sytene un Dateie automatisch beobachte",
        "tog-watchdeletion": "Sälber gleschti Sytene un Dateie automatisch beobachte",
+       "tog-watchrollback": "Syte, wun i zruckgsetzt haa, automatisch beobachte",
        "tog-minordefault": "Alli dyni Änderigen als «chlyni Änderige» markiere",
        "tog-previewontop": "Vorschou vor em Editierfänschter aazeige",
        "tog-previewonfirst": "Vorschou aazeige bim erschten Editiere",
        "otherlanguages": "Anderi Sproche",
        "redirectedfrom": "(Witergleitet vun $1)",
        "redirectpagesub": "Umgleiteti Syte",
+       "redirectto": "Wyterleitig uf:",
        "lastmodifiedat": "Letschti Änderig vo dere Syte: $2, $1<br />",
        "viewcount": "Die Syte isch {{PLURAL:$1|eimol|$1 Mol}} bsuecht wore.",
        "protectedpage": "Gschützti Syte",
        "hidetoc": "zueklappe",
        "collapsible-collapse": "zueklappe",
        "collapsible-expand": "ufklappe",
+       "confirmable-confirm": "{{GENDER:$1|Bisch}} sicher?",
+       "confirmable-yes": "Jo",
+       "confirmable-no": "Nei",
        "thisisdeleted": "Aaluege oder widerherstelle vu $1?",
        "viewdeleted": "$1 aaluege?",
        "restorelink": "{{PLURAL:$1|gleschti Änderig|$1 gleschti Ändrige}}",
        "viewsourcetext": "Quelltext vo dere Syte:",
        "viewyourtext": "Du chasch dr Quälltext vu '''Dyre Bearbeitig''' vu däre Syte aaluege un kopiere:",
        "protectedinterface": "In däre Syte het s Text fir s Sproch-Interface vu dr Software un si isch gsperrt, zum Missbruch z verhindre.",
-       "editinginterface": "'''Obacht:''' Du bisch e Syten am Verändere, wu zum User.Interface ghert. Wänn Du die Syte veränderesch, no änderet sich s User-Interface au fir di andere Benutzer vu däm Wiki. Fir Ibersetzige lueg bitte, eb Du doodefir s [//translatewiki.net/wiki/Main_Page?setlang=gsw Translatewiki] witt bruuche, s MediaWiki-Lokalisierigsprojäkt.",
+       "editinginterface": "<strong>Warnig:</strong> Uf däre Syte het s Täxt, wu vu dr MediaWiki-Software brucht wird. Änderigen uf däre Syte wirke si uf d Benutzeroberflechi vu däm Wiki uus.",
+       "translateinterface": "Go Ibersetzige fir alli Wiki zuefiege oder ändere, bruuch bitte [//translatewiki.net/ translatewiki.net], s MediaWiki-Lokalisierigsprojäkt.",
        "cascadeprotected": "Die Syte isch fir s Bearbeite gsperrt. Si isch yybunde in {{PLURAL:$1|die Syte, wu do chunnt|die Syte, wu do chemme}} , wu mit ere Kaskadesperroption gschitzt {{PLURAL:$1|isch|sin}}:\n$2",
        "namespaceprotected": "Du hesch kei Berächtigung, die Syte im '''$1'''-Namensruum z bearbeite.",
        "customcssprotected": "Du bisch nid berächtigt, die Syte mit CSS z bearbeite, wel si zue dr persenlige Yystellige vun eme andere Benutzer ghert.",
        "invalidtitle-knownnamespace": "Nit-gültige Titel mit Namensruum „$2“ un Text „$3“",
        "invalidtitle-unknownnamespace": "Ungültige Titel mit unbekannte Namensruumnummer $1 un Text „$2“",
        "exception-nologin": "Nit aagmäldet",
-       "exception-nologin-text": "Du muesch Di [[Special:Userlogin|aamälde]] go die Syte oder Aktion ufruefe chenne.",
+       "exception-nologin-text": "Du muesch Di aamälde go die Syte oder Aktion ufruefe chenne.",
        "exception-nologin-text-manual": "Du muesch Di $1 go die Syte oder Aktion ufruefe chenne.",
        "virus-badscanner": "Fählerhafti Konfiguration: Virescanner, wu nid bekannt isch: ''$1''",
        "virus-scanfailed": "Scan het nid funktioniert (code $1)",
        "createaccount-text": "Fir Dii isch e Benutzerkonto \"$2\" uf {{SITENAME}} ($4) aaglait wore. S Passwort fir \"$2\" , wu automatisch generiert woren isch, isch \"$3\". Du sottsch Di jetz aamälde un s Passwort ändere.\n\nWänn s Benutzerkonto us Versäh aaglait woren isch, chasch die Nochricht ignoriere.",
        "login-throttled": "Du hesch z vilmol umesuscht versuecht, Di aazmälde. Bitte wart $1, voreb Du s non emol versuechsch.",
        "login-abort-generic": "Dyy Aamäldig isch nit erfolgryych gsii – Abbroche",
+       "login-migrated-generic": "Dy Benutzerkonto isch migriert wore, Dy Benutzername git s nimi uf däm Wiki.",
        "loginlanguagelabel": "Sproch: $1",
        "suspicious-userlogout": "Dyy Versuech di abzmälde isch abbroche wore, wel s uusgsäh het, wie wänn s vun eme bschedigte Browser oder eme Cacheproxy uus gsändet woren isch.",
        "createacct-another-realname-tip": "Dr richtig Name isch optional.\nWänn Du ne aagiisch, wird er bruucht fir d Zueornig vu dr Byytreg.",
        "resetpass-temp-password": "Temporär Passwort:",
        "resetpass-abort-generic": "D Passwortänderig isch dur e Erwyterig abbroche wore.",
        "resetpass-expired": "Dy Passwort isch abglofe. Bitte leg e nej Passwort fir d Aamäldig fescht.",
+       "resetpass-expired-soft": "Dy Passwort isch abgloffe un mueß zruckgsetzt wäre. Bitte wehl jetz e nei Passwort uus oder klick uf „{{int:resetpass-submit-cancel}}“, go s speter zrucksetze.",
+       "resetpass-validity-soft": "Dy Passwort isch nit giltig: $1\n\nBitte wehl jetz e nei Passwort oder klick uf „{{int:resetpass-submit-cancel}}“, go s speter zrucksetze.",
        "passwordreset": "Passwort zruggsetze",
        "passwordreset-text-one": "Fill des Formular uus go Dy Passwort zrucksetze.",
        "passwordreset-text-many": "{{PLURAL:$1|Fill eis vu dr Fälder uus go Dy Passwort zrucksetze.}}",
        "changeemail-none": "(nyt)",
        "changeemail-password": "Dy {{SITENAME}}-Passwort:",
        "changeemail-submit": "E-Mail Adräss ändre",
+       "changeemail-throttled": "Du hesch z vilmol versuecht Di aazmälde. Bitte wart $1, voreb Du s non emol versuechsch.",
        "resettokens": "Token zrucksetze",
        "resettokens-text": "Du chasch Token zrucksetze, wu Dir dr Zuegriff uf bstimmti privati Date megli mache, wu mit Dym Benutzerkonto do verchnipft sin.\n\nDes sottsch nume mache, wänn Du d Token us Versää mit eberem teilt hesch oder Dy Konto gfehrdet isch.",
        "resettokens-no-tokens": "S git kei Token zum Zrucksetze.",
        "preview": "Vorschou",
        "showpreview": "Vorschau aaluege",
        "showdiff": "Zeig Änderige",
-       "anoneditwarning": "'''Warnig:''' Si sin nit aagmäldet. Ihri IP-Adrässe wird in de Gschicht vo däm Artikel gspeicheret.",
+       "blankarticle": "<strong>Warnig:</strong> D Syte, wu Du aalaisch, isch läär.\nWänn Du nomol uf „{{int:savearticle}}“ klicksch, wird d Syte ohni Inhalt aaglait.",
+       "anoneditwarning": "<strong>Warnig:</strong> Du bisch nit aagmäldet. Dy IP-Adräss wird effetli sichtbar, wänn Du Bearbeitige machsch. Wänn di <strong>[$1 aamälde duesch]</strong> oder <strong>[$2 e Benutzerkonto aalaisch]</strong>, wäre Bearbeitige zämme mit andere Bytreg Dym Nenutzername zuegordnet.",
        "anonpreviewwarning": "''Du bisch nit aagmäldet. Bim Spychere wird Dyy IP-Adräss yydrait in d Versionsgschicht vu däre Syte.''",
        "missingsummary": "'''Obacht:''' Du hesch kei Zämefassig aagee. Wenn du nomol uf Spychere drucksch, wird d Änderung ohni gspychert.",
        "missingcommenttext": "Bitte gib Dyy Kommentar unte yy.",
        "edit-gone-missing": "D Syte het nid chenne aktalisiert wäre.\nSi isch schyns glescht wore.",
        "edit-conflict": "Bearbeitigskonflikt.",
        "edit-no-change": "Dyyni Bearbeitig isch ignoriert wore, wel kei Änderig am Täxt gmacht woren isch.",
+       "postedit-confirmation-created": "D Syte isch aaglait wore.",
+       "postedit-confirmation-restored": "D Syte isch widerhärgstellt wore.",
        "postedit-confirmation-saved": "Dyy Bearbeitig isch gspycheret wore.",
        "edit-already-exists": "Di nej Syte het nid chenne aaglait wäre, wel s si scho git.",
        "defaultmessagetext": "Standardtext",
        "content-failed-to-parse": "Parse vum Inhalt $2 fir Modell $1 fählgschlaa: $3",
        "invalid-content-data": "Uugiltigi Inhaltsdate",
        "content-not-allowed-here": "Dr Inhalt „$1“ isch uf dr Syte [[$2]] nit erlaubt",
-       "editwarning-warning": "Wänn Du die Syte verlosch, cha s syy, ass Du alli Bearbeitige verliersch, wu Du do dra gmacht hesch.\nWänn Du aagmäldet bisch, chasch s Anzeige vu däre Warnig im „Bearbeite“-Beryych vu Dyyne Yystelligen abstelle.",
+       "editwarning-warning": "Wänn Du die Syte verlosch, cha s syy, ass Du alli Bearbeitige verliersch, wu Du do dra gmacht hesch.\nWänn Du aagmäldet bisch, chasch s Anzeige vu däre Warnig im „{{int:prefs-editing}}“-Beryych vu Dyyne Yystelligen abstelle.",
+       "editpage-notsupportedcontentformat-title": "S Inhaltsformat wird nit unterstitzt",
+       "editpage-notsupportedcontentformat-text": "S Inhaltsformat $1 wird vum Inhaltsmodäll $2 nit unterstitzt.",
        "content-model-wikitext": "Wikitext",
        "content-model-text": "Klartext",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
+       "duplicate-args-category": "Syte, wu doppleti Argumänt in Vorlagenufruef verwände",
+       "duplicate-args-category-desc": "Uf dr Syte het s Vorlagenufruef, wu Duplikat vu Argumänt verwände, wie <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> oder <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "expensive-parserfunction-warning": "Achtig: In däre Syte het s z vyyl Ufruef vu ufwändige Parserfunktione.\n\nS {{PLURAL:$2|derf nid meh wie ein Ufruef|derfe nid meh wie $1 Ufruef}} gee.",
        "expensive-parserfunction-category": "Syte, wu ufwändigi Parserfunktione z vyylmol ufruefe",
        "post-expand-template-inclusion-warning": "Warnig: D Gressi vu yybundene Vorlage isch z gross, e Teil Vorlage chenne nid yybunde wäre.",
        "parser-template-recursion-depth-warning": "Vorlagerekursionstiefegränz iberschritte ($1)",
        "language-converter-depth-warning": "Gränz vu dr Sprochkonvertertiefi iberschritte ($1)",
        "node-count-exceeded-category": "Syte, wo d Chnotezaal überschritte hen",
-       "node-count-exceeded-warning": "Die Syte het d Chnotepunktzaal überschritte.",
+       "node-count-exceeded-category-desc": "D Syte iberschrytet di maximal Chnotenaazahl.",
+       "node-count-exceeded-warning": "Die Syte het d Chnotepunktzaal iberschritte.",
        "expansion-depth-exceeded-category": "Syte, wo d Expansionsdiefi überschritte hen",
+       "expansion-depth-exceeded-category-desc": "D Syte iberschrytet di maximal Expandierigstiefi.",
        "expansion-depth-exceeded-warning": "Die Syte het d Expansionsdiefi überschritte.",
        "parser-unstrip-loop-warning": "Zirkelbezug festgstellt",
        "parser-unstrip-recursion-limit": "Rekursionsgränz bim Ufflöse überschritte ($1)",
        "undo-success": "Zum die Änderig ruckgängig z mache, kontrollier bitte d Bearbeitig in dr Verglichsaasicht un druck derno uf „Syte spichere“.",
        "undo-failure": "D Änderig het nid chenne ruckgängig gmacht wäre, wel dää Abschnitt mittlerwyli gänderet woren isch.",
        "undo-norev": "D Bearbeitig het nid chenne ruckgängig gmacht wäre, wel si nid vorhande oder glescht isch.",
+       "undo-nochange": "Schyns isch die Bearbeitig scho rugggängig gmacht wore.",
        "undo-summary": "D Änderig $1 vu [[Special:Contributions/$2|$2]] ([[User talk:$2|Diskussion]]) isch ruckgängig gmacht wore.",
        "undo-summary-username-hidden": "Änderig $1 vun eme versteckte Benutzer ruckgängig gmacht.",
        "cantcreateaccounttitle": "Benutzerkonto cha nid aagleit wäre.",
        "cantcreateaccount-text": "S Aalege vu me Benutzerkonto vu dr IP-Adräss '''($1)''' isch dur [[User:$3|$3]] gsperrt wore.\n\nGrund vu dr Sperri: ''$2''",
+       "cantcreateaccount-range-text": "S Aalege vu Benutzerkonte vu IP-Adrässen im Berych '''$1''', wu s Dyni IP-Adräss ('''$4''') din het, isch vu [[User:$3|$3]] gsperrt wore.\n\nDr Grund, wu vu $3 aagee woren isch: ''$2''",
        "viewpagelogs": "Logbüecher für die Syten azeige",
        "nohistory": "S git kei Versionsgschicht fir die Syte.",
        "currentrev": "Itzigi Version",
        "currentrev-asof": "Aktuälli Version vu $1",
        "revisionasof": "Version vo $1",
-       "revision-info": "Alti Bearbeitig vom $1 dür $2",
+       "revision-info": "Version vu $4, $5 Uhr vu {{GENDER:$6|$2}}$7",
        "previousrevision": "← Vorderi Version",
        "nextrevision": "Nächschti Version →",
        "currentrevisionlink": "Itzigi Version",
        "rev-deleted-event": "(Logbuechaktion uusegnuh)",
        "rev-deleted-user-contribs": "[Benutzername oder IP-Adräss uusegnuu - Bearbeitig in dr Byytragslischt versteckt]",
        "rev-deleted-text-permission": "Die Version isch '''glescht''' wore.\nInformation zue dr Leschig un e Begrindig het s im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lesch-Logbuech].",
+       "rev-suppressed-text-permission": "Die Version isch <strong>unterdruckt</strong> wore.\nEinzelheite het s im [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} Oversight-Logbuech].",
        "rev-deleted-text-unhide": "Die Version isch '''gelöscht''' worde.\nMee Angabe chasch im [{{fullurl:{{#special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lösch-Logbuech] finde.\nDu chasch [$1 die Version allno aaluege], wänn de wottsch.",
        "rev-suppressed-text-unhide": "Die Version isch '''unterdrückt''' worde.\nMee Angabe chasch im [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} Unterdrückigs-Logbuech] finde.\nDu chasch [$1 die Version allno aaluege], wänn de wottsch.",
        "rev-deleted-text-view": "Die Version isch '''glescht''' wore.\nDu chasch si allno aaluege. Details findsch im [{{fullurl:{{#special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lösch-Logbuech].",
        "revdelete-no-file": "D Datei, wu Du aagee hesch, git s nit.",
        "revdelete-show-file-confirm": "Bisch sicher, ass Du di glescht Version vu dr Datei „<nowiki>$1</nowiki>“ vum $2 am $3 witt aaluege?",
        "revdelete-show-file-submit": "Jo",
+       "revdelete-selected-text": "Uusgwehlti {{PLURAL:$1|Version|Versione}} vu [[:$2]]:",
+       "revdelete-selected-file": "Uusgwehlti {{PLURAL:$1|Dateiversion|Dateiversione}} vu [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Usgwehlte Logbuechyytrag|Usgwehlti Logbuechyytreg}}:",
+       "revdelete-text-text": "Gleschti Versione blybe no dr Versionsgschicht, Teil vu ihrem Inhalt sin aber nimi effetli zuegängig.",
+       "revdelete-text-file": "Gleschti Dateiversione blybe no in dr Datei-Versionsgschicht, Teil vu ihrem Inhalt sin aber nimi effetli zuegängig.",
+       "logdelete-text": "Gleschti Logbuechyytreg blybe no dr Logbiecher, Teil vu ihrem Inhalt sin aber nimi effetli zuegängig.",
+       "revdelete-text-others": "Anderi Administratore hän no Zuegriff uf dr verdeckt Inhalt un chenne ne au widerhärstelle, solang keini zuesetzlige Bschränkige feschtgleit wäre.",
        "revdelete-confirm": "Bitte tue bstetige, ass Du vor hesch, des z mache, d Konsequänze drus verstohsch un s machsch in Inbereinstimmig mit dr [[{{MediaWiki:Policy-url}}|Richtlinie]].",
        "revdelete-suppress-text": "Unterdruckige sotte '''nume''' in däne Fäll bruucht wäre:\n* Nit aabrochti Informatione\n*: ''Adrässe, Telifonnummere, Sozialversicherigsnummere usw.'",
        "revdelete-legend": "Setze vu dr Sichtbarkeits-Yyschränkige",
index 27ece9c..f2c38c2 100644 (file)
        "api-error-stashfailed": "שגיאה פנימית: השרת נכשל באחסון הקובץ הזמני.",
        "api-error-publishfailed": "שגיאה פנימית: השרת נכשל בפרסום הקובץ הזמני.",
        "api-error-stasherror": "הייתה שגיאה בהעלאת הקובץ למאגר.",
+       "api-error-stashedfilenotfound": "הקובץ שבמאגר לא נמצא בעת הניסיון להעלות אותו מהמאגר.",
+       "api-error-stashpathinvalid": "הנתיב שבו הקובץ שבמאגר אמור היה להימצא היה בלתי תקין.",
+       "api-error-stashfilestorage": "הייתה שגיאה בעת אחסון הקובץ במאגר.",
+       "api-error-stashzerolength": "השרת לא יכול היה לאחסן במאגר את הקובץ, כי אורכו היה אפס.",
+       "api-error-stashnotloggedin": "נדרשת כניסה לחשבון כדי לשמור קבצים במאגר ההעלאות.",
+       "api-error-stashwrongowner": "הקובץ שניסית לגשת אליו במאגר אינו שייך לך.",
+       "api-error-stashnosuchfilekey": "מפתח הקובץ שניסית לגשת אליו במאגר אינו קיים.",
        "api-error-timeout": "השרת לא השיב בזמן המצופה.",
        "api-error-unclassified": "אירעה שגיאה בלתי ידועה.",
        "api-error-unknown-code": "שגיאה בלתי ידועה: \"$1\".",
index 2a150b5..d1db2de 100644 (file)
@@ -7,7 +7,8 @@
                        "Sapral Mikail",
                        "Tagir",
                        "Умар",
-                       "아라"
+                       "아라",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Ӏинкаш белгалде:",
        "searchresults": "Тохкама гIулакхахилар",
        "searchresults-title": "\"$1\" тохка",
        "notextmatches": "ОагIувнаша яздамий вIашагIакхетараш дац",
-       "prevn": "{{PLURAL:$1|хьалхйоаг|ар $1|хьалхйоаг|араш $1|хьалхйоаг|араш $1}}",
+       "prevn": "{{PLURAL:$1|хьалхйоагlар $1|хьалхйоагlараш $1|хьалхйоагlараш $1}}",
        "nextn": "{{PLURAL:$1|тlехьайоагlар $1|тlехьайоагlараш $1|тlехьайоагlараш $1}}",
        "prevn-title": "{{PLURAL:$1|1=$1 хьалхара йоазув|$1 хьалхара йоазувнаш}}",
        "nextn-title": "{{PLURAL:$1|1=$1 тIехьара йоазув|$1 тIехьара йоазувнаш}}",
index 0a50ac3..2485ed8 100644 (file)
@@ -40,7 +40,8 @@
                        "Keysuck",
                        "Infinity",
                        "Bluemersen",
-                       "Revi"
+                       "Revi",
+                       "Namoroka"
                ]
        },
        "tog-underline": "링크에 밑줄:",
        "content-model-text": "일반 텍스트",
        "content-model-javascript": "자바스크립트",
        "content-model-css": "CSS",
+       "duplicate-args-category": "중복된 인수를 사용한 틀을 포함한 문서",
        "expensive-parserfunction-warning": "'''경고:''' 이 문서는 너무 많은 파서 함수를 포함하고 있습니다.\n\n$2개 보다 적게 {{PLURAL:$2|써야}} 하지만 {{PLURAL:$1|지금은 $1개를 쓰고 있습니다}}.",
        "expensive-parserfunction-category": "느린 파서 함수 호출을 너무 많이 하는 문서",
        "post-expand-template-inclusion-warning": "'''경고:''' 틀 포함 크기가 너무 큽니다.\n일부 틀은 포함되지 않을 수 있습니다.",
index 977022d..ad62999 100644 (file)
        "virus-unknownscanner": "onbekend antivirusprogramma:",
        "logouttext": "'''U bent nu afgemeld.'''\n\nSommige pagina's kunnen blijven weergegeven alsof u nog aangemeld bent, totdat u uw browsercache leegt.",
        "welcomeuser": "Welkom, $1!",
-       "welcomecreation-msg": "Uw account is aangemaakt.\nIndien gewenst kunt u uw [[Special:Preferences|voorkeuren]] voor {{SITENAME}} aanpassen.",
+       "welcomecreation-msg": "Uw gebruiker is aangemaakt.\nIndien gewenst kunt u uw [[Special:Preferences|voorkeuren]] voor {{SITENAME}} aanpassen.",
        "yourname": "Gebruikersnaam:",
        "userlogin-yourname": "Gebruikersnaam",
        "userlogin-yourname-ph": "Geef uw gebruikersnaam op",
        "userlogin-signwithsecure": "Beveiligde verbinding gebruiken",
        "yourdomainname": "Uw domein:",
        "password-change-forbidden": "U kunt uw wachtwoord niet wijzigen in deze wiki.",
-       "externaldberror": "Er is een fout opgetreden bij het aanmelden bij de database of u hebt geen toestemming uw externe account bij te werken.",
+       "externaldberror": "Er is een fout opgetreden bij het aanmelden bij de database of u hebt geen toestemming uw externe gebruiker bij te werken.",
        "login": "Aanmelden",
        "nav-login-createaccount": "Aanmelden / registreren",
        "userlogin": "Aanmelden / registreren",
        "logout": "Afmelden",
        "userlogout": "Afmelden",
        "notloggedin": "Niet aangemeld",
-       "userlogin-noaccount": "Hebt u geen account?",
+       "userlogin-noaccount": "Hebt u geen gebruiker?",
        "userlogin-joinproject": "Word lid van {{SITENAME}}",
-       "nologin": "Hebt u geen account? $1.",
+       "nologin": "Hebt u geen gebruiker? $1.",
        "nologinlink": "Registreren",
        "createaccount": "Registreren",
-       "gotaccount": "Hebt u al een account? $1.",
+       "gotaccount": "Hebt u al een gebruiker? $1.",
        "gotaccountlink": "Aanmelden",
        "userlogin-resetlink": "Bent u uw aanmeldgegevens vergeten?",
        "userlogin-resetpassword-link": "Wachtwoord vergeten?",
        "userlogin-helplink2": "Hulp bij aanmelden",
        "userlogin-loggedin": "U bent al aangemeld als {{GENDER:$1|$1}}.\nGebruik het onderstaande formulier om aan te melden als een andere gebruiker.",
-       "userlogin-createanother": "Een andere account aanmaken",
+       "userlogin-createanother": "Een andere gebruiker aanmaken",
        "createacct-emailrequired": "E-mailadres",
        "createacct-emailoptional": "E-mailadres (optioneel)",
        "createacct-email-ph": "Geef uw e-mailadres op",
        "noname": "U hebt geen geldige gebruikersnaam opgegeven.",
        "loginsuccesstitle": "Aanmelden geslaagd",
        "loginsuccess": "'''U bent nu aangemeld bij {{SITENAME}} als \"$1\".'''",
-       "nosuchuser": "De gebruiker \"$1\" bestaat niet.\nGebruikersnamen zijn hoofdlettergevoelig.\nControleer de schrijfwijze of [[Special:UserLogin/signup|maak een nieuw account aan]].",
+       "nosuchuser": "De gebruiker \"$1\" bestaat niet.\nGebruikersnamen zijn hoofdlettergevoelig.\nControleer de schrijfwijze of [[Special:UserLogin/signup|maak een nieuw gebruiker aan]].",
        "nosuchusershort": "De gebruiker \"$1\" bestaat niet.\nControleer de schrijfwijze.",
        "nouserspecified": "Geef een gebruikersnaam op.",
        "login-userblocked": "Deze gebruiker is geblokkeerd.\nAanmelden is niet mogelijk.",
        "invalidemailaddress": "Het e-mailadres is niet aanvaard, omdat het een ongeldige opmaak heeft.\nGeef een geldig e-mailadres op of laat het veld leeg.",
        "cannotchangeemail": "Het e-mailadres voor een gebruiker kan op deze wiki niet gewijzigd worden.",
        "emaildisabled": "Deze site kan geen e-mails verzenden.",
-       "accountcreated": "Account aangemaakt",
+       "accountcreated": "Gebruiker aangemaakt",
        "accountcreatedtext": "Het gebruikersaccount voor [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|overleg]]) is aangemaakt.",
        "createaccount-title": "Gebruikers registreren voor {{SITENAME}}",
        "createaccount-text": "Iemand heeft een gebruiker op {{SITENAME}} ($4) aangemaakt met de naam \"$2\" en uw e-mailadres.\nHet wachtwoord voor \"$2\" is \"$3\".\nMeld u aan en wijzig uw wachtwoord.\n\nNegeer dit bericht als deze gebruiker zonder uw medeweten is aangemaakt.",
        "login-throttled": "U heeft recentelijk te veel mislukte aanmeldpogingen gedaan.\nWacht alstublieft $1 voordat u het opnieuw probeert.",
        "login-abort-generic": "U bent niet aangemeld. De procedure is afgebroken.",
-       "login-migrated-generic": "Uw account is verplaatst, en uw gebruikersnaam bestaat niet langer op deze wiki.",
+       "login-migrated-generic": "Uw gebruikersnaam is hernoemd, en uw gebruikersnaam bestaat niet langer op deze wiki.",
        "loginlanguagelabel": "Taal: $1",
        "suspicious-userlogout": "Uw verzoek om af te melden is genegeerd, omdat het lijkt alsof het verzoek is verzonden door een browser of cacheproxy die stuk is.",
        "createacct-another-realname-tip": "Echte naam is optioneel.\nAls u deze opgeeft, wordt deze naam gebruikt worden om u erkenning te geven voor uw werk.",
        "showpreview": "Bewerking ter controle bekijken",
        "showdiff": "Wijzigingen bekijken",
        "blankarticle": "<strong>Waarschuwing:</strong> de pagina die u wilt aanmaken is leeg.\nAls u opnieuw op \"{{int:savearticle}}\" klikt, wordt de pagina aangemaakt zonder enige inhoud.",
-       "anoneditwarning": "<strong>Waarschuwing:</strong> u bent niet aangemeld.\nUw IP-adres wordt opgeslagen als u wijzigingen op deze pagina maakt. Wanneer u <strong>[$1 inlogt]</strong> of <strong>[$2 een account maakt]</strong zullen uw bewerkingen verschijnen onder u gebruikersnaam, met ook andere voordelen.",
+       "anoneditwarning": "<strong>Waarschuwing:</strong> u bent niet aangemeld.\nUw IP-adres wordt opgeslagen als u wijzigingen op deze pagina maakt. Wanneer u <strong>[$1 aanmeldt]</strong> of <strong>[$2 een gebruiker aanmaakt]</strong verschijnen uw bewerkingen onder uw gebruikersnaam, naast andere voordelen.",
        "anonpreviewwarning": "''U bent niet aangemeld.''\n''Door uw bewerking op te slaan wordt uw IP-adres opgeslagen in de paginageschiedenis.''",
        "missingsummary": "'''Let op:''' u hebt geen bewerkingssamenvatting opgegeven.\nAls u nogmaals op \"{{int:savearticle}}\" klikt wordt de bewerking zonder samenvatting opgeslagen.",
        "missingcommenttext": "Plaats uw reactie hieronder.",
index ceb5b66..d02a4dd 100644 (file)
@@ -75,6 +75,7 @@
                        "MIKHEIL",
                        "Malafaya",
                        "MarkvA",
+                       "marktraceur",
                        "Matma Rex",
                        "MaxSem",
                        "McDutchie",
        "rcshowhidebots": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either {{msg-mw|rcshowhidebots-show}} or {{msg-mw|rcshowhidebots-hide}}\n{{Identical|$1 bots}}",
        "rcshowhidebots-show": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidebots}}.\n\nSee also:\n* {{msg-mw|rcshowhidebots-show}}\n{{Identical|Show}}",
        "rcshowhidebots-hide": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidebots}}.\n\nSee also:\n* {{msg-mw|rcshowhidebots-hide}}\n{{Identical|Hide}}",
-       "rcshowhideliu": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - any one of the following messages:\n** {{msg-mw|rcshowhideliu-show}}\n** {{msg-mw|rcshowhideliu-hide}}",
+       "rcshowhideliu": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - any one of the following messages:\n** {{msg-mw|rcshowhideliu-show}}\n** {{msg-mw|rcshowhideliu-hide}}\n{{Identical|Registered user}}",
        "rcshowhideliu-show": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhideliu}}.\n\nSee also:\n* {{msg-mw|rcshowhideliu-hide}}\n{{Identical|Show}}",
        "rcshowhideliu-hide": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhideliu}}.\n\nSee also:\n* {{msg-mw|rcshowhideliu-show}}\n{{Identical|Hide}}",
        "rcshowhideanons": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either {{msg-mw|Rcshowhideanons-show}} or {{msg-mw|Rcshowhideanons-hide}}\n{{Identical|Anonymous user}}",
        "tooltip-ca-move": "See also:\n* {{msg-mw|Move}}\n* {{msg-mw|Accesskey-ca-move}}\n* {{msg-mw|Tooltip-ca-move}}\n{{Identical|Move this page}}",
        "tooltip-ca-watch": "See also:\n* {{msg-mw|Watch}}\n* {{msg-mw|Accesskey-ca-watch}}\n* {{msg-mw|Tooltip-ca-watch}}\n{{Identical|Add this page to your watchlist}}",
        "tooltip-ca-unwatch": "Tooltip shown when hovering over the {{msg-mw|Unwatch}} tab.\n\nSee also:\n* {{msg-mw|Unwatch}}\n* {{msg-mw|Accesskey-ca-unwatch}}\n* {{msg-mw|Tooltip-ca-unwatch}}",
-       "tooltip-search": "The tooltip when hovering over the search menu.\n\nSee also:\n* {{msg-mw|Search}}\n* {{msg-mw|Accesskey-search}}\n* {{msg-mw|Tooltip-search}}",
+       "tooltip-search": "The tooltip when hovering over the search menu.\n\nSee also:\n* {{msg-mw|Search}}\n* {{msg-mw|Accesskey-search}}\n* {{msg-mw|Tooltip-search}}\n{{Identical|Search}}",
        "tooltip-search-go": "This is the text of the tooltip displayed when hovering the mouse over the {{msg-mw|Go}} button next to the search box.\n\nSee also:\n* {{msg-mw|Go}}\n* {{msg-mw|Accesskey-search-go}}\n* {{msg-mw|Tooltip-search-go}}",
        "tooltip-search-fulltext": "This is the text of the tooltip displayed when hovering the mouse over the {{msg-mw|Search}} button under the search box.\n\nSee also:\n* {{msg-mw|Search}}\n* {{msg-mw|Accesskey-search-fulltext}}\n* {{msg-mw|Tooltip-search-fulltext}}",
        "tooltip-p-logo": "Tool tip shown when hovering the mouse over the logo that links to [[Main Page]].\n\nSee also:\n* {{msg-mw|Accesskey-p-logo}}\n* {{msg-mw|Tooltip-p-logo}}\n{{Identical|Visit the main page}}",
        "version-credits-title": "Page title for an about/credits page for a MediaWiki extension.\n\nUsed as heading in credits page. e.g. [[Special:Version/Credits]] and [[Special:Version/Credits/UniversalLanguageSelector]]\n\nParameters:\n* $1 - the name of the extension",
        "version-credits-not-found": "Descriptive error used when detailed about/credits for an extension are not available.\n\nSee example: [[Special:Version/Credits/Foo]]",
        "version-poweredby-credits": "Message shown on [[Special:Version]]. Parameters:\n* $1 - the current year\n* $2 - a list of selected MediaWiki authors",
-       "version-poweredby-others": "Used at the end of {{msg-mw|version-poweredby-credits}} on [[Special:Version]]. First, there's a long list of selected MediaWiki authors, then a comma and then this translation, which is supposed to credit the many other people than developer helping with MediaWiki.",
+       "version-poweredby-others": "Used at the end of {{msg-mw|version-poweredby-credits}} on [[Special:Version]]. First, there's a long list of selected MediaWiki authors, then a comma and then this translation, which is supposed to credit the many other people than developer helping with MediaWiki.\n{{Identical|Other}}",
        "version-poweredby-translators": "Used as label for a link to [[Translating:MediaWiki/Credits]].\n\nPreceded by {{msg-mw|version-poweredby-others}} and {{msg-mw|and}}.\n\nUsed at the end of {{msg-mw|version-poweredby-credits}} on [[Special:Version]].",
        "version-credits-summary": "Summary of the [[Special:Version/Credits]] sub page, which lists all developers etc. who contributed to MediaWiki. Shown at the top.",
        "version-license-info": "[[wikipedia:GNU GPL|GNU GPL]] notice shown at [[Special:Version]]. See //www.gnu.org/licenses/old-licenses/gpl-2.0-translations.html for available translations.",
        "api-error-stashfailed": "API error message that can be used for client side localisation of API errors.",
        "api-error-publishfailed": "API error message that can be used for client side localisation of API errors.",
        "api-error-stasherror": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashedfilenotfound": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashpathinvalid": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashfilestorage": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashzerolength": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashnotloggedin": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashwrongowner": "API error message that can be used for client side localisation of API errors.",
+       "api-error-stashnosuchfilekey": "API error message that can be used for client side localisation of API errors.",
        "api-error-timeout": "API error message that can be used for client side localisation of API errors.",
        "api-error-unclassified": "API error message that can be used for client side localisation of API errors.",
        "api-error-unknown-code": "API error message that can be used for client side localisation of API errors.\n\nParameters:\n* $1 - may contain more error details\n{{Identical|Unknown error}}",
index e215d1d..bc7a915 100644 (file)
        "deletepage": "Обриши страницу",
        "confirm": "Потврди",
        "excontent": "садржај је био: „$1“",
-       "excontentauthor": "садржај је био: „$1“ (а једини уредник је био „[[Special:Contribs/$2|$2]]“)",
+       "excontentauthor": "садржај је био: „$1“ (а једини уредник [[Special:Contribs/$2|$2]])",
        "exbeforeblank": "садржај пре брисања је био: „$1“",
        "delete-confirm": "Брисање странице „$1“",
        "delete-legend": "Обриши",
index bcbe108..c656998 100644 (file)
        "deletepage": "Obriši stranicu",
        "confirm": "Potvrdi",
        "excontent": "sadržaj je bio: „$1“",
-       "excontentauthor": "sadržaj je bio: „$1“ (a jedini urednik je bio „[[Special:Contribs/$2|$2]]“)",
+       "excontentauthor": "sadržaj je bio: „$1“ (a jedini urednik [[Special:Contribs/$2|$2]])",
        "exbeforeblank": "sadržaj pre brisanja je bio: „$1“",
        "delete-confirm": "Brisanje stranice „$1“",
        "delete-legend": "Obriši",
index 1fac63a..bb86cf9 100644 (file)
        "file-info-size-pages": "$1 × $2 pixel, ukuran berkas: $3, tipeu MIME: $4, $5 {{PLURAL:$5|kaca|kaca}}",
        "file-nohires": "Euweuh résolusi nu leuwih luhur.",
        "svg-long-desc": "Koropak SVG, nominalna $1 × $2 piksel, ukuranana $3",
-       "show-big-image": "Résolusi pinuh",
+       "show-big-image": "Berkas asli",
        "show-big-image-preview": "Ukuran ieu pramidang: $1.",
        "show-big-image-other": "Résolusi lianna: $1.",
        "show-big-image-size": "$1 × $2 pixel",
        "exif-orientation-3": "Diputer 180°",
        "exif-orientation-4": "Dibalikkeun vértikal",
        "exif-orientation-5": "Diputer 90° CCW jeung dibalikkeun vértikal",
-       "exif-orientation-6": "Diputer 90° CW",
+       "exif-orientation-6": "Diputer 90° CCW",
        "exif-orientation-7": "Diputer 90° CW jeung dibalikkeun vértikal",
        "exif-orientation-8": "Diputer 90° CCW",
        "exif-planarconfiguration-2": "format datar",
        "version-hook-name": "Ngaran kait",
        "version-hook-subscribedby": "Didaptarkeun ku",
        "version-version": "(Vérsi $1)",
-       "version-license": "Lisénsi",
+       "version-license": "Lisénsi MediaWiki",
        "version-software": "Sopwér nu geus diinstal",
        "version-software-product": "Produk",
        "version-software-version": "Vérsi",
        "fileduplicatesearch-result-1": "Koropak \"$1\" teu boga duplikat idéntik.",
        "fileduplicatesearch-result-n": "Koropak \"$1\" mibanda {{PLURAL:$2|1 duplikat idéntik|$2 duplikat idéntik}}.",
        "specialpages": "Kaca husus",
-       "specialpages-note": "* Kaca husus bisa di buka ku umum.\n* <strong class=\"mw-specialpagerestricted\">Cetak kandel kaca husus nu kawates.</strong>",
+       "specialpages-note": "* Kaca husus normal.\n* <span class=\"mw-specialpagerestricted\">Kaca husus diwates.</span>",
        "specialpages-group-maintenance": "Laporan pigawéeun",
        "specialpages-group-other": "Kaca husus lainna",
-       "specialpages-group-login": "Asup log / Nyieun rekening",
+       "specialpages-group-login": "Asup log / jieun akun",
        "specialpages-group-changes": "Nuanyar robah sarta log",
        "specialpages-group-media": "Laporan sarta muatkeun koropak",
        "specialpages-group-users": "Pamaké sarta hak pamaké",
        "specialpages-group-highuse": "Pamakéan kaca nu badag",
        "specialpages-group-pages": "Daptar kaca",
        "specialpages-group-pagetools": "Parabot kaca",
-       "specialpages-group-wiki": "Data wiki jeung parabot",
+       "specialpages-group-wiki": "Data jeung parabot",
        "specialpages-group-redirects": "Alihan kaca husus",
        "specialpages-group-spam": "Parabot Spam",
        "blankpage": "Kaca kosong",
index 57ce143..1fa4813 100644 (file)
        "tooltip-n-portal": "เกี่ยวกับโครงการ สิ่งที่คุณทำได้ วิธีการค้นหา",
        "tooltip-n-currentevents": "ค้นหาเหตุการณ์ปัจจุบัน",
        "tooltip-n-recentchanges": "รายการปรับปรุงล่าสุดในวิกินี้",
-       "tooltip-n-randompage": "สุà¹\88มหà¸\99à¹\89าà¸\82ึà¹\89à¸\99มา",
+       "tooltip-n-randompage": "à¹\82หลà¸\94หà¸\99à¹\89าสุà¹\88ม",
        "tooltip-n-help": "อธิบายการใช้งาน",
        "tooltip-t-whatlinkshere": "รายการทุกหน้าวิกิที่ลิงก์มาที่นี่",
        "tooltip-t-recentchangeslinked": "รายการปรับปรุงล่าสุดในหน้าที่ลิงก์จากหน้านี้",
index a8d6b12..42d4ba1 100644 (file)
        "templatesusedpreview": "Sjabloon'n gebruukt in deêze voevertoônieng:",
        "templatesusedsection": "Sjabloon'n die an gebruukt worn in deêze subkop:",
        "template-protected": "(beveiligd)",
-       "template-semiprotected": "(semi-beveiligd)",
+       "template-semiprotected": "(semibeveiligd)",
        "hiddencategories": "Deêze pagina val in de volhende verborhen {{PLURAL:$1|categorie|categorieën}}:",
        "nocreatetext": "{{SITENAME}} ei de meuhlijkeid om nieuwe pagina's an te maeken beperkt.\nJe kan a bestaende pagina's wiezigen, of je kan [[Special:UserLogin|jen eihen anmelden of een gebruker  anmaeken]].",
        "nocreate-loggedin": "Je kan hin nieuwe pagina's anmaeken.",
index 773786c..bd746e4 100644 (file)
        "listingcontinuesabbrev": "续",
        "index-category": "已索引页面",
        "noindex-category": "不可索引页面",
-       "broken-file-category": "有受损文件链接的页面",
+       "broken-file-category": "有受损文件链接的页面",
        "categoryviewer-pagedlinks": "($1)($2)",
        "about": "关于",
        "article": "内容页面",
index 7293ffd..fe17eeb 100644 (file)
        "createacct-another-submit": "建立另一個帳號",
        "createacct-benefit-heading": "{{SITENAME}} 是由像您一樣貢獻的人所建立的。",
        "createacct-benefit-body1": " {{PLURAL:$1|次編輯}}",
-       "createacct-benefit-body2": " $1 頁",
+       "createacct-benefit-body2": "$1 頁",
        "createacct-benefit-body3": " 位最近的{{PLURAL:$1|貢獻者}}",
        "badretype": "兩次輸入的密碼並不相同。",
        "userexists": "您所輸入的使用者名稱已存在,請另選一個名稱。",
index 655f309..fc6cf98 100644 (file)
--- a/load.php
+++ b/load.php
@@ -23,7 +23,7 @@
  */
 
 // Bail if PHP is too low
-if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
+if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.3' ) < 0 ) {
        // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
        require dirname( __FILE__ ) . '/includes/PHPVersionError.php';
        wfPHPVersionError( 'load.php' );
index 8d30df4..d740f56 100644 (file)
@@ -20,8 +20,8 @@
  * @defgroup Maintenance Maintenance
  */
 
-// Make sure we're on PHP5.3.2 or better
-if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
+// Make sure we're on PHP5.3.3 or better
+if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.3' ) < 0 ) {
        // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
        require_once dirname( __FILE__ ) . '/../includes/PHPVersionError.php';
        wfPHPVersionError( 'cli' );
index 9e88c13..6234db4 100644 (file)
@@ -37,6 +37,9 @@ require_once __DIR__ . '/cleanupTable.inc';
  * @ingroup Maintenance
  */
 class CapsCleanup extends TableCleanup {
+
+       private $user;
+
        public function __construct() {
                parent::__construct();
                $this->mDescription = "Script to cleanup capitalization";
@@ -44,13 +47,13 @@ class CapsCleanup extends TableCleanup {
        }
 
        public function execute() {
-               global $wgCapitalLinks, $wgUser;
+               global $wgCapitalLinks;
 
                if ( $wgCapitalLinks ) {
                        $this->error( "\$wgCapitalLinks is on -- no need for caps links cleanup.", true );
                }
 
-               $wgUser = User::newFromName( 'Conversion script' );
+               $this->user = User::newFromName( 'Conversion script' );
 
                $this->namespace = intval( $this->getOption( 'namespace', 0 ) );
                $this->dryrun = $this->hasOption( 'dry-run' );
@@ -87,7 +90,9 @@ class CapsCleanup extends TableCleanup {
                        $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
                        $ok = true;
                } else {
-                       $ok = $current->moveTo( $target, false, 'Converting page titles to lowercase' );
+                       $mp = new MovePage( $current, $target );
+                       $status = $mp->move( $this->user, 'Converting page titles to lowercase', true );
+                       $ok = $status->isOK() ? 'OK' : $status->getWikiText();
                        $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
                }
                if ( $ok === true ) {
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index d6163bd..145749a 100644 (file)
@@ -13,7 +13,8 @@
                                        "mw.html",
                                        "mw.html.Cdata",
                                        "mw.html.Raw",
-                                       "mw.hook"
+                                       "mw.hook",
+                                       "mw.template"
                                ]
                        },
                        {
index 713753f..a27a772 100644 (file)
@@ -103,10 +103,10 @@ class MoveBatch extends Maintenance {
 
                        $this->output( $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText() );
                        $dbw->begin( __METHOD__ );
-                       $err = $source->moveTo( $dest, false, $reason, !$noredirects );
-                       if ( $err !== true ) {
-                               $msg = array_shift( $err[0] );
-                               $this->output( "\nFAILED: " . wfMessage( $msg, $err[0] )->text() );
+                       $mp = new MovePage( $source, $dest );
+                       $status = $mp->move( $wgUser, $reason, !$noredirects );
+                       if ( !$status->isOK() ) {
+                               $this->output( "\nFAILED: " . $status->getWikiText() );
                        }
                        $dbw->commit( __METHOD__ );
                        $this->output( "\n" );
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 046d73c..657a2fc 100755 (executable)
@@ -26,7 +26,7 @@
  * @ingroup Maintenance
  */
 
-if ( !function_exists( 'version_compare' ) || ( version_compare( PHP_VERSION, '5.3.2' ) < 0 ) ) {
+if ( !function_exists( 'version_compare' ) || ( version_compare( PHP_VERSION, '5.3.3' ) < 0 ) ) {
        require dirname( __FILE__ ) . '/../includes/PHPVersionError.php';
        wfPHPVersionError( 'cli' );
 }
index a6cebc3..ed3e7f4 100644 (file)
@@ -21,7 +21,7 @@
  */
 
 // Bail if PHP is too low
-if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
+if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.3' ) < 0 ) {
        // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
        require dirname( dirname( __FILE__ ) ) . '/includes/PHPVersionError.php';
        wfPHPVersionError( 'mw-config/index.php' );
index ca90efa..b2dfe1e 100644 (file)
@@ -779,6 +779,10 @@ return array(
                        'mediawiki.hlist',
                ),
        ),
+       'mediawiki.template' => array(
+               'scripts' => 'resources/src/mediawiki/mediawiki.template.js',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
        'mediawiki.apipretty' => array(
                'styles' => 'resources/src/mediawiki/mediawiki.apipretty.css',
                'targets' => array( 'desktop', 'mobile' ),
@@ -851,6 +855,9 @@ return array(
                'position' => 'bottom',
        ),
        'mediawiki.feedback' => array(
+               'templates' => array(
+                       'dialog.html' => 'resources/src/mediawiki/templates/dialog.html',
+               ),
                'scripts' => 'resources/src/mediawiki/mediawiki.feedback.js',
                'styles' => 'resources/src/mediawiki/mediawiki.feedback.css',
                'dependencies' => array(
@@ -1063,6 +1070,9 @@ return array(
                ),
        ),
        'mediawiki.action.view.postEdit' => array(
+               'templates' => array(
+                       'postEdit.html' => 'resources/src/mediawiki.action/templates/postEdit.html',
+               ),
                'scripts' => 'resources/src/mediawiki.action/mediawiki.action.view.postEdit.js',
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.view.postEdit.css',
                'dependencies' => array(
@@ -1337,6 +1347,9 @@ return array(
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.undelete.js',
        ),
        'mediawiki.special.upload' => array(
+               'templates' => array(
+                       'thumbnail.html' => 'resources/src/mediawiki.special/templates/thumbnail.html',
+               ),
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.upload.js',
                'messages' => array(
                        'widthheight',
index be2a24f..746cec3 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:27Z
+ * Date: 2014-10-30T00:10:19Z
  */
 /* @noflip */
 .oo-ui-rtl {
        margin-left: 0;
 }
 .oo-ui-progressBarWidget {
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
        border: solid 1px #a6cee1;
        border-radius: 0.25em;
 }
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
        position: relative;
        display: inline-block;
        margin: 0.25em 0;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-inlineMenuWidget-handle {
        width: 100%;
 .oo-ui-comboBoxWidget {
        display: inline-block;
        position: relative;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-comboBoxWidget > .oo-ui-selectWidget {
        width: 100%;
index 3fa88c6..0db21a4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:18Z
+ * Date: 2014-10-30T00:10:09Z
  */
 /* Instantiation */
 
index b5fc881..c1bd8a0 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:27Z
+ * Date: 2014-10-30T00:10:19Z
  */
 /* @noflip */
 .oo-ui-rtl {
        margin-left: 0;
 }
 .oo-ui-progressBarWidget {
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
        border: solid 1px #a6cee1;
        border-radius: 0.25em;
 }
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
        position: relative;
        display: inline-block;
        margin: 0.25em 0;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-inlineMenuWidget-handle {
        width: 100%;
 .oo-ui-comboBoxWidget {
        display: inline-block;
        position: relative;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-comboBoxWidget > .oo-ui-selectWidget {
        width: 100%;
index e06261d..5743427 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:27Z
+ * Date: 2014-10-30T00:10:19Z
  */
 /* @noflip */
 .oo-ui-rtl {
        background-color: #ffffff;
 }
 .oo-ui-progressBarWidget {
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
        border: solid 1px #0274ff;
        border-radius: 0.1em;
 }
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
        position: relative;
        display: inline-block;
        margin: 0.25em 0;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-inlineMenuWidget-handle {
        width: 100%;
 .oo-ui-comboBoxWidget {
        display: inline-block;
        position: relative;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-comboBoxWidget > .oo-ui-selectWidget {
        width: 100%;
index 6632aae..5a7462a 100644 (file)
@@ -1,16 +1,16 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:18Z
+ * Date: 2014-10-30T00:10:09Z
  */
 /**
  * @class
- * @extends {OO.ui.Theme}
+ * @extends OO.ui.Theme
  *
  * @constructor
  */
index c876309..3495e3d 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:27Z
+ * Date: 2014-10-30T00:10:19Z
  */
 /* @noflip */
 .oo-ui-rtl {
        background-color: #ffffff;
 }
 .oo-ui-progressBarWidget {
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
        border: solid 1px #0274ff;
        border-radius: 0.1em;
 }
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
        position: relative;
        display: inline-block;
        margin: 0.25em 0;
-       min-width: 20em;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-inlineMenuWidget-handle {
        width: 100%;
 .oo-ui-comboBoxWidget {
        display: inline-block;
        position: relative;
+       width: 100%;
+       max-width: 50em;
 }
 .oo-ui-comboBoxWidget > .oo-ui-selectWidget {
        width: 100%;
index 96befc5..d4ac121 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (deccd11549)
+ * OOjs UI v0.1.0-pre (da4b0d5c14)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-10-28T16:52:18Z
+ * Date: 2014-10-30T00:10:09Z
  */
 ( function ( OO ) {
 
@@ -693,7 +693,7 @@ OO.ui.ActionSet.prototype.organize = function () {
  * @constructor
  * @param {Object} [config] Configuration options
  * @cfg {Function} [$] jQuery for the frame the widget is in
- * @cfg {string[]} [classes] CSS class names
+ * @cfg {string[]} [classes] CSS class names to add
  * @cfg {string} [text] Text to insert
  * @cfg {jQuery} [$content] Content elements to append (after text)
  */
@@ -1011,7 +1011,7 @@ OO.ui.Element.getClosestScrollableContainer = function ( el, dimension ) {
  *
  * @static
  * @param {HTMLElement} el Element to scroll into view
- * @param {Object} [config={}] Configuration config
+ * @param {Object} [config] Configuration options
  * @param {string} [config.duration] jQuery animation duration value
  * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit
  *  to scroll in both directions
@@ -1112,7 +1112,7 @@ OO.ui.Element.offDOMEvent = function ( el, event, callback ) {
  * Check if element supports one or more methods.
  *
  * @param {string|string[]} methods Method or list of methods to check
- * @return boolean All methods are supported
+ * @return {boolean} All methods are supported
  */
 OO.ui.Element.prototype.supports = function ( methods ) {
        var i, len,
@@ -1217,7 +1217,7 @@ OO.ui.Element.prototype.setElementGroup = function ( group ) {
 /**
  * Scroll element into view.
  *
- * @param {Object} [config={}]
+ * @param {Object} [config] Configuration options
  */
 OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
        return OO.ui.Element.scrollIntoView( this.$element[0], config );
@@ -3545,8 +3545,8 @@ OO.initClass( OO.ui.Theme );
 /**
  * Get a list of classes to be applied to a widget.
  *
- * @localdoc The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or
- *   removes, otherwise state transitions will not work properly.
+ * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
+ * otherwise state transitions will not work properly.
  *
  * @param {OO.ui.Element} element Element for which to get classes
  * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
@@ -3560,7 +3560,7 @@ OO.ui.Theme.prototype.getElementClasses = function ( /* element */ ) {
  *
  * For elements with theme logic hooks, this should be called anytime there's a state change.
  *
- * @param {OO.ui.Element} Element for which to update classes
+ * @param {OO.ui.Element} element Element for which to update classes
  * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
  */
 OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
@@ -3584,7 +3584,8 @@ OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
  * @param {Object} [config] Configuration options
  * @cfg {jQuery} [$button] Button node, assigned to #$button, omit to use a generated `<a>`
  * @cfg {boolean} [framed=true] Render button with a frame
- * @cfg {number} [tabIndex=0] Button's tab index, use null to have no tabIndex
+ * @cfg {number} [tabIndex=0] Button's tab index. Use 0 to use default ordering, use -1 to prevent
+ *   tab focusing.
  * @cfg {string} [accessKey] Button's access key
  */
 OO.ui.ButtonElement = function OoUiButtonElement( config ) {
@@ -3883,7 +3884,7 @@ OO.ui.GroupElement.prototype.aggregate = function ( events ) {
 /**
  * Add items.
  *
- * Adding an existing item (by value) will move it.
+ * Adding an existing item will move it.
  *
  * @param {OO.ui.Element[]} items Items
  * @param {number} [index] Index to insert items at
@@ -4091,7 +4092,7 @@ OO.ui.IconElement.prototype.setIconElement = function ( $icon ) {
 };
 
 /**
- * Set icon.
+ * Set icon name.
  *
  * @param {Object|string|null} icon Symbolic icon name, or map of icon names keyed by language ID;
  *  use the 'default' key to specify the icon to be used when there is no icon in the user's
@@ -4147,9 +4148,9 @@ OO.ui.IconElement.prototype.setIconTitle = function ( iconTitle ) {
 };
 
 /**
- * Get icon.
+ * Get icon name.
  *
- * @return {string} Icon
+ * @return {string} Icon name
  */
 OO.ui.IconElement.prototype.getIcon = function () {
        return this.icon;
@@ -4199,7 +4200,7 @@ OO.initClass( OO.ui.IndicatorElement );
  *
  * @static
  * @inheritable
- * @property {string|null} Symbolic indicator name or null for no indicator
+ * @property {string|null} Symbolic indicator name
  */
 OO.ui.IndicatorElement.static.indicator = null;
 
@@ -4238,7 +4239,7 @@ OO.ui.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
 };
 
 /**
- * Set indicator.
+ * Set indicator name.
  *
  * @param {string|null} indicator Symbolic name of indicator to use or null for no indicator
  * @chainable
@@ -4291,7 +4292,7 @@ OO.ui.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle )
 };
 
 /**
- * Get indicator.
+ * Get indicator name.
  *
  * @return {string} title Symbolic name of indicator
  */
@@ -4372,7 +4373,7 @@ OO.ui.LabelElement.prototype.setLabelElement = function ( $label ) {
  * Set the label.
  *
  * An empty string will result in the label being hidden. A string containing only whitespace will
- * be converted to a single &nbsp;
+ * be converted to a single `&nbsp;`.
  *
  * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or
  *  text; or null for no label
@@ -4487,7 +4488,7 @@ OO.ui.PopupElement.prototype.getPopup = function () {
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string[]} [flags=[]] Styling flags, e.g. 'primary', 'destructive' or 'constructive'
+ * @cfg {string|string[]} [flags] Styling flags, e.g. 'primary', 'destructive' or 'constructive'
  * @cfg {jQuery} [$flagged] Flagged node, assigned to #$flagged, omit to use #$element
  */
 OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) {
@@ -4658,7 +4659,8 @@ OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) {
  * @constructor
  * @param {Object} [config] Configuration options
  * @cfg {jQuery} [$titled] Titled node, assigned to #$titled, omit to use #$element
- * @cfg {string|Function} [title] Title text or a function that returns text
+ * @cfg {string|Function} [title] Title text or a function that returns text. If not provided, the
+ *    static property 'title' is used.
  */
 OO.ui.TitledElement = function OoUiTitledElement( config ) {
        // Config intialization
@@ -6596,10 +6598,6 @@ OO.ui.BookletLayout.prototype.updateOutlineWidget = function () {
 /**
  * Layout made of a field and optional label.
  *
- * @class
- * @extends OO.ui.Layout
- * @mixins OO.ui.LabelElement
- *
  * Available label alignment modes include:
  *  - left: Label is before the field and aligned away from it, best for when the user will be
  *    scanning for a specific label in a form with many fields
@@ -6610,6 +6608,10 @@ OO.ui.BookletLayout.prototype.updateOutlineWidget = function () {
  *  - inline: Label is after the field and aligned toward it, best for small boolean fields like
  *    checkboxes or radio buttons
  *
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.LabelElement
+ *
  * @constructor
  * @param {OO.ui.Widget} fieldWidget Field widget
  * @param {Object} [config] Configuration options
@@ -6668,6 +6670,10 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
 OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
 OO.mixinClass( OO.ui.FieldLayout, OO.ui.LabelElement );
 
+/* Static Properties */
+
+OO.ui.FieldLayout.static.tagName = 'label';
+
 /* Methods */
 
 /**
@@ -6701,6 +6707,7 @@ OO.ui.FieldLayout.prototype.getField = function () {
 /**
  * Set the field alignment mode.
  *
+ * @private
  * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
  * @chainable
  */
@@ -6738,8 +6745,8 @@ OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
  *
  * @class
  * @extends OO.ui.Layout
- * @mixins OO.ui.LabelElement
  * @mixins OO.ui.IconElement
+ * @mixins OO.ui.LabelElement
  * @mixins OO.ui.GroupElement
  *
  * @constructor
@@ -8085,6 +8092,18 @@ OO.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () {
        return [];
 };
 
+/**
+ * Get lookup cache item from server response data.
+ *
+ * @abstract
+ * @param {Mixed} data Response from server
+ * @return {Mixed} Cached result data
+ */
+OO.ui.LookupInputWidget.prototype.getLookupCacheItemFromData = function () {
+       // Stub, implemented in subclass
+       return [];
+};
+
 /**
  * Set of controls for an OO.ui.OutlineWidget.
  *
@@ -8271,7 +8290,7 @@ OO.ui.ToggleWidget.prototype.setValue = function ( value ) {
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {OO.ui.ButtonWidget} [items] Buttons to add
+ * @cfg {OO.ui.ButtonWidget[]} [items] Buttons to add
  */
 OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
        // Parent constructor
@@ -8915,12 +8934,11 @@ OO.ui.InlineMenuWidget.prototype.onClick = function ( e ) {
  * @param {Object} [config] Configuration options
  * @cfg {string} [name=''] HTML input name
  * @cfg {string} [value=''] Input value
- * @cfg {boolean} [readOnly=false] Prevent changes
  * @cfg {Function} [inputFilter] Filter function to apply to the input. Takes a string argument and returns a string.
  */
 OO.ui.InputWidget = function OoUiInputWidget( config ) {
-       // Config intialization
-       config = $.extend( { readOnly: false }, config );
+       // Configuration initialization
+       config = config || {};
 
        // Parent constructor
        OO.ui.InputWidget.super.call( this, config );
@@ -8931,7 +8949,6 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) {
        // Properties
        this.$input = this.getInputElement( config );
        this.value = '';
-       this.readOnly = false;
        this.inputFilter = config.inputFilter;
 
        // Events
@@ -8941,7 +8958,6 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) {
        this.$input
                .attr( 'name', config.name )
                .prop( 'disabled', this.isDisabled() );
-       this.setReadOnly( config.readOnly );
        this.$element.addClass( 'oo-ui-inputWidget' ).append( this.$input );
        this.setValue( config.value );
 };
@@ -8963,6 +8979,7 @@ OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement );
 /**
  * Get input element.
  *
+ * @private
  * @param {Object} [config] Configuration options
  * @return {jQuery} Input element
  */
@@ -9033,8 +9050,9 @@ OO.ui.InputWidget.prototype.setValue = function ( value ) {
 /**
  * Sanitize incoming value.
  *
- * Ensures value is a string, and converts undefined and null to empty strings.
+ * Ensures value is a string, and converts undefined and null to empty string.
  *
+ * @private
  * @param {string} value Original value
  * @return {string} Sanitized value
  */
@@ -9061,29 +9079,6 @@ OO.ui.InputWidget.prototype.simulateLabelClick = function () {
        }
 };
 
-/**
- * Check if the widget is read-only.
- *
- * @return {boolean}
- */
-OO.ui.InputWidget.prototype.isReadOnly = function () {
-       return this.readOnly;
-};
-
-/**
- * Set the read-only state of the widget.
- *
- * This should probably change the widgets's appearance and prevent it from being used.
- *
- * @param {boolean} state Make input read-only
- * @chainable
- */
-OO.ui.InputWidget.prototype.setReadOnly = function ( state ) {
-       this.readOnly = !!state;
-       this.$input.prop( 'readOnly', this.readOnly );
-       return this;
-};
-
 /**
  * @inheritdoc
  */
@@ -9116,7 +9111,7 @@ OO.ui.InputWidget.prototype.blur = function () {
 };
 
 /**
- * A button that is an input widget. Intended to be used within FormLayouts.
+ * A button that is an input widget. Intended to be used within a OO.ui.FormLayout.
  *
  * @class
  * @extends OO.ui.InputWidget
@@ -9139,6 +9134,9 @@ OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
        // Configuration initialization
        config = $.extend( { type: 'button', useInputTag: false }, config );
 
+       // Properties (must be set before parent constructor, which calls #setValue)
+       this.useInputTag = config.useInputTag;
+
        // Parent constructor
        OO.ui.ButtonInputWidget.super.call( this, config );
 
@@ -9150,9 +9148,6 @@ OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
        OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
        OO.ui.FlaggedElement.call( this, config );
 
-       // Properties
-       this.useInputTag = config.useInputTag;
-
        // Events
        this.$input.on( {
                click: this.onClick.bind( this ),
@@ -9187,6 +9182,7 @@ OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.FlaggedElement );
 /**
  * Get input element.
  *
+ * @private
  * @param {Object} [config] Configuration options
  * @return {jQuery} Input element
  */
@@ -9196,7 +9192,7 @@ OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) {
 };
 
 /**
- * Set the label.
+ * Set label value.
  *
  * Overridden to support setting the 'value' of `<input/>` elements.
  *
@@ -9223,6 +9219,21 @@ OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
        return this;
 };
 
+/**
+ * Set the value of the input.
+ *
+ * Overridden to disable for `<input/>` elements, which have value identical to the label.
+ *
+ * @param {string} value New value
+ * @chainable
+ */
+OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
+       if ( !this.useInputTag ) {
+               OO.ui.ButtonInputWidget.super.prototype.setValue.call( this, value );
+       }
+       return this;
+};
+
 /**
  * Handles mouse click events.
  *
@@ -9275,6 +9286,7 @@ OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget );
 /**
  * Get input element.
  *
+ * @private
  * @return {jQuery} Input element
  */
 OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
@@ -9330,6 +9342,7 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
  * @param {Object} [config] Configuration options
  * @cfg {string} [type='text'] HTML tag `type` attribute
  * @cfg {string} [placeholder] Placeholder text
+ * @cfg {boolean} [readOnly=false] Prevent changes
  * @cfg {boolean} [multiline=false] Allow multiple lines of text
  * @cfg {boolean} [autosize=false] Automatically resize to fit content
  * @cfg {boolean} [maxRows=10] Maximum number of rows to make visible when autosizing
@@ -9338,7 +9351,7 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
  */
 OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        // Configuration initialization
-       config = config || {};
+       config = $.extend( { readOnly: false }, config );
 
        // Parent constructor
        OO.ui.TextInputWidget.super.call( this, config );
@@ -9349,6 +9362,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        OO.ui.PendingElement.call( this, config );
 
        // Properties
+       this.readOnly = false;
        this.multiline = !!config.multiline;
        this.autosize = !!config.autosize;
        this.maxRows = config.maxRows !== undefined ? config.maxRows : 10;
@@ -9369,6 +9383,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        this.$element
                .addClass( 'oo-ui-textInputWidget' )
                .append( this.$icon, this.$indicator );
+       this.setReadOnly( config.readOnly );
        if ( config.placeholder ) {
                this.$input.attr( 'placeholder', config.placeholder );
        }
@@ -9484,6 +9499,29 @@ OO.ui.TextInputWidget.prototype.setValue = function ( value ) {
        return this;
 };
 
+/**
+ * Check if the widget is read-only.
+ *
+ * @return {boolean}
+ */
+OO.ui.TextInputWidget.prototype.isReadOnly = function () {
+       return this.readOnly;
+};
+
+/**
+ * Set the read-only state of the widget.
+ *
+ * This should probably change the widgets's appearance and prevent it from being used.
+ *
+ * @param {boolean} state Make input read-only
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
+       this.readOnly = !!state;
+       this.$input.prop( 'readOnly', this.readOnly );
+       return this;
+};
+
 /**
  * Automatically adjust the size of the text input.
  *
@@ -9527,6 +9565,7 @@ OO.ui.TextInputWidget.prototype.adjustSize = function () {
 /**
  * Get input element.
  *
+ * @private
  * @param {Object} [config] Configuration options
  * @return {jQuery} Input element
  */
index 4d2c47a..95ef62c 100644 (file)
                        data.message = $.parseHTML( mw.message( 'postedit-confirmation-saved', data.user || mw.user ).escaped() );
                }
 
-               $div = $(
-                       '<div class="postedit-container">' +
-                               '<div class="postedit">' +
-                                       '<div class="postedit-icon postedit-icon-checkmark postedit-content"></div>' +
-                                       '<a href="#" class="postedit-close">&times;</a>' +
-                               '</div>' +
-                       '</div>'
-               );
+               $div = mw.template.get( 'mediawiki.action.view.postEdit', 'postEdit.html' ).render();
 
                if ( typeof data.message === 'string' ) {
                        $div.find( '.postedit-content' ).text( data.message );
diff --git a/resources/src/mediawiki.action/templates/postEdit.html b/resources/src/mediawiki.action/templates/postEdit.html
new file mode 100644 (file)
index 0000000..dbb482a
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="postedit-container">
+       <div class="postedit">
+               <div class="postedit-icon postedit-icon-checkmark postedit-content"></div>
+               <a href="#" class="postedit-close">&times;</a>
+       </div>
+</div>
index 392a753..3a19e02 100644 (file)
                'nomodule',
                'mustbeposted',
                'badaccess-groups',
-               'stashfailed',
                'missingresult',
                'missingparam',
                'invalid-file-key',
                'fetchfileerror',
                'fileexists-shared-forbidden',
                'invalidtitle',
-               'notloggedin'
+               'notloggedin',
+
+               // Stash-specific errors - expanded
+               'stashfailed',
+               'stasherror',
+               'stashedfilenotfound',
+               'stashpathinvalid',
+               'stashfilestorage',
+               'stashzerolength',
+               'stashnotloggedin',
+               'stashwrongowner',
+               'stashnosuchfilekey'
        ];
 
        /**
old mode 100755 (executable)
new mode 100644 (file)
index 04bc978..87de646 100644 (file)
                                ctx,
                                meta,
                                previewSize = 180,
-                               thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
-                                                       '<div class="thumbinner">' +
-                                                               '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
-                                                               '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
-                                                       '</div>' +
-                                               '</div>' );
+                               thumb = mw.template.get( 'mediawiki.special.upload', 'thumbnail.html' ).render();
 
                        thumb.find( '.filename' ).text( file.name ).end()
                                .find( '.fileinfo' ).text( prettySize( file.size ) ).end();
diff --git a/resources/src/mediawiki.special/templates/thumbnail.html b/resources/src/mediawiki.special/templates/thumbnail.html
new file mode 100644 (file)
index 0000000..73042f2
--- /dev/null
@@ -0,0 +1,9 @@
+<div id="mw-upload-thumbnail" class="thumb tright">
+       <div class="thumbinner">
+               <div class="mw-small-spinner" style="width: 180px; height: 180px"></div>
+               <div class="thumbcaption">
+                       <div class="filename"></div>
+                       <div class="fileinfo"></div>
+               </div>
+       </div>
+</div>
index 177367d..7fc5c42 100644 (file)
                                }, 1 );
 
                                // show an alert with this message
-                               return options.message;
+                               if ( $.isFunction( options.message ) ) {
+                                       return options.message();
+                               } else {
+                                       return options.message;
+                               }
                        }
                } ).on( showEventName, function () {
                        // Re-add onbeforeunload handler
index 1c0d833..6bcb87f 100644 (file)
                                target: '_blank'
                        } );
 
-                       // TODO: Use a stylesheet instead of these inline styles
-                       this.$dialog =
-                               $( '<div style="position: relative;"></div>' ).append(
-                                       $( '<div class="feedback-mode feedback-form"></div>' ).append(
-                                               $( '<small>' ).append(
-                                                       $( '<p>' ).msg(
-                                                               'feedback-bugornote',
-                                                               $bugNoteLink,
-                                                               fb.title.getNameText(),
-                                                               $feedbackPageLink.clone()
-                                                       )
-                                               ),
-                                               $( '<div style="margin-top: 1em;"></div>' )
-                                               .msg( 'feedback-subject' )
-                                               .append(
-                                                       $( '<br>' ),
-                                                       $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;"/>' )
-                                               ),
-                                               $( '<div style="margin-top: 0.4em;"></div>' )
-                                               .msg( 'feedback-message' )
-                                               .append(
-                                                       $( '<br>' ),
-                                                       $( '<textarea name="message" class="feedback-message" rows="5" cols="60"></textarea>' )
-                                               )
-                                       ),
-                                       $( '<div class="feedback-mode feedback-bugs"></div>' ).append(
-                                               $( '<p>' ).msg( 'feedback-bugcheck', $bugsListLink )
-                                       ),
-                                       $( '<div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' )
-                                       .msg( 'feedback-adding' )
-                                       .append(
-                                               $( '<br>' ),
-                                               $( '<span class="feedback-spinner"></span>' )
-                                       ),
-                                       $( '<div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>' ).msg(
-                                               'feedback-thanks', fb.title.getNameText(), $feedbackPageLink.clone()
-                                       ),
-                                       $( '<div class="feedback-mode feedback-error" style="position: relative;"></div>' ).append(
-                                               $( '<div class="feedback-error-msg style="color: #990000; margin-top: 0.4em;"></div>' )
-                                       )
-                               );
+                       // TODO: Use a stylesheet instead of these inline styles in the template
+                       this.$dialog = mw.template.get( 'mediawiki.feedback', 'dialog.html' ).render();
+                       this.$dialog.find( '.feedback-mode small p' ).msg(
+                               'feedback-bugornote',
+                               $bugNoteLink,
+                               fb.title.getNameText(),
+                               $feedbackPageLink.clone()
+                       );
+                       this.$dialog.find( '.feedback-form .subject span' ).msg( 'feedback-subject' );
+                       this.$dialog.find( '.feedback-form .message span' ).msg( 'feedback-message' );
+                       this.$dialog.find( '.feedback-bugs p' ).msg( 'feedback-bugcheck', $bugsListLink );
+                       this.$dialog.find( '.feedback-submitting span' ).msg( 'feedback-adding' );
+                       this.$dialog.find( '.feedback-thanks' ).msg( 'feedback-thanks', fb.title.getNameText(),
+                               $feedbackPageLink.clone() );
 
                        this.$dialog.dialog( {
                                width: 500,
index e29c734..cfdb5a7 100644 (file)
                 */
                messages: new Map(),
 
+               /**
+                * Templates associated with a module
+                * @property {mw.Map}
+                */
+               templates: new Map(),
+
                /* Public Methods */
 
                /**
                                        mw.messages.set( registry[module].messages );
                                }
 
+                               // Initialise templates
+                               if ( registry[module].templates ) {
+                                       mw.templates.set( module, registry[module].templates );
+                               }
+
                                if ( $.isReady || registry[module].async ) {
                                        // Make sure we don't run the scripts until all (potentially asynchronous)
                                        // stylesheet insertions have completed.
                                 * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
                                 *
                                 * @param {Object} msgs List of key/value pairs to be added to mw#messages.
+                                * @param {Object} [templates] List of key/value pairs to be added to mw#templates.
                                 */
-                               implement: function ( module, script, style, msgs ) {
+                               implement: function ( module, script, style, msgs, templates ) {
                                        // Validate input
                                        if ( typeof module !== 'string' ) {
                                                throw new Error( 'module must be a string, not a ' + typeof module );
                                        if ( !$.isPlainObject( msgs ) ) {
                                                throw new Error( 'msgs must be an object, not a ' + typeof msgs );
                                        }
+                                       if ( templates !== undefined && !$.isPlainObject( templates ) ) {
+                                               throw new Error( 'templates must be an object, not a ' + typeof templates );
+                                       }
                                        // Automatically register module
                                        if ( registry[module] === undefined ) {
                                                mw.loader.register( module );
                                        registry[module].script = script;
                                        registry[module].style = style;
                                        registry[module].messages = msgs;
+                                       // Templates are optional (for back-compat)
+                                       registry[module].templates = templates || {};
                                        // The module may already have been marked as erroneous
                                        if ( $.inArray( registry[module].state, ['error', 'missing'] ) === -1 ) {
                                                registry[module].state = 'loaded';
                                 * @param {Function} [ready] Callback to execute when all dependencies are ready
                                 * @param {Function} [error] Callback to execute if one or more dependencies failed
                                 * @return {jQuery.Promise}
+                                * @since 1.23 this returns a promise
                                 */
                                using: function ( dependencies, ready, error ) {
                                        var deferred = $.Deferred();
                                                        // Unversioned, private, or site-/user-specific
                                                        ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) ||
                                                        // Partial descriptor
-                                                       $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1
+                                                       $.inArray( undefined, [ descriptor.script, descriptor.style,
+                                                                       descriptor.messages, descriptor.templates ] ) !== -1
                                                ) {
                                                        // Decline to store
                                                        return false;
                                                                        String( descriptor.script ) :
                                                                        JSON.stringify( descriptor.script ),
                                                                JSON.stringify( descriptor.style ),
-                                                               JSON.stringify( descriptor.messages )
+                                                               JSON.stringify( descriptor.messages ),
+                                                               JSON.stringify( descriptor.templates )
                                                        ];
                                                        // Attempted workaround for a possible Opera bug (bug 57567).
                                                        // This regex should never match under sane conditions.
diff --git a/resources/src/mediawiki/mediawiki.template.js b/resources/src/mediawiki/mediawiki.template.js
new file mode 100644 (file)
index 0000000..79f43d1
--- /dev/null
@@ -0,0 +1,123 @@
+/**
+ * @class mw.template
+ * @singleton
+ */
+( function ( mw, $ ) {
+       var compiledTemplates = {},
+               compilers = {};
+
+       mw.template = {
+               /**
+                * Register a new compiler and template.
+                *
+                * @param {string} name of compiler. Should also match with any file extensions of templates that want to use it.
+                * @param {Function} compiler which must implement a compile function
+                */
+               registerCompiler: function ( name, compiler ) {
+                       if ( !compiler.compile ) {
+                               throw new Error( 'Compiler must implement compile method.' );
+                       }
+                       compilers[name] = compiler;
+               },
+
+               /**
+                * Get the name of the compiler associated with a template based on its name.
+                *
+                * @param {string} templateName Name of template (including file suffix)
+                * @return {String} Name of compiler
+                */
+               getCompilerName: function ( templateName ) {
+                       var templateParts = templateName.split( '.' );
+
+                       if ( templateParts.length < 2 ) {
+                               throw new Error( 'Unable to identify compiler. Template name must have a suffix.' );
+                       }
+                       return templateParts[ templateParts.length - 1 ];
+               },
+
+               /**
+                * Get the compiler for a given compiler name.
+                *
+                * @param {string} compilerName Name of the compiler
+                * @return {Object} The compiler associated with that name
+                */
+               getCompiler: function ( compilerName ) {
+                       var compiler = compilers[ compilerName ];
+                       if ( !compiler ) {
+                               throw new Error( 'Unknown compiler ' + compilerName );
+                       }
+                       return compiler;
+               },
+
+               /**
+                * Register a template associated with a module.
+                *
+                * Compiles the newly added template based on the suffix in its name.
+                *
+                * @param {string} moduleName Name of ResourceLoader module to get the template from
+                * @param {string} templateName Name of template to add including file extension
+                * @param {string} templateBody Contents of a template (e.g. html markup)
+                * @return {Function} Compiled template
+                */
+               add: function ( moduleName, templateName, templateBody ) {
+                       var compiledTemplate,
+                               compilerName = this.getCompilerName( templateName );
+
+                       if (!compiledTemplates[moduleName]) {
+                               compiledTemplates[moduleName] = {};
+                       }
+
+                       compiledTemplate = this.compile( templateBody, compilerName );
+                       compiledTemplates[moduleName][ templateName ] = compiledTemplate;
+                       return compiledTemplate;
+               },
+
+               /**
+                * Retrieve a template by module and template name.
+                *
+                * @param {string} moduleName Name of the module to retrieve the template from
+                * @param {string} templateName Name of template to be retrieved
+                * @return {Object} Compiled template
+                */
+               get: function ( moduleName, templateName ) {
+                       var moduleTemplates, compiledTemplate;
+
+                       // Check if the template has already been compiled, compile it if not
+                       if ( !compiledTemplates[ moduleName ] || !compiledTemplates[ moduleName ][ templateName ] ) {
+                               moduleTemplates = mw.templates.get( moduleName );
+                               if ( !moduleTemplates || !moduleTemplates[ templateName ] ) {
+                                       throw new Error( 'Template ' + templateName + ' not found in module ' + moduleName );
+                               }
+
+                               // Add compiled version
+                               compiledTemplate = this.add( moduleName, templateName, moduleTemplates[ templateName ] );
+                       } else {
+                               compiledTemplate = compiledTemplates[ moduleName ][ templateName ];
+                       }
+                       return compiledTemplate;
+               },
+
+               /**
+                * Wrap our template engine of choice.
+                *
+                * @param {string} templateBody Template body
+                * @param {string} compilerName The name of a registered compiler
+                * @return {Object} Template interface
+                */
+               compile: function ( templateBody, compilerName ) {
+                       return this.getCompiler( compilerName ).compile( templateBody );
+               }
+       };
+
+       // Register basic html compiler
+       mw.template.registerCompiler( 'html', {
+               compile: function ( src ) {
+                       return {
+                               render: function () {
+                                       return $( $.parseHTML( $.trim( src ) ) );
+                               }
+                       };
+               }
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/templates/dialog.html b/resources/src/mediawiki/templates/dialog.html
new file mode 100644 (file)
index 0000000..e116f3e
--- /dev/null
@@ -0,0 +1,25 @@
+<div style="position: relative; display: block;" class="ui-dialog-content ui-widget-content">
+       <div class="feedback-mode feedback-form">
+               <small><p></p></small>
+               <div class="subject" style="margin-top: 1em;">
+                       <span></span><br>
+                       <input type="text" class="feedback-subject" name="subject" maxlength="60"
+                               style="width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;">
+               </div>
+               <div class="message" style="margin-top: 0.4em;">
+                       <span></span><br>
+                       <textarea name="message" class="feedback-message" rows="5" cols="60"></textarea>
+               </div>
+       </div>
+       <div class="feedback-mode feedback-bugs">
+               <p></p>
+       </div>
+       <div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;">
+               <span></span><br>
+               <span class="feedback-spinner"></span>
+       </div>
+       <div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>
+       <div class="feedback-mode feedback-error" style="position: relative;">
+               <div class="feedback-error-msg" style=" color:#990000; margin-top:0.4em;"></div>
+       </div>
+</div>
index 8630f5d..c011a68 100644 (file)
@@ -50,6 +50,7 @@ module.exports = function ( grunt ) {
                },
                banana: {
                        core: 'languages/i18n/',
+                       api: 'includes/api/i18n/',
                        installer: 'includes/installer/i18n/'
                },
                watch: {
index 3acc48e..2bfabe4 100644 (file)
@@ -322,16 +322,16 @@ class GlobalTest extends MediaWikiTestCase {
                $wgDebugTimestamps = false;
 
                wfDebug( "This is a normal string" );
-               $this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) );
+               $this->assertEquals( "This is a normal string\n", file_get_contents( $wgDebugLogFile ) );
                unlink( $wgDebugLogFile );
 
                wfDebug( "This is nöt an ASCII string" );
-               $this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) );
+               $this->assertEquals( "This is nöt an ASCII string\n", file_get_contents( $wgDebugLogFile ) );
                unlink( $wgDebugLogFile );
 
                wfDebug( "\00305This has böth UTF and control chars\003" );
                $this->assertEquals(
-                       " 05This has böth UTF and control chars ",
+                       " 05This has böth UTF and control chars \n",
                        file_get_contents( $wgDebugLogFile )
                );
                unlink( $wgDebugLogFile );
index d7e8cd3..89d1de7 100644 (file)
@@ -172,7 +172,7 @@ mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"});
                        array(
                                array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ),
                                '<script>if(window.mw){
-mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{});
+mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{},{});
 
 }</script>
 '
index 39822dc..754568d 100644 (file)
@@ -28,7 +28,7 @@ class TestUser {
 
        private function assertNotReal() {
                global $wgDBprefix;
-               if( $wgDBprefix !== MediaWikiTestCase::DB_PREFIX && $wgDBprefix !== MediaWikiTestCase::ORA_DB_PREFIX ) {
+               if ( $wgDBprefix !== MediaWikiTestCase::DB_PREFIX && $wgDBprefix !== MediaWikiTestCase::ORA_DB_PREFIX ) {
                        throw new MWException( "Can't create user on real database" );
                }
        }
@@ -114,9 +114,9 @@ class TestUser {
                $passwordFactory = $this->user->getPasswordFactory();
                $oldDefaultType = $passwordFactory->getDefaultType();
 
-                // B is salted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'B' ); // @TODO: Change this to A once that is fixed: https://gerrit.wikimedia.org/r/167523
-               $newPassword = $passwordFactory->newFromPlaintext( $password , $this->user->getPassword() );
+               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
+               $passwordFactory->setDefaultType( 'A' );
+               $newPassword = $passwordFactory->newFromPlaintext( $password, $this->user->getPassword() );
 
                $change = false;
                if ( !$this->user->getPassword()->equals( $newPassword ) ) {
index 49c0108..6af1862 100644 (file)
@@ -650,10 +650,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        public function testActionPermissions() {
                $this->setUserPerm( array( "createpage" ) );
                $this->setTitle( NS_MAIN, "test page" );
-               $this->title->mTitleProtection['pt_create_perm'] = '';
-               $this->title->mTitleProtection['pt_user'] = $this->user->getID();
-               $this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity();
-               $this->title->mTitleProtection['pt_reason'] = 'test';
+               $this->title->mTitleProtection['permission'] = '';
+               $this->title->mTitleProtection['user'] = $this->user->getID();
+               $this->title->mTitleProtection['expiry'] = wfGetDB( DB_SLAVE )->getInfinity();
+               $this->title->mTitleProtection['reason'] = 'test';
                $this->title->mCascadeRestriction = false;
 
                $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ),
@@ -661,7 +661,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $this->assertEquals( false,
                        $this->title->userCan( 'create', $this->user ) );
 
-               $this->title->mTitleProtection['pt_create_perm'] = 'sysop';
+               $this->title->mTitleProtection['permission'] = 'editprotected';
                $this->setUserPerm( array( 'createpage', 'protect' ) );
                $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ),
                        $this->title->getUserPermissionsErrors( 'create', $this->user ) );
index fb436ee..95da847 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+/**
+ * @group ResourceLoader
+ */
 class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
 
        protected function setUp() {
@@ -15,6 +18,76 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                );
        }
 
+       public static function getModules() {
+               $base = array(
+                       'localBasePath' => realpath( dirname( __FILE__ ) ),
+               );
+
+               return array(
+                       'noTemplateModule' => array(),
+
+                       'htmlTemplateModule' => $base + array(
+                               'templates' => array(
+                                       'templates/template.html',
+                                       'templates/template2.html',
+                               )
+                       ),
+
+                       'aliasedHtmlTemplateModule' => $base + array(
+                               'templates' => array(
+                                       'foo.html' => 'templates/template.html',
+                                       'bar.html' => 'templates/template2.html',
+                               )
+                       ),
+
+                       'templateModuleHandlebars' => $base + array(
+                               'templates' => array(
+                                       'templates/template_awesome.handlebars',
+                               ),
+                       ),
+               );
+       }
+
+       public static function providerGetTemplates() {
+               $modules = self::getModules();
+
+               return array(
+                       array(
+                               $modules['noTemplateModule'],
+                               array(),
+                       ),
+                       array(
+                               $modules['templateModuleHandlebars'],
+                               array(
+                                       'templates/template_awesome.handlebars' => "wow\n",
+                               ),
+                       ),
+                       array(
+                               $modules['htmlTemplateModule'],
+                               array(
+                                       'templates/template.html' => "<strong>hello</strong>\n",
+                                       'templates/template2.html' => "<div>goodbye</div>\n",
+                               ),
+                       ),
+                       array(
+                               $modules['aliasedHtmlTemplateModule'],
+                               array(
+                                       'foo.html' => "<strong>hello</strong>\n",
+                                       'bar.html' => "<div>goodbye</div>\n",
+                               ),
+                       ),
+               );
+       }
+
+       public static function providerGetModifiedTime() {
+               $modules = self::getModules();
+
+               return array(
+                       // Check the default value when no templates present in module is 1
+                       array( $modules['noTemplateModule'], 1 ),
+               );
+       }
+
        /**
         * @covers ResourceLoaderFileModule::getAllSkinStyleFiles
         */
@@ -58,4 +131,25 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                        array_map( 'basename', $module->getAllStyleFiles() )
                );
        }
+
+       /**
+        * @dataProvider providerGetTemplates
+        * @covers ResourceLoaderFileModule::getTemplates
+        */
+       public function testGetTemplates( $module, $expected ) {
+               $rl = new ResourceLoaderFileModule( $module );
+
+               $this->assertEquals( $rl->getTemplates(), $expected );
+       }
+
+       /**
+        * @dataProvider providerGetModifiedTime
+        * @covers ResourceLoaderFileModule::getModifiedTime
+        */
+       public function testGetModifiedTime( $module, $expected ) {
+               $rl = new ResourceLoaderFileModule( $module );
+               $ts = $rl->getModifiedTime( new ResourceLoaderContext(
+                       new ResourceLoader, new FauxRequest() ) );
+               $this->assertEquals( $ts, $expected );
+       }
 }
diff --git a/tests/phpunit/includes/resourceloader/templates/template.html b/tests/phpunit/includes/resourceloader/templates/template.html
new file mode 100644 (file)
index 0000000..1f6a7d2
--- /dev/null
@@ -0,0 +1 @@
+<strong>hello</strong>
diff --git a/tests/phpunit/includes/resourceloader/templates/template2.html b/tests/phpunit/includes/resourceloader/templates/template2.html
new file mode 100644 (file)
index 0000000..a322f67
--- /dev/null
@@ -0,0 +1 @@
+<div>goodbye</div>
diff --git a/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars b/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars
new file mode 100644 (file)
index 0000000..5f5c07d
--- /dev/null
@@ -0,0 +1 @@
+wow
index 34007ed..a6fbfac 100644 (file)
@@ -66,6 +66,7 @@ return array(
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
+                       'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
@@ -106,6 +107,7 @@ return array(
                        'mediawiki.toc',
                        'mediawiki.Uri',
                        'mediawiki.user',
+                       'mediawiki.template',
                        'mediawiki.util',
                        'mediawiki.special.recentchanges',
                        'mediawiki.language',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js
new file mode 100644 (file)
index 0000000..86fd828
--- /dev/null
@@ -0,0 +1,63 @@
+( function ( mw ) {
+
+       QUnit.module( 'mediawiki.template', {
+               setup: function () {
+                       var abcCompiler = {
+                               compile: function () {
+                                       return 'abc default compiler';
+                               }
+                       };
+
+                       // Register some template compiler languages
+                       mw.template.registerCompiler( 'abc', abcCompiler );
+                       mw.template.registerCompiler( 'xyz', {
+                               compile: function () {
+                                       return 'xyz compiler';
+                               }
+                       } );
+
+                       // Stub register some templates
+                       this.sandbox.stub( mw.templates, 'get' ).returns( {
+                               'test_templates_foo.xyz': 'goodbye',
+                               'test_templates_foo.abc': 'thankyou'
+                       } );
+               }
+       } );
+
+       QUnit.test( 'add', 1, function ( assert ) {
+               assert.throws(
+                       function () {
+                               mw.template.add( 'module', 'test_templates_foo', 'hello' );
+                       },
+                       'When no prefix throw exception'
+               );
+       } );
+
+       QUnit.test( 'compile', 1, function ( assert ) {
+               assert.throws(
+                       function () {
+                               mw.template.compile( '{{foo}}', 'rainbow' );
+                       },
+                       'Unknown compiler names throw exceptions'
+               );
+       } );
+
+       QUnit.test( 'get', 4, function ( assert ) {
+               assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.xyz' ), 'xyz compiler' );
+               assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.abc' ), 'abc default compiler' );
+               assert.throws(
+                       function () {
+                               mw.template.get( 'this.should.not.exist', 'hello' );
+                       },
+                       'When bad module name given throw error.'
+               );
+
+               assert.throws(
+                       function () {
+                               mw.template.get( 'mediawiki.template', 'hello' );
+                       },
+                       'The template hello should not exist in the mediawiki.templates module and should throw an exception.'
+               );
+       } );
+
+}( mediaWiki ) );