Merge "Factor out preview parse"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 30 Jun 2016 20:06:55 +0000 (20:06 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 30 Jun 2016 20:06:55 +0000 (20:06 +0000)
121 files changed:
HISTORY
RELEASE-NOTES-1.27 [deleted file]
RELEASE-NOTES-1.28
autoload.php
composer.json
docs/hooks.txt
includes/DefaultSettings.php
includes/MediaWikiServices.php
includes/Message.php
includes/OutputPage.php
includes/ServiceWiring.php
includes/WatchedItemQueryService.php [new file with mode: 0644]
includes/api/ApiBase.php
includes/api/ApiCSPReport.php [new file with mode: 0644]
includes/api/ApiMain.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiStashEdit.php
includes/api/ApiUpload.php
includes/api/i18n/ce.json
includes/api/i18n/cs.json
includes/api/i18n/en.json
includes/api/i18n/ko.json
includes/api/i18n/qqq.json
includes/api/i18n/udm.json [new file with mode: 0644]
includes/debug/logger/monolog/KafkaHandler.php
includes/diff/DifferenceEngine.php
includes/export/WikiExporter.php
includes/filerepo/file/LocalFile.php
includes/installer/i18n/cs.json
includes/installer/i18n/gl.json
includes/installer/i18n/ko.json
includes/installer/i18n/mk.json
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/page/Article.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/parser/ParserOptions.php
includes/parser/ParserOutput.php
includes/skins/Skin.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialSearch.php
includes/upload/UploadBase.php
languages/i18n/ar.json
languages/i18n/azb.json
languages/i18n/be-tarask.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/got.json
languages/i18n/he.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/ksh.json
languages/i18n/lad.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/mr.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/pl.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/sat.json
languages/i18n/sv.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/yi.json
maintenance/backup.inc
maintenance/copyFileBackend.php
maintenance/dumpBackup.php
resources/Resources.php
resources/lib/oojs-ui/i18n/id.json
resources/lib/oojs-ui/i18n/inh.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/jv.json
resources/lib/oojs-ui/i18n/ku-latn.json
resources/lib/oojs-ui/i18n/pnb.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/sw.json
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/src/jquery/jquery.accessKeyLabel.js
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.suggestions.js
resources/src/mediawiki.action/mediawiki.action.history.styles.css
resources/src/mediawiki.action/mediawiki.action.view.filepage.css
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.less/mediawiki.ui/mixins.less
resources/src/mediawiki.ui/components/inputs.less
resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js
resources/src/mediawiki/api/edit.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.searchSuggest.js
tests/parser/parserTests.txt
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js [new file with mode: 0644]

diff --git a/HISTORY b/HISTORY
index e833154..868b21a 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,5 +1,588 @@
 Change notes from older releases. For current info see RELEASE-NOTES-1.28.
 
+= MediaWiki 1.27 =
+
+== MediaWiki 1.27.0 ==
+
+=== PHP version requirement in 1.27 ===
+As of 1.27, MediaWiki now requires PHP 5.5.9 or higher (see Compatibility
+section). Additionally, the following PHP extensions are required:
+* ctype
+* iconv
+* json
+* mbstring (new requirement in 1.27)
+* xml
+The following PHP extensions are strongly recommended:
+* openssl
+
+=== Configuration changes in 1.27 ===
+* $wgAllowMicrodataAttributes and $wgAllowRdfaAttributes were removed,
+  now always enabled. If you use RDFa on your wiki, you now have to explicitly
+  set $wgHtml5Version to 'HTML+RDFa 1.0' or 'XHTML+RDFa 1.0'.
+* $wgUseLinkNamespaceDBFields was removed.
+* Deprecated $wgResourceLoaderMinifierStatementsOnOwnLine and
+  $wgResourceLoaderMinifierMaxLineLength, because there was little value in
+  making the behavior configurable. The default values (`false` for the former,
+  1000 for the latter) are now hard-coded.
+* $wgDebugDumpSqlLength was removed (deprecated in 1.24).
+* $wgDebugDBTransactions was removed (deprecated in 1.20).
+* $wgUseXVO has been removed, as it provides functionality only used by
+  custom Wikimedia patches against Squid 2.x that probably noone uses in
+  production anymore. There is now $wgUseKeyHeader that provides similar
+  functionality but instead of the MediaWiki-specific X-Vary-Options header,
+  uses the draft Key header standard.
+* $wgScriptExtension (and support for '.php5' entry points) was removed. See the
+  deprecation notice in the release notes for version 1.25 for advice on how to
+  preserve support for '.php5' entry points via URL rewriting.
+* Password handling via the User object has been deprecated and partially
+  removed, pending the future introduction of AuthManager. In particular:
+** expirePassword(), getPasswordExpireDate(), resetPasswordExpiration(), and
+   getPasswordExpired() have been removed. They were unused outside of core.
+** The mPassword, mNewpassword, mNewpassTime, and mPasswordExpires fields are
+   now private and will be removed in the future.
+** The getPassword() and getTemporaryPassword() methods now throw
+   BadMethodCallException and will be removed in the future.
+** The ability to pass 'password' and 'newpassword' to createNew() has been
+   removed. The only users of it seem to have been using it to set invalid
+   passwords, and so shouldn't be greatly affected.
+** setPassword(), setInternalPassword(), and setNewpassword() have been
+   deprecated, pending the introduction of AuthManager.
+** User::randomPassword() is deprecated in favor of a new method
+   PasswordFactory::generateRandomPasswordString()
+** User::getPasswordFactory() is deprecated, callers should just create a
+   PasswordFactory themselves.
+** A new constructor, User::newSystemUser(), has been added to simplify the
+   creation of passwordless "system" users for logged actions.
+* $wgMaxSquidPurgeTitles was removed.
+* $wgAjaxWatch was removed. This is now enabled by default.
+* $wgUseInstantCommons now hotlinks Commons images by default instead of
+  downloading originals and thumbnailing them locally. This allows wikis to save
+  on CPU and bandwidth while reducing time to first byte for pages, even without
+  a thumbnail handler. See $wgForeignFileRepos documentation for tweaks.
+* (T27397) WebP is enabled by default as an uploadable filetype.
+* (T48998) $wgArticlePath must now be either a full url, or start with a "/".
+* $wgRateLimitLog was removed; use $wgDebugLogGroups['ratelimit'] instead.
+* Deprecated API formats dbg, txt, and yaml have been removed.
+* CLDRPluralRule* classes have been replaced with
+  wikimedia/cldr-plural-rule-parser.
+* Removed $wgProfilePerHost, $wgUDPProfilerHost, $wgUDPProfilerPort,
+  $wgUDPProfilerFormatString, $wgStatsMethod, $wgAggregateStatsID,
+  $wgStatsFormatString, and $wgProfileCallTree (deprecated since 1.20).
+* For proper operation of LocalIdLookup with shared user tables, ensure that
+  $wgSharedDB and $wgSharedTables are properly set even on the "central" wiki
+  that all others are sharing from and that $wgLocalDatabases is set to the
+  full list of sharing wikis on all those wikis.
+* Massive overhaul to session handling:
+** $wgSessionsInObjectCache is no longer supported and must be true, due to
+   MediaWiki\Session\SessionManager. $wgSessionHandler is similarly no longer
+   used.
+** ObjectCacheSessionHandler is removed, replaced with
+   MediaWiki\Session\PhpSessionHandler.
+** PHP session handling in general ($_SESSION, session_id(), and so on) is
+   deprecated. Use MediaWiki\Session\SessionManager instead. A new config
+   variable, $wgPHPSessionHandling, is available to cause use of $_SESSION to
+   issue a deprecation warning or to cause most PHP session handling to throw
+   exceptions.
+** Deprecated UserSetCookies hook. Session-handling extensions should generally
+   be creating a custom subclass of CookieSessionProvider. Other extensions
+   messing with cookies can no longer count on user data being saved in cookies
+   versus other methods.
+** Deprecated UserLoadFromSession hook, extensions should create a
+   MediaWiki\Session\SessionProvider.
+** The User cannot be loaded from session until after Setup.php completes.
+   Attempts to do so will be ignored and the User will remain unloaded.
+** CSRF tokens may be fetched from the MediaWiki\Session\Session, which uses
+   the MediaWiki\Session\Token class.
+* MediaWiki will now auto-create users as necessary, removing the need for
+  extensions to do so. An 'autocreateaccount' right is added to allow
+  auto-creation when 'createaccount' is not granted to all users.
+* Deprecated AuthPluginAutoCreate hook in favor of LocalUserCreated.
+* Most cookie-handling methods in User are deprecated.
+* $wgAllowAsyncCopyUploads and $CopyUploadAsyncTimeout were removed. This was an
+  experimental feature that has never worked.
+* Login and createaccount tokens now vary by timestamp.
+* LoginForm::getLoginToken() and LoginForm::getCreateaccountToken()
+  return a MediaWiki\Session\Token, and tokens must be checked using that
+  class's methods.
+* $wgEnotifUseJobQ was removed and the job queue is always used.
+* The functionality of the ApiSandbox extension has been merged into core. The
+  extension should no longer be used.
+* $wgPreloadJavaScriptMwUtil was removed (deprecated in 1.26).
+  Extensions, skins, gadgets and scripts that use the mediawiki.util module must
+  express a dependency on it.
+* $wgIncludeLegacyJavaScript, deprecated in MediaWiki 1.26, now defaults false.
+  Extensions, skins, gadgets and scripts that need the mediawiki.legacy.wikibits
+  module should express a dependency on it.
+* Removed configuration option $wgCopyrightIcon (deprecated since 1.18). Use
+  $wgFooterIcons['copyright']['copyright'] instead.
+* If the openssl and mcrypt PHP extensions are both unavailable, secure
+  session storage (used for login) will raise an exception. This exception may
+  be bypassed by setting $wgSessionInsecureSecrets = true.
+* Massive overhaul to authentication:
+** AuthPlugin and AuthPluginUser are deprecated.
+** LoginForm and associated templates are deprecated. Extensions which called
+   static LoginForm methods should be converted into authentication providers.
+** The following hooks are deprecated:
+*** AbortAutoAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AbortLogin (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AbortNewAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AddNewAccount (use LocalUserCreated instead)
+*** AuthPluginSetup (create a MediaWiki\Auth\PrimaryAuthenticationProvider instead)
+*** ChangePasswordForm (use AuthChangeFormFields instead, or security levels)
+*** LoginUserMigrated (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** UserCreateForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
+*** UserLoginForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
+** The following hooks are removed:
+*** AbortChangePassword
+*** LoginPasswordResetMessage
+*** PrefsPasswordAudit
+** The UserLoginComplete hook will no longer be called for all logins, only for
+   those via the web UI. Use UserLoggedIn if you need to do something on all
+   logins.
+** $wgRequirePasswordforEmailChange is removed.
+
+=== New features in 1.27 ===
+* $wgDataCenterUpdateStickTTL was also added. This decides how long a user
+  sticks to the primary DC (via cookies) after they make changes to the site.
+* Added a new hook, 'UserMailerTransformContent', to transform the contents
+  of an email. This is similar to the EmailUser hook but applies to all mail
+  sent via UserMailer.
+* Added a new hook, 'UserMailerTransformMessage', to transform the contents
+  of an emai after MIME encoding.
+* Added a new hook, 'UserMailerSplitTo', to control which users have to be
+  emailed separately (ie. there is a single address in the To: field) so
+  user-specific changes to the email can be applied safely.
+* $wgCdnMaxageLagged was added, which limits the CDN cache TTL
+  when any load balancer uses a DB that is lagged beyond the 'max lag'
+  setting in the relevant section of $wgLBFactoryConf.
+* User::newSystemUser() may be used to simplify the creation of passwordless
+  "system" users for logged actions from scripts and extensions.
+* Extensions can now return detailed error information via the API when
+  preventing user actions using 'getUserPermissionsErrors' and similar hooks
+  by using ApiMessage instances instead of strings for the $result value.
+* $wgAPIMaxLagThreshold was added to limit bot changes when databases lag
+  becomes too high.
+* Skins and extensions can now use FlexBox mixins (.flex-display(@display: flex)
+  and .flex(@grow: 1, @shrink: 1, @width: auto, @order: 1)) in Less to create
+  cross-browser-compatible FlexBox rules. Users will still need to add fallback
+  float rules or the like for compatibility with IE9- separately.
+* Added MWTimestamp::getTimezoneString() which returns the localized timezone
+  string, if available. To localize this string, see the comments of
+  $wgLocaltimezone in includes/DefaultSettings.php.
+* Added CentralIdLookup, a service that allows extensions needing a concept of
+  "central" users to get that without having to know about specific central
+  authentication extensions.
+* $wgMaxUserDBWriteDuration added to limit huge user-generated transactions.
+  Regular web request transactions that takes longer than this are aborted.
+* Added a new hook, 'TitleMoveCompleting', which runs before a page move is
+  committed.
+* $wgCdnReboundPurgeDelay was added to provide secondary delayed purges of URLs
+  from CDN to mitigate DB replication lag and WAN cache purge lag.
+* (T49162) Installer will default to setting CACHE_ACCEL as the main cache type
+  if it is available.
+* It is now possible to patrol file uploads (both for new files and new versions
+  of existing files). Special:NewFiles has gained an option to filter by patrol
+  status. This functionality can be disabled using $wgUseFilePatrol.
+* MediaWiki\Session infrastructure allows for easier use of session mechanisms
+  other than the usual cookies.
+** SessionMetadata and SessionCheckInfo hooks allow for setting and checking
+   custom session metadata.
+* Added MWGrants and associated configuration settings $wgGrantPermissions and
+  $wgGrantPermissionGroups to hold configuration for authentication features
+  such as OAuth that want to allow restricting the user rights a user may make
+  use of.
+** If you're already using the OAuth extension, these new variables are
+   identical to (and will replace) $wgMWOAuthGrantPermissions and
+   $wgMWOAuthGrantPermissionGroups.
+* Added MWRestrictions as a class to check restrictions on a WebRequest, e.g.
+  to assert that the request comes from a particular IP range.
+* Added bot passwords, a rights-restricted login mechanism for API-using bots.
+* Whitelisted the following HTML attributes for all elements in wikitext:
+  aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns.
+* Removed "presentation" restriction on the HTML role attribute in wikitext.
+  All values are now allowed for the role attribute.
+* $wgContentHandlers now also supports callbacks to create an instance of the
+  appropriate ContentHandler subclass.
+* Added $wgAuthenticationTokenVersion, which if non-null prevents the
+  user_token database field from being exposed in cookies. Setting this would
+  be a good idea, but will log out all current sessions.
+* $wgEventRelayerConfig was added, for managing PubSub event relay configuration,
+  specifically for reliable CDN url purges.
+* Requests have unique IDs, equal to the UNIQUE_ID environment variable (when
+  MediaWiki is behind Apache+mod_unique_id or something similar) or a randomly-
+  generated 24-character string. This request ID is used to annotate log records
+  and error messages. It is available client-side via mw.config.get( 'wgRequestId' ).
+  The request ID supplants exception IDs. Accordingly, MWExceptionHandler::getLogId()
+  is deprecated.
+* (T33313) Add a preference for watching uploads by default, also applies
+  to API-based upload tools.
+* $wgJpegPixelFormat was added to override chroma subsampling for JPEG image
+  thumbnails created via ImageMagick. Defaults to 'yuv420', providing bandwidth
+  savings versus the previous behavior on many files.
+* MediaWiki\Auth infrastructure (called "AuthManager") allows for more flexible
+  configuration of multiple authentication pieces that was possible with
+  AuthPlugin. For example, it's now easy to plug in second-factor
+  authentication, or add additional checks to the login process, or to support
+  multiple login methods at once, or to support non-password-based login methods.
+** Providers are configured via the global setting $wgAuthManagerConfig.
+** A global, $wgDisableAuthManager, is temporarily available to disable
+   AuthManager until extensions are ready to support it.
+** New hook, AuthChangeFormFields, to adjust the form fields on
+   AuthManager-related special pages.
+** New hook, AuthManagerLoginAuthenticateAudit, for additional logging of
+   AuthManager-related authentication requests.
+** New hook, ChangeAuthenticationDataAudit, for additional logging of
+   AuthManager-related authentication data changes.
+** New hook, SecuritySensitiveOperationStatus, to work with the new mechanism
+   for requiring a recent login before taking security-sensitive operations
+   like changing a password.
+** Two new globals, $wgChangeCredentialsBlacklist and $wgRemoveCredentialsBlacklist
+   can be used to prevent the web UI and the API changing certain authentication data.
+* The file upload dialog (available if you install WikiEditor or VisualEditor)
+  can now be configured using $wgUploadDialog.
+
+=== External library changes in 1.27 ===
+
+==== Upgraded external libraries ====
+* Updated oojs/oojs-ui from v0.12.12 to v0.13.3.
+* Updated composer/semver from v1.0.0 to v1.2.0.
+* Updated liuggio/statsd-php-client to 1.0.18.
+* Updated QUnit from v1.18.0 to v1.22.0.
+
+==== New external libraries ====
+* Added wikimedia/base-convert v1.0.1.
+* Added wikimedia/cldr-plural-rule-parser v1.0.0.
+* Added wikimedia/relpath v1.0.3.
+* Added wikimedia/running-stat v1.1.0.
+* Added wikimedia/php-session-serializer v1.0.3.
+
+==== Removed and replaced external libraries ====
+
+=== Bug fixes in 1.27 ===
+* Special:Upload will now display correct maximum allowed file size when running
+  under HHVM (T116347).
+* (T54077) The APIEditBeforeSave hook will once again give only the content of
+  the section being edited, rather than the whole revision. This reverts the
+  change made in MediaWiki 1.22.
+
+=== Action API changes in 1.27 ===
+* Added list=allrevisions.
+* generator=recentchanges now has the option to generate revids.
+* ApiPageSet::setRedirectMergePolicy() was added. This allows generator
+  modules to define how generator data for a redirect source gets merged
+  into the redirect destination.
+* prop=imageinfo&iiprop=uploadwarning will no longer include the possibility of
+  "was-deleted" warning.
+* Added difftotextpst to query=revisions which preforms a pre-save transform on
+  the text before diffing it.
+* Deprecated formats dbg, txt, and yaml have been removed.
+* (T47988) The protect log event details now use new-style formatting.
+* The following response properties from action=login are deprecated, and may
+  be removed in the future: lgtoken, cookieprefix, sessionid. Clients should
+  handle cookies to properly manage session state.
+* action=login transparently allows login using bot passwords. Clients should
+  merely need to change the username and password used after setting up a bot
+  password.
+* action=upload no longer understands statuskey, asyncdownload or leavemessage.
+* Several changes when $wgDisableAuthManager is false:
+** action=login is deprecated for uses other than bot passwords.
+** list=users can now indicate if a missing username is creatable.
+** action=createaccount is changed in a non-backwards-compatible manner.
+** Added action=query&meta=authmanagerinfo.
+** Added action=clientlogin to be used to log into the main account instead of
+   action=login.
+** Added action=linkaccount.
+** Added action=unlinkaccount.
+** Added action=changeauthenticationdata.
+** Added action=removeauthenticationdata.
+** Added action=resetpassword.
+
+=== Action API internal changes in 1.27 ===
+* ApiQueryORM removed.
+* The following classes have been removed:
+** ApiFormatDbg
+** ApiFormatTxt
+** ApiFormatYaml
+* ApiBase::addTokenProperties() was removed (deprecated since 1.24).
+* ApiBase::getFinalPossibleErrors() was removed (deprecated since 1.24).
+* ApiBase::getFinalResultProperties() was removed (deprecated since 1.24).
+* ApiBase::getRequireAtLeastOneParameterErrorMessages() was removed (deprecated since 1.24).
+* ApiBase::getPossibleErrors() was removed (deprecated since 1.24).
+* ApiBase::getRequireMaxOneParameterErrorMessages() was removed (deprecated since 1.24).
+* ApiBase::getRequireOnlyOneParameterErrorMessages() was removed (deprecated since 1.24).
+* ApiBase::getResultProperties() was removed (deprecated since 1.24).
+* ApiBase::getTitleOrPageIdErrorMessage() was removed (deprecated since 1.24).
+* ApiBase::parseErrors() was removed (deprecated since 1.24).
+* ApiQueryBase::titleToKey(), ApiQueryBase::keyToTitle() and
+  ApiQueryBase::keyPartToTitle() all removed (deprecated since 1.24).
+* ApiQueryBase::checkRowCount() was removed (deprecated since 1.24).
+* ApiQueryBase::getDirectionDescription() was removed (deprecated since 1.25).
+* ApiQuery::getGenerators() was removed (deprecated since 1.21).
+* ApiQuery::getModules() was removed (deprecated since 1.21).
+* ApiQuery::getModuleType() was removed (deprecated since 1.21).
+* ApiQuery::setGeneratorContinue() was removed (deprecated since 1.24).
+* ApiMain::getModules() was removed (deprecated since 1.21).
+* ApiBase::getVersion() was removed (deprecated since 1.21).
+* ApiMain::getShowVersions() was removed (deprecated in 1.21).
+* ApiMain::addModule() was removed (deprecated in 1.21).
+* ApiMain::addFormat() was removed (deprecated in 1.21).
+* ApiMain::getFormats() was removed (deprecated in 1.21).
+* ApiPageSet::finishPageSetGeneration() was removed (deprecated in 1.21).
+* ApiCreateAccount is deprecated, and will be removed soon.
+
+=== Languages updated in 1.27 ===
+
+MediaWiki supports over 350 languages. Many localisations are updated
+regularly. Below only new and removed languages are listed, as well as
+changes to languages because of Phabricator reports.
+
+* (T113688) Change default numerals from Gurmukhi to Arabic for Punjabi locale.
+* (T116020) Aliases of magic words in MessagesXx.php are sorted by usage.
+
+=== Other changes in 1.27 ===
+* Added dependency injection (DI) infrastructure, see docs/injection.txt for details.
+  It is planned to incrementally move MediaWiki code towards using DI, using the
+  service locator (SL) pattern as a stepping stone.
+* ProfilerOutputUdp was removed. Note that there is a ProfilerOutputStats class.
+* WikiPage::doDeleteArticleReal() and WikiPage::doDeleteArticle() now
+  ignore the 2nd and 3rd arguments (formerly $id and $commit).
+* Removed "loaderScripts" option from ResourceLoaderFileModule class.
+* Removed ORM-like wrapper added in 1.20.
+* LinkCache::getGoodLinks and LinkCache::getBadLinks were removed
+  (deprecated in 1.26).
+* WikiPage::doQuickEdit() was removed (deprecated since 1.21).
+* Removed SiteObject and SiteArray classes (deprecated in 1.21).
+* MessageBlobStore::getInstance() was removed (deprecated since 1.25).
+* (T84937) Free external links ("autolinked" urls) will now be terminated
+  by &nbsp; and HTML entity encodings of &nbsp, <, and >.
+* (T36948) The default file revert message's timestamp is now in
+  $wgLocaltimezone, instead of UTC.
+* The default name of the 'suppress' group page has been changed from
+  'Project:Oversight' to 'Project:Suppress'.
+* DatabaseBase::resultObject() is now protected (use outside Database classes
+  not necessary since 1.11).
+* Calling ResourceLoaderFileModule::readStyleFiles() without a
+  ResourceLoaderContext instance is deprecated.
+* ResourceLoader::getLessCompiler() now takes an optional parameter of
+  additional LESS variables to set for the compiler.
+* wfBaseConvert() marked as deprecated, use Wikimedia\base_convert() directly
+  instead.
+* Obsolete maintenance scripts clearCacheStats.php and showCacheStats.php
+  were removed. The underlying data is sent to StatsD (see $wgStatsdServer).
+* Removed msg_resource_links database table and associated code.
+* Removed msg_resource database table and associated code.
+* Skin::getNamespaceNotice() was removed.
+* wfIsConfiguredProxy() was removed (deprecated since 1.24).
+* wfDebugTimer() was removed (deprecated since 1.25).
+* wfIsTrustedProxy() was removed (deprecated since 1.24).
+* wfGetIP() was removed (deprecated since 1.19).
+* MWHookException was removed.
+* OutputPage::appendSubtitle() was removed (deprecated since 1.19).
+* OutputPage::loginToUse() was removed (deprecated since 1.19).
+* Article::loadContent() was removed (deprecated since 1.19).
+* User::editToken() was removed (deprecated since 1.19).
+* Removed --force-normal option of dumpBackup.php, as it no longer served
+  any useful purpose since 1.22.
+* The functions processOption() and processArgs() on the BackupDumper and
+  TextPassDumper classes have been removed.
+* The maintenance/backupTextPass.inc file was deleted. You should include
+  maintenance/dumpTextPass.php instead.
+* WikiPage::getUsedTemplates() was removed (deprecated since 1.19).
+* wfEmptyMsg() was removed (deprecated since 1.18).
+* OutputPage::permissionRequired() was removed (deprecated since 1.18).
+* OutputPage::blockedPage() was removed (deprecated since 1.18).
+* User::getSkin() was removed (deprecated since 1.18).
+* OutputPage::includeJQuery() was removed (deprecated since 1.17).
+* WikiPage::updateRestrictions() was removed (deprecated since 1.19).
+* WikiPage::testPreSaveTransform() was removed (deprecated since 1.19).
+* LogPage::logName() was removed (deprecated since 1.19).
+* LogPage::logHeader() was removed (deprecated since 1.19).
+* wfCheckLimits() was removed (deprecated since 1.24).
+* Linker::makeKnownLinkObj() was removed (deprecated since 1.16).
+* Linker::makeLinkObj() was removed (deprecated since 1.16).
+* wfMsgForContentNoTrans() was removed (deprecated since 1.18).
+* ChangesList::usePatrol was removed (deprecated since 1.22).
+* wfMsgNoTrans() was removed (deprecated since 1.18).
+* Linker::makeImageLink2 was removed (deprecated since 1.20).
+* Title::userIsWatching() was removed (deprecated since 1.20).
+* Removed WaitForSlave maintenance script; use SELECT MASTER_POS_WAIT()
+  database function directly instead.
+* wfMsg() was removed (deprecated since 1.18).
+* wfMsgForContent() was removed (deprecated since 1.18).
+* wfMsgReal() was removed (deprecated since 1.18).
+* wfMsgGetKey() was removed (deprecated since 1.18).
+* wfMsgHtml() was removed (deprecated since 1.18).
+* wfMsgWikiHtml() was removed (deprecated since 1.18).
+* wfMsgExt() was removed (deprecated since 1.18).
+* Language::armourMath() was removed (deprecated since 1.22).
+* LanguageConverter::armourMath() was removed (deprecated since 1.22).
+* FakeConverter::armourMath() was removed (deprecated since 1.22).
+* The unused jquery.validate ResourceLoader module was removed.
+* FileRepo::getRootUrl() was removed (deprecated since 1.20).
+* User::generateToken() was removed (deprecated since 1.20).
+* WikiPage::getRawText() was removed (deprecated since 1.21).
+* ParserOutput::hasCustomDataUpdates() was removed (deprecated since 1.25).
+* ParserOutput::addSecondaryDataUpdate() was removed (deprecated since 1.25).
+* ParserOutput::getSecondaryDataUpdates() was removed (deprecated since 1.25).
+* Gallery images with multiple caption pipes no longer concatenate them all
+  together but instead pick the final one, similar to image syntax.
+* XML-like parser tags (such as <gallery>), when unclosed, will be left unparsed
+  rather than consume everything until the end of the page.
+* New maintenance script resetUserEmail.php allows sysadmins to reset user emails in case
+  a user forgot password/account was stolen.
+* wfCheckEntropy() was removed (deprecated in 1.27).
+* Browser support for Internet Explorer 8 lowered from Grade A to Grade C.
+* ContentHandler::supportsCategories method added. Default is true.
+  CategoryMembershipChangeJob updates are skipped for content that
+  does not support categories.
+* wikidiff difference engine is no longer supported, anyone still using it are encouraged
+  to upgrade to wikidiff2 which is actively maintained and has better package availability.
+* Database logic was removed from WatchedItem and a WatchedItemStore was created:
+** WatchedItem::IGNORE_USER_RIGHTS and WatchedItem::CHECK_USER_RIGHTS were deprecated.
+   User::IGNORE_USER_RIGHTS and User::CHECK_USER_RIGHTS were introduced.
+** WatchedItem::fromUserTitle was deprecated in favour of the constructor.
+** WatchedItem::resetNotificationTimestamp was deprecated.
+** WatchedItem::batchAddWatch was deprecated.
+** WatchedItem::addWatch was deprecated.
+** WatchedItem::removeWatch was deprecated.
+** WatchedItem::isWatched was deprecated.
+** WatchedItem::duplicateEntries was deprecated.
+** EmailNotification::updateWatchlistTimestamp was deprecated.
+** User::getWatchedItem was removed.
+* Unit tests don't work with external PHPUnit anymore, Composer is now the only supported
+  way. Run `composer install` to install it and other dev dependencies to run unit tests.
+* wl_id field added to the watchlist table.
+* Revision::getRawText() was removed (deprecated since 1.21).
+* WikiPage::replaceSection() was removed (deprecated since 1.21).
+* Article::replaceSection() was removed (deprecated since 1.21).
+* Language::getLangObj() was removed (deprecated since 1.24).
+* Language::getLanguageName() was removed (deprecated since 1.20).
+* Language::getLanguageNames() was removed (deprecated since 1.20).
+* Language::getTranslatedLanguageNames() was removed (deprecated since 1.20).
+* Language::specialPage() was removed (deprecated since 1.24).
+* MediaWikiTestCase::assertException() was removed (deprecated since 1.22).
+* OutputPage::getHeadItems() was removed (deprecated since 1.24).
+* OutputPage::getScript() was removed (deprecated since 1.24).
+* OutputPage::out() was removed (deprecated since 1.22).
+* OutputPage::setAllowedModules() was removed (deprecated since 1.24).
+* UserrightsPage::makeGroupNameListForLog() was removed (deprecated since 1.21).
+* MediaWikiSite::newFromGlobalId() was removed (deprecated since 1.21).
+* Title::newFromRedirect() was removed (deprecated since 1.21).
+* Skin::commonPrintStylesheet() was removed (deprecated since 1.22).
+* Skin::getCommonStylePath() was removed (deprecated since 1.24).
+* Skin::newFromKey() was removed (deprecated since 1.24).
+* Skin::getUsableSkins() was removed (deprecated since 1.23).
+* LoadBalancer::pickRandom() was removed (deprecated in 1.21).
+* Article::getUndoText() and WikiPage::getUndoText were removed (deprecated since
+  1.21).
+* DifferenceEngine::setText() was removed (deprecated in 1.21).
+* Title::newFromRedirectArray() was removed (deprecated in 1.21).
+* UserMailer::send() no longer accepts $replyto as the 5th argument and $contentType
+  as the 6th. These must be passed in the options array now.
+* Title::newFromRedirectRecurse() was removed (deprecated in 1.21).
+* Skin::accesskey was removed (deprecated since 1.21).
+* Skin::blockLink was removed (deprecated since 1.21).
+* Skin::buildRollbackLink was removed (deprecated since 1.21).
+* Skin::emailLink was removed (deprecated since 1.21).
+* Skin::formatComment was removed (deprecated since 1.21).
+* Skin::formatHiddenCategories was removed (deprecated since 1.21).
+* Skin::formatLinksInComment was removed (deprecated since 1.21).
+* Skin::formatRevisionSize was removed (deprecated since 1.21).
+* Skin::formatSize was removed (deprecated since 1.21).
+* Skin::formatTemplates was removed (deprecated since 1.21).
+* Skin::generateTOC was removed (deprecated since 1.21).
+* Skin::getInternalLinkAttributes was removed (deprecated since 1.21).
+* Skin::getInternalLinkAttributesObj was removed (deprecated since 1.21).
+* Skin::getInterwikiLinkAttributes was removed (deprecated since 1.21).
+* Skin::getInvalidTitleDescription was removed (deprecated since 1.21).
+* Skin::getLinkColour was removed (deprecated since 1.21).
+* Skin::getRevDeleteLink was removed (deprecated since 1.21).
+* Skin::getRollbackEditCount was removed (deprecated since 1.21).
+* Skin::makeBrokenImageLinkObj was removed (deprecated since 1.21).
+* Skin::makeCommentLink was removed (deprecated since 1.21).
+* Skin::makeExternalImage was removed (deprecated since 1.21).
+* Skin::makeExternalLink was removed (deprecated since 1.21).
+* Skin::makeHeadline was removed (deprecated since 1.21).
+* Skin::makeImageLink was removed (deprecated since 1.21).
+* Skin::makeMediaLinkFile was removed (deprecated since 1.21).
+* Skin::makeMediaLinkObj was removed (deprecated since 1.21).
+* Skin::makeSelfLinkObj was removed (deprecated since 1.21).
+* Skin::makeThumbLink2 was removed (deprecated since 1.21).
+* Skin::makeThumbLinkObj was removed (deprecated since 1.21).
+* Skin::normaliseSpecialPage was removed (deprecated since 1.21).
+* Skin::normalizeSubpageLink was removed (deprecated since 1.21).
+* Skin::processResponsiveImages was removed (deprecated since 1.21).
+* Skin::revComment was removed (deprecated since 1.21).
+* Skin::revDeleteLink was removed (deprecated since 1.21).
+* Skin::revDeleteLinkDisabled was removed (deprecated since 1.21).
+* Skin::revUserLink was removed (deprecated since 1.21).
+* Skin::revUserTools was removed (deprecated since 1.21).
+* Skin::specialLink was removed (deprecated since 1.21).
+* Skin::splitTrail was removed (deprecated since 1.21).
+* Skin::titleAttrib was removed (deprecated since 1.21).
+* Skin::tocIndent was removed (deprecated since 1.21).
+* Skin::tocLine was removed (deprecated since 1.21).
+* Skin::tocLineEnd was removed (deprecated since 1.21).
+* Skin::tocList was removed (deprecated since 1.21).
+* Skin::tocUnindent was removed (deprecated since 1.21).
+* Skin::tooltip was removed (deprecated since 1.21).
+* Skin::tooltipAndAccesskeyAttribs was removed (deprecated since 1.21).
+* Skin::userTalkLink was removed (deprecated since 1.21).
+* Skin::userToolLinksRedContribs was removed (deprecated since 1.21).
+* wikidiff3 is now the default and only PHP diff engine. It provides improved diff
+  performance on complex changes. $wgExternalDiffEngine = 'wikidiff3' therefore
+  makes no difference now. Users are still recommended to use wikidiff2 if possible,
+  though.
+* User::addNewUserLogEntry() was deprecated.
+* User::addNewUserLogEntryAutoCreate() was deprecated.
+* User::isPasswordReminderThrottled() was deprecated.
+* Bot-oriented parameters to Special:UserLogin (wpCookieCheck, wpSkipCookieCheck)
+  were removed.
+* Installer can now be customized without patching MediaWiki code, see
+  mw-config/overrides/README for details.
+
+=== Compatibility ===
+
+MediaWiki 1.27 requires PHP 5.5.9 or later. There is experimental support for
+HHVM 3.6.5 or later.
+
+MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
+support for them is somewhat less mature. There is experimental support for
+Oracle and Microsoft SQL Server.
+
+The supported versions are:
+
+* MySQL 5.0.3 or later
+* PostgreSQL 8.3 or later
+* SQLite 3.3.7 or later
+* Oracle 9.0.1 or later
+* Microsoft SQL Server 2005 (9.00.1399)
+
+=== Upgrading ===
+
+1.27 has several database changes since 1.26, and will not work without schema
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
+
+If upgrading from before 1.11, and you are using a wiki as a commons
+repository, make sure that it is updated as well. Otherwise, errors may arise
+due to database schema changes.
+
+If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
+new database fields are filled with data.
+
+If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to
+1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed
+with MediaWiki 1.21.
+
+Don't forget to always back up your database before upgrading!
+
+See the file UPGRADE for more detailed upgrade instructions.
+
+For notes on 1.26.x and older releases, see HISTORY.
+
+
 = MediaWiki 1.26 =
 
 == MediaWiki 1.26.2 ==
diff --git a/RELEASE-NOTES-1.27 b/RELEASE-NOTES-1.27
deleted file mode 100644 (file)
index 6c93676..0000000
+++ /dev/null
@@ -1,609 +0,0 @@
-== MediaWiki 1.27 ==
-
-THIS IS NOT A RELEASE YET
-
-MediaWiki 1.27 is an alpha-quality branch and is not recommended for use in
-production.
-
-=== PHP version requirement ===
-As of 1.27, MediaWiki now requires PHP 5.5.9 or higher (see Compatibility
-section). Additionally, the following PHP extensions are required:
-* ctype
-* iconv
-* json
-* mbstring (new requirement in 1.27)
-* xml
-The following PHP extensions are strongly recommended:
-* openssl
-
-=== Configuration changes in 1.27 ===
-* $wgAllowMicrodataAttributes and $wgAllowRdfaAttributes were removed,
-  now always enabled. If you use RDFa on your wiki, you now have to explicitly
-  set $wgHtml5Version to 'HTML+RDFa 1.0' or 'XHTML+RDFa 1.0'.
-* $wgUseLinkNamespaceDBFields was removed.
-* Deprecated $wgResourceLoaderMinifierStatementsOnOwnLine and
-  $wgResourceLoaderMinifierMaxLineLength, because there was little value in
-  making the behavior configurable. The default values (`false` for the former,
-  1000 for the latter) are now hard-coded.
-* $wgDebugDumpSqlLength was removed (deprecated in 1.24).
-* $wgDebugDBTransactions was removed (deprecated in 1.20).
-* $wgUseXVO has been removed, as it provides functionality only used by
-  custom Wikimedia patches against Squid 2.x that probably noone uses in
-  production anymore. There is now $wgUseKeyHeader that provides similar
-  functionality but instead of the MediaWiki-specific X-Vary-Options header,
-  uses the draft Key header standard.
-* $wgScriptExtension (and support for '.php5' entry points) was removed. See the
-  deprecation notice in the release notes for version 1.25 for advice on how to
-  preserve support for '.php5' entry points via URL rewriting.
-* Password handling via the User object has been deprecated and partially
-  removed, pending the future introduction of AuthManager. In particular:
-** expirePassword(), getPasswordExpireDate(), resetPasswordExpiration(), and
-   getPasswordExpired() have been removed. They were unused outside of core.
-** The mPassword, mNewpassword, mNewpassTime, and mPasswordExpires fields are
-   now private and will be removed in the future.
-** The getPassword() and getTemporaryPassword() methods now throw
-   BadMethodCallException and will be removed in the future.
-** The ability to pass 'password' and 'newpassword' to createNew() has been
-   removed. The only users of it seem to have been using it to set invalid
-   passwords, and so shouldn't be greatly affected.
-** setPassword(), setInternalPassword(), and setNewpassword() have been
-   deprecated, pending the introduction of AuthManager.
-** User::randomPassword() is deprecated in favor of a new method
-   PasswordFactory::generateRandomPasswordString()
-** User::getPasswordFactory() is deprecated, callers should just create a
-   PasswordFactory themselves.
-** A new constructor, User::newSystemUser(), has been added to simplify the
-   creation of passwordless "system" users for logged actions.
-* $wgMaxSquidPurgeTitles was removed.
-* $wgAjaxWatch was removed. This is now enabled by default.
-* $wgUseInstantCommons now hotlinks Commons images by default instead of
-  downloading originals and thumbnailing them locally. This allows wikis to save
-  on CPU and bandwidth while reducing time to first byte for pages, even without
-  a thumbnail handler. See $wgForeignFileRepos documentation for tweaks.
-* (T27397) WebP is enabled by default as an uploadable filetype.
-* (T48998) $wgArticlePath must now be either a full url, or start with a "/".
-* $wgRateLimitLog was removed; use $wgDebugLogGroups['ratelimit'] instead.
-* Deprecated API formats dbg, txt, and yaml have been removed.
-* CLDRPluralRule* classes have been replaced with
-  wikimedia/cldr-plural-rule-parser.
-* Removed $wgProfilePerHost, $wgUDPProfilerHost, $wgUDPProfilerPort,
-  $wgUDPProfilerFormatString, $wgStatsMethod, $wgAggregateStatsID,
-  $wgStatsFormatString, and $wgProfileCallTree (deprecated since 1.20).
-* For proper operation of LocalIdLookup with shared user tables, ensure that
-  $wgSharedDB and $wgSharedTables are properly set even on the "central" wiki
-  that all others are sharing from and that $wgLocalDatabases is set to the
-  full list of sharing wikis on all those wikis.
-* Massive overhaul to session handling:
-** $wgSessionsInObjectCache is no longer supported and must be true, due to
-   MediaWiki\Session\SessionManager. $wgSessionHandler is similarly no longer
-   used.
-** ObjectCacheSessionHandler is removed, replaced with
-   MediaWiki\Session\PhpSessionHandler.
-** PHP session handling in general ($_SESSION, session_id(), and so on) is
-   deprecated. Use MediaWiki\Session\SessionManager instead. A new config
-   variable, $wgPHPSessionHandling, is available to cause use of $_SESSION to
-   issue a deprecation warning or to cause most PHP session handling to throw
-   exceptions.
-** Deprecated UserSetCookies hook. Session-handling extensions should generally
-   be creating a custom subclass of CookieSessionProvider. Other extensions
-   messing with cookies can no longer count on user data being saved in cookies
-   versus other methods.
-** Deprecated UserLoadFromSession hook, extensions should create a
-   MediaWiki\Session\SessionProvider.
-** The User cannot be loaded from session until after Setup.php completes.
-   Attempts to do so will be ignored and the User will remain unloaded.
-** CSRF tokens may be fetched from the MediaWiki\Session\Session, which uses
-   the MediaWiki\Session\Token class.
-* MediaWiki will now auto-create users as necessary, removing the need for
-  extensions to do so. An 'autocreateaccount' right is added to allow
-  auto-creation when 'createaccount' is not granted to all users.
-* Deprecated AuthPluginAutoCreate hook in favor of LocalUserCreated.
-* Most cookie-handling methods in User are deprecated.
-* $wgAllowAsyncCopyUploads and $CopyUploadAsyncTimeout were removed. This was an
-  experimental feature that has never worked.
-* Login and createaccount tokens now vary by timestamp.
-* LoginForm::getLoginToken() and LoginForm::getCreateaccountToken()
-  return a MediaWiki\Session\Token, and tokens must be checked using that
-  class's methods.
-* $wgEnotifUseJobQ was removed and the job queue is always used.
-* The functionality of the ApiSandbox extension has been merged into core. The
-  extension should no longer be used.
-* $wgPreloadJavaScriptMwUtil was removed (deprecated in 1.26).
-  Extensions, skins, gadgets and scripts that use the mediawiki.util module must
-  express a dependency on it.
-* $wgIncludeLegacyJavaScript, deprecated in MediaWiki 1.26, now defaults false.
-  Extensions, skins, gadgets and scripts that need the mediawiki.legacy.wikibits
-  module should express a dependency on it.
-* Removed configuration option $wgCopyrightIcon (deprecated since 1.18). Use
-  $wgFooterIcons['copyright']['copyright'] instead.
-* If the openssl and mcrypt PHP extensions are both unavailable, secure
-  session storage (used for login) will raise an exception. This exception may
-  be bypassed by setting $wgSessionInsecureSecrets = true.
-* Massive overhaul to authentication:
-** AuthPlugin and AuthPluginUser are deprecated.
-** LoginForm and associated templates are deprecated. Extensions which called
-   static LoginForm methods should be converted into authentication providers.
-** The following hooks are deprecated:
-*** AbortAutoAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
-*** AbortLogin (create a MediaWiki\Auth\PreAuthenticationProvider instead)
-*** AbortNewAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
-*** AddNewAccount (use LocalUserCreated instead)
-*** AuthPluginSetup (create a MediaWiki\Auth\PrimaryAuthenticationProvider instead)
-*** ChangePasswordForm (use AuthChangeFormFields instead, or security levels)
-*** LoginUserMigrated (create a MediaWiki\Auth\PreAuthenticationProvider instead)
-*** UserCreateForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
-*** UserLoginForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
-** The following hooks are removed:
-*** AbortChangePassword
-*** LoginPasswordResetMessage
-*** PrefsPasswordAudit
-** The UserLoginComplete hook will no longer be called for all logins, only for
-   those via the web UI. Use UserLoggedIn if you need to do something on all
-   logins.
-** $wgRequirePasswordforEmailChange is removed.
-
-=== New features in 1.27 ===
-* $wgDataCenterUpdateStickTTL was also added. This decides how long a user
-  sticks to the primary DC (via cookies) after they make changes to the site.
-* Added a new hook, 'UserMailerTransformContent', to transform the contents
-  of an email. This is similar to the EmailUser hook but applies to all mail
-  sent via UserMailer.
-* Added a new hook, 'UserMailerTransformMessage', to transform the contents
-  of an emai after MIME encoding.
-* Added a new hook, 'UserMailerSplitTo', to control which users have to be
-  emailed separately (ie. there is a single address in the To: field) so
-  user-specific changes to the email can be applied safely.
-* $wgCdnMaxageLagged was added, which limits the CDN cache TTL
-  when any load balancer uses a DB that is lagged beyond the 'max lag'
-  setting in the relevant section of $wgLBFactoryConf.
-* User::newSystemUser() may be used to simplify the creation of passwordless
-  "system" users for logged actions from scripts and extensions.
-* Extensions can now return detailed error information via the API when
-  preventing user actions using 'getUserPermissionsErrors' and similar hooks
-  by using ApiMessage instances instead of strings for the $result value.
-* $wgAPIMaxLagThreshold was added to limit bot changes when databases lag
-  becomes too high.
-* Skins and extensions can now use FlexBox mixins (.flex-display(@display: flex)
-  and .flex(@grow: 1, @shrink: 1, @width: auto, @order: 1)) in Less to create
-  cross-browser-compatible FlexBox rules. Users will still need to add fallback
-  float rules or the like for compatibility with IE9- separately.
-* Added MWTimestamp::getTimezoneString() which returns the localized timezone
-  string, if available. To localize this string, see the comments of
-  $wgLocaltimezone in includes/DefaultSettings.php.
-* Added CentralIdLookup, a service that allows extensions needing a concept of
-  "central" users to get that without having to know about specific central
-  authentication extensions.
-* $wgMaxUserDBWriteDuration added to limit huge user-generated transactions.
-  Regular web request transactions that takes longer than this are aborted.
-* Added a new hook, 'TitleMoveCompleting', which runs before a page move is
-  committed.
-* $wgCdnReboundPurgeDelay was added to provide secondary delayed purges of URLs
-  from CDN to mitigate DB replication lag and WAN cache purge lag.
-* (T49162) Installer will default to setting CACHE_ACCEL as the main cache type
-  if it is available.
-* It is now possible to patrol file uploads (both for new files and new versions
-  of existing files). Special:NewFiles has gained an option to filter by patrol
-  status. This functionality can be disabled using $wgUseFilePatrol.
-* MediaWiki\Session infrastructure allows for easier use of session mechanisms
-  other than the usual cookies.
-** SessionMetadata and SessionCheckInfo hooks allow for setting and checking
-   custom session metadata.
-* Added MWGrants and associated configuration settings $wgGrantPermissions and
-  $wgGrantPermissionGroups to hold configuration for authentication features
-  such as OAuth that want to allow restricting the user rights a user may make
-  use of.
-** If you're already using the OAuth extension, these new variables are
-   identical to (and will replace) $wgMWOAuthGrantPermissions and
-   $wgMWOAuthGrantPermissionGroups.
-* Added MWRestrictions as a class to check restrictions on a WebRequest, e.g.
-  to assert that the request comes from a particular IP range.
-* Added bot passwords, a rights-restricted login mechanism for API-using bots.
-* Whitelisted the following HTML attributes for all elements in wikitext:
-  aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns.
-* Removed "presentation" restriction on the HTML role attribute in wikitext.
-  All values are now allowed for the role attribute.
-* $wgContentHandlers now also supports callbacks to create an instance of the
-  appropriate ContentHandler subclass.
-* Added $wgAuthenticationTokenVersion, which if non-null prevents the
-  user_token database field from being exposed in cookies. Setting this would
-  be a good idea, but will log out all current sessions.
-* $wgEventRelayerConfig was added, for managing PubSub event relay configuration,
-  specifically for reliable CDN url purges.
-* Requests have unique IDs, equal to the UNIQUE_ID environment variable (when
-  MediaWiki is behind Apache+mod_unique_id or something similar) or a randomly-
-  generated 24-character string. This request ID is used to annotate log records
-  and error messages. It is available client-side via mw.config.get( 'wgRequestId' ).
-  The request ID supplants exception IDs. Accordingly, MWExceptionHandler::getLogId()
-  is deprecated.
-* (T33313) Add a preference for watching uploads by default, also applies
-  to API-based upload tools.
-* $wgJpegPixelFormat was added to override chroma subsampling for JPEG image
-  thumbnails created via ImageMagick. Defaults to 'yuv420', providing bandwidth
-  savings versus the previous behavior on many files.
-* MediaWiki\Auth infrastructure (called "AuthManager") allows for more flexible
-  configuration of multiple authentication pieces that was possible with
-  AuthPlugin. For example, it's now easy to plug in second-factor
-  authentication, or add additional checks to the login process, or to support
-  multiple login methods at once, or to support non-password-based login methods.
-** Providers are configured via the global setting $wgAuthManagerConfig.
-** A global, $wgDisableAuthManager, is temporarily available to disable
-   AuthManager until extensions are ready to support it.
-** New hook, AuthChangeFormFields, to adjust the form fields on
-   AuthManager-related special pages.
-** New hook, AuthManagerLoginAuthenticateAudit, for additional logging of
-   AuthManager-related authentication requests.
-** New hook, ChangeAuthenticationDataAudit, for additional logging of
-   AuthManager-related authentication data changes.
-** New hook, SecuritySensitiveOperationStatus, to work with the new mechanism
-   for requiring a recent login before taking security-sensitive operations
-   like changing a password.
-** Two new globals, $wgChangeCredentialsBlacklist and $wgRemoveCredentialsBlacklist
-   can be used to prevent the web UI and the API changing certain authentication data.
-* The file upload dialog (available if you install WikiEditor or VisualEditor)
-  can now be configured using $wgUploadDialog.
-
-=== External library changes in 1.27 ===
-
-==== Upgraded external libraries ====
-* Updated oojs/oojs-ui from v0.12.12 to v0.13.3.
-* Updated composer/semver from v1.0.0 to v1.2.0.
-* Updated liuggio/statsd-php-client to 1.0.18.
-* Updated QUnit from v1.18.0 to v1.22.0.
-
-==== New external libraries ====
-* Added wikimedia/base-convert v1.0.1.
-* Added wikimedia/cldr-plural-rule-parser v1.0.0.
-* Added wikimedia/relpath v1.0.3.
-* Added wikimedia/running-stat v1.1.0.
-* Added wikimedia/php-session-serializer v1.0.3.
-
-==== Removed and replaced external libraries ====
-
-=== Bug fixes in 1.27 ===
-* Special:Upload will now display correct maximum allowed file size when running
-  under HHVM (T116347).
-* (T54077) The APIEditBeforeSave hook will once again give only the content of
-  the section being edited, rather than the whole revision. This reverts the
-  change made in MediaWiki 1.22.
-
-=== Action API changes in 1.27 ===
-* Added list=allrevisions.
-* generator=recentchanges now has the option to generate revids.
-* ApiPageSet::setRedirectMergePolicy() was added. This allows generator
-  modules to define how generator data for a redirect source gets merged
-  into the redirect destination.
-* prop=imageinfo&iiprop=uploadwarning will no longer include the possibility of
-  "was-deleted" warning.
-* Added difftotextpst to query=revisions which preforms a pre-save transform on
-  the text before diffing it.
-* Deprecated formats dbg, txt, and yaml have been removed.
-* (T47988) The protect log event details now use new-style formatting.
-* The following response properties from action=login are deprecated, and may
-  be removed in the future: lgtoken, cookieprefix, sessionid. Clients should
-  handle cookies to properly manage session state.
-* action=login transparently allows login using bot passwords. Clients should
-  merely need to change the username and password used after setting up a bot
-  password.
-* action=upload no longer understands statuskey, asyncdownload or leavemessage.
-* Several changes when $wgDisableAuthManager is false:
-** action=login is deprecated for uses other than bot passwords.
-** list=users can now indicate if a missing username is creatable.
-** action=createaccount is changed in a non-backwards-compatible manner.
-** Added action=query&meta=authmanagerinfo.
-** Added action=clientlogin to be used to log into the main account instead of
-   action=login.
-** Added action=linkaccount.
-** Added action=unlinkaccount.
-** Added action=changeauthenticationdata.
-** Added action=removeauthenticationdata.
-** Added action=resetpassword.
-
-=== Action API internal changes in 1.27 ===
-* ApiQueryORM removed.
-* The following classes have been removed:
-** ApiFormatDbg
-** ApiFormatTxt
-** ApiFormatYaml
-* ApiBase::addTokenProperties() was removed (deprecated since 1.24).
-* ApiBase::getFinalPossibleErrors() was removed (deprecated since 1.24).
-* ApiBase::getFinalResultProperties() was removed (deprecated since 1.24).
-* ApiBase::getRequireAtLeastOneParameterErrorMessages() was removed (deprecated since 1.24).
-* ApiBase::getPossibleErrors() was removed (deprecated since 1.24).
-* ApiBase::getRequireMaxOneParameterErrorMessages() was removed (deprecated since 1.24).
-* ApiBase::getRequireOnlyOneParameterErrorMessages() was removed (deprecated since 1.24).
-* ApiBase::getResultProperties() was removed (deprecated since 1.24).
-* ApiBase::getTitleOrPageIdErrorMessage() was removed (deprecated since 1.24).
-* ApiBase::parseErrors() was removed (deprecated since 1.24).
-* ApiQueryBase::titleToKey(), ApiQueryBase::keyToTitle() and
-  ApiQueryBase::keyPartToTitle() all removed (deprecated since 1.24).
-* ApiQueryBase::checkRowCount() was removed (deprecated since 1.24).
-* ApiQueryBase::getDirectionDescription() was removed (deprecated since 1.25).
-* ApiQuery::getGenerators() was removed (deprecated since 1.21).
-* ApiQuery::getModules() was removed (deprecated since 1.21).
-* ApiQuery::getModuleType() was removed (deprecated since 1.21).
-* ApiQuery::setGeneratorContinue() was removed (deprecated since 1.24).
-* ApiMain::getModules() was removed (deprecated since 1.21).
-* ApiBase::getVersion() was removed (deprecated since 1.21).
-* ApiMain::getShowVersions() was removed (deprecated in 1.21).
-* ApiMain::addModule() was removed (deprecated in 1.21).
-* ApiMain::addFormat() was removed (deprecated in 1.21).
-* ApiMain::getFormats() was removed (deprecated in 1.21).
-* ApiPageSet::finishPageSetGeneration() was removed (deprecated in 1.21).
-* ApiCreateAccount is deprecated, and will be removed soon.
-
-=== Languages updated in 1.27 ===
-
-MediaWiki supports over 350 languages. Many localisations are updated
-regularly. Below only new and removed languages are listed, as well as
-changes to languages because of Phabricator reports.
-
-* (T113688) Change default numerals from Gurmukhi to Arabic for Punjabi locale.
-* (T116020) Aliases of magic words in MessagesXx.php are sorted by usage.
-
-=== Other changes in 1.27 ===
-* Added dependency injection (DI) infrastructure, see docs/injection.txt for details.
-  It is planned to incrementally move MediaWiki code towards using DI, using the
-  service locator (SL) pattern as a stepping stone.
-* ProfilerOutputUdp was removed. Note that there is a ProfilerOutputStats class.
-* WikiPage::doDeleteArticleReal() and WikiPage::doDeleteArticle() now
-  ignore the 2nd and 3rd arguments (formerly $id and $commit).
-* Removed "loaderScripts" option from ResourceLoaderFileModule class.
-* Removed ORM-like wrapper added in 1.20.
-* LinkCache::getGoodLinks and LinkCache::getBadLinks were removed
-  (deprecated in 1.26).
-* WikiPage::doQuickEdit() was removed (deprecated since 1.21).
-* Removed SiteObject and SiteArray classes (deprecated in 1.21).
-* MessageBlobStore::getInstance() was removed (deprecated since 1.25).
-* (T84937) Free external links ("autolinked" urls) will now be terminated
-  by &nbsp; and HTML entity encodings of &nbsp, <, and >.
-* (T36948) The default file revert message's timestamp is now in
-  $wgLocaltimezone, instead of UTC.
-* The default name of the 'suppress' group page has been changed from
-  'Project:Oversight' to 'Project:Suppress'.
-* DatabaseBase::resultObject() is now protected (use outside Database classes
-  not necessary since 1.11).
-* Calling ResourceLoaderFileModule::readStyleFiles() without a
-  ResourceLoaderContext instance is deprecated.
-* ResourceLoader::getLessCompiler() now takes an optional parameter of
-  additional LESS variables to set for the compiler.
-* wfBaseConvert() marked as deprecated, use Wikimedia\base_convert() directly
-  instead.
-* Obsolete maintenance scripts clearCacheStats.php and showCacheStats.php
-  were removed. The underlying data is sent to StatsD (see $wgStatsdServer).
-* Removed msg_resource_links database table and associated code.
-* Removed msg_resource database table and associated code.
-* Skin::getNamespaceNotice() was removed.
-* wfIsConfiguredProxy() was removed (deprecated since 1.24).
-* wfDebugTimer() was removed (deprecated since 1.25).
-* wfIsTrustedProxy() was removed (deprecated since 1.24).
-* wfGetIP() was removed (deprecated since 1.19).
-* MWHookException was removed.
-* OutputPage::appendSubtitle() was removed (deprecated since 1.19).
-* OutputPage::loginToUse() was removed (deprecated since 1.19).
-* Article::loadContent() was removed (deprecated since 1.19).
-* User::editToken() was removed (deprecated since 1.19).
-* Removed --force-normal option of dumpBackup.php, as it no longer served
-  any useful purpose since 1.22.
-* The functions processOption() and processArgs() on the BackupDumper and
-  TextPassDumper classes have been removed.
-* The maintenance/backupTextPass.inc file was deleted. You should include
-  maintenance/dumpTextPass.php instead.
-* WikiPage::getUsedTemplates() was removed (deprecated since 1.19).
-* wfEmptyMsg() was removed (deprecated since 1.18).
-* OutputPage::permissionRequired() was removed (deprecated since 1.18).
-* OutputPage::blockedPage() was removed (deprecated since 1.18).
-* User::getSkin() was removed (deprecated since 1.18).
-* OutputPage::includeJQuery() was removed (deprecated since 1.17).
-* WikiPage::updateRestrictions() was removed (deprecated since 1.19).
-* WikiPage::testPreSaveTransform() was removed (deprecated since 1.19).
-* LogPage::logName() was removed (deprecated since 1.19).
-* LogPage::logHeader() was removed (deprecated since 1.19).
-* wfCheckLimits() was removed (deprecated since 1.24).
-* Linker::makeKnownLinkObj() was removed (deprecated since 1.16).
-* Linker::makeLinkObj() was removed (deprecated since 1.16).
-* wfMsgForContentNoTrans() was removed (deprecated since 1.18).
-* ChangesList::usePatrol was removed (deprecated since 1.22).
-* wfMsgNoTrans() was removed (deprecated since 1.18).
-* Linker::makeImageLink2 was removed (deprecated since 1.20).
-* Title::userIsWatching() was removed (deprecated since 1.20).
-* Removed WaitForSlave maintenance script; use SELECT MASTER_POS_WAIT()
-  database function directly instead.
-* wfMsg() was removed (deprecated since 1.18).
-* wfMsgForContent() was removed (deprecated since 1.18).
-* wfMsgReal() was removed (deprecated since 1.18).
-* wfMsgGetKey() was removed (deprecated since 1.18).
-* wfMsgHtml() was removed (deprecated since 1.18).
-* wfMsgWikiHtml() was removed (deprecated since 1.18).
-* wfMsgExt() was removed (deprecated since 1.18).
-* Language::armourMath() was removed (deprecated since 1.22).
-* LanguageConverter::armourMath() was removed (deprecated since 1.22).
-* FakeConverter::armourMath() was removed (deprecated since 1.22).
-* The unused jquery.validate ResourceLoader module was removed.
-* FileRepo::getRootUrl() was removed (deprecated since 1.20).
-* User::generateToken() was removed (deprecated since 1.20).
-* WikiPage::getRawText() was removed (deprecated since 1.21).
-* ParserOutput::hasCustomDataUpdates() was removed (deprecated since 1.25).
-* ParserOutput::addSecondaryDataUpdate() was removed (deprecated since 1.25).
-* ParserOutput::getSecondaryDataUpdates() was removed (deprecated since 1.25).
-* Gallery images with multiple caption pipes no longer concatenate them all
-  together but instead pick the final one, similar to image syntax.
-* XML-like parser tags (such as <gallery>), when unclosed, will be left unparsed
-  rather than consume everything until the end of the page.
-* New maintenance script resetUserEmail.php allows sysadmins to reset user emails in case
-  a user forgot password/account was stolen.
-* wfCheckEntropy() was removed (deprecated in 1.27).
-* Browser support for Internet Explorer 8 lowered from Grade A to Grade C.
-* ContentHandler::supportsCategories method added. Default is true.
-  CategoryMembershipChangeJob updates are skipped for content that
-  does not support categories.
-* wikidiff difference engine is no longer supported, anyone still using it are encouraged
-  to upgrade to wikidiff2 which is actively maintained and has better package availability.
-* Database logic was removed from WatchedItem and a WatchedItemStore was created:
-** WatchedItem::IGNORE_USER_RIGHTS and WatchedItem::CHECK_USER_RIGHTS were deprecated.
-   User::IGNORE_USER_RIGHTS and User::CHECK_USER_RIGHTS were introduced.
-** WatchedItem::fromUserTitle was deprecated in favour of the constructor.
-** WatchedItem::resetNotificationTimestamp was deprecated.
-** WatchedItem::batchAddWatch was deprecated.
-** WatchedItem::addWatch was deprecated.
-** WatchedItem::removeWatch was deprecated.
-** WatchedItem::isWatched was deprecated.
-** WatchedItem::duplicateEntries was deprecated.
-** EmailNotification::updateWatchlistTimestamp was deprecated.
-** User::getWatchedItem was removed.
-* Unit tests don't work with external PHPUnit anymore, Composer is now the only supported
-  way. Run `composer install` to install it and other dev dependencies to run unit tests.
-* wl_id field added to the watchlist table.
-* Revision::getRawText() was removed (deprecated since 1.21).
-* WikiPage::replaceSection() was removed (deprecated since 1.21).
-* Article::replaceSection() was removed (deprecated since 1.21).
-* Language::getLangObj() was removed (deprecated since 1.24).
-* Language::getLanguageName() was removed (deprecated since 1.20).
-* Language::getLanguageNames() was removed (deprecated since 1.20).
-* Language::getTranslatedLanguageNames() was removed (deprecated since 1.20).
-* Language::specialPage() was removed (deprecated since 1.24).
-* MediaWikiTestCase::assertException() was removed (deprecated since 1.22).
-* OutputPage::getHeadItems() was removed (deprecated since 1.24).
-* OutputPage::getScript() was removed (deprecated since 1.24).
-* OutputPage::out() was removed (deprecated since 1.22).
-* OutputPage::setAllowedModules() was removed (deprecated since 1.24).
-* UserrightsPage::makeGroupNameListForLog() was removed (deprecated since 1.21).
-* MediaWikiSite::newFromGlobalId() was removed (deprecated since 1.21).
-* Title::newFromRedirect() was removed (deprecated since 1.21).
-* Skin::commonPrintStylesheet() was removed (deprecated since 1.22).
-* Skin::getCommonStylePath() was removed (deprecated since 1.24).
-* Skin::newFromKey() was removed (deprecated since 1.24).
-* Skin::getUsableSkins() was removed (deprecated since 1.23).
-* LoadBalancer::pickRandom() was removed (deprecated in 1.21).
-* Article::getUndoText() and WikiPage::getUndoText were removed (deprecated since
-  1.21).
-* DifferenceEngine::setText() was removed (deprecated in 1.21).
-* Title::newFromRedirectArray() was removed (deprecated in 1.21).
-* UserMailer::send() no longer accepts $replyto as the 5th argument and $contentType
-  as the 6th. These must be passed in the options array now.
-* Title::newFromRedirectRecurse() was removed (deprecated in 1.21).
-* Skin::accesskey was removed (deprecated since 1.21).
-* Skin::blockLink was removed (deprecated since 1.21).
-* Skin::buildRollbackLink was removed (deprecated since 1.21).
-* Skin::emailLink was removed (deprecated since 1.21).
-* Skin::formatComment was removed (deprecated since 1.21).
-* Skin::formatHiddenCategories was removed (deprecated since 1.21).
-* Skin::formatLinksInComment was removed (deprecated since 1.21).
-* Skin::formatRevisionSize was removed (deprecated since 1.21).
-* Skin::formatSize was removed (deprecated since 1.21).
-* Skin::formatTemplates was removed (deprecated since 1.21).
-* Skin::generateTOC was removed (deprecated since 1.21).
-* Skin::getInternalLinkAttributes was removed (deprecated since 1.21).
-* Skin::getInternalLinkAttributesObj was removed (deprecated since 1.21).
-* Skin::getInterwikiLinkAttributes was removed (deprecated since 1.21).
-* Skin::getInvalidTitleDescription was removed (deprecated since 1.21).
-* Skin::getLinkColour was removed (deprecated since 1.21).
-* Skin::getRevDeleteLink was removed (deprecated since 1.21).
-* Skin::getRollbackEditCount was removed (deprecated since 1.21).
-* Skin::makeBrokenImageLinkObj was removed (deprecated since 1.21).
-* Skin::makeCommentLink was removed (deprecated since 1.21).
-* Skin::makeExternalImage was removed (deprecated since 1.21).
-* Skin::makeExternalLink was removed (deprecated since 1.21).
-* Skin::makeHeadline was removed (deprecated since 1.21).
-* Skin::makeImageLink was removed (deprecated since 1.21).
-* Skin::makeMediaLinkFile was removed (deprecated since 1.21).
-* Skin::makeMediaLinkObj was removed (deprecated since 1.21).
-* Skin::makeSelfLinkObj was removed (deprecated since 1.21).
-* Skin::makeThumbLink2 was removed (deprecated since 1.21).
-* Skin::makeThumbLinkObj was removed (deprecated since 1.21).
-* Skin::normaliseSpecialPage was removed (deprecated since 1.21).
-* Skin::normalizeSubpageLink was removed (deprecated since 1.21).
-* Skin::processResponsiveImages was removed (deprecated since 1.21).
-* Skin::revComment was removed (deprecated since 1.21).
-* Skin::revDeleteLink was removed (deprecated since 1.21).
-* Skin::revDeleteLinkDisabled was removed (deprecated since 1.21).
-* Skin::revUserLink was removed (deprecated since 1.21).
-* Skin::revUserTools was removed (deprecated since 1.21).
-* Skin::specialLink was removed (deprecated since 1.21).
-* Skin::splitTrail was removed (deprecated since 1.21).
-* Skin::titleAttrib was removed (deprecated since 1.21).
-* Skin::tocIndent was removed (deprecated since 1.21).
-* Skin::tocLine was removed (deprecated since 1.21).
-* Skin::tocLineEnd was removed (deprecated since 1.21).
-* Skin::tocList was removed (deprecated since 1.21).
-* Skin::tocUnindent was removed (deprecated since 1.21).
-* Skin::tooltip was removed (deprecated since 1.21).
-* Skin::tooltipAndAccesskeyAttribs was removed (deprecated since 1.21).
-* Skin::userTalkLink was removed (deprecated since 1.21).
-* Skin::userToolLinksRedContribs was removed (deprecated since 1.21).
-* wikidiff3 is now the default and only PHP diff engine. It provides improved diff
-  performance on complex changes. $wgExternalDiffEngine = 'wikidiff3' therefore
-  makes no difference now. Users are still recommended to use wikidiff2 if possible,
-  though.
-* User::addNewUserLogEntry() was deprecated.
-* User::addNewUserLogEntryAutoCreate() was deprecated.
-* User::isPasswordReminderThrottled() was deprecated.
-* Bot-oriented parameters to Special:UserLogin (wpCookieCheck, wpSkipCookieCheck)
-  were removed.
-* Installer can now be customized without patching MediaWiki code, see
-  mw-config/overrides/README for details.
-
-== Compatibility ==
-
-MediaWiki 1.27 requires PHP 5.5.9 or later. There is experimental support for
-HHVM 3.6.5 or later.
-
-MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
-support for them is somewhat less mature. There is experimental support for
-Oracle and Microsoft SQL Server.
-
-The supported versions are:
-
-* MySQL 5.0.3 or later
-* PostgreSQL 8.3 or later
-* SQLite 3.3.7 or later
-* Oracle 9.0.1 or later
-* Microsoft SQL Server 2005 (9.00.1399)
-
-== Upgrading ==
-
-1.27 has several database changes since 1.26, and will not work without schema
-updates. Note that due to changes to some very large tables like the revision
-table, the schema update may take quite long (minutes on a medium sized site,
-many hours on a large site).
-
-If upgrading from before 1.11, and you are using a wiki as a commons
-repository, make sure that it is updated as well. Otherwise, errors may arise
-due to database schema changes.
-
-If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
-new database fields are filled with data.
-
-If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to
-1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed
-with MediaWiki 1.21.
-
-Don't forget to always back up your database before upgrading!
-
-See the file UPGRADE for more detailed upgrade instructions.
-
-For notes on 1.26.x and older releases, see HISTORY.
-
-== Online documentation ==
-
-Documentation for both end-users and site administrators is available on
-MediaWiki.org, and is covered under the GNU Free Documentation License (except
-for pages that explicitly state that their contents are in the public domain):
-
-       https://www.mediawiki.org/wiki/Documentation
-
-== Mailing list ==
-
-A mailing list is available for MediaWiki user support and discussion:
-
-       https://lists.wikimedia.org/mailman/listinfo/mediawiki-l
-
-A low-traffic announcements-only list is also available:
-
-       https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce
-
-It's highly recommended that you sign up for one of these lists if you're
-going to run a public MediaWiki, so you can be notified of security fixes.
-
-== IRC help ==
-
-There's usually someone online in #mediawiki on irc.freenode.net.
index 4c075fc..d0bb57f 100644 (file)
@@ -54,6 +54,7 @@ changes to languages because of Phabricator reports.
 * [BREAKING CHANGE] $wgExtendedLoginCookies has been removed.  You can
   use or update a custom session provider if needed.
 * Deprecated APIEditBeforeSave hook in favor of EditFilterMergedContent.
+* The 'UploadVerification' hook is deprecated. Use 'UploadVerifyFile' instead.
 
 == Compatibility ==
 
index 87ba225..d994016 100644 (file)
@@ -28,6 +28,7 @@ $wgAutoloadLocalClasses = [
        'ApiComparePages' => __DIR__ . '/includes/api/ApiComparePages.php',
        'ApiContinuationManager' => __DIR__ . '/includes/api/ApiContinuationManager.php',
        'ApiCreateAccount' => __DIR__ . '/includes/api/ApiCreateAccount.php',
+       'ApiCSPReport' => __DIR__ . '/includes/api/ApiCSPReport.php',
        'ApiDelete' => __DIR__ . '/includes/api/ApiDelete.php',
        'ApiDisabled' => __DIR__ . '/includes/api/ApiDisabled.php',
        'ApiEditPage' => __DIR__ . '/includes/api/ApiEditPage.php',
@@ -607,6 +608,7 @@ $wgAutoloadLocalClasses = [
        'InstallerSessionProvider' => __DIR__ . '/includes/installer/InstallerSessionProvider.php',
        'Interwiki' => __DIR__ . '/includes/interwiki/Interwiki.php',
        'InvalidPassword' => __DIR__ . '/includes/password/InvalidPassword.php',
+       'InvalidateUserSesssions' => __DIR__ . '/maintenance/invalidateUserSessions.php',
        'IteratorDecorator' => __DIR__ . '/includes/utils/iterators/IteratorDecorator.php',
        'IuConverter' => __DIR__ . '/languages/classes/LanguageIu.php',
        'JSCompilerContext' => __DIR__ . '/includes/libs/jsminplus.php',
@@ -1485,6 +1487,7 @@ $wgAutoloadLocalClasses = [
        'WantedTemplatesPage' => __DIR__ . '/includes/specials/SpecialWantedtemplates.php',
        'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php',
        'WatchedItem' => __DIR__ . '/includes/WatchedItem.php',
+       'WatchedItemQueryService' => __DIR__ . '/includes/WatchedItemQueryService.php',
        'WatchedItemStore' => __DIR__ . '/includes/WatchedItemStore.php',
        'WatchlistCleanup' => __DIR__ . '/maintenance/cleanupWatchlist.php',
        'WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php',
index a261449..0e512a6 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.4",
+               "oojs/oojs-ui": "0.17.5",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
index c0c01f4..f9f8333 100644 (file)
@@ -1136,6 +1136,85 @@ $page: SpecialPage object for DeletedContributions
 $row: the DB row for this line
 &$classes: the classes to add to the surrounding <li>
 
+'DifferenceEngineMarkPatrolledLink': Allows extensions to change the "mark as patrolled" link
+which is shown both on the diff header as well as on the bottom of a page, usually
+wrapped in a span element which has class="patrollink".
+$differenceEngine: DifferenceEngine object
+&$markAsPatrolledLink: The "mark as patrolled" link HTML (string)
+$rcid: Recent change ID (rc_id) for this change (int)
+$token: Patrol token; $rcid is used in generating this variable
+
+'DifferenceEngineMarkPatrolledRCID': Allows extensions to possibly change the rcid parameter.
+For example the rcid might be set to zero due to the user being the same as the
+performer of the change but an extension might still want to show it under certain
+conditions.
+&$rcid: rc_id (int) of the change or 0
+$differenceEngine: DifferenceEngine object
+$change: RecentChange object
+$user: User object representing the current user
+
+'DifferenceEngineNewHeader': Allows extensions to change the $newHeader variable, which
+contains information about the new revision, such as the revision's author, whether
+the revision was marked as a minor edit or not, etc.
+$differenceEngine: DifferenceEngine object
+&$newHeader: The string containing the various #mw-diff-otitle[1-5] divs, which
+include things like revision author info, revision comment, RevisionDelete link and more
+$formattedRevisionTools: Array containing revision tools, some of which may have
+been injected with the DiffRevisionTools hook
+$nextlink: String containing the link to the next revision (if any); also included in $newHeader
+$rollback: Rollback link (string) to roll this revision back to the previous one, if any
+$newminor: String indicating if the new revision was marked as a minor edit
+$diffOnly: Boolean parameter passed to DifferenceEngine#showDiffPage, indicating
+whether we should show just the diff; passed in as a query string parameter to the
+various URLs constructed here (i.e. $nextlink)
+$rdel: RevisionDelete link for the new revision, if the current user is allowed
+to use the RevisionDelete feature
+$unhide: Boolean parameter indicating whether to show RevisionDeleted revisions
+
+'DifferenceEngineOldHeader': Allows extensions to change the $oldHeader variable, which
+contains information about the old revision, such as the revision's author, whether
+the revision was marked as a minor edit or not, etc.
+$differenceEngine: DifferenceEngine object
+&$oldHeader: The string containing the various #mw-diff-otitle[1-5] divs, which
+include things like revision author info, revision comment, RevisionDelete link and more
+$prevlink: String containing the link to the previous revision (if any); also included in $oldHeader
+$oldminor: String indicating if the old revision was marked as a minor edit
+$diffOnly: Boolean parameter passed to DifferenceEngine#showDiffPage, indicating
+whether we should show just the diff; passed in as a query string parameter to the
+various URLs constructed here (i.e. $prevlink)
+$ldel: RevisionDelete link for the old revision, if the current user is allowed
+to use the RevisionDelete feature
+$unhide: Boolean parameter indicating whether to show RevisionDeleted revisions
+
+'DifferenceEngineOldHeaderNoOldRev': Change the $oldHeader variable in cases when
+there is no old revision
+&$oldHeader: empty string by default
+
+'DifferenceEngineRenderRevisionAddParserOutput': Allows extensions to change the parser output.
+Return false to not add parser output via OutputPage's addParserOutput method.
+$differenceEngine: DifferenceEngine object
+$out: OutputPage object
+$parserOutput: ParserOutput object
+$wikiPage: WikiPage object
+
+'DifferenceEngineRenderRevisionShowFinalPatrolLink': An extension can hook into this hook
+point and return false to not show the final "mark as patrolled" link on the bottom
+of a page.
+This hook has no arguments.
+
+'DifferenceEngineShowDiff': Allows extensions to affect the diff text which
+eventually gets sent to the OutputPage object.
+$differenceEngine: DifferenceEngine object
+
+'DifferenceEngineShowEmptyOldContent': Allows extensions to change the diff table
+body (without header) in cases when there is no old revision or the old and new
+revisions are identical.
+$differenceEngine: DifferenceEngine object
+
+'DifferenceEngineShowDiffPage': Add additional output via the available OutputPage
+object into the diff view
+$out: OutputPage object
+
 'DiffRevisionTools': Override or extend the revision tools available from the
 diff view, i.e. undo, etc.
 $newRev: Revision object of the "new" revision
@@ -3285,8 +3364,8 @@ added to the descriptor
 &$radio: Boolean, if source type should be shown as radio button
 $selectedSourceType: The selected source type
 
-'UploadVerification': Additional chances to reject an uploaded file. Consider
-using UploadVerifyFile instead.
+'UploadVerification': DEPRECATED! Use UploadVerifyFile instead.
+Additional chances to reject an uploaded file.
 $saveName: (string) destination file name
 $tempName: (string) filesystem path to the temporary file for checks
 &$error: (string) output: message key for message to show if upload canceled by
index 39e22a0..6d08eec 100644 (file)
@@ -284,10 +284,10 @@ $wgLogo = false;
  *
  * @par Example:
  * @code
- * $wgLogoHD = array(
+ * $wgLogoHD = [
  *     "1.5x" => "path/to/1.5x_version.png",
  *     "2x" => "path/to/2x_version.png"
- * );
+ * ];
  * @endcode
  *
  * @since 1.25
@@ -532,7 +532,7 @@ $wgUseInstantCommons = false;
  * The string 'local' signifies the default local file repository.
  *
  * Example:
- * $wgForeignUploadTargets = array( 'shared' );
+ * $wgForeignUploadTargets = [ 'shared' ];
  */
 $wgForeignUploadTargets = [ 'local' ];
 
@@ -755,10 +755,10 @@ $wgCopyUploadTimeout = false;
  *
  * @par Example:
  * @code
- * $wgMaxUploadSize = array(
+ * $wgMaxUploadSize = [
  *     '*' => 250 * 1024,
  *     'url' => 500 * 1024,
- * );
+ * ];
  * @endcode
  * Sets the maximum for all uploads to 250 kB except for upload-by-url, which
  * will have a maximum of 500 kB.
@@ -1163,9 +1163,9 @@ $wgMaxAnimatedGifArea = 1.25e7;
  * @par Example:
  * @code
  *  // PNG is lossless, but inefficient for photos
- *  $wgTiffThumbnailType = array( 'png', 'image/png' );
+ *  $wgTiffThumbnailType = [ 'png', 'image/png' ];
  *  // JPEG is good for photos, but has no transparency support. Bad for diagrams.
- *  $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
+ *  $wgTiffThumbnailType = [ 'jpg', 'image/jpeg' ];
  * @endcode
  */
 $wgTiffThumbnailType = false;
@@ -1321,7 +1321,7 @@ $wgTrivialMimeDetection = false;
 
 /**
  * Additional XML types we can allow via MIME-detection.
- * array = ( 'rootElement' => 'associatedMimeType' )
+ * array = [ 'rootElement' => 'associatedMimeType' ]
  */
 $wgXMLMimeTypes = [
        'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
@@ -1378,7 +1378,7 @@ $wgThumbnailBuckets = null;
  * needs in order to be used as the reference for a given thumbnail. For example, with the
  * following buckets:
  *
- * $wgThumbnailBuckets = array ( 128, 256, 512 );
+ * $wgThumbnailBuckets = [ 128, 256, 512 ];
  *
  * and a distance of 50:
  *
@@ -1624,14 +1624,14 @@ $wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days
  * Default to false or fill an array :
  *
  * @code
- * $wgSMTP = array(
+ * $wgSMTP = [
  *     'host'     => 'SMTP domain',
  *     'IDHost'   => 'domain for MessageID',
  *     'port'     => '25',
  *     'auth'     => [true|false],
  *     'username' => [SMTP username],
  *     'password' => [SMTP password],
- * );
+ * ];
  * @endcode
  */
 $wgSMTP = false;
@@ -2068,7 +2068,7 @@ $wgCompressRevisions = false;
  *
  * Short names of ExternalStore classes may be specified in an array here:
  * @code
- * $wgExternalStores = array("http","file","custom")...
+ * $wgExternalStores = [ "http","file","custom" ]...
  * @endcode
  *
  * CAUTION: Access to database might lead to code execution
@@ -2081,9 +2081,9 @@ $wgExternalStores = [];
  * @par Example:
  * Create a cluster named 'cluster1' containing three servers:
  * @code
- * $wgExternalServers = array(
- *     'cluster1' => array( 'srv28', 'srv29', 'srv30' )
- * );
+ * $wgExternalServers = [
+ *     'cluster1' => [ 'srv28', 'srv29', 'srv30' ]
+ * ];
  * @endcode
  *
  * Used by LBFactorySimple, may be ignored if $wgLBFactoryConf is set to
@@ -2100,7 +2100,7 @@ $wgExternalServers = [];
  *
  * @par Example:
  * @code
- * $wgDefaultExternalStore = array( 'DB://cluster1', 'DB://cluster2' );
+ * $wgDefaultExternalStore = [ 'DB://cluster1', 'DB://cluster2' ];
  * @endcode
  *
  * @var array
@@ -2551,12 +2551,6 @@ $wgSidebarCacheExpiry = 86400;
  */
 $wgUseGzip = false;
 
-/**
- * Whether MediaWiki should send an ETag header. Seems to cause
- * broken behavior with Squid 2.6, see bug 7098.
- */
-$wgUseETag = false;
-
 /**
  * Clock skew or the one-second resolution of time() can occasionally cause cache
  * problems when the user requests two pages within a short period of time. This
@@ -2744,16 +2738,16 @@ $wgSquidPurgeUseHostHeader = true;
  * @par Example configuration to send purges for upload.wikimedia.org to one
  * multicast group and all other purges to another:
  * @code
- * $wgHTCPRouting = array(
- *         '|^https?://upload\.wikimedia\.org|' => array(
+ * $wgHTCPRouting = [
+ *         '|^https?://upload\.wikimedia\.org|' => [
  *                 'host' => '239.128.0.113',
  *                 'port' => 4827,
- *         ),
- *         '' => array(
+ *         ],
+ *         '' => [
  *                 'host' => '239.128.0.112',
  *                 'port' => 4827,
- *         ),
- * );
+ *         ],
+ * ];
  * @endcode
  *
  * You can also pass an array of hosts to send purges too. This is useful when
@@ -2762,16 +2756,16 @@ $wgSquidPurgeUseHostHeader = true;
  *
  * @par Example of sending purges to multiple hosts:
  * @code
- * $wgHTCPRouting = array(
- *     '' => array(
+ * $wgHTCPRouting = [
+ *     '' => [
  *         // Purges to text caches using multicast
- *         array( 'host' => '239.128.0.114', 'port' => '4827' ),
+ *         [ 'host' => '239.128.0.114', 'port' => '4827' ],
  *         // Purges to a hardcoded list of caches
- *         array( 'host' => '10.88.66.1', 'port' => '4827' ),
- *         array( 'host' => '10.88.66.2', 'port' => '4827' ),
- *         array( 'host' => '10.88.66.3', 'port' => '4827' ),
- *     ),
- * );
+ *         [ 'host' => '10.88.66.1', 'port' => '4827' ],
+ *         [ 'host' => '10.88.66.2', 'port' => '4827' ],
+ *         [ 'host' => '10.88.66.3', 'port' => '4827' ],
+ *     ],
+ * ];
  * @endcode
  *
  * @since 1.22
@@ -3094,7 +3088,7 @@ $wgLoginLanguageSelector = false;
  * To allow language-specific main page and community
  * portal:
  * @code
- *     $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
+ *     $wgForceUIMsgAsContentMsg = [ 'mainpage', 'portal-url' ];
  * @endcode
  */
 $wgForceUIMsgAsContentMsg = [];
@@ -3464,13 +3458,13 @@ $wgMangleFlashPolicy = true;
  *
  * @par Example:
  * @code
- *   $wgResourceModules['ext.myExtension'] = array(
+ *   $wgResourceModules['ext.myExtension'] = [
  *      'scripts' => 'myExtension.js',
  *      'styles' => 'myExtension.css',
- *      'dependencies' => array( 'jquery.cookie', 'jquery.tabIndex' ),
+ *      'dependencies' => [ 'jquery.cookie', 'jquery.tabIndex' ],
  *      'localBasePath' => __DIR__,
  *      'remoteExtPath' => 'MyExtension',
- *   );
+ *   ];
  * @endcode
  */
 $wgResourceModules = [];
@@ -3485,27 +3479,27 @@ $wgResourceModules = [];
  *
  * @par Example:
  * @code
- *   $wgResourceModules['bar'] = array(
+ *   $wgResourceModules['bar'] = [
  *     'scripts' => 'resources/bar/bar.js',
  *     'styles' => 'resources/bar/main.css',
- *   );
+ *   ];
  *
- *   $wgResourceModuleSkinStyles['foo'] = array(
+ *   $wgResourceModuleSkinStyles['foo'] = [
  *     'bar' => 'skins/Foo/bar.css',
- *   );
+ *   ];
  * @endcode
  *
  * This is mostly equivalent to:
  *
  * @par Equivalent:
  * @code
- *   $wgResourceModules['bar'] = array(
+ *   $wgResourceModules['bar'] = [
  *     'scripts' => 'resources/bar/bar.js',
  *     'styles' => 'resources/bar/main.css',
- *     'skinStyles' => array(
+ *     'skinStyles' => [
  *       'foo' => skins/Foo/bar.css',
- *     ),
- *   );
+ *     ],
+ *   ];
  * @endcode
  *
  * If the module already defines its own entry in `skinStyles` for a given skin, then
@@ -3516,34 +3510,34 @@ $wgResourceModules = [];
  *
  * @par Example:
  * @code
- *   $wgResourceModules['bar'] = array(
+ *   $wgResourceModules['bar'] = [
  *     'scripts' => 'resources/bar/bar.js',
  *     'styles' => 'resources/bar/basic.css',
- *     'skinStyles' => array(
- *       'default' => 'resources/bar/additional.css',
- *     ),
- *   );
+ *     'skinStyles' => [
+ *      'default' => 'resources/bar/additional.css',
+ *     ],
+ *   ];
  *   // Note the '+' character:
- *   $wgResourceModuleSkinStyles['foo'] = array(
+ *   $wgResourceModuleSkinStyles['foo'] = [
  *     '+bar' => 'skins/Foo/bar.css',
- *   );
+ *   ];
  * @endcode
  *
  * This is mostly equivalent to:
  *
  * @par Equivalent:
  * @code
- *   $wgResourceModules['bar'] = array(
+ *   $wgResourceModules['bar'] = [
  *     'scripts' => 'resources/bar/bar.js',
  *     'styles' => 'resources/bar/basic.css',
- *     'skinStyles' => array(
+ *     'skinStyles' => [
  *       'default' => 'resources/bar/additional.css',
- *       'foo' => array(
+ *       'foo' => [
  *         'resources/bar/additional.css',
  *         'skins/Foo/bar.css',
- *       ),
- *     ),
- *   );
+ *       ],
+ *     ],
+ *   ];
  * @endcode
  *
  * In other words, as a module author, use the `styles` list for stylesheets that may not be
@@ -3555,12 +3549,12 @@ $wgResourceModules = [];
  *
  * @par Example:
  * @code
- *   $wgResourceModuleSkinStyles['foo'] = array(
+ *   $wgResourceModuleSkinStyles['foo'] = [
  *     'bar' => 'bar.css',
  *     'quux' => 'quux.css',
  *     'remoteSkinPath' => 'Foo',
  *     'localBasePath' => __DIR__,
- *   );
+ *   ];
  * @endcode
  */
 $wgResourceModuleSkinStyles = [];
@@ -3717,11 +3711,11 @@ $wgResourceLoaderValidateStaticJS = false;
  *
  * @par Example:
  * @code
- *   $wgResourceLoaderLESSVars = array(
+ *   $wgResourceLoaderLESSVars = [
  *     'baseFontSize'  => '1em',
  *     'smallFontSize' => '0.75em',
  *     'WikimediaBlue' => '#006699',
- *   );
+ *   ];
  * @endcode
  * @since 1.22
  */
@@ -3819,12 +3813,12 @@ $wgMetaNamespaceTalk = false;
  *
  * @par Example:
  * @code
- * $wgExtraNamespaces = array(
+ * $wgExtraNamespaces = [
  *    100 => "Hilfe",
  *    101 => "Hilfe_Diskussion",
  *    102 => "Aide",
  *    103 => "Discussion_Aide"
- * );
+ * ];
  * @endcode
  *
  * @todo Add a note about maintenance/namespaceDupes.php
@@ -3851,10 +3845,10 @@ $wgExtraGenderNamespaces = [];
  *
  * @par Example:
  * @code
- *    $wgNamespaceAliases = array(
+ *    $wgNamespaceAliases = [
  *        'Wikipedian' => NS_USER,
  *        'Help' => 100,
- *    );
+ *    ];
  * @endcode
  */
 $wgNamespaceAliases = [];
@@ -4181,7 +4175,7 @@ $wgAllowExternalImages = false;
  * @par Examples:
  * @code
  * $wgAllowExternalImagesFrom = 'http://127.0.0.1/';
- * $wgAllowExternalImagesFrom = array( 'http://127.0.0.1/', 'http://example.com' );
+ * $wgAllowExternalImagesFrom = [ 'http://127.0.0.1/', 'http://example.com' ];
  * @endcode
  */
 $wgAllowExternalImagesFrom = '';
@@ -4303,8 +4297,7 @@ $wgNoFollowNsExceptions = [];
  * (or any subdomains) will not be set to rel="nofollow" regardless of the
  * value of $wgNoFollowLinks.  For instance:
  *
- * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org',
- * 'mediawiki.org' );
+ * $wgNoFollowDomainExceptions = [ 'en.wikipedia.org', 'wiktionary.org', 'mediawiki.org' ];
  *
  * This would add rel="nofollow" to links to de.wikipedia.org, but not
  * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
@@ -4691,14 +4684,14 @@ $wgPasswordDefault = 'pbkdf2';
  *
  * An advanced example:
  * @code
- * $wgPasswordConfig['bcrypt-peppered'] = array(
+ * $wgPasswordConfig['bcrypt-peppered'] = [
  *     'class' => 'EncryptedPassword',
  *     'underlying' => 'bcrypt',
- *     'secrets' => array(),
+ *     'secrets' => [],
  *     'cipher' => MCRYPT_RIJNDAEL_256,
  *     'mode' => MCRYPT_MODE_CBC,
  *     'cost' => 5,
- * );
+ * ];
  * @endcode
  *
  * @since 1.24
@@ -4987,7 +4980,7 @@ $wgWhitelistRead = false;
  * @par Example:
  * To whitelist [[Main Page]]:
  * @code
- * $wgWhitelistReadRegexp = array( "/Main Page/" );
+ * $wgWhitelistReadRegexp = [ "/Main Page/" ];
  * @endcode
  *
  * @note Unless ^ and/or $ is specified, a regular expression might match
@@ -4997,7 +4990,7 @@ $wgWhitelistRead = false;
  * @par Example:
  * To allow reading any page starting with 'User' regardless of the case:
  * @code
- * $wgWhitelistReadRegexp = array( "@^UsEr.*@i" );
+ * $wgWhitelistReadRegexp = [ "@^UsEr.*@i" ];
  * @endcode
  * Will allow both [[User is banned]] and [[User:JohnDoe]]
  *
@@ -5032,7 +5025,7 @@ $wgHideIdentifiableRedirects = true;
  * combined with the permissions of all groups that a given user is listed
  * in in the user_groups table.
  *
- * Note: Don't set $wgGroupPermissions = array(); unless you know what you're
+ * Note: Don't set $wgGroupPermissions = []; unless you know what you're
  * doing! This will wipe all permissions, and may mean that your users are
  * unable to perform certain essential tasks or access new functionality
  * when new permissions are introduced and default grants established.
@@ -5198,13 +5191,13 @@ $wgImplicitGroups = [ '*', 'user', 'autoconfirmed' ];
  * @par Example:
  * To allow sysops to add themselves to the "bot" group:
  * @code
- *    $wgGroupsAddToSelf = array( 'sysop' => array( 'bot' ) );
+ *    $wgGroupsAddToSelf = [ 'sysop' => [ 'bot' ] ];
  * @endcode
  *
  * @par Example:
  * Implicit groups may be used for the source group, for instance:
  * @code
- *    $wgGroupsRemoveFromSelf = array( '*' => true );
+ *    $wgGroupsRemoveFromSelf = [ '*' => true ];
  * @endcode
  * This allows users in the '*' group (i.e. any user) to remove themselves from
  * any group that they happen to be in.
@@ -5322,18 +5315,18 @@ $wgAutoConfirmCount = 0;
  * @todo Redocument $wgAutopromote
  *
  * The format is
- *   array( '&' or '|' or '^' or '!', cond1, cond2, ... )
+ *   [ '&' or '|' or '^' or '!', cond1, cond2, ... ]
  * where cond1, cond2, ... are themselves conditions; *OR*
  *   APCOND_EMAILCONFIRMED, *OR*
- *   array( APCOND_EMAILCONFIRMED ), *OR*
- *   array( APCOND_EDITCOUNT, number of edits ), *OR*
- *   array( APCOND_AGE, seconds since registration ), *OR*
- *   array( APCOND_INGROUPS, group1, group2, ... ), *OR*
- *   array( APCOND_ISIP, ip ), *OR*
- *   array( APCOND_IPINRANGE, range ), *OR*
- *   array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR*
- *   array( APCOND_BLOCKED ), *OR*
- *   array( APCOND_ISBOT ), *OR*
+ *   [ APCOND_EMAILCONFIRMED ], *OR*
+ *   [ APCOND_EDITCOUNT, number of edits ], *OR*
+ *   [ APCOND_AGE, seconds since registration ], *OR*
+ *   [ APCOND_INGROUPS, group1, group2, ... ], *OR*
+ *   [ APCOND_ISIP, ip ], *OR*
+ *   [ APCOND_IPINRANGE, range ], *OR*
+ *   [ APCOND_AGE_FROM_EDIT, seconds since first edit ], *OR*
+ *   [ APCOND_BLOCKED ], *OR*
+ *   [ APCOND_ISBOT ], *OR*
  *   similar constructs defined by extensions.
  *
  * If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any
@@ -5354,7 +5347,7 @@ $wgAutopromote = [
  *
  * The format is:
  * @code
- *    array( event => criteria, ... )
+ *    [ event => criteria, ... ]
  * @endcode
  * Where event is either:
  *    - 'onEdit' (when user edits)
@@ -5385,15 +5378,15 @@ $wgAutopromoteOnceLogInRC = true;
  * @endcode
  * Bureaucrats can only remove bots and sysops:
  * @code
- * $wgRemoveGroups['bureaucrat'] = array( 'bot', 'sysop' );
+ * $wgRemoveGroups['bureaucrat'] = [ 'bot', 'sysop' ];
  * @endcode
  * Sysops can make bots:
  * @code
- * $wgAddGroups['sysop'] = array( 'bot' );
+ * $wgAddGroups['sysop'] = [ 'bot' ];
  * @endcode
  * Sysops can disable other sysops in an emergency, and disable bots:
  * @code
- * $wgRemoveGroups['sysop'] = array( 'sysop', 'bot' );
+ * $wgRemoveGroups['sysop'] = [ 'sysop', 'bot' ];
  * @endcode
  */
 $wgAddGroups = [];
@@ -5464,15 +5457,15 @@ $wgEnableDnsBlacklist = false;
  *
  * @par Example:
  * @code
- * $wgDnsBlacklistUrls = array(
+ * $wgDnsBlacklistUrls = [
  *   // String containing URL
  *   'http.dnsbl.sorbs.net.',
  *   // Array with URL and key, for services that require a key
- *   array( 'dnsbl.httpbl.net.', 'mykey' ),
+ *   [ 'dnsbl.httpbl.net.', 'mykey' ],
  *   // Array with just the URL. While this works, it is recommended that you
  *   // just use a string as shown above
- *   array( 'opm.tornevall.org.' )
- * );
+ *   [ 'opm.tornevall.org.' ]
+ * ];
  * @endcode
  *
  * @note You should end the domain name with a . to avoid searching your
@@ -5504,21 +5497,21 @@ $wgApplyIpBlocksToXff = false;
  * @par Example:
  * To set a generic maximum of 4 hits in 60 seconds:
  * @code
- *     $wgRateLimits = array( 4, 60 );
+ *     $wgRateLimits = [ 4, 60 ];
  * @endcode
  *
  * @par Example:
  * You could also limit per action and then type of users.
  * @code
- *     $wgRateLimits = array(
- *         'edit' => array(
- *             'anon' => array( x, y ), // any and all anonymous edits (aggregate)
- *             'user' => array( x, y ), // each logged-in user
- *             'newbie' => array( x, y ), // each new autoconfirmed accounts; overrides 'user'
- *             'ip' => array( x, y ), // each anon and recent account
- *             'subnet' => array( x, y ), // ... within a /24 subnet in IPv4 or /64 in IPv6
- *         )
- *     )
+ *     $wgRateLimits = [
+ *         'edit' => [
+ *             'anon' => [ x, y ], // any and all anonymous edits (aggregate)
+ *             'user' => [ x, y ], // each logged-in user
+ *             'newbie' => [ x, y ], // each new autoconfirmed accounts; overrides 'user'
+ *             'ip' => [ x, y ], // each anon and recent account
+ *             'subnet' => [ x, y ], // ... within a /24 subnet in IPv4 or /64 in IPv6
+ *         ]
+ *     ]
  * @endcode
  *
  * @warning Requires that $wgMainCacheType is set to something persistent
@@ -6015,11 +6008,11 @@ $wgTrxProfilerLimits = [
  *
  * @par Advanced example:
  * @code
- * $wgDebugLogGroups['memcached'] = array(
+ * $wgDebugLogGroups['memcached'] = [
  *     'destination' => '/var/log/mediawiki/memcached.log',
  *     'sample' => 1000,  // log 1 message out of every 1,000.
  *     'level' => \Psr\Log\LogLevel::WARNING
- * );
+ * ];
  * @endcode
  */
 $wgDebugLogGroups = [];
@@ -6038,7 +6031,7 @@ $wgDebugLogGroups = [];
  *
  * @par To completely disable logging:
  * @code
- * $wgMWLoggerDefaultSpi = array( 'class' => '\\MediaWiki\\Logger\\NullSpi' );
+ * $wgMWLoggerDefaultSpi = [ 'class' => '\\MediaWiki\\Logger\\NullSpi' ];
  * @endcode
  *
  * @since 1.25
@@ -6348,10 +6341,10 @@ $wgSitemapNamespaces = false;
  * This should be a map of namespace IDs to priority
  * @par Example:
  * @code
- *  $wgSitemapNamespacesPriorities = array(
+ *  $wgSitemapNamespacesPriorities = [
  *      NS_USER => '0.9',
  *      NS_HELP => '0.0',
- *  );
+ *  ];
  * @endcode
  */
 $wgSitemapNamespacesPriorities = false;
@@ -6558,18 +6551,18 @@ $wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
  *  The JSON-specific options are:
  *   * 'channel' -- if set, the 'channel' parameter is also set in JSON values.
  *
- * @example $wgRCFeeds['example'] = array(
+ * @example $wgRCFeeds['example'] = [
  *             'formatter' => 'JSONRCFeedFormatter',
  *             'uri' => "udp://localhost:1336",
  *             'add_interwiki_prefix' => false,
  *             'omit_bots' => true,
- *     );
- * @example $wgRCFeeds['exampleirc'] = array(
+ *     ];
+ * @example $wgRCFeeds['exampleirc'] = [
  *             'formatter' => 'IRCColourfulRCFeedFormatter',
  *             'uri' => "udp://localhost:1338",
  *             'add_interwiki_prefix' => false,
  *             'omit_bots' => true,
- *     );
+ *     ];
  * @since 1.22
  */
 $wgRCFeeds = [];
@@ -6731,7 +6724,7 @@ $wgUnwatchedPageThreshold = false;
  *
  * To register a new one:
  * @code
- * $wgRecentChangesFlags['flag'] => array(
+ * $wgRecentChangesFlags['flag'] => [
  *   // message for the letter displayed next to rows on changes lists
  *   'letter' => 'letter-msg',
  *   // message for the tooltip of the letter
@@ -6744,7 +6737,7 @@ $wgUnwatchedPageThreshold = false;
  *   // will set the top-level flag if any line contains the flag, 'all' will
  *   // only be set if all lines contain the flag.
  *   'grouping' => 'any',
- * );
+ * ];
  * @endcode
  *
  * @since 1.22
@@ -6850,11 +6843,11 @@ $wgShowCreditsIfMax = true;
  * subprojects on the interwiki map of the target wiki, or a mix of the two,
  * e.g.
  * @code
- *     $wgImportSources = array(
- *         'wikipedia' => array( 'cs', 'en', 'fr', 'zh' ),
+ *     $wgImportSources = [
+ *         'wikipedia' => [ 'cs', 'en', 'fr', 'zh' ],
  *         'wikispecies',
- *         'wikia' => array( 'animanga', 'brickipedia', 'desserts' ),
- *     );
+ *         'wikia' => [ 'animanga', 'brickipedia', 'desserts' ],
+ *     ];
  * @endcode
  *
  * If you have a very complex import sources setup, you can lazy-load it using
@@ -6982,11 +6975,11 @@ $wgExtensionMessagesFiles = [];
  *
  * @par Complex example:
  * @code
- *    $wgMessagesDirs['Example'] = array(
+ *    $wgMessagesDirs['Example'] = [
  *        __DIR__ . '/lib/ve/i18n',
  *        __DIR__ . '/lib/oojs-ui/i18n',
  *        __DIR__ . '/i18n',
- *    )
+ *    ]
  * @endcode
  * @since 1.23
  */
@@ -7056,18 +7049,18 @@ $wgAutoloadAttemptLowercase = true;
  * All but 'name', 'path' and 'author' can be omitted.
  *
  * @code
- * $wgExtensionCredits[$type][] = array(
+ * $wgExtensionCredits[$type][] = [
  *     'path' => __FILE__,
  *     'name' => 'Example extension',
  *     'namemsg' => 'exampleextension-name',
- *     'author' => array(
+ *     'author' => [
  *         'Foo Barstein',
- *     ),
+ *     ],
  *     'version' => '1.9.0',
  *     'url' => 'http://example.org/example-extension/',
  *     'descriptionmsg' => 'exampleextension-desc',
  *     'license-name' => 'GPL-2.0+',
- * );
+ * ];
  * @endcode
  *
  * The extensions are listed on Special:Version. This page also looks for a file
@@ -7126,11 +7119,11 @@ $wgAuth = null;
  * @endcode
  * - A function with some data:
  * @code
- *     $wgHooks['event_name'][] = array( $function, $data );
+ *     $wgHooks['event_name'][] = [ $function, $data ];
  * @endcode
  * - A an object method:
  * @code
- *     $wgHooks['event_name'][] = array( $object, 'method' );
+ *     $wgHooks['event_name'][] = [ $object, 'method' ];
  * @endcode
  * - A closure:
  * @code
@@ -7255,7 +7248,7 @@ $wgSpecialPageCacheUpdates = [
  * Hooks that are used for outputting exceptions.  Format is:
  *   $wgExceptionHooks[] = $funcname
  * or:
- *   $wgExceptionHooks[] = array( $class, $funcname )
+ *   $wgExceptionHooks[] = [ $class, $funcname ]
  * Hooks should return strings or false
  */
 $wgExceptionHooks = [];
@@ -7371,10 +7364,7 @@ $wgLogRestrictions = [
  *
  * @par Example:
  * @code
- *   $wgFilterLogTypes = array(
- *      'move' => true,
- *      'import' => false,
- *   );
+ *   $wgFilterLogTypes = [ 'move' => true, 'import' => false ];
  * @endcode
  *
  * Will display show/hide links for the move and import logs. Move logs will be
@@ -7658,7 +7648,7 @@ $wgDefaultRobotPolicy = 'index,follow';
  *
  * @par Example:
  * @code
- *   $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' );
+ *   $wgNamespaceRobotPolicies = [ NS_TALK => 'noindex' ];
  * @endcode
  */
 $wgNamespaceRobotPolicies = [];
@@ -7670,23 +7660,23 @@ $wgNamespaceRobotPolicies = [];
  *
  * @par Example:
  * @code
- * $wgArticleRobotPolicies = array(
+ * $wgArticleRobotPolicies = [
  *         'Main Page' => 'noindex,follow',
  *         'User:Bob' => 'index,follow',
- * );
+ * ];
  * @endcode
  *
  * @par Example that DOES NOT WORK because the names are not canonical text
  * forms:
  * @code
- *   $wgArticleRobotPolicies = array(
+ *   $wgArticleRobotPolicies = [
  *     # Underscore, not space!
  *     'Main_Page' => 'noindex,follow',
  *     # "Project", not the actual project name!
  *     'Project:X' => 'index,follow',
  *     # Needs to be "Abc", not "abc" (unless $wgCapitalLinks is false for that namespace)!
  *     'abc' => 'noindex,nofollow'
- *   );
+ *   ];
  * @endcode
  */
 $wgArticleRobotPolicies = [];
@@ -7698,7 +7688,7 @@ $wgArticleRobotPolicies = [];
  *
  * @par Example:
  * @code
- *   $wgExemptFromUserRobotsControl = array( NS_MAIN, NS_TALK, NS_PROJECT );
+ *   $wgExemptFromUserRobotsControl = [ NS_MAIN, NS_TALK, NS_PROJECT ];
  * @endcode
  */
 $wgExemptFromUserRobotsControl = null;
@@ -7767,14 +7757,14 @@ $wgDebugAPI = false;
  *
  * @code
  *  $wgAPIModules['foo'] = 'ApiFoo';
- *  $wgAPIModules['bar'] = array(
+ *  $wgAPIModules['bar'] = [
  *    'class' => 'ApiBar',
  *    'factory' => function( $main, $name ) { ... }
- *  );
- *  $wgAPIModules['xyzzy'] = array(
+ *  ];
+ *  $wgAPIModules['xyzzy'] = [
  *    'class' => 'ApiXyzzy',
- *    'factory' => array( 'XyzzyFactory', 'newApiModule' )
- *  );
+ *    'factory' => [ 'XyzzyFactory', 'newApiModule' ]
+ *  ];
  * @endcode
  *
  * Extension modules may override the core modules.
@@ -7904,12 +7894,12 @@ $wgAjaxEditStash = true;
  *
  * @par Example:
  * @code
- * $wgCrossSiteAJAXdomains = array(
+ * $wgCrossSiteAJAXdomains = [
  *     'www.mediawiki.org',
  *     '*.wikipedia.org',
  *     '*.wikimedia.org',
  *     '*.wiktionary.org',
- * );
+ * ];
  * @endcode
  */
 $wgCrossSiteAJAXdomains = [];
@@ -8126,13 +8116,13 @@ $wgRedirectOnLogin = null;
  *
  * @par Example:
  * @code
- *   $wgPoolCounterConf = array( 'ArticleView' => array(
+ *   $wgPoolCounterConf = [ 'ArticleView' => [
  *     'class' => 'PoolCounter_Client',
  *     'timeout' => 15, // wait timeout in seconds
  *     'workers' => 5, // maximum number of active threads in each pool
  *     'maxqueue' => 50, // maximum number of total threads in each pool
  *     ... any extension-specific options...
- *   );
+ *   ];
  * @endcode
  */
 $wgPoolCounterConf = null;
@@ -8279,11 +8269,11 @@ $wgPageLanguageUseDB = false;
  *
  * Example config for Parsoid:
  *
- *   $wgVirtualRestConfig['modules']['parsoid'] = array(
+ *   $wgVirtualRestConfig['modules']['parsoid'] = [
  *     'url' => 'http://localhost:8000',
  *     'prefix' => 'enwiki',
  *     'domain' => 'en.wikipedia.org',
- *   );
+ *   ];
  *
  * @var array
  * @since 1.25
index 6613db1..ff292cf 100644 (file)
@@ -23,6 +23,7 @@ use SearchEngineFactory;
 use SiteLookup;
 use SiteStore;
 use WatchedItemStore;
+use WatchedItemQueryService;
 use SkinFactory;
 use TitleFormatter;
 use TitleParser;
@@ -502,6 +503,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'WatchedItemStore' );
        }
 
+       /**
+        * @since 1.28
+        * @return WatchedItemQueryService
+        */
+       public function getWatchedItemQueryService() {
+               return $this->getService( 'WatchedItemQueryService' );
+       }
+
        /**
         * @since 1.28
         * @return GenderCache
index 712d3f1..2c979de 100644 (file)
@@ -402,8 +402,8 @@ class Message implements MessageSpecifier, Serializable {
                        $value = array_shift( $params );
                }
 
-               if ( $value instanceof RawMessage ) {
-                       $message = new RawMessage( $value->getKey(), $value->getParams() );
+               if ( $value instanceof Message ) { // Message, RawMessage, ApiMessage, etc
+                       $message = clone( $value );
                } elseif ( $value instanceof MessageSpecifier ) {
                        $message = new Message( $value );
                } elseif ( is_string( $value ) ) {
@@ -802,10 +802,13 @@ class Message implements MessageSpecifier, Serializable {
                $string = $this->fetchMessage();
 
                if ( $string === false ) {
-                       if ( $this->format === 'plain' || $this->format === 'text' ) {
-                               return '<' . $this->key . '>';
-                       }
-                       return '&lt;' . htmlspecialchars( $this->key ) . '&gt;';
+                       // Err on the side of safety, ensure that the output
+                       // is always html safe in the event the message key is
+                       // missing, since in that case its highly likely the
+                       // message key is user-controlled.
+                       // '⧼' is used instead of '<' to side-step any
+                       // double-escaping issues.
+                       return '⧼' . htmlspecialchars( $this->key ) . '⧽';
                }
 
                # Replace $* with a list of parameters for &uselang=qqx.
index 5c7203d..15b70c8 100644 (file)
@@ -104,22 +104,11 @@ class OutputPage extends ContextSource {
        protected $mStatusCode;
 
        /**
-        * @var string Variable mLastModified and mEtag are used for sending cache control.
+        * @var string Used for sending cache control.
         *   The whole caching system should probably be moved into its own class.
         */
        protected $mLastModified = '';
 
-       /**
-        * Contains an HTTP Entity Tags (see RFC 2616 section 3.13) which is used
-        * as a unique identifier for the content. It is later used by the client
-        * to compare its cached version with the server version. Client sends
-        * headers If-Match and If-None-Match containing its locally cached ETAG value.
-        *
-        * To get more information, you will have to look at HTTP/1.1 protocol which
-        * is properly described in RFC 2616 : http://tools.ietf.org/html/rfc2616
-        */
-       private $mETag = false;
-
        /** @var array */
        protected $mCategoryLinks = [];
 
@@ -694,12 +683,10 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Set the value of the ETag HTTP header, only used if $wgUseETag is true
-        *
-        * @param string $tag Value of "ETag" header
+        * @deprecated since 1.28 Obsolete - wgUseETag experiment was removed.
+        * @param string $tag
         */
-       function setETag( $tag ) {
-               $this->mETag = $tag;
+       public function setETag( $tag ) {
        }
 
        /**
@@ -2156,9 +2143,6 @@ class OutputPage extends ContextSource {
        public function sendCacheControl() {
                $response = $this->getRequest()->response();
                $config = $this->getConfig();
-               if ( $config->get( 'UseETag' ) && $this->mETag ) {
-                       $response->header( "ETag: $this->mETag" );
-               }
 
                $this->addVaryHeader( 'Cookie' );
                $this->addAcceptLanguage();
index b076d07..9ee4236 100644 (file)
@@ -154,6 +154,10 @@ return [
                return $store;
        },
 
+       'WatchedItemQueryService' => function( MediaWikiServices $services ) {
+               return new WatchedItemQueryService( $services->getDBLoadBalancer() );
+       },
+
        'LinkCache' => function( MediaWikiServices $services ) {
                return new LinkCache(
                        $services->getTitleFormatter()
diff --git a/includes/WatchedItemQueryService.php b/includes/WatchedItemQueryService.php
new file mode 100644 (file)
index 0000000..14d6aac
--- /dev/null
@@ -0,0 +1,474 @@
+<?php
+
+use Wikimedia\Assert\Assert;
+
+/**
+ * Class performing complex database queries related to WatchedItems.
+ *
+ * @since 1.28
+ *
+ * @file
+ * @ingroup Watchlist
+ *
+ * @license GNU GPL v2+
+ */
+class WatchedItemQueryService {
+
+       const DIR_OLDER = 'older';
+       const DIR_NEWER = 'newer';
+
+       const INCLUDE_FLAGS = 'flags';
+       const INCLUDE_USER = 'user';
+       const INCLUDE_USER_ID = 'userid';
+       const INCLUDE_COMMENT = 'comment';
+       const INCLUDE_PATROL_INFO = 'patrol';
+       const INCLUDE_SIZES = 'sizes';
+       const INCLUDE_LOG_INFO = 'loginfo';
+
+       // FILTER_* constants are part of public API (are used
+       // in ApiQueryWatchlist class) and should not be changed.
+       // Changing values of those constants will result in a breaking change in the API
+       const FILTER_MINOR = 'minor';
+       const FILTER_NOT_MINOR = '!minor';
+       const FILTER_BOT = 'bot';
+       const FILTER_NOT_BOT = '!bot';
+       const FILTER_ANON = 'anon';
+       const FILTER_NOT_ANON = '!anon';
+       const FILTER_PATROLLED = 'patrolled';
+       const FILTER_NOT_PATROLLED = '!patrolled';
+       const FILTER_UNREAD = 'unread';
+       const FILTER_NOT_UNREAD = '!unread';
+
+       /**
+        * @var LoadBalancer
+        */
+       private $loadBalancer;
+
+       public function __construct( LoadBalancer $loadBalancer ) {
+               $this->loadBalancer = $loadBalancer;
+       }
+
+       /**
+        * @return DatabaseBase
+        * @throws MWException
+        */
+       private function getConnection() {
+               return $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+       }
+
+       /**
+        * @param DatabaseBase $connection
+        * @throws MWException
+        */
+       private function reuseConnection( DatabaseBase $connection ) {
+               $this->loadBalancer->reuseConnection( $connection );
+       }
+
+       /**
+        * @param User $user
+        * @param array $options Allowed keys:
+        *        'includeFields'       => string[] RecentChange fields to be included in the result,
+        *                                 self::INCLUDE_* constants should be used
+        *        'filters'             => string[] optional filters to narrow down resulted items
+        *        'namespaceIds'        => int[] optional namespace IDs to filter by
+        *                                 (defaults to all namespaces)
+        *        'allRevisions'        => bool return multiple revisions of the same page if true,
+        *                                 only the most recent if false (default)
+        *        'rcTypes'             => int[] which types of RecentChanges to include
+        *                                 (defaults to all types), allowed values: RC_EDIT, RC_NEW,
+        *                                 RC_LOG, RC_EXTERNAL, RC_CATEGORIZE
+        *        'onlyByUser'          => string only list changes by a specified user
+        *        'notByUser'           => string do not incluide changes by a specified user
+        *        'dir'                 => string in which direction to enumerate, accepted values:
+        *                                 - DIR_OLDER list newest first
+        *                                 - DIR_NEWER list oldest first
+        *        'start'               => string (format accepted by wfTimestamp) requires 'dir' option,
+        *                                 timestamp to start enumerating from
+        *        'end'                 => string (format accepted by wfTimestamp) requires 'dir' option,
+        *                                 timestamp to end enumerating
+        *        'startFrom'           => [ string $rcTimestamp, int $rcId ] requires 'dir' option,
+        *                                 return items starting from the RecentChange specified by this,
+        *                                 $rcTimestamp should be in the format accepted by wfTimestamp
+        *        'watchlistOwner'      => User user whose watchlist items should be listed if different
+        *                                 than the one specified with $user param,
+        *                                 requires 'watchlistOwnerToken' option
+        *        'watchlistOwnerToken' => string a watchlist token used to access another user's
+        *                                 watchlist, used with 'watchlistOwnerToken' option
+        *        'limit'               => int maximum numbers of items to return
+        *        'usedInGenerator'     => bool include only RecentChange id field required by the
+        *                                 generator ('rc_cur_id' or 'rc_this_oldid') if true, or all
+        *                                 id fields ('rc_cur_id', 'rc_this_oldid', 'rc_last_oldid')
+        *                                 if false (default)
+        * @return array of pairs ( WatchedItem $watchedItem, string[] $recentChangeInfo ),
+        *         where $recentChangeInfo contains the following keys:
+        *         - 'rc_id',
+        *         - 'rc_namespace',
+        *         - 'rc_title',
+        *         - 'rc_timestamp',
+        *         - 'rc_type',
+        *         - 'rc_deleted',
+        *         Additional keys could be added by specifying the 'includeFields' option
+        */
+       public function getWatchedItemsWithRecentChangeInfo( User $user, array $options = [] ) {
+               $options += [
+                       'includeFields' => [],
+                       'namespaceIds' => [],
+                       'filters' => [],
+                       'allRevisions' => false,
+                       'usedInGenerator' => false
+               ];
+
+               Assert::parameter(
+                       !isset( $options['rcTypes'] )
+                               || !array_diff( $options['rcTypes'], [ RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL, RC_CATEGORIZE ] ),
+                       '$options[\'rcTypes\']',
+                       'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
+               );
+               Assert::parameter(
+                       !isset( $options['dir'] ) || in_array( $options['dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
+                       '$options[\'dir\']',
+                       'must be DIR_OLDER or DIR_NEWER'
+               );
+               Assert::parameter(
+                       !isset( $options['start'] ) && !isset( $options['end'] ) && !isset( $options['startFrom'] )
+                               || isset( $options['dir'] ),
+                       '$options[\'dir\']',
+                       'must be provided when providing any of options: start, end, startFrom'
+               );
+               Assert::parameter(
+                       !isset( $options['startFrom'] )
+                               || ( is_array( $options['startFrom'] ) && count( $options['startFrom'] ) === 2 ),
+                       '$options[\'startFrom\']',
+                       'must be a two-element array'
+               );
+               if ( array_key_exists( 'watchlistOwner', $options ) ) {
+                       Assert::parameterType(
+                               User::class,
+                               $options['watchlistOwner'],
+                               '$options[\'watchlistOwner\']'
+                       );
+                       Assert::parameter(
+                               isset( $options['watchlistOwnerToken'] ),
+                               '$options[\'watchlistOwnerToken\']',
+                               'must be provided when providing watchlistOwner option'
+                       );
+               }
+
+               $tables = [ 'recentchanges', 'watchlist' ];
+               if ( !$options['allRevisions'] ) {
+                       $tables[] = 'page';
+               }
+
+               $db = $this->getConnection();
+
+               $fields = $this->getFields( $options );
+               $conds = $this->getConds( $db, $user, $options );
+               $dbOptions = $this->getDbOptions( $options );
+               $joinConds = $this->getJoinConds( $options );
+
+               $res = $db->select(
+                       $tables,
+                       $fields,
+                       $conds,
+                       __METHOD__,
+                       $dbOptions,
+                       $joinConds
+               );
+
+               $this->reuseConnection( $db );
+
+               $items = [];
+               foreach ( $res as $row ) {
+                       $items[] = [
+                               new WatchedItem(
+                                       $user,
+                                       new TitleValue( (int)$row->rc_namespace, $row->rc_title ),
+                                       $row->wl_notificationtimestamp
+                               ),
+                               $this->getRecentChangeFieldsFromRow( $row )
+                       ];
+               }
+
+               return $items;
+       }
+
+       private function getRecentChangeFieldsFromRow( stdClass $row ) {
+               // This can be simplified to single array_filter call filtering by key value,
+               // once we stop supporting PHP 5.5
+               $allFields = get_object_vars( $row );
+               $rcKeys = array_filter(
+                       array_keys( $allFields ),
+                       function( $key ) {
+                               return substr( $key, 0, 3 ) === 'rc_';
+                       }
+               );
+               return array_intersect_key( $allFields, array_flip( $rcKeys ) );
+       }
+
+       private function getFields( array $options ) {
+               $fields = [
+                       'rc_id',
+                       'rc_namespace',
+                       'rc_title',
+                       'rc_timestamp',
+                       'rc_type',
+                       'rc_deleted',
+                       'wl_notificationtimestamp'
+               ];
+
+               $rcIdFields = [
+                       'rc_cur_id',
+                       'rc_this_oldid',
+                       'rc_last_oldid',
+               ];
+               if ( $options['usedInGenerator'] ) {
+                       if ( $options['allRevisions'] ) {
+                               $rcIdFields = [ 'rc_this_oldid' ];
+                       } else {
+                               $rcIdFields = [ 'rc_cur_id' ];
+                       }
+               }
+               $fields = array_merge( $fields, $rcIdFields );
+
+               if ( in_array( self::INCLUDE_FLAGS, $options['includeFields'] ) ) {
+                       $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
+               }
+               if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
+                       $fields[] = 'rc_user_text';
+               }
+               if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
+                       $fields[] = 'rc_user';
+               }
+               if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
+                       $fields[] = 'rc_comment';
+               }
+               if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
+                       $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
+               }
+               if ( in_array( self::INCLUDE_SIZES, $options['includeFields'] ) ) {
+                       $fields = array_merge( $fields, [ 'rc_old_len', 'rc_new_len' ] );
+               }
+               if ( in_array( self::INCLUDE_LOG_INFO, $options['includeFields'] ) ) {
+                       $fields = array_merge( $fields, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
+               }
+
+               return $fields;
+       }
+
+       private function getConds( DatabaseBase $db, User $user, array $options ) {
+               $watchlistOwnerId = $this->getWatchlistOwnerId( $user, $options );
+               $conds = [ 'wl_user' => $watchlistOwnerId ];
+
+               if ( !$options['allRevisions'] ) {
+                       $conds[] = $db->makeList(
+                               [ 'rc_this_oldid=page_latest', 'rc_type=' . RC_LOG ],
+                               LIST_OR
+                       );
+               }
+
+               if ( $options['namespaceIds'] ) {
+                       $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
+               }
+
+               if ( array_key_exists( 'rcTypes', $options ) ) {
+                       $conds['rc_type'] = array_map( 'intval',  $options['rcTypes'] );
+               }
+
+               $conds = array_merge( $conds, $this->getFilterConds( $user, $options ) );
+
+               $conds = array_merge( $conds, $this->getStartEndConds( $db, $options ) );
+
+               if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
+                       if ( $db->getType() === 'mysql' ) {
+                               // This is an index optimization for mysql
+                               $conds[] = "rc_timestamp > ''";
+                       }
+               }
+
+               $conds = array_merge( $conds, $this->getUserRelatedConds( $db, $user, $options ) );
+
+               $deletedPageLogCond = $this->getExtraDeletedPageLogEntryRelatedCond( $db, $user );
+               if ( $deletedPageLogCond ) {
+                       $conds[] = $deletedPageLogCond;
+               }
+
+               if ( array_key_exists( 'startFrom', $options ) ) {
+                       $conds[] = $this->getStartFromConds( $db, $options );
+               }
+
+               return $conds;
+       }
+
+       private function getWatchlistOwnerId( User $user, array $options ) {
+               if ( array_key_exists( 'watchlistOwner', $options ) ) {
+                       /** @var User $watchlistOwner */
+                       $watchlistOwner = $options['watchlistOwner'];
+                       $ownersToken = $watchlistOwner->getOption( 'watchlisttoken' );
+                       $token = $options['watchlistOwnerToken'];
+                       if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
+                               throw new UsageException(
+                                       'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
+                                       'bad_wltoken'
+                               );
+                       }
+                       return $watchlistOwner->getId();
+               }
+               return $user->getId();
+       }
+
+       private function getFilterConds( User $user, array $options ) {
+               $conds = [];
+
+               if ( in_array( self::FILTER_MINOR, $options['filters'] ) ) {
+                       $conds[] = 'rc_minor != 0';
+               } elseif ( in_array( self::FILTER_NOT_MINOR, $options['filters'] ) ) {
+                       $conds[] = 'rc_minor = 0';
+               }
+
+               if ( in_array( self::FILTER_BOT, $options['filters'] ) ) {
+                       $conds[] = 'rc_bot != 0';
+               } elseif ( in_array( self::FILTER_NOT_BOT, $options['filters'] ) ) {
+                       $conds[] = 'rc_bot = 0';
+               }
+
+               if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
+                       $conds[] = 'rc_user = 0';
+               } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
+                       $conds[] = 'rc_user != 0';
+               }
+
+               if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
+                       // TODO: not sure if this should simply ignore patrolled filters if user does not have the patrol
+                       // right, or maybe rather fail loud at this point, same as e.g. ApiQueryWatchlist does?
+                       if ( in_array( self::FILTER_PATROLLED, $options['filters'] ) ) {
+                               $conds[] = 'rc_patrolled != 0';
+                       } elseif ( in_array( self::FILTER_NOT_PATROLLED, $options['filters'] ) ) {
+                               $conds[] = 'rc_patrolled = 0';
+                       }
+               }
+
+               if ( in_array( self::FILTER_UNREAD, $options['filters'] ) ) {
+                       $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
+               } elseif ( in_array( self::FILTER_NOT_UNREAD, $options['filters'] ) ) {
+                       // TODO: should this be changed to use Database::makeList?
+                       $conds[] = 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
+               }
+
+               return $conds;
+       }
+
+       private function getStartEndConds( DatabaseBase $db, array $options ) {
+               if ( !isset( $options['start'] ) && ! isset( $options['end'] ) ) {
+                       return [];
+               }
+
+               $conds = [];
+
+               if ( isset( $options['start'] ) ) {
+                       $after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
+                       $conds[] = 'rc_timestamp ' . $after . ' ' . $db->addQuotes( $options['start'] );
+               }
+               if ( isset( $options['end'] ) ) {
+                       $before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
+                       $conds[] = 'rc_timestamp ' . $before . ' ' . $db->addQuotes( $options['end'] );
+               }
+
+               return $conds;
+       }
+
+       private function getUserRelatedConds( DatabaseBase $db, User $user, array $options ) {
+               if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
+                       return [];
+               }
+
+               $conds = [];
+
+               if ( array_key_exists( 'onlyByUser', $options ) ) {
+                       $conds['rc_user_text'] = $options['onlyByUser'];
+               } elseif ( array_key_exists( 'notByUser', $options ) ) {
+                       $conds[] = 'rc_user_text != ' . $db->addQuotes( $options['notByUser'] );
+               }
+
+               // Avoid brute force searches (bug 17342)
+               $bitmask = 0;
+               if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       $bitmask = Revision::DELETED_USER;
+               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+               }
+               if ( $bitmask ) {
+                       $conds[] = $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask";
+               }
+
+               return $conds;
+       }
+
+       private function getExtraDeletedPageLogEntryRelatedCond( DatabaseBase $db, User $user ) {
+               // LogPage::DELETED_ACTION hides the affected page, too. So hide those
+               // entirely from the watchlist, or someone could guess the title.
+               $bitmask = 0;
+               if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       $bitmask = LogPage::DELETED_ACTION;
+               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                       $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+               }
+               if ( $bitmask ) {
+                       return $db->makeList( [
+                               'rc_type != ' . RC_LOG,
+                               $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+                       ], LIST_OR );
+               }
+               return '';
+       }
+
+       private function getStartFromConds( DatabaseBase $db, array $options ) {
+               $op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
+               list( $rcTimestamp, $rcId ) = $options['startFrom'];
+               $rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
+               $rcId = (int)$rcId;
+               return $db->makeList(
+                       [
+                               "rc_timestamp $op $rcTimestamp",
+                               $db->makeList(
+                                       [
+                                               "rc_timestamp = $rcTimestamp",
+                                               "rc_id $op= $rcId"
+                                       ],
+                                       LIST_AND
+                               )
+                       ],
+                       LIST_OR
+               );
+       }
+
+       private function getDbOptions( array $options ) {
+               $dbOptions = [];
+
+               if ( array_key_exists( 'dir', $options ) ) {
+                       $sort = $options['dir'] === self::DIR_OLDER ? ' DESC' : '';
+                       $dbOptions['ORDER BY'] = [ 'rc_timestamp' . $sort, 'rc_id' . $sort ];
+               }
+
+               if ( array_key_exists( 'limit', $options ) ) {
+                       $dbOptions['LIMIT'] = (int)$options['limit'];
+               }
+
+               return $dbOptions;
+       }
+
+       private function getJoinConds( array $options ) {
+               $joinConds = [
+                       'watchlist' => [ 'INNER JOIN',
+                               [
+                                       'wl_namespace=rc_namespace',
+                                       'wl_title=rc_title'
+                               ]
+                       ]
+               ];
+               if ( !$options['allRevisions'] ) {
+                       $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
+               }
+               return $joinConds;
+       }
+
+}
index 639f6be..3e57e89 100644 (file)
@@ -2093,7 +2093,7 @@ abstract class ApiBase extends ContextSource {
 
        /**
         * Output the error message related to a certain array
-        * @param array|string $error Element of a getUserPermissionsErrors()-style array
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
         * @throws UsageException always
         */
        public function dieUsageMsg( $error ) {
@@ -2110,7 +2110,7 @@ abstract class ApiBase extends ContextSource {
        /**
         * Will only set a warning instead of failing if the global $wgDebugAPI
         * is set to true. Otherwise behaves exactly as dieUsageMsg().
-        * @param array|string $error Element of a getUserPermissionsErrors()-style array
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
         * @throws UsageException
         * @since 1.21
         */
@@ -2143,32 +2143,38 @@ abstract class ApiBase extends ContextSource {
 
        /**
         * Return the error message related to a certain array
-        * @param array $error Element of a getUserPermissionsErrors()-style array
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
         * @return array('code' => code, 'info' => info)
         */
        public function parseMsg( $error ) {
-               $error = (array)$error; // It seems strings sometimes make their way in here
-               $key = array_shift( $error );
-
-               // Check whether the error array was nested
-               // array( array( <code>, <params> ), array( <another_code>, <params> ) )
-               if ( is_array( $key ) ) {
-                       $error = $key;
-                       $key = array_shift( $error );
+               // Check whether someone passed the whole array, instead of one element as
+               // documented. This breaks if it's actually an array of fallback keys, but
+               // that's long-standing misbehavior introduced in r87627 to incorrectly
+               // fix T30797.
+               if ( is_array( $error ) ) {
+                       $first = reset( $error );
+                       if ( is_array( $first ) ) {
+                               wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
+                               $error = $first;
+                       }
                }
 
-               if ( $key instanceof IApiMessage ) {
+               $msg = Message::newFromSpecifier( $error );
+
+               if ( $msg instanceof IApiMessage ) {
                        return [
-                               'code' => $key->getApiCode(),
-                               'info' => $key->inLanguage( 'en' )->useDatabase( false )->text(),
-                               'data' => $key->getApiData()
+                               'code' => $msg->getApiCode(),
+                               'info' => $msg->inLanguage( 'en' )->useDatabase( false )->text(),
+                               'data' => $msg->getApiData()
                        ];
                }
 
+               $key = $msg->getKey();
                if ( isset( self::$messageMap[$key] ) ) {
+                       $params = $msg->getParams();
                        return [
-                               'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
-                               'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
+                               'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $params ),
+                               'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $params )
                        ];
                }
 
diff --git a/includes/api/ApiCSPReport.php b/includes/api/ApiCSPReport.php
new file mode 100644 (file)
index 0000000..5271996
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/**
+ * Copyright © 2015 Brian Wolff
+ *
+ * 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
+ */
+
+use MediaWiki\Logger\LoggerFactory;
+
+/**
+ * Api module to receive and log CSP violation reports
+ *
+ * @ingroup API
+ */
+class ApiCSPReport extends ApiBase {
+
+       private $log;
+
+       /**
+        * These reports should be small. Ignore super big reports out of paranoia
+        */
+       const MAX_POST_SIZE = 8192;
+
+       /**
+        * Logs a content-security-policy violation report from web browser.
+        */
+       public function execute() {
+               $reportOnly = $this->getParameter( 'reportonly' );
+               $logname = $reportOnly ? 'csp-report-only' : 'csp';
+               $this->log = LoggerFactory::getInstance( $logname );
+               $userAgent = $this->getRequest()->getHeader( 'user-agent' );
+
+               $this->verifyPostBodyOk();
+               $report = $this->getReport();
+               $flags = $this->getFlags( $report );
+
+               $warningText = $this->generateLogLine( $flags, $report );
+               $this->logReport( $flags, $warningText, [
+                       // XXX Is it ok to put untrusted data into log??
+                       'csp-report' => $report,
+                       'method' => __METHOD__,
+                       'user' => $this->getUser()->getName(),
+                       'user-agent' => $userAgent,
+                       'source' => $this->getParameter( 'source' ),
+               ] );
+               $this->getResult()->addValue( null, $this->getModuleName(), 'success' );
+       }
+
+       /**
+        * Log CSP report, with a different severity depending on $flags
+        * @param $flags Array Flags for this report
+        * @param $logLine String text of log entry
+        * @param $context Array logging context
+        */
+       private function logReport( $flags, $logLine, $context ) {
+               if ( in_array( 'false-positive', $flags ) ) {
+                       // These reports probably don't matter much
+                       $this->log->debug( $logLine, $context );
+               } else {
+                       // Normal report.
+                       $this->log->warning( $logLine, $context );
+               }
+       }
+
+       /**
+        * Get extra notes about the report.
+        *
+        * @param $report Array The CSP report
+        * @return Array
+        */
+       private function getFlags( $report ) {
+               $reportOnly = $this->getParameter( 'reportonly' );
+               $userAgent = $this->getRequest()->getHeader( 'user-agent' );
+               $source = $this->getParameter( 'source' );
+
+               $flags = [];
+               if ( $source !== 'internal' ) {
+                       $flags[] = 'source=' . $source;
+               }
+               if ( $reportOnly ) {
+                       $flags[] = 'report-only';
+               }
+               return $flags;
+       }
+
+       /**
+        * Output an api error if post body is obviously not OK.
+        */
+       private function verifyPostBodyOk() {
+               $req = $this->getRequest();
+               $contentType = $req->getHeader( 'content-type' );
+               if ( $contentType !== 'application/json'
+                       && $contentType !=='application/csp-report'
+               ) {
+                       $this->error( 'wrongformat', __METHOD__ );
+               }
+               if ( $req->getHeader( 'content-length' ) > self::MAX_POST_SIZE ) {
+                       $this->error( 'toobig', __METHOD__ );
+               }
+       }
+
+       /**
+        * Get the report from post body and turn into associative array.
+        *
+        * @return Array
+        */
+       private function getReport() {
+               $postBody = $this->getRequest()->getRawInput();
+               if ( strlen( $postBody ) > self::MAX_POST_SIZE ) {
+                       // paranoia, already checked content-length earlier.
+                       $this->error( 'toobig', __METHOD__ );
+               }
+               $status = FormatJson::parse( $postBody, FormatJson::FORCE_ASSOC );
+               if ( !$status->isGood() ) {
+                       list( $code, ) = $this->getErrorFromStatus( $status );
+                       $this->error( $code, __METHOD__ );
+               }
+
+               $report = $status->getValue();
+
+               if ( !isset( $report['csp-report'] ) ) {
+                       $this->error( 'missingkey', __METHOD__ );
+               }
+               return $report['csp-report'];
+       }
+
+       /**
+        * Get text of log line.
+        *
+        * @param $flags Array of additional markers for this report
+        * @param $report Array the csp report
+        * @return String Text to put in log
+        */
+       private function generateLogLine( $flags, $report ) {
+               $flagText = '';
+               if ( $flags ) {
+                       $flagText = '[' . implode( $flags, ', ' ) . ']';
+               }
+
+               $blockedFile = isset( $report['blocked-uri'] ) ? $report['blocked-uri'] : 'n/a';
+               $page = isset( $report['document-uri'] ) ? $report['document-uri'] : 'n/a';
+               $line = isset( $report['line-number'] ) ? ':' . $report['line-number'] : '';
+               $warningText = $flagText .
+                       ' Received CSP report: <' . $blockedFile .
+                       '> blocked from being loaded on <' . $page . '>' . $line;
+               return $warningText;
+       }
+
+       /**
+        * Stop processing the request, and output/log an error
+        *
+        * @param $code String error code
+        * @param $method String method that made error
+        * @throws UsageException Always
+        */
+       private function error( $code, $method ) {
+               $this->log->info( 'Error reading CSP report: ' . $code, [
+                       'method' => $method,
+                       'user-agent' => $this->getRequest()->getHeader( 'user-agent' )
+               ] );
+               // 500 so it shows up in browser's developer console.
+               $this->dieUsage( "Error processing CSP report: $code", 'cspreport-' . $code, 500 );
+       }
+
+       public function getAllowedParams() {
+               return [
+                       'reportonly' => [
+                               ApiBase::PARAM_TYPE => 'boolean',
+                               ApiBase::PARAM_DFLT => false
+                       ],
+                       'source' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_DFLT => 'internal',
+                               ApiBase::PARAM_REQUIRED => false
+                       ]
+               ];
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function isWriteMode() {
+               return false;
+       }
+
+       /**
+        * Mark as internal. This isn't meant to be used by normal api users
+        */
+       public function isInternal() {
+               return true;
+       }
+
+       /**
+        * Even if you don't have read rights, we still want your report.
+        */
+       public function isReadMode() {
+               return false;
+       }
+
+       /**
+        * Doesn't touch db, so max lag should be rather irrelavent.
+        *
+        * Also, this makes sure that reports aren't lost during lag events.
+        */
+       public function shouldCheckMaxLag() {
+               return false;
+       }
+}
index ce9587f..a9541d3 100644 (file)
@@ -71,6 +71,7 @@ class ApiMain extends ApiBase {
                'compare' => 'ApiComparePages',
                'tokens' => 'ApiTokens',
                'checktoken' => 'ApiCheckToken',
+               'cspreport' => 'ApiCSPReport',
 
                // Write modules
                'purge' => 'ApiPurge',
@@ -1901,6 +1902,14 @@ class UsageException extends MWException {
                parent::__construct( $message, $code );
                $this->mCodestr = $codestr;
                $this->mExtraData = $extradata;
+
+               // This should never happen, so throw an exception about it that will
+               // hopefully get logged with a backtrace (T138585)
+               if ( !is_string( $codestr ) || $codestr === '' ) {
+                       throw new InvalidArgumentException( 'Invalid $codestr, was ' .
+                               ( $codestr === '' ? 'empty string' : gettype( $codestr ) )
+                       );
+               }
        }
 
        /**
index 590a712..97042af 100644 (file)
@@ -181,6 +181,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                $data['legaltitlechars'] = Title::legalChars();
                $data['invalidusernamechars'] = $config->get( 'InvalidUsernameCharacters' );
 
+               $data['allunicodefixes'] = (bool)$config->get( 'AllUnicodeFixes' );
+               $data['fixarabicunicode'] = (bool)$config->get( 'FixArabicUnicode' );
+               $data['fixmalayalamunicode'] = (bool)$config->get( 'FixMalayalamUnicode' );
+
                global $IP;
                $git = SpecialVersion::getGitHeadSha1( $IP );
                if ( $git ) {
@@ -221,6 +225,8 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                }
                $data['writeapi'] = (bool)$config->get( 'EnableWriteAPI' );
 
+               $data['maxarticlesize'] = $config->get( 'MaxArticleSize' ) * 1024;
+
                $tz = $config->get( 'Localtimezone' );
                $offset = $config->get( 'LocalTZoffset' );
                if ( is_null( $tz ) ) {
index db2cf86..e2599d1 100644 (file)
@@ -24,6 +24,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This query action allows clients to retrieve a list of recently modified pages
  * that are part of the logged-in user's watchlist.
@@ -85,96 +87,59 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                        }
                }
 
-               $this->addFields( [
-                       'rc_id',
-                       'rc_namespace',
-                       'rc_title',
-                       'rc_timestamp',
-                       'rc_type',
-                       'rc_deleted',
-               ] );
+               $options = [
+                       'dir' => $params['dir'] === 'older'
+                               ? WatchedItemQueryService::DIR_OLDER
+                               : WatchedItemQueryService::DIR_NEWER,
+               ];
 
                if ( is_null( $resultPageSet ) ) {
-                       $this->addFields( [
-                               'rc_cur_id',
-                               'rc_this_oldid',
-                               'rc_last_oldid',
-                       ] );
-
-                       $this->addFieldsIf( [ 'rc_type', 'rc_minor', 'rc_bot' ], $this->fld_flags );
-                       $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
-                       $this->addFieldsIf( 'rc_user_text', $this->fld_user );
-                       $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
-                       $this->addFieldsIf( [ 'rc_patrolled', 'rc_log_type' ], $this->fld_patrol );
-                       $this->addFieldsIf( [ 'rc_old_len', 'rc_new_len' ], $this->fld_sizes );
-                       $this->addFieldsIf( 'wl_notificationtimestamp', $this->fld_notificationtimestamp );
-                       $this->addFieldsIf(
-                               [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
-                               $this->fld_loginfo
-                       );
-               } elseif ( $params['allrev'] ) {
-                       $this->addFields( 'rc_this_oldid' );
+                       $options['includeFields'] = $this->getFieldsToInclude();
                } else {
-                       $this->addFields( 'rc_cur_id' );
+                       $options['usedInGenerator'] = true;
                }
 
-               $this->addTables( [
-                       'recentchanges',
-                       'watchlist',
-               ] );
-
-               $userId = $wlowner->getId();
-               $this->addJoinConds( [ 'watchlist' => [ 'INNER JOIN',
-                       [
-                               'wl_user' => $userId,
-                               'wl_namespace=rc_namespace',
-                               'wl_title=rc_title'
-                       ]
-               ] ] );
-
-               $db = $this->getDB();
-
-               $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'],
-                       $params['start'], $params['end'] );
-               // Include in ORDER BY for uniqueness
-               $this->addWhereRange( 'rc_id', $params['dir'], null, null );
+               if ( $params['start'] ) {
+                       $options['start'] = $params['start'];
+               }
+               if ( $params['end'] ) {
+                       $options['end'] = $params['end'];
+               }
 
                if ( !is_null( $params['continue'] ) ) {
                        $cont = explode( '|', $params['continue'] );
                        $this->dieContinueUsageIf( count( $cont ) != 2 );
-                       $op = ( $params['dir'] === 'newer' ? '>' : '<' );
-                       $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+                       $continueTimestamp = $cont[0];
                        $continueId = (int)$cont[1];
                        $this->dieContinueUsageIf( $continueId != $cont[1] );
-                       $this->addWhere( "rc_timestamp $op $continueTimestamp OR " .
-                               "(rc_timestamp = $continueTimestamp AND " .
-                               "rc_id $op= $continueId)"
-                       );
+                       $options['startFrom'] = [ $continueTimestamp, $continueId ];
                }
 
-               $this->addWhereFld( 'wl_namespace', $params['namespace'] );
+               if ( $wlowner !== $user ) {
+                       $options['watchlistOwner'] = $wlowner;
+                       $options['watchlistOwnerToken'] = $params['token'];
+               }
 
-               if ( !$params['allrev'] ) {
-                       $this->addTables( 'page' );
-                       $this->addJoinConds( [ 'page' => [ 'LEFT JOIN', 'rc_cur_id=page_id' ] ] );
-                       $this->addWhere( 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG );
+               if ( !is_null( $params['namespace'] ) ) {
+                       $options['namespaceIds'] = $params['namespace'];
+               }
+
+               if ( $params['allrev'] ) {
+                       $options['allRevisions'] = true;
                }
 
                if ( !is_null( $params['show'] ) ) {
                        $show = array_flip( $params['show'] );
 
                        /* Check for conflicting parameters. */
-                       if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
-                               || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
-                               || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
-                               || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
-                               || ( isset( $show['unread'] ) && isset( $show['!unread'] ) )
-                       ) {
+                       if ( $this->showParamsConflicting( $show ) ) {
                                $this->dieUsageMsg( 'show' );
                        }
 
                        // Check permissions.
-                       if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
+                       if ( isset( $show[WatchedItemQueryService::FILTER_PATROLLED] )
+                               || isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] )
+                       ) {
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
                                        $this->dieUsage(
                                                'You need the patrol right to request the patrolled flag',
@@ -183,25 +148,12 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                }
                        }
 
-                       /* Add additional conditions to query depending upon parameters. */
-                       $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
-                       $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
-                       $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
-                       $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
-                       $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
-                       $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
-                       $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
-                       $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
-                       $this->addWhereIf( 'rc_timestamp >= wl_notificationtimestamp', isset( $show['unread'] ) );
-                       $this->addWhereIf(
-                               'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp',
-                               isset( $show['!unread'] )
-                       );
+                       $options['filters'] = array_keys( $show );
                }
 
                if ( !is_null( $params['type'] ) ) {
                        try {
-                               $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
+                               $options['rcTypes'] = RecentChange::parseToRCType( $params['type'] );
                        } catch ( Exception $e ) {
                                ApiBase::dieDebug( __METHOD__, $e->getMessage() );
                        }
@@ -211,74 +163,46 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                        $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
                }
                if ( !is_null( $params['user'] ) ) {
-                       $this->addWhereFld( 'rc_user_text', $params['user'] );
+                       $options['onlyByUser'] = $params['user'];
                }
                if ( !is_null( $params['excludeuser'] ) ) {
-                       $this->addWhere( 'rc_user_text != ' . $db->addQuotes( $params['excludeuser'] ) );
+                       $options['notByUser'] = $params['excludeuser'];
                }
 
-               // This is an index optimization for mysql, as done in the Special:Watchlist page
-               $this->addWhereIf(
-                       "rc_timestamp > ''",
-                       !isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql'
-               );
-
-               // Paranoia: avoid brute force searches (bug 17342)
-               if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
-                       if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                               $bitmask = Revision::DELETED_USER;
-                       } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
-                               $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
-                       } else {
-                               $bitmask = 0;
-                       }
-                       if ( $bitmask ) {
-                               $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
-                       }
-               }
-
-               // LogPage::DELETED_ACTION hides the affected page, too. So hide those
-               // entirely from the watchlist, or someone could guess the title.
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                       $bitmask = LogPage::DELETED_ACTION;
-               } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
-                       $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
-               } else {
-                       $bitmask = 0;
-               }
-               if ( $bitmask ) {
-                       $this->addWhere( $this->getDB()->makeList( [
-                               'rc_type != ' . RC_LOG,
-                               $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
-                       ], LIST_OR ) );
-               }
-
-               $this->addOption( 'LIMIT', $params['limit'] + 1 );
+               $options['limit'] = $params['limit'] + 1;
 
                $ids = [];
                $count = 0;
-               $res = $this->select( __METHOD__ );
+               $watchedItemQuery = MediaWikiServices::getInstance()->getWatchedItemQueryService();
+               $items = $watchedItemQuery->getWatchedItemsWithRecentChangeInfo( $wlowner, $options );
 
-               foreach ( $res as $row ) {
+               foreach ( $items as list ( $watchedItem, $recentChangeInfo ) ) {
+                       /** @var WatchedItem $watchedItem */
                        if ( ++$count > $params['limit'] ) {
                                // We've reached the one extra which shows that there are
                                // additional pages to be had. Stop here...
-                               $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
+                               $this->setContinueEnumParameter(
+                                       'continue',
+                                       $recentChangeInfo['rc_timestamp'] . '|' . $recentChangeInfo['rc_id']
+                               );
                                break;
                        }
 
                        if ( is_null( $resultPageSet ) ) {
-                               $vals = $this->extractRowInfo( $row );
+                               $vals = $this->extractOutputData( $watchedItem, $recentChangeInfo );
                                $fit = $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
                                if ( !$fit ) {
-                                       $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
+                                       $this->setContinueEnumParameter(
+                                               'continue',
+                                               $recentChangeInfo['rc_timestamp'] . '|' . $recentChangeInfo['rc_id']
+                                       );
                                        break;
                                }
                        } else {
                                if ( $params['allrev'] ) {
-                                       $ids[] = intval( $row->rc_this_oldid );
+                                       $ids[] = intval( $recentChangeInfo['rc_this_oldid'] );
                                } else {
-                                       $ids[] = intval( $row->rc_cur_id );
+                                       $ids[] = intval( $recentChangeInfo['rc_cur_id'] );
                                }
                        }
                }
@@ -295,56 +219,106 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                }
        }
 
-       private function extractRowInfo( $row ) {
+       private function getFieldsToInclude() {
+               $includeFields = [];
+               if ( $this->fld_flags ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_FLAGS;
+               }
+               if ( $this->fld_user || $this->fld_userid ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_USER_ID;
+               }
+               if ( $this->fld_user ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_USER;
+               }
+               if ( $this->fld_comment || $this->fld_parsedcomment ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_COMMENT;
+               }
+               if ( $this->fld_patrol ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_PATROL_INFO;
+               }
+               if ( $this->fld_sizes ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_SIZES;
+               }
+               if ( $this->fld_loginfo ) {
+                       $includeFields[] = WatchedItemQueryService::INCLUDE_LOG_INFO;
+               }
+               return $includeFields;
+       }
+
+       private function showParamsConflicting( array $show ) {
+               return ( isset( $show[WatchedItemQueryService::FILTER_MINOR] )
+                       && isset( $show[WatchedItemQueryService::FILTER_NOT_MINOR] ) )
+               || ( isset( $show[WatchedItemQueryService::FILTER_BOT] )
+                       && isset( $show[WatchedItemQueryService::FILTER_NOT_BOT] ) )
+               || ( isset( $show[WatchedItemQueryService::FILTER_ANON] )
+                       && isset( $show[WatchedItemQueryService::FILTER_NOT_ANON] ) )
+               || ( isset( $show[WatchedItemQueryService::FILTER_PATROLLED] )
+                       && isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] ) )
+               || ( isset( $show[WatchedItemQueryService::FILTER_UNREAD] )
+                       && isset( $show[WatchedItemQueryService::FILTER_NOT_UNREAD] ) );
+       }
+
+       private function extractOutputData( WatchedItem $watchedItem, array $recentChangeInfo ) {
                /* Determine the title of the page that has been changed. */
-               $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+               $title = Title::makeTitle(
+                       $watchedItem->getLinkTarget()->getNamespace(),
+                       $watchedItem->getLinkTarget()->getDBkey()
+               );
                $user = $this->getUser();
 
                /* Our output data. */
                $vals = [];
-               $type = intval( $row->rc_type );
+               $type = intval( $recentChangeInfo['rc_type'] );
                $vals['type'] = RecentChange::parseFromRCType( $type );
                $anyHidden = false;
 
                /* Create a new entry in the result for the title. */
                if ( $this->fld_title || $this->fld_ids ) {
                        // These should already have been filtered out of the query, but just in case.
-                       if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
+                       if ( $type === RC_LOG && ( $recentChangeInfo['rc_deleted'] & LogPage::DELETED_ACTION ) ) {
                                $vals['actionhidden'] = true;
                                $anyHidden = true;
                        }
                        if ( $type !== RC_LOG ||
-                               LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
+                               LogEventsList::userCanBitfield(
+                                       $recentChangeInfo['rc_deleted'],
+                                       LogPage::DELETED_ACTION,
+                                       $user
+                               )
                        ) {
                                if ( $this->fld_title ) {
                                        ApiQueryBase::addTitleInfo( $vals, $title );
                                }
                                if ( $this->fld_ids ) {
-                                       $vals['pageid'] = intval( $row->rc_cur_id );
-                                       $vals['revid'] = intval( $row->rc_this_oldid );
-                                       $vals['old_revid'] = intval( $row->rc_last_oldid );
+                                       $vals['pageid'] = intval( $recentChangeInfo['rc_cur_id'] );
+                                       $vals['revid'] = intval( $recentChangeInfo['rc_this_oldid'] );
+                                       $vals['old_revid'] = intval( $recentChangeInfo['rc_last_oldid'] );
                                }
                        }
                }
 
                /* Add user data and 'anon' flag, if user is anonymous. */
                if ( $this->fld_user || $this->fld_userid ) {
-                       if ( $row->rc_deleted & Revision::DELETED_USER ) {
+                       if ( $recentChangeInfo['rc_deleted'] & Revision::DELETED_USER ) {
                                $vals['userhidden'] = true;
                                $anyHidden = true;
                        }
-                       if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
+                       if ( Revision::userCanBitfield(
+                               $recentChangeInfo['rc_deleted'],
+                               Revision::DELETED_USER,
+                               $user
+                       ) ) {
                                if ( $this->fld_userid ) {
-                                       $vals['userid'] = (int)$row->rc_user;
+                                       $vals['userid'] = (int)$recentChangeInfo['rc_user'];
                                        // for backwards compatibility
-                                       $vals['user'] = (int)$row->rc_user;
+                                       $vals['user'] = (int)$recentChangeInfo['rc_user'];
                                }
 
                                if ( $this->fld_user ) {
-                                       $vals['user'] = $row->rc_user_text;
+                                       $vals['user'] = $recentChangeInfo['rc_user_text'];
                                }
 
-                               if ( !$row->rc_user ) {
+                               if ( !$recentChangeInfo['rc_user'] ) {
                                        $vals['anon'] = true;
                                }
                        }
@@ -352,65 +326,73 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
                /* Add flags, such as new, minor, bot. */
                if ( $this->fld_flags ) {
-                       $vals['bot'] = (bool)$row->rc_bot;
-                       $vals['new'] = $row->rc_type == RC_NEW;
-                       $vals['minor'] = (bool)$row->rc_minor;
+                       $vals['bot'] = (bool)$recentChangeInfo['rc_bot'];
+                       $vals['new'] = $recentChangeInfo['rc_type'] == RC_NEW;
+                       $vals['minor'] = (bool)$recentChangeInfo['rc_minor'];
                }
 
                /* Add sizes of each revision. (Only available on 1.10+) */
                if ( $this->fld_sizes ) {
-                       $vals['oldlen'] = intval( $row->rc_old_len );
-                       $vals['newlen'] = intval( $row->rc_new_len );
+                       $vals['oldlen'] = intval( $recentChangeInfo['rc_old_len'] );
+                       $vals['newlen'] = intval( $recentChangeInfo['rc_new_len'] );
                }
 
                /* Add the timestamp. */
                if ( $this->fld_timestamp ) {
-                       $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
+                       $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $recentChangeInfo['rc_timestamp'] );
                }
 
                if ( $this->fld_notificationtimestamp ) {
-                       $vals['notificationtimestamp'] = ( $row->wl_notificationtimestamp == null )
+                       $vals['notificationtimestamp'] = ( $watchedItem->getNotificationTimestamp() == null )
                                ? ''
-                               : wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
+                               : wfTimestamp( TS_ISO_8601, $watchedItem->getNotificationTimestamp() );
                }
 
                /* Add edit summary / log summary. */
                if ( $this->fld_comment || $this->fld_parsedcomment ) {
-                       if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
+                       if ( $recentChangeInfo['rc_deleted'] & Revision::DELETED_COMMENT ) {
                                $vals['commenthidden'] = true;
                                $anyHidden = true;
                        }
-                       if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
-                               if ( $this->fld_comment && isset( $row->rc_comment ) ) {
-                                       $vals['comment'] = $row->rc_comment;
+                       if ( Revision::userCanBitfield(
+                               $recentChangeInfo['rc_deleted'],
+                               Revision::DELETED_COMMENT,
+                               $user
+                       ) ) {
+                               if ( $this->fld_comment && isset( $recentChangeInfo['rc_comment'] ) ) {
+                                       $vals['comment'] = $recentChangeInfo['rc_comment'];
                                }
 
-                               if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+                               if ( $this->fld_parsedcomment && isset( $recentChangeInfo['rc_comment'] ) ) {
+                                       $vals['parsedcomment'] = Linker::formatComment( $recentChangeInfo['rc_comment'], $title );
                                }
                        }
                }
 
                /* Add the patrolled flag */
                if ( $this->fld_patrol ) {
-                       $vals['patrolled'] = $row->rc_patrolled == 1;
-                       $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
+                       $vals['patrolled'] = $recentChangeInfo['rc_patrolled'] == 1;
+                       $vals['unpatrolled'] = ChangesList::isUnpatrolled( (object)$recentChangeInfo, $user );
                }
 
-               if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
-                       if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
+               if ( $this->fld_loginfo && $recentChangeInfo['rc_type'] == RC_LOG ) {
+                       if ( $recentChangeInfo['rc_deleted'] & LogPage::DELETED_ACTION ) {
                                $vals['actionhidden'] = true;
                                $anyHidden = true;
                        }
-                       if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
-                               $vals['logid'] = intval( $row->rc_logid );
-                               $vals['logtype'] = $row->rc_log_type;
-                               $vals['logaction'] = $row->rc_log_action;
-                               $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
+                       if ( LogEventsList::userCanBitfield(
+                               $recentChangeInfo['rc_deleted'],
+                               LogPage::DELETED_ACTION,
+                               $user
+                       ) ) {
+                               $vals['logid'] = intval( $recentChangeInfo['rc_logid'] );
+                               $vals['logtype'] = $recentChangeInfo['rc_log_type'];
+                               $vals['logaction'] = $recentChangeInfo['rc_log_action'];
+                               $vals['logparams'] = LogFormatter::newFromRow( $recentChangeInfo )->formatParametersForApi();
                        }
                }
 
-               if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
+               if ( $anyHidden && ( $recentChangeInfo['rc_deleted'] & Revision::DELETED_RESTRICTED ) ) {
                        $vals['suppressed'] = true;
                }
 
@@ -473,16 +455,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                        'show' => [
                                ApiBase::PARAM_ISMULTI => true,
                                ApiBase::PARAM_TYPE => [
-                                       'minor',
-                                       '!minor',
-                                       'bot',
-                                       '!bot',
-                                       'anon',
-                                       '!anon',
-                                       'patrolled',
-                                       '!patrolled',
-                                       'unread',
-                                       '!unread',
+                                       WatchedItemQueryService::FILTER_MINOR,
+                                       WatchedItemQueryService::FILTER_NOT_MINOR,
+                                       WatchedItemQueryService::FILTER_BOT,
+                                       WatchedItemQueryService::FILTER_NOT_BOT,
+                                       WatchedItemQueryService::FILTER_ANON,
+                                       WatchedItemQueryService::FILTER_NOT_ANON,
+                                       WatchedItemQueryService::FILTER_PATROLLED,
+                                       WatchedItemQueryService::FILTER_NOT_PATROLLED,
+                                       WatchedItemQueryService::FILTER_UNREAD,
+                                       WatchedItemQueryService::FILTER_NOT_UNREAD,
                                ]
                        ],
                        'type' => [
index c8a330a..446a98c 100644 (file)
@@ -258,7 +258,10 @@ class ApiStashEdit extends ApiBase {
                } elseif ( $editInfo->output->getFlag( 'vary-revision' ) ) {
                        // This can be used for the initial parse, e.g. for filters or doEditContent(),
                        // but a second parse will be triggered in doEditUpdates(). This is not optimal.
-                       $logger->info( "Partially usable cache for key '$key' ('$title') [vary_revision]." );
+                       $logger->info( "Cache for key '$key' ('$title') has vary_revision." );
+               } elseif ( $editInfo->output->getFlag( 'vary-revision-id' ) ) {
+                       // Similar to the above if we didn't guess the ID correctly.
+                       $logger->info( "Cache for key '$key' ('$title') has vary_revision_id." );
                }
 
                return $editInfo;
index 0a79aa4..15c1e39 100644 (file)
@@ -347,7 +347,7 @@ class ApiUpload extends ApiBase {
         * Throw an error that the user can recover from by providing a better
         * value for $parameter
         *
-        * @param array $error Error array suitable for passing to dieUsageMsg()
+        * @param array|string|MessageSpecifier $error Error suitable for passing to dieUsageMsg()
         * @param string $parameter Parameter that needs revising
         * @param array $data Optional extra data to pass to the user
         * @throws UsageException
index 45d0576..fa7c460 100644 (file)
@@ -15,6 +15,7 @@
        "apihelp-emailuser-param-subject": "Хьедаран корта.",
        "apihelp-emailuser-param-text": "Кехатан чулацам",
        "apihelp-expandtemplates-param-title": "АгӀонан корта.",
+       "apihelp-feedrecentchanges-param-hideminor": "Къайладаха жима нисдарш.",
        "apihelp-feedrecentchanges-param-tagfilter": "Тегийн луьттург.",
        "apihelp-login-example-login": "ЧугӀо",
        "apihelp-logout-description": "ЧугӀой сессийн хаамаш дӀацӀанбе.",
index fbddc8d..e0be24b 100644 (file)
@@ -9,7 +9,8 @@
                        "Utar",
                        "Macofe",
                        "Danny B.",
-                       "LordMsz"
+                       "LordMsz",
+                       "Dvorapa"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Dokumentace]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-mailová konference]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Oznámení k API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Chyby a požadavky]\n</div>\n<strong>Stav:</strong> Všechny funkce uvedené na této stránce by měly fungovat, ale API se stále aktivně vyvíjí a může se kdykoli změnit. Upozornění na změny získáte přihlášením se k [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ e-mailové konferenci mediawiki-api-announce].\n\n<strong>Chybné požadavky:</strong> Pokud jsou do API zaslány chybné požadavky, bude vrácena HTTP hlavička s klíčem „MediaWiki-API-Error“ a hodnota této hlavičky a chybový kód budou nastaveny na stejnou hodnotu. Více informací najdete [[mw:API:Errors_and_warnings|v dokumentaci]].\n\n<strong>Testování:</strong> Pro jednoduché testování požadavků na API zkuste [[Special:ApiSandbox]].",
@@ -74,7 +75,7 @@
        "apihelp-edit-param-text": "Obsah stránky.",
        "apihelp-edit-param-minor": "Malá editace.",
        "apihelp-edit-param-notminor": "Nemalá editace.",
-       "apihelp-edit-param-bot": "Označit tuto editaci jako editaci bota.",
+       "apihelp-edit-param-bot": "Označit tuto editaci jako editaci robota.",
        "apihelp-edit-param-createonly": "Needitovat stránku, pokud již existuje.",
        "apihelp-edit-param-nocreate": "Pokud stránka neexistuje, vrátit chybu.",
        "apihelp-edit-param-watch": "Přidat stránku na seznam sledovaných.",
index 5124955..24b66ab 100644 (file)
@@ -73,7 +73,9 @@
        "apihelp-createaccount-param-language": "Language code to set as default for the user (optional, defaults to content language).",
        "apihelp-createaccount-example-pass": "Create user <kbd>testuser</kbd> with password <kbd>test123</kbd>.",
        "apihelp-createaccount-example-mail": "Create user <kbd>testmailuser</kbd> and email a randomly-generated password.",
-
+       "apihelp-cspreport-description": "Used by browsers to report violations of the Content Security Policy. This module should never be used, except when used automatically by a CSP compliant web browser.",
+       "apihelp-cspreport-param-reportonly": "Mark as being a report from a monitoring policy, not an enforced policy",
+       "apihelp-cspreport-param-source": "What generated the CSP header that triggered this report",
        "apihelp-delete-description": "Delete a page.",
        "apihelp-delete-param-title": "Title of the page to delete. Cannot be used together with <var>$1pageid</var>.",
        "apihelp-delete-param-pageid": "Page ID of the page to delete. Cannot be used together with <var>$1title</var>.",
index 6c87129..c580a33 100644 (file)
@@ -47,6 +47,7 @@
        "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> 토큰의 유효성을 테스트합니다.",
        "apihelp-clearhasmsg-description": "현재 사용자의 <code>hasmsg</code> 플래그를 비웁니다.",
        "apihelp-clearhasmsg-example-1": "현재 계정의 <code>hasmsg</code> 플래그를 삭제합니다.",
+       "apihelp-clientlogin-description": "상호작용 플로우를 이용하여 위키에 로그인합니다.",
        "apihelp-compare-description": "두 문서 간의 차이를 가져옵니다.\n\n대상이 되는 두 문서의 판 번호나 문서 제목 또는 문서 ID를 지정해야 합니다.",
        "apihelp-compare-param-fromtitle": "비교할 첫 이름.",
        "apihelp-compare-param-fromid": "비교할 첫 문서 ID.",
        "apihelp-expandtemplates-param-text": "변환할 위키텍스트.",
        "apihelp-expandtemplates-paramvalue-prop-wikitext": "확장된 위키텍스트.",
        "apihelp-expandtemplates-paramvalue-prop-parsetree": "입력값의 XML 파서 트리.",
+       "apihelp-expandtemplates-param-generatexml": "XML 구문 분석 트리를 생성합니다. ($1prop=parsetree로 대체함)",
+       "apihelp-expandtemplates-example-simple": "위키텍스트 <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>를 확장합니다.",
        "apihelp-feedcontributions-description": "사용자 기여 피드를 반환합니다.",
        "apihelp-feedcontributions-param-feedformat": "피드 포맷.",
        "apihelp-feedcontributions-param-user": "기여를 읽을 사용자 이름.",
        "apihelp-feedcontributions-param-namespace": "기여를 분류할 이름공간",
        "apihelp-feedcontributions-param-deletedonly": "삭제된 기여만 봅니다.",
        "apihelp-feedcontributions-param-toponly": "최신 판인 편집만 봅니다.",
+       "apihelp-feedcontributions-param-newonly": "새 글인 편집만 봅니다.",
        "apihelp-feedcontributions-param-hideminor": "사소한 편집을 숨깁니다.",
        "apihelp-feedcontributions-param-showsizediff": "판 사이의 크기 차이를 보여줍니다.",
+       "apihelp-feedrecentchanges-description": "최근 바뀜 피드를 반환합니다.",
        "apihelp-feedrecentchanges-param-feedformat": "피드 포맷.",
+       "apihelp-feedrecentchanges-param-namespace": "결과를 제한할 이름공간.",
        "apihelp-feedrecentchanges-param-invert": "선택한 항목을 제외한 모든 이름공간.",
+       "apihelp-feedrecentchanges-param-associated": "관련 (토론 또는 일반) 이름공간을 포함합니다.",
+       "apihelp-feedrecentchanges-param-limit": "반환할 결과의 최대 수.",
        "apihelp-feedrecentchanges-param-from": "이후의 변경 사항을 보여줍니다.",
        "apihelp-feedrecentchanges-param-hideminor": "사소한 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-param-hidebots": "봇의 편집을 숨깁니다.",
        "apihelp-filerevert-example-revert": "<kbd>Wiki.png</kbd>를 <kbd>2011-03-05T15:27:40Z</kbd> 판으로 되돌립니다.",
        "apihelp-help-description": "지정된 모듈의 도움말을 보여줍니다.",
        "apihelp-help-param-helpformat": "도움말 출력 포맷.",
+       "apihelp-imagerotate-description": "하나 이상의 그림을 회전합니다.",
+       "apihelp-imagerotate-param-rotation": "시계 방향으로 회전할 그림의 각도.",
        "apihelp-import-param-xml": "업로드한 XML 파일.",
        "apihelp-login-param-name": "계정 이름.",
        "apihelp-login-param-password": "비밀번호.",
        "apihelp-login-param-domain": "도메인 (선택).",
+       "apihelp-login-example-gettoken": "로그인 토큰을 검색합니다.",
        "apihelp-login-example-login": "로그인.",
+       "apihelp-logout-description": "로그아웃하고 세션 데이터를 지웁니다.",
        "apihelp-logout-example-logout": "현재 사용자를 로그아웃합니다.",
        "apihelp-mergehistory-description": "문서 역사를 합칩니다.",
        "apihelp-mergehistory-param-reason": "문서 병합 이유.",
        "apihelp-opensearch-param-format": "출력 포맷.",
        "apihelp-options-param-reset": "사이트 기본으로 설정 초기화",
        "apihelp-options-example-reset": "모든 설정 초기화",
+       "apihelp-paraminfo-description": "API 모듈의 정보를 가져옵니다.",
        "apihelp-paraminfo-param-helpformat": "도움말 문자열 포맷.",
+       "apihelp-parse-param-summary": "구문 분석할 요약입니다.",
+       "apihelp-parse-paramvalue-prop-categorieshtml": "분류의 HTML 버전을 제공합니다.",
+       "apihelp-parse-paramvalue-prop-links": "구문 분석된 위키텍스트의 내부 링크를 제공합니다.",
+       "apihelp-parse-paramvalue-prop-templates": "구문 분석된 위키텍스트의 틀을 제공합니다.",
+       "apihelp-parse-paramvalue-prop-images": "구문 분석된 위키텍스트의 그림을 제공합니다.",
+       "apihelp-parse-paramvalue-prop-externallinks": "구문 분석된 위키텍스트의 외부 링크를 제공합니다.",
+       "apihelp-parse-paramvalue-prop-sections": "구문 분석된 위키텍스트의 문단을 제공합니다.",
+       "apihelp-parse-paramvalue-prop-revid": "구문 분석된 페이지의 판 ID를 추가합니다.",
+       "apihelp-parse-paramvalue-prop-displaytitle": "구문 분석된 위키텍스트의 제목을 추가합니다.",
+       "apihelp-parse-paramvalue-prop-iwlinks": "구문 분석된 위키텍스트의 인터위키 링크를 제공합니다.",
+       "apihelp-parse-paramvalue-prop-wikitext": "구문 분석된 위키텍스트 원문을 제공합니다.",
+       "apihelp-parse-paramvalue-prop-properties": "구문 분석된 위키텍스트에 정의된 다양한 속성을 제공합니다.",
+       "apihelp-parse-param-disablelimitreport": "파서 출력에서 제한 보고서(\"NewPP limit report\")를 제외합니다.",
+       "apihelp-parse-param-disablepp": "<var>$1disablelimitreport</var>를 대신 사용합니다.",
+       "apihelp-parse-param-disableeditsection": "파서 출력에서 문단 편집 링크를 제외합니다.",
+       "apihelp-parse-param-disabletidy": "파서 출력에서 HTML 정리(예: tidy)를 수행하지 않습니다.",
+       "apihelp-parse-param-preview": "미리 보기 모드에서 구문 분석을 합니다.",
+       "apihelp-parse-param-sectionpreview": "문단 미리 보기 모드에서 구문 분석을 합니다. (미리 보기 모드도 활성화함)",
+       "apihelp-parse-param-disabletoc": "출력에서 목차를 제외합니다.",
        "apihelp-parse-example-page": "페이지의 구문을 분석합니다.",
        "apihelp-parse-example-text": "위키텍스트의 구문을 분석합니다.",
        "apihelp-parse-example-summary": "요약을 구문 분석합니다.",
        "apihelp-query+allcategories-param-prop": "얻고자 하는 속성:",
        "apihelp-query+allcategories-paramvalue-prop-size": "페이지 수를 분류에 추가합니다.",
        "apihelp-query+allfileusages-paramvalue-prop-title": "파일의 제목을 추가합니다.",
+       "apihelp-query+alllinks-paramvalue-prop-title": "링크의 제목을 추가합니다.",
+       "apihelp-query+alllinks-param-namespace": "열거할 이름공간.",
        "apihelp-query+alllinks-param-limit": "반환할 총 항목 수입니다.",
+       "apihelp-query+allmessages-description": "이 사이트에서 반환할 메시지.",
        "apihelp-query+allmessages-example-ipb": "<kbd>ipb-</kbd>로 시작하는 메시지를 보입니다.",
+       "apihelp-query+allpages-param-namespace": "열거할 이름공간.",
+       "apihelp-query+allredirects-paramvalue-prop-title": "넘겨주기의 제목을 추가합니다.",
+       "apihelp-query+allredirects-param-namespace": "열거할 이름공간.",
+       "apihelp-query+allredirects-param-limit": "반환할 총 항목 수입니다.",
        "apihelp-query+allrevisions-description": "모든 판 표시.",
+       "apihelp-query+mystashedfiles-param-limit": "가져올 파일의 갯수.",
+       "apihelp-query+alltransclusions-param-namespace": "열거할 이름공간.",
+       "apihelp-query+allusers-description": "등록된 모든 사용자를 열거합니다.",
+       "apihelp-query+allusers-param-witheditsonly": "편집을 한 사용자만 나열합니다.",
+       "apihelp-query+allusers-example-Y": "<kbd>Y</kbd>로 시작하는 사용자를 나열합니다.",
+       "apihelp-query+authmanagerinfo-description": "현재의 인증 상태에 대한 정보를 검색합니다.",
+       "apihelp-query+backlinks-param-namespace": "열거할 이름공간.",
+       "apihelp-query+blocks-description": "차단된 모든 사용자와 IP 주소를 나열합니다.",
+       "apihelp-query+blocks-param-start": "나열을 시작할 타임스탬프",
+       "apihelp-query+blocks-param-end": "나열을 끝낼 타임스탬프",
+       "apihelp-query+blocks-param-ids": "나열할 차단 ID 목록 (선택 사항).",
+       "apihelp-query+blocks-param-users": "검색할 사용자 목록 (선택 사항).",
+       "apihelp-query+blocks-param-prop": "얻고자 하는 속성:",
+       "apihelp-query+blocks-paramvalue-prop-user": "차단된 사용자의 사용자 이름을 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-userid": "차단된 사용자의 사용자 ID를 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-by": "차단을 수행하는 사용자의 사용자 이름을 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-byid": "차단을 수행하는 사용자의 사용자 ID를 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-timestamp": "차단된 시점의 타임스탬프를 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-expiry": "차단 만료 시점의 타임스탬프를 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-reason": "차단 이유를 추가합니다.",
+       "apihelp-query+blocks-paramvalue-prop-range": "차단에 영향을 받는 IP 주소 대역을 추가합니다.",
+       "apihelp-query+categories-param-limit": "반환할 분류의 갯수입니다.",
+       "apihelp-query+categorymembers-paramvalue-prop-ids": "페이지 ID를 추가합니다.",
+       "apihelp-query+categorymembers-paramvalue-prop-title": "문서의 제목과 이름공간 ID를 추가합니다.",
+       "apihelp-query+categorymembers-paramvalue-prop-timestamp": "문서가 포함된 시기의 타임스탬프를 추가합니다.",
+       "apihelp-query+categorymembers-param-limit": "반환할 문서의 최대 수입니다.",
+       "apihelp-query+categorymembers-param-startsortkey": "$1starthexsortkey를 대신 사용해 주십시오.",
+       "apihelp-query+categorymembers-param-endsortkey": "$1endhexsortkey를 대신 사용해 주십시오.",
+       "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|모드|모드}}: $2",
+       "apihelp-query+deletedrevs-param-start": "나열을 시작할 타임스탬프",
+       "apihelp-query+deletedrevs-param-end": "나열을 끝낼 타임스탬프",
+       "apihelp-query+deletedrevs-param-limit": "나열할 판의 최대 양.",
+       "apihelp-query+duplicatefiles-param-limit": "반환할 중복 파일의 수.",
+       "apihelp-query+embeddedin-param-namespace": "열거할 이름공간.",
+       "apihelp-query+extlinks-param-limit": "반환할 링크의 수.",
+       "apihelp-query+exturlusage-param-prop": "포함할 정보:",
+       "apihelp-query+exturlusage-paramvalue-prop-ids": "문서의 ID를 추가합니다.",
+       "apihelp-query+exturlusage-paramvalue-prop-title": "문서의 제목과 이름공간 ID를 추가합니다.",
+       "apihelp-query+exturlusage-paramvalue-prop-url": "문서에 사용된 URL을 추가합니다.",
+       "apihelp-query+exturlusage-param-namespace": "열거할 문서 이름공간.",
+       "apihelp-query+exturlusage-param-limit": "반환할 문서 수.",
+       "apihelp-query+filearchive-description": "삭제된 모든 파일을 순서대로 열거합니다.",
+       "apihelp-query+filearchive-paramvalue-prop-mime": "그림의 MIME를 추가합니다.",
+       "apihelp-query+filearchive-paramvalue-prop-mediatype": "그림의 미디어 유형을 추가합니다.",
+       "apihelp-query+filearchive-paramvalue-prop-metadata": "그림의 버전에 대한 Exif 메타데이터를 나열합니다.",
+       "apihelp-query+filearchive-example-simple": "삭제된 모든 파일의 목록을 표시합니다.",
+       "apihelp-query+filerepoinfo-description": "위키에 구성된 그림 저장소에 대한 메타 정보를 반환합니다.",
+       "apihelp-query+filerepoinfo-example-simple": "파일 저장소의 정보를 가져옵니다.",
+       "apihelp-query+fileusage-param-prop": "얻고자 하는 속성:",
+       "apihelp-query+fileusage-paramvalue-prop-pageid": "각 문서의 페이지 ID.",
+       "apihelp-query+fileusage-paramvalue-prop-title": "각 문서의 제목.",
+       "apihelp-query+fileusage-paramvalue-prop-redirect": "문서가 넘겨주기이면 표시합니다.",
+       "apihelp-query+fileusage-param-namespace": "이 이름공간의 문서만 포함합니다.",
+       "apihelp-query+fileusage-param-limit": "반환할 항목 수.",
+       "apihelp-query+fileusage-param-show": "이 기준을 충족하는 항목만 표시합니다:\n;redirect:넘겨주기만 표시합니다.\n;!redirect:넘겨주기가 아닌 항목만 표시합니다.",
+       "apihelp-query+imageinfo-paramvalue-prop-mediatype": "파일의 미디어 유형을 추가합니다.",
+       "apihelp-query+images-param-limit": "반환할 파일 수.",
+       "apihelp-query+imageusage-param-namespace": "열거할 이름공간.",
+       "apihelp-query+imageusage-example-generator": "[[:File:Albert Einstein Head.jpg]]를 이용하여 페이지의 정보를 가져옵니다.",
+       "apihelp-query+info-description": "기본 페이지 정보를 가져옵니다.",
+       "apihelp-query+info-param-prop": "얻고자 하는 추가 속성:",
+       "apihelp-query+info-paramvalue-prop-protection": "각 문서의 보호 수준을 나열합니다.",
+       "apihelp-query+iwbacklinks-param-prefix": "인터위키의 접두사.",
+       "apihelp-query+iwbacklinks-param-title": "검색할 인터위키 링크. <var>$1blprefix</var>와 함께 사용해야 합니다.",
+       "apihelp-query+iwbacklinks-param-limit": "반활한 총 문서 수.",
+       "apihelp-query+iwbacklinks-param-prop": "얻고자 하는 속성:",
+       "apihelp-query+iwbacklinks-paramvalue-prop-iwprefix": "인터위키의 접두사를 추가합니다.",
+       "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "인터위키의 제목을 추가합니다.",
+       "apihelp-query+iwlinks-paramvalue-prop-url": "전체 URL을 추가합니다.",
+       "apihelp-query+langbacklinks-paramvalue-prop-lllang": "언어 링크의 언어 코드를 추가합니다.",
+       "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "언어 링크의 제목을 추가합니다.",
+       "apihelp-query+langlinks-paramvalue-prop-url": "전체 URL을 추가합니다.",
+       "apihelp-query+langlinks-param-lang": "이 언어 코드의 언어 링크만 반환합니다.",
+       "apihelp-query+linkshere-param-namespace": "이 이름공간의 문서만 포함합니다.",
+       "apihelp-query+linkshere-param-limit": "반환할 항목 수.",
+       "apihelp-query+linkshere-param-show": "이 기준을 충족하는 항목만 표시합니다:\n;redirect:넘겨주기만 표시합니다.\n;!redirect:넘겨주기가 아닌 항목만 표시합니다.",
+       "apihelp-query+pagepropnames-param-limit": "반환할 이름의 최대 수.",
+       "apihelp-query+pageswithprop-param-prop": "포함할 정보:",
+       "apihelp-query+pageswithprop-paramvalue-prop-ids": "페이지 ID를 추가합니다.",
+       "apihelp-query+pageswithprop-paramvalue-prop-title": "문서의 제목과 이름공간 ID를 추가합니다.",
        "apihelp-query+pageswithprop-param-limit": "나타낼 문서의 최대 수입니다.",
        "apihelp-query+pageswithprop-param-dir": "정렬 순서",
        "apihelp-query+prefixsearch-param-search": "문자열 검색",
+       "apihelp-query+prefixsearch-param-namespace": "검색할 이름공간.",
+       "apihelp-query+prefixsearch-param-limit": "반환할 결과의 최대 수",
        "apihelp-query+prefixsearch-param-profile": "검색 프로파일 사용",
+       "apihelp-query+protectedtitles-paramvalue-prop-level": "보호 수준을 추가합니다.",
+       "apihelp-query+recentchanges-param-prop": "추가 정보를 포함합니다:",
+       "apihelp-query+recentchanges-paramvalue-prop-user": "편집에 임할 사용자를 추가하고 IP인 경우 태그합니다.",
+       "apihelp-query+recentchanges-paramvalue-prop-userid": "편집에 임할 사용자를 추가합니다.",
        "apihelp-query+search-param-qiprofile": "쿼리 독립적인 프로파일 사용(순위 알고리즘에 영향있음)",
+       "apihelp-query+tags-param-limit": "나열할 태그의 최대 수.",
+       "apihelp-query+templates-param-limit": "반환할 틀 수.",
+       "apihelp-query+transcludedin-paramvalue-prop-pageid": "각 문서의 페이지 ID.",
+       "apihelp-query+transcludedin-paramvalue-prop-title": "각 문서의 제목.",
+       "apihelp-query+transcludedin-paramvalue-prop-redirect": "문서가 넘겨주기이면 표시합니다.",
+       "apihelp-query+transcludedin-param-namespace": "이 이름공간의 문서만 포함합니다.",
+       "apihelp-query+transcludedin-param-limit": "반환할 항목 수.",
+       "apihelp-query+transcludedin-param-show": "이 기준을 충족하는 항목만 표시합니다:\n;redirect:넘겨주기만 표시합니다.\n;!redirect:넘겨주기가 아닌 항목만 표시합니다.",
+       "apihelp-query+usercontribs-description": "한 사용자의 모든 편집을 가져옵니다.",
+       "apihelp-query+usercontribs-param-limit": "반환할 기여의 최대 수.",
+       "apihelp-query+usercontribs-paramvalue-prop-ids": "페이지 ID와 판 ID를 추가합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-title": "문서의 제목과 이름공간 ID를 추가합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-timestamp": "편집의 타임스탬프를 추가합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-size": "편집의 새로운 크기를 추가합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-flags": "편집의 플래그를 추가합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-patrolled": "점검한 편집을 태그합니다.",
+       "apihelp-query+usercontribs-paramvalue-prop-tags": "편집의 태그를 나열합니다.",
+       "apihelp-query+usercontribs-param-tag": "이 태그로 태그된 판만을 나열합니다.",
+       "apihelp-query+usercontribs-param-toponly": "최신 판인 변경 사항만 나열합니다.",
+       "apihelp-query+usercontribs-example-user": "사용자 <kbd>Example</kbd>의 기여를 표시합니다.",
+       "apihelp-query+usercontribs-example-ipprefix": "<kbd>192.0.2.</kbd>로 시작하는 모든 IP 주소의 기여를 표시합니다.",
+       "apihelp-query+userinfo-description": "현재 사용자의 정보를 가져옵니다.",
+       "apihelp-query+userinfo-param-prop": "포함할 정보:",
+       "apihelp-query+userinfo-paramvalue-prop-blockinfo": "현재 사용자가 차단되면 누구에 의해 무슨 이유로 차단되었는지 태그합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-hasmsg": "현재 사용자가 대기 중인 메시지가 있다면 <samp>messages</samp> 태그를 추가합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-groups": "현재 사용자가 소속된 모든 그룹을 나열합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-implicitgroups": "현재 사용자가 자동으로 소속된 모든 그룹을 나열합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-rights": "현재 사용자가 가진 모든 권한을 나열합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-changeablegroups": "현재 사용자가 추가 및 제거할 수 있는 그룹을 나열합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-options": "현재 사용자가 설정한 모든 설정을 나열합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-editcount": "현재 사용자의 편집 수를 추가합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-realname": "사용자의 실명을 추가합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-email": "사용자의 이메일 주소와 이메일 인증 날짜를 추가합니다.",
+       "apihelp-query+userinfo-paramvalue-prop-registrationdate": "사용자의 등록 날짜를 추가합니다.",
+       "apihelp-query+userinfo-example-simple": "현재 사용자의 정보를 가져옵니다.",
+       "apihelp-query+userinfo-example-data": "현재 사용자의 추가 정보를 가져옵니다.",
+       "apihelp-query+users-description": "사용자 목록에 대한 정보를 가져옵니다.",
+       "apihelp-query+users-param-prop": "포함할 정보:",
+       "apihelp-query+users-paramvalue-prop-editcount": "사용자의 편집 수를 추가합니다.",
+       "apihelp-query+users-paramvalue-prop-registration": "사용자의 등록 타임스탬프를 추가합니다.",
+       "apihelp-query+users-example-simple": "사용자 <kbd>Example</kbd>의 정보를 반환합니다.",
+       "apihelp-query+watchlist-description": "현재 사용자의 주시목록의 문서의 최근 바뀜을 가져옵니다.",
+       "apihelp-query+watchlist-param-user": "이 사용자의 변경 사항만 나열합니다.",
+       "apihelp-query+watchlist-param-excludeuser": "이 사용자의 변경 사항을 나열하지 않습니다.",
+       "apihelp-query+watchlist-paramvalue-prop-loginfo": "적절한 곳에 로그 정보를 추가합니다.",
+       "apihelp-removeauthenticationdata-description": "현재 사용자의 인증 데이터를 제거합니다.",
+       "apihelp-revisiondelete-description": "판을 삭제하거나 되살립니다.",
        "apihelp-rollback-param-tags": "되돌리기를 적용하기 위해 태그합니다.",
+       "apihelp-stashedit-param-sectiontitle": "새 문단을 위한 제목.",
+       "apihelp-stashedit-param-text": "문서 내용.",
+       "apihelp-stashedit-param-contentmodel": "새 콘텐츠의 콘텐츠 모델.",
+       "apihelp-tag-param-reason": "변경 이유.",
+       "apihelp-tokens-param-type": "요청할 토큰의 종류.",
        "apihelp-unblock-description": "사용자를 차단 해제합니다.",
+       "apihelp-unblock-param-reason": "차단 해제 이유.",
+       "apihelp-userrights-param-user": "사용자 이름.",
+       "apihelp-userrights-param-userid": "사용자 ID.",
        "apihelp-rawfm-description": "출력 데이터, 디버깅 요소를 포함, (HTML에 포함된)JSON형식.",
+       "apihelp-xml-param-includexmlnamespace": "지정하면 XML 이름공간을 추가합니다.",
+       "api-format-title": "미디어위키 API 결과",
        "api-help-title": "미디어위키 API 도움말",
        "api-help-lead": "이 페이지는 자동으로 생성된 미디어위키 API 도움말 문서입니다.\n\n설명 문서 및 예시: https://www.mediawiki.org/wiki/API",
        "api-help-main-header": "메인 모듈",
        "api-help-flag-deprecated": "이 모듈은 사용되지 않습니다.",
+       "api-help-flag-internal": "<strong>이 모듈은 내부용이거나 불안정합니다.</strong> 동작은 예고 없이 변경될 수 있습니다.",
        "api-help-flag-readrights": "이 모듈은 read 권한을 요구합니다.",
        "api-help-flag-writerights": "이 모듈은 write 권한을 요구합니다.",
        "api-help-flag-mustbeposted": "이 모듈은 POST 요청만을 허용합니다.",
+       "api-help-flag-generator": "이 모듈은 생성기로 사용할 수 있습니다.",
        "api-help-source": "출처: $1",
+       "api-help-source-unknown": "소스: <span class=\"apihelp-unknown\">알 수 없음</span>",
        "api-help-license": "라이선스: [[$1|$2]]",
        "api-help-license-noname": "라이선스: [[$1|링크 참조]]",
        "api-help-license-unknown": "라이선스: <span class=\"apihelp-unknown\">알 수 없음</span>",
        "api-help-param-deprecated": "사용 중지됨.",
        "api-help-param-required": "이 변수는 필수 입력 사항입니다.",
        "api-help-datatypes-header": "데이터 유형",
-       "api-help-datatypes": "API 요청 내 몇몇 매개변수형에 대해 더 자세히 설명해보겠습니다:\n;boolean\n:Boolean 매개변수들은 HTML 체크박스처럼 동작합니다: 만약 매개변수가 지정되었다면, 값에 상관없이 참의 값으로 여겨집니다. 거짓값은 매개변수 전체를 생략하세요.\n;timestamp\n:타임스탬프들은 여러 형식으로 표현될 수 있으나 ISO 8601 날짜와 시간이 추천됩니다. 모든 시간은 UTC이어야 하며, 포함된 시간대는 모두 무시됩니다.\n:* ISO 8601 날짜와 시간, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (구두점과 <kbd>Z</kbd>는 선택입니다.)\n:* ISO 8601 날짜와 시간과 (무시되는) 소수 초, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (대시, 콜론과 <kbd>Z</kbd> 는 선택입니다.)\n:* 미디어위키 형식, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* 일반적인 수 형식 <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (<kbd>GMT</kbd>, <kbd>+<var>##</var></kbd>, 또는 <kbd>-<var>##</var></kbd>와 같은 선택적 시간대는 무시됩니다)\n:*RFC 2822 형식 (시간대는 생략될 수 있음), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* RFC 850 형식 (시간대는 생략될 수 있음), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* C ctime 형식, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* 1부터 13자리까지의 숫자로 표현된 1970-01-01T00:00:00Z 부터 흐른 시간(초) (<kbd>0</kbd>을 제외)\n:* 문자열 <kbd>now</kbd>",
+       "api-help-datatypes": "API 요청 내 몇몇 매개변수형에 대해 더 자세히 설명해보겠습니다:\n;boolean\n:Boolean 매개변수들은 HTML 체크박스처럼 동작합니다: 만약 매개변수가 지정되었다면, 값에 상관없이 참의 값으로 여겨집니다. 거짓값은 매개변수 전체를 생략하세요.\n;timestamp\n:타임스탬프들은 여러 형식으로 표현될 수 있으나 ISO 8601 날짜와 시간이 추천됩니다. 모든 시간은 UTC이어야 하며, 포함된 시간대는 모두 무시됩니다.\n:* ISO 8601 날짜와 시간, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (구두점과 <kbd>Z</kbd>는 선택입니다.)\n:* ISO 8601 날짜와 시간과 (무시되는) 소수 초, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (대시, 콜론과 <kbd>Z</kbd>는 선택입니다.)\n:* 미디어위키 형식, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* 일반적인 수 형식 <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (<kbd>GMT</kbd>, <kbd>+<var>##</var></kbd>, 또는 <kbd>-<var>##</var></kbd>와 같은 선택적 시간대는 무시됩니다)\n:*RFC 2822 형식 (시간대는 생략될 수 있음), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* RFC 850 형식 (시간대는 생략될 수 있음), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* C ctime 형식, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* 1부터 13자리까지의 숫자로 표현된 1970-01-01T00:00:00Z 부터 흐른 시간(초) (<kbd>0</kbd>을 제외)\n:* 문자열 <kbd>now</kbd>",
+       "api-help-param-type-limit": "유형: 정수 또는 <kbd>max</kbd>",
        "api-help-param-type-integer": "유형: {{PLURAL:$1|1=정수|2=정수 목록}}",
        "api-help-param-type-boolean": "유형: 부울 ([[Special:ApiHelp/main#main/datatypes|자세한 정보]])",
        "api-help-param-list": "{{PLURAL:$1|1=하나의 값|2=값 (\"{{!}}\"로 구분)}}: $2",
+       "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=비어 있어야 함|비어 있을 수 있거나 $2}}",
+       "api-help-param-limit": "$1 초과는 허용되지 않습니다.",
+       "api-help-param-limit2": "$1 초과는 허용되지 않습니다. (봇의 경우 $2)",
+       "api-help-param-integer-min": "{{PLURAL:$1|1=값|2=값들}}은 $2 이상이어야 합니다.",
+       "api-help-param-integer-max": "{{PLURAL:$1|1=값|2=값들}}은 $3 이하여야 합니다.",
+       "api-help-param-integer-minmax": "{{PLURAL:$1|1=값|2=값들}}은 $2와 $3 사이여야 합니다.",
+       "api-help-param-multi-max": "값들의 최대 수는 {{PLURAL:$1|$1}}입니다. (봇의 경우 {{PLURAL:$2|$2}})",
        "api-help-param-default": "기본값: $1",
        "api-help-param-default-empty": "기본값: <span class=\"apihelp-empty\">(비어 있음)</span>",
+       "api-help-param-continue": "더 많은 결과를 이용할 수 있을 때, 계속하려면 이것을 사용하십시오.",
        "api-help-param-no-description": "<span class=\"apihelp-empty\">(설명 없음)</span>",
        "api-help-examples": "{{PLURAL:$1|예시}}:",
        "api-help-permissions": "{{PLURAL:$1|권한}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|다음 그룹에 부여됨}}: $2",
+       "api-help-open-in-apisandbox": "<small>[연습장에서 열기]</small>",
        "api-credits": "API 개발자:\n* Roan Kattouw (선임 개발자, 2007년 9월–2009년)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (초기 개발자, 선임 개발자 2006년 9월~2007년 9월)\n* Brad Jorsch (선임 개발자 2013년–현재)\n\n당신의 의견이나 제안, 질문은 mediawiki-api@lists.wikimedia.org 로 보내주시거나,\nhttps://phabricator.wikimedia.org/ 에 버그 신고를 해 주시기 바랍니다.."
 }
index ed9952f..6c47aa4 100644 (file)
@@ -75,6 +75,9 @@
        "apihelp-createaccount-param-language": "{{doc-apihelp-param|createaccount|language}}",
        "apihelp-createaccount-example-pass": "{{doc-apihelp-example|createaccount}}",
        "apihelp-createaccount-example-mail": "{{doc-apihelp-example|createaccount}}",
+       "apihelp-cspreport-description": "{{doc-apihelp-description|cspreport}}",
+       "apihelp-cspreport-param-reportonly": "{{doc-apihelp-param|cspreport|reportonly}}",
+       "apihelp-cspreport-param-source": "{{doc-apihelp-param|cspreport|source}}",
        "apihelp-delete-description": "{{doc-apihelp-description|delete}}",
        "apihelp-delete-param-title": "{{doc-apihelp-param|delete|title}}",
        "apihelp-delete-param-pageid": "{{doc-apihelp-param|delete|pageid}}",
diff --git a/includes/api/i18n/udm.json b/includes/api/i18n/udm.json
new file mode 100644 (file)
index 0000000..a627a75
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Kaganer"
+               ]
+       },
+       "apihelp-login-example-login": "Пырон"
+}
index 0fd3b08..432a9e1 100644 (file)
@@ -22,6 +22,7 @@ namespace MediaWiki\Logger\Monolog;
 
 use Kafka\MetaDataFromKafka;
 use Kafka\Produce;
+use Kafka\Protocol\Decoder;
 use MediaWiki\Logger\LoggerFactory;
 use Monolog\Handler\AbstractProcessingHandler;
 use Monolog\Logger;
@@ -68,6 +69,7 @@ class KafkaHandler extends AbstractProcessingHandler {
                'alias' => [], // map from monolog channel to kafka topic
                'swallowExceptions' => false, // swallow exceptions sending records
                'logExceptions' => null, // A PSR3 logger to inform about errors
+               'requireAck' => 0,
        ];
 
        /**
@@ -118,6 +120,10 @@ class KafkaHandler extends AbstractProcessingHandler {
                        $options['logExceptions'] = LoggerFactory::getInstance( $options['logExceptions'] );
                }
 
+               if ( isset( $options['requireAck'] ) ) {
+                       $produce->setRequireAck( $options['requireAck'] );
+               }
+
                return new self( $produce, $options, $level, $bubble );
        }
 
@@ -165,13 +171,42 @@ class KafkaHandler extends AbstractProcessingHandler {
         */
        protected function send() {
                try {
-                       $this->produce->send();
+                       $response = $this->produce->send();
                } catch ( \Kafka\Exception $e ) {
                        $ignore = $this->warning(
                                'Error sending records to kafka: {exception}',
                                [ 'exception' => $e ] );
                        if ( !$ignore ) {
                                throw $e;
+                       } else {
+                               return;
+                       }
+               }
+
+               if ( is_bool( $response ) ) {
+                       return;
+               }
+
+               $errors = [];
+               foreach ( $response as $topicName => $partitionResponse ) {
+                       foreach ( $partitionResponse as $partition => $info ) {
+                               if ( $info['errCode'] === 0 ) {
+                                       // no error
+                                       continue;
+                               }
+                               $errors[] = sprintf(
+                                       'Error producing to %s (errno %d): %s',
+                                       $topicName,
+                                       $info['errCode'],
+                                       Decoder::getError( $info['errCode'] )
+                               );
+                       }
+               }
+
+               if ( $errors ) {
+                       $error = implode( "\n", $errors );
+                       if ( !$this->warning( $error ) ) {
+                               throw new \RuntimeException( $error );
                        }
                }
        }
index f35356c..c5e94c5 100644 (file)
@@ -236,12 +236,14 @@ class DifferenceEngine extends ContextSource {
        }
 
        public function showDiffPage( $diffOnly = false ) {
-
                # Allow frames except in certain special cases
                $out = $this->getOutput();
                $out->allowClickjacking();
                $out->setRobotPolicy( 'noindex,nofollow' );
 
+               // Allow extensions to add any extra output here
+               Hooks::run( 'DifferenceEngineShowDiffPage', [ $out ] );
+
                if ( !$this->loadRevisionData() ) {
                        $this->showMissingRevision();
 
@@ -283,6 +285,8 @@ class DifferenceEngine extends ContextSource {
                        $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
                        $samePage = true;
                        $oldHeader = '';
+                       // Allow extensions to change the $oldHeader variable
+                       Hooks::run( 'DifferenceEngineOldHeaderNoOldRev', [ &$oldHeader ] );
                } else {
                        Hooks::run( 'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] );
 
@@ -352,6 +356,10 @@ class DifferenceEngine extends ContextSource {
                                '<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' .
                                '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
 
+                       // Allow extensions to change the $oldHeader variable
+                       Hooks::run( 'DifferenceEngineOldHeader', [ $this, &$oldHeader, $prevlink, $oldminor,
+                               $diffOnly, $ldel, $this->unhide ] );
+
                        if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
                                $deleted = true; // old revisions text is hidden
                                if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
@@ -413,6 +421,10 @@ class DifferenceEngine extends ContextSource {
                        '<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' .
                        '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
 
+               // Allow extensions to change the $newHeader variable
+               Hooks::run( 'DifferenceEngineNewHeader', [ $this, &$newHeader, $formattedRevisionTools,
+                       $nextlink, $rollback, $newminor, $diffOnly, $rdel, $this->unhide ] );
+
                if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
                        $deleted = true; // new revisions text is hidden
                        if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
@@ -485,6 +497,9 @@ class DifferenceEngine extends ContextSource {
                                                        'token' => $linkInfo['token'],
                                                ]
                                        ) . ']</span>';
+                               // Allow extensions to change the markpatrolled link
+                               Hooks::run( 'DifferenceEngineMarkPatrolledLink', [ $this,
+                                       &$this->mMarkPatrolledLink, $linkInfo['rcid'], $linkInfo['token'] ] );
                        }
                }
                return $this->mMarkPatrolledLink;
@@ -528,6 +543,13 @@ class DifferenceEngine extends ContextSource {
                                // If the user could patrol this it already would be patrolled
                                $rcid = 0;
                        }
+
+                       // Allow extensions to possibly change the rcid here
+                       // For example the rcid might be set to zero due to the user
+                       // being the same as the performer of the change but an extension
+                       // might still want to show it under certain conditions
+                       Hooks::run( 'DifferenceEngineMarkPatrolledRCID', [ &$rcid, $this, $change, $user ] );
+
                        // Build the link
                        if ( $rcid ) {
                                $this->getOutput()->preventClickjacking();
@@ -615,15 +637,20 @@ class DifferenceEngine extends ContextSource {
 
                                # WikiPage::getParserOutput() should not return false, but just in case
                                if ( $parserOutput ) {
-                                       $out->addParserOutput( $parserOutput );
+                                       // Allow extensions to change parser output here
+                                       if ( !Hooks::run( 'DifferenceEngineRenderRevisionAddParserOutput', [ $this, $out, $parserOutput, $wikiPage ] ) ) {
+                                               $out->addParserOutput( $parserOutput );
+                                       }
                                }
                        }
                }
                # @codingStandardsIgnoreEnd
 
-               # Add redundant patrol link on bottom...
-               $out->addHTML( $this->markPatrolledLink() );
-
+               // Allow extensions to optionally not show the final patrolled link
+               if ( Hooks::run( 'DifferenceEngineRenderRevisionShowFinalPatrolLink' ) ) {
+                       # Add redundant patrol link on bottom...
+                       $out->addHTML( $this->markPatrolledLink() );
+               }
        }
 
        protected function getParserOutput( WikiPage $page, Revision $rev ) {
@@ -649,6 +676,9 @@ class DifferenceEngine extends ContextSource {
         * @return bool
         */
        public function showDiff( $otitle, $ntitle, $notice = '' ) {
+               // Allow extensions to affect the output here
+               Hooks::run( 'DifferenceEngineShowDiff', [ $this ] );
+
                $diff = $this->getDiff( $otitle, $ntitle, $notice );
                if ( $diff === false ) {
                        $this->showMissingRevision();
@@ -718,7 +748,9 @@ class DifferenceEngine extends ContextSource {
                if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
                        && $this->mOldRev->getId() == $this->mNewRev->getId() )
                ) {
-                       return '';
+                       if ( !Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
+                               return '';
+                       }
                }
                // Cacheable?
                $key = false;
index 54de26d..c1f2d59 100644 (file)
@@ -134,13 +134,21 @@ class WikiExporter {
         * @param int $start Inclusive lower limit (this id is included)
         * @param int $end Exclusive upper limit (this id is not included)
         *   If 0, no upper limit.
+        * @param bool $orderRevs order revisions within pages in ascending order
         */
-       public function pagesByRange( $start, $end ) {
-               $condition = 'page_id >= ' . intval( $start );
-               if ( $end ) {
-                       $condition .= ' AND page_id < ' . intval( $end );
+       public function pagesByRange( $start, $end, $orderRevs ) {
+               if ( $orderRevs ) {
+                       $condition = 'rev_page >= ' . intval( $start );
+                       if ( $end ) {
+                               $condition .= ' AND rev_page < ' . intval( $end );
+                       }
+               } else {
+                       $condition = 'page_id >= ' . intval( $start );
+                       if ( $end ) {
+                               $condition .= ' AND page_id < ' . intval( $end );
+                       }
                }
-               $this->dumpFrom( $condition );
+               $this->dumpFrom( $condition, $orderRevs );
        }
 
        /**
@@ -245,7 +253,7 @@ class WikiExporter {
         * @throws MWException
         * @throws Exception
         */
-       protected function dumpFrom( $cond = '' ) {
+       protected function dumpFrom( $cond = '', $orderRevs = false ) {
                # For logging dumps...
                if ( $this->history & self::LOGS ) {
                        $where = [ 'user_id = log_user' ];
@@ -332,7 +340,16 @@ class WikiExporter {
                                }
                        } elseif ( $this->history & WikiExporter::FULL ) {
                                # Full history dumps...
-                               $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
+                               # query optimization for history stub dumps
+                               if ( $this->text == WikiExporter::STUB && $orderRevs ) {
+                                       $tables = [ 'revision', 'page' ];
+                                       $opts[] = 'STRAIGHT_JOIN';
+                                       $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
+                                       $opts['USE INDEX']['revision'] = 'rev_page_id';
+                                       $join['page'] = [ 'INNER JOIN', 'rev_page=page_id' ];
+                               } else {
+                                       $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
+                               }
                        } elseif ( $this->history & WikiExporter::CURRENT ) {
                                # Latest revision dumps...
                                if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
@@ -369,7 +386,6 @@ class WikiExporter {
                        if ( $this->buffer == WikiExporter::STREAM ) {
                                $prev = $this->db->bufferResults( false );
                        }
-
                        $result = null; // Assuring $result is not undefined, if exception occurs early
                        try {
                                Hooks::run( 'ModifyExportQuery',
index 2c846e5..066da60 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileAbstraction
  */
 
+use \MediaWiki\Logger\LoggerFactory;
+
 /**
  * Bump this number when serialized cache records may be incompatible.
  */
@@ -1910,6 +1912,7 @@ class LocalFile extends File {
         */
        function lock() {
                if ( !$this->locked ) {
+                       $logger = LoggerFactory::getInstance( 'LocalFile' );
                        $dbw = $this->repo->getMasterDB();
                        if ( !$dbw->trxLevel() ) {
                                $dbw->begin( __METHOD__ );
@@ -1925,12 +1928,17 @@ class LocalFile extends File {
                                if ( $this->lockedOwnTrx ) {
                                        $dbw->rollback( __METHOD__ );
                                }
+                               $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
+
                                throw new LocalFileLockError( $status );
                        }
                        // Release the lock *after* commit to avoid row-level contention
                        $this->locked++;
-                       $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
-                               $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX );
+                       $dbw->onTransactionIdle( function () use ( $backend, $lockPaths, $logger ) {
+                               $status = $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX );
+                               if ( !$status->isGood() ) {
+                                       $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
+                               }
                        } );
                }
 
index ec12e51..5e24b04 100644 (file)
        "config-install-extension-tables": "Vytvářejí se tabulky pro zapnutá rozšíření",
        "config-install-mainpage-failed": "Nepodařilo se vložit hlavní stranu: $1",
        "config-install-done": "<strong>Gratulujeme!</strong>\nNainstalovali jste MediaWiki.\n\nInstalátor vytvořil soubor <code>LocalSettings.php</code>.\nTen obsahuje veškerou vaši konfiguraci.\n\nBudete si ho muset stáhnout a uložit do základního adresáře vaší instalace wiki (do stejného adresáře jako soubor index.php). Stažení souboru se mělo spustit automaticky.\n\nPokud se vám stažení nenabídlo nebo jste ho zrušili, můžete ho spustit znovu kliknutím na následující odkaz:\n\n$3\n\n<strong>Poznámka</strong>: Pokud to neuděláte hned, tento vygenerovaný konfigurační soubor nebude později dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.\n\nAž to dokončíte, můžete <strong>[$2 vstoupit do své wiki]</strong>.",
+       "config-install-done-path": "<strong>Gratulujeme!</strong>\nNainstalovali jste MediaWiki.\n\nInstalátor vytvořil soubor <code>LocalSettings.php</code>.\nTen obsahuje veškerou vaši konfiguraci.\n\nBudete si ho muset stáhnout a uložit do <code>$4</code>. Stažení souboru se mělo spustit automaticky.\n\nPokud se vám stažení nenabídlo nebo jste ho zrušili, můžete ho spustit znovu kliknutím na následující odkaz:\n\n$3\n\n<strong>Poznámka:</strong> Pokud to neuděláte hned, tento vygenerovaný konfigurační soubor nebude později dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.\n\nAž to dokončíte, můžete <strong>[$2 vstoupit do své wiki]</strong>.",
        "config-download-localsettings": "Stáhnout <code>LocalSettings.php</code>",
        "config-help": "nápověda",
        "config-help-tooltip": "rozbalíte kliknutím",
index 51a96ae..d57e19f 100644 (file)
        "config-install-extension-tables": "Creando as táboas para as extensións activadas",
        "config-install-mainpage-failed": "Non se puido inserir a páxina principal: $1",
        "config-install-done": "<strong>Parabéns!</strong>\nInstalou MediaWiki.\n\nO programa de instalación xerou un ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contén toda a súa configuración.\n\nTerá que descargalo e poñelo na base da instalación do seu wiki (no mesmo directorio ca index.php). A descarga debería comezar automaticamente.\n\nSe non comezou a descarga ou se a cancelou, pode facer que comece de novo premendo na ligazón que aparece a continuación:\n\n$3\n\n<strong>Nota:</strong> Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.\n\nCando faga todo isto, xa poderá <strong>[$2 entrar no seu wiki]</strong>.",
+       "config-install-done-path": "<strong>Parabéns!</strong>\nInstalou MediaWiki.\n\nO instalador xerou un ficheiro <code>LocalSettings.php</code>.\nEste contén toda a súa configuración.\n\nDeberá descargalo e poñerlo en <code>$4</code>. A descarga debería ter comezado automaticamente.\n\nSe non comenzou a descarga, ou se a cancelou, podes reiniciala descarga premendo na seguinte ligazón:\n\n$3\n\n<strong>Nota</strong>: se non fai isto agora, este ficheiro de configuración xerado non estará dispoñible máis tarde se sae da instalación sen descargarlo.\n\nCando o teña feito, poderá <strong>[$2 entrar na súa wiki]</strong>.",
        "config-download-localsettings": "Descargar o <code>LocalSettings.php</code>",
        "config-help": "axuda",
        "config-help-tooltip": "prema para expandir",
index 915f938..3782c94 100644 (file)
        "config-install-extension-tables": "활성화된 확장 기능을 위한 테이블을 만드는 중",
        "config-install-mainpage-failed": "대문을 삽입할 수 없습니다: $1",
        "config-install-done": "<strong>축하합니다!</strong>\n미디어위키를 설치했습니다.\n\n설치 관리자가 <code>LocalSettings.php</code> 파일을 만들었습니다.\n여기에 모든 설정이 포함되어 있습니다.\n\n파일을 다운로드하여 위키 설치의 거점에 넣어야 합니다. (index.php와 같은 디렉터리) 다운로드가 자동으로 시작됩니다.\n\n다운로드가 제공되지 않을 경우나 그것을 취소한 경우에는 아래의 링크를 클릭하여 다운로드를 다시 시작할 수 있습니다:\n\n$3\n\n<strong>참고:</strong> 이 생성한 설정 파일을 다운로드하지 않고 설치를 끝내면 이 파일은 나중에 사용할 수 없습니다.\n\n완료되었으면 <strong>[$2 위키에 들어갈 수 있습니다]</strong>.",
+       "config-install-done-path": "<strong>축하합니다!</strong>\n미디어위키가 설치되었습니다.\n\n설치 관리자가 <code>LocalSettings.php</code> 파일을 생성했습니다.\n이 파일에 모든 설정이 포함되어 있습니다.\n\n이 파일을 다운로드하여 <code>$4</code> 위치에 넣으세요. 다운로드가 자동으로 시작되었을 것입니다.\n\n다운로드가 시작되지 않았거나 취소했다면, 아래 링크를 클릭하여 다운로드를 재시작할 수 있습니다.\n\n$3\n\n<strong>알림:</strong> 지금 다운로드를 받지 않는다면, 이후에는 이 설정 파일을 다운로드받을 수 없습니다.\n\n모든 작업이 완료되었다면, <strong>[$2 위키에 들어갈 수 있습니다]</strong>.",
        "config-download-localsettings": "<code>LocalSettings.php</code> 다운로드",
        "config-help": "도움말",
        "config-help-tooltip": "확장하려면 클릭",
index dd94192..36ec508 100644 (file)
        "config-install-extension-tables": "Изработка на табели за овозможени додатоци",
        "config-install-mainpage-failed": "Не можев да вметнам главна страница: $1",
        "config-install-done": "<strong>Честитаме!</strong>\nУспешно го воспоставивте МедијаВики.\n\nВоспоставувачот создаде податотека <code>LocalSettings.php</code>.\nТаму се содржат сите ваши нагодувања.\n\nЌе треба да ја преземете и да ја ставите во основата на воспоставката (истата папка во која се наоѓа index.php). Преземањето треба да е започнато автоматски.\n\nАко не ви е понудено преземање, или пак ако сте го откажале, можете да го почнете одново стискајќи на следнава врска:\n\n$3\n\n<strong>Напомена</strong>: Ако ова не го направите сега, податотеката со поставки повеќе нема да биде на достапна.\n\nОткога ќе завршите со тоа, можете да <strong>[$2 влезете на вашето вики]</strong>.",
+       "config-install-done-path": "<strong>Честитаме!</strong>\nГо воспоставивте МедијаВики.\n\nВоспоставувачот создаде податотека <code>LocalSettings.php</code>.\nТаму се содржат сите ваши нагодувања.\n\nЌе треба да ја преземете и да ја ставите во <code>$4</code>. Преземањето треба да е започнато автоматски.\n\nАко не ви е понудено преземање, или пак ако сте го откажале, можете да го почнете одново стискајќи на следнава врска:\n\n$3\n\n<strong>Напомена</strong>: Ако ова не го направите сега, создадената податотека со поставки повеќе нема да биде на достапна, освен ако не ја преземете пред да излезете.\n\nОткога ќе завршите со тоа, можете да <strong>[$2 влезете на вашето вики]</strong>.",
        "config-download-localsettings": "Преземи го <code>LocalSettings.php</code>",
        "config-help": "помош",
        "config-help-tooltip": "стиснете да расклопите",
index 7acbdf2..a14cdd7 100644 (file)
@@ -102,18 +102,15 @@ class HTMLCacheUpdateJob extends Job {
                        return;
                }
 
-               // The page_touched field will need to be bumped for these pages.
-               // Only bump it to the present time if no "rootJobTimestamp" was known.
-               // If it is known, it can be used instead, which avoids invalidating output
-               // that was in fact generated *after* the relevant dependency change time
-               // (e.g. template edit). This is particularily useful since refreshLinks jobs
-               // save back parser output and usually run along side htmlCacheUpdate jobs;
-               // their saved output would be invalidated by using the current timestamp.
-               if ( isset( $this->params['rootJobTimestamp'] ) ) {
-                       $touchTimestamp = $this->params['rootJobTimestamp'];
-               } else {
-                       $touchTimestamp = wfTimestampNow();
-               }
+               // Bump page_touched to the current timestamp. This used to use the root job timestamp
+               // (e.g. template/file edit time), which was a bit more efficient when template edits are
+               // rare and don't effect the same pages much. However, this way allows for better
+               // de-duplication, which is much more useful for wikis with high edit rates. Note that
+               // RefreshLinksJob, which is enqueued alongside HTMLCacheUpdateJob, saves the parser output
+               // since it has to parse anyway. We assume that vast majority of the cache jobs finish
+               // before the link jobs, so using the current timestamp instead of the root timestamp is
+               // not expected to invalidate these cache entries too often.
+               $touchTimestamp = wfTimestampNow();
 
                $dbw = wfGetDB( DB_MASTER );
                // Update page_touched (skipping pages already touched since the root job).
index 1f1e8d6..2a6f88c 100644 (file)
@@ -467,7 +467,7 @@ class Article implements Page {
         * page of the given title.
         */
        public function view() {
-               global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
+               global $wgUseFileCache, $wgDebugToolbar, $wgMaxRedirects;
 
                # Get variables from query string
                # As side effect this will load the revision and update the title
@@ -520,10 +520,6 @@ class Article implements Page {
 
                # Try client and file cache
                if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
-                       if ( $wgUseETag ) {
-                               $outputPage->setETag( $parserCache->getETag( $this->mPage, $parserOptions ) );
-                       }
-
                        # Use the greatest of the page's timestamp or the timestamp of any
                        # redirect in the chain (bug 67849)
                        $timestamp = $this->mPage->getTouched();
index a416d56..b06b519 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use \MediaWiki\Logger\LoggerFactory;
+
 /**
  * Class representing a MediaWiki article and history.
  *
@@ -2106,6 +2108,16 @@ class WikiPage implements Page, IDBAccessObject {
                                                }
                                        }
                                );
+                       } else {
+                               // Try to avoid a second parse if {{REVISIONID}} is used
+                               $edit->popts->setSpeculativeRevIdCallback( function () {
+                                       return 1 + (int)wfGetDB( DB_MASTER )->selectField(
+                                               'revision',
+                                               'MAX(rev_id)',
+                                               [],
+                                               __METHOD__
+                                       );
+                               } );
                        }
                        $edit->output = $edit->pstContent
                                ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
@@ -2168,14 +2180,20 @@ class WikiPage implements Page, IDBAccessObject {
                ];
                $content = $revision->getContent();
 
+               $logger = LoggerFactory::getInstance( 'SaveParse' );
+
                // See if the parser output before $revision was inserted is still valid
                $editInfo = false;
                if ( !$this->mPreparedEdit ) {
-                       wfDebug( __METHOD__ . ": No prepared edit...\n" );
+                       $logger->debug( __METHOD__ . ": No prepared edit...\n" );
                } elseif ( $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
-                       wfDebug( __METHOD__ . ": Prepared edit has vary-revision...\n" );
+                       $logger->info( __METHOD__ . ": Prepared edit has vary-revision...\n" );
+               } elseif ( $this->mPreparedEdit->output->getFlag( 'vary-revision-id' )
+                       && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->getId()
+               ) {
+                       $logger->info( __METHOD__ . ": Prepared edit has vary-revision-id with wrong ID...\n" );
                } elseif ( $this->mPreparedEdit->output->getFlag( 'vary-user' ) && !$options['changed'] ) {
-                       wfDebug( __METHOD__ . ": Prepared edit has vary-user and is null...\n" );
+                       $logger->info( __METHOD__ . ": Prepared edit has vary-user and is null...\n" );
                } else {
                        wfDebug( __METHOD__ . ": Using prepared edit...\n" );
                        $editInfo = $this->mPreparedEdit;
index 26b4bd9..bd2f131 100644 (file)
@@ -2600,9 +2600,13 @@ class Parser {
                        case 'revisionid':
                                # Let the edit saving system know we should parse the page
                                # *after* a revision ID has been assigned.
-                               $this->mOutput->setFlag( 'vary-revision' );
-                               wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" );
+                               $this->mOutput->setFlag( 'vary-revision-id' );
+                               wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
                                $value = $this->mRevisionId;
+                               if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
+                                       $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
+                                       $this->mOutput->setSpeculativeRevIdUsed( $value );
+                               }
                                break;
                        case 'revisionday':
                                # Let the edit saving system know we should parse the page
index 91cd0f4..891c4dd 100644 (file)
@@ -117,17 +117,22 @@ class ParserOptions {
        private $mRemoveComments = true;
 
        /**
-        * Callback for current revision fetching. Used as first argument to call_user_func().
+        * @var callable Callback for current revision fetching; first argument to call_user_func().
         */
        private $mCurrentRevisionCallback =
                [ 'Parser', 'statelessFetchRevision' ];
 
        /**
-        * Callback for template fetching. Used as first argument to call_user_func().
+        * @var callable Callback for template fetching; first argument to call_user_func().
         */
        private $mTemplateCallback =
                [ 'Parser', 'statelessFetchTemplate' ];
 
+       /**
+        * @var callable|null Callback to generate a guess for {{REVISIONID}}
+        */
+       private $mSpeculativeRevIdCallback;
+
        /**
         * Enable limit report in an HTML comment on output
         */
@@ -302,6 +307,11 @@ class ParserOptions {
                return $this->mTemplateCallback;
        }
 
+       /** @since 1.28 */
+       public function getSpeculativeRevIdCallback() {
+               return $this->mSpeculativeRevIdCallback;
+       }
+
        public function getEnableLimitReport() {
                return $this->mEnableLimitReport;
        }
@@ -483,6 +493,11 @@ class ParserOptions {
                return wfSetVar( $this->mCurrentRevisionCallback, $x );
        }
 
+       /** @since 1.28 */
+       public function setSpeculativeRevIdCallback( $x ) {
+               return wfSetVar( $this->mSpeculativeRevIdCallback, $x );
+       }
+
        public function setTemplateCallback( $x ) {
                return wfSetVar( $this->mTemplateCallback, $x );
        }
index 6c7ad4e..3462d10 100644 (file)
@@ -208,6 +208,9 @@ class ParserOutput extends CacheTime {
         */
        private $mFlags = [];
 
+       /** @var integer|null Assumed rev ID for {{REVISIONID}} if no revision is set */
+       private $mSpeculativeRevId;
+
        const EDITSECTION_REGEX =
                '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
 
@@ -272,6 +275,19 @@ class ParserOutput extends CacheTime {
                return $text;
        }
 
+       /**
+        * @param integer $id
+        * @since 1.28
+        */
+       public function setSpeculativeRevIdUsed( $id ) {
+               $this->mSpeculativeRevId = $id;
+       }
+
+       /** @since 1.28 */
+       public function getSpeculativeRevIdUsed() {
+               return $this->mSpeculativeRevId;
+       }
+
        public function &getLanguageLinks() {
                return $this->mLanguageLinks;
        }
index fce68bb..729f564 100644 (file)
@@ -141,10 +141,8 @@ abstract class Skin extends ContextSource {
        /**
         * @param OutputPage $out
         */
-       function initPage( OutputPage $out ) {
-
+       public function initPage( OutputPage $out ) {
                $this->preloadExistence();
-
        }
 
        /**
@@ -199,30 +197,29 @@ abstract class Skin extends ContextSource {
        /**
         * Preload the existence of three commonly-requested pages in a single query
         */
-       function preloadExistence() {
+       protected function preloadExistence() {
                $titles = [];
 
-               $user = $this->getUser();
-               $title = $this->getRelevantTitle();
-
                // User/talk link
+               $user = $this->getUser();
                if ( $user->isLoggedIn() ) {
                        $titles[] = $user->getUserPage();
                        $titles[] = $user->getTalkPage();
                }
 
                // Check, if the page can hold some kind of content, otherwise do nothing
-               if ( !$title->canExist() ) {
-                       // nothing
-               } elseif ( $title->isTalkPage() ) {
-                       $titles[] = $title->getSubjectPage();
-               } else {
-                       $titles[] = $title->getTalkPage();
+               $title = $this->getRelevantTitle();
+               if ( $title->canExist() ) {
+                       if ( $title->isTalkPage() ) {
+                               $titles[] = $title->getSubjectPage();
+                       } else {
+                               $titles[] = $title->getTalkPage();
+                       }
                }
 
                Hooks::run( 'SkinPreloadExistence', [ &$titles, $this ] );
 
-               if ( count( $titles ) ) {
+               if ( $titles ) {
                        $lb = new LinkBatch( $titles );
                        $lb->setCaller( __METHOD__ );
                        $lb->execute();
@@ -649,18 +646,22 @@ abstract class Skin extends ContextSource {
        }
 
        /**
+        * @param OutputPage $out Defaults to $this->getOutput() if left as null
         * @return string
         */
-       function subPageSubtitle() {
-               $out = $this->getOutput();
+       function subPageSubtitle( $out = null ) {
+               if ( $out === null ) {
+                       $out = $this->getOutput();
+               }
+               $title = $out->getTitle();
                $subpages = '';
 
                if ( !Hooks::run( 'SkinSubPageSubtitle', [ &$subpages, $this, $out ] ) ) {
                        return $subpages;
                }
 
-               if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
-                       $ptext = $this->getTitle()->getPrefixedText();
+               if ( $out->isArticle() && MWNamespace::hasSubpages( $title->getNamespace() ) ) {
+                       $ptext = $title->getPrefixedText();
                        if ( strpos( $ptext, '/' ) !== false ) {
                                $links = explode( '/', $ptext );
                                array_pop( $links );
index 162ef60..058d1ce 100644 (file)
@@ -80,8 +80,7 @@ class SpecialMergeHistory extends SpecialPage {
                $this->mTargetObj = $targetObj;
                $this->mDestObj = $destObj;
 
-               if ( $opts->getValue( 'merge' ) && $targetObj &&
-                       $destObj && $opts->getValue( 'mergepoint' ) !== '' ) {
+               if ( $opts->getValue( 'merge' ) && $targetObj && $destObj ) {
                        $this->merge();
 
                        return;
index 45d2415..aed2fa6 100644 (file)
@@ -720,7 +720,7 @@ class SpecialSearch extends SpecialPage {
 
                $out .= "<ul class='mw-search-results'>\n";
                while ( $result ) {
-                       $out .= $this->showHit( $result, $terms, ++$pos );
+                       $out .= $this->showHit( $result, $terms, $pos++ );
                        $result = $matches->next();
                }
                $out .= "</ul>\n";
index ba5171f..08fbeea 100644 (file)
@@ -353,7 +353,7 @@ abstract class UploadBase {
 
                $error = '';
                if ( !Hooks::run( 'UploadVerification',
-                       [ $this->mDestName, $this->mTempPath, &$error ] )
+                       [ $this->mDestName, $this->mTempPath, &$error ], '1.28' )
                ) {
                        return [ 'status' => self::HOOK_ABORTED, 'error' => $error ];
                }
@@ -1416,7 +1416,10 @@ abstract class UploadBase {
                                return [ 'uploaded-event-handler-on-svg', $attrib, $value ];
                        }
 
-                       # href with non-local target (don't allow http://, javascript:, etc)
+                       # Do not allow relative links, or unsafe url schemas.
+                       # For <a> tags, only data:, http: and https: and same-document
+                       # fragment links are allowed. For all other tags, only data:
+                       # and fragment are allowed.
                        if ( $stripped == 'href'
                                && strpos( $value, 'data:' ) !== 0
                                && strpos( $value, '#' ) !== 0
index 8c6c493..6f908cc 100644 (file)
        "password-change-forbidden": "أنت لا يمكنك تغيير كلمات السر على هذا الويكي.",
        "externaldberror": "هناك إما خطأ في دخول قاعدة البيانات الخارجية أو أنه غير مسموح لك بتحديث حسابك الخارجي.",
        "login": "تسجيل الدخول",
+       "login-security": "توكيد هويتك",
        "nav-login-createaccount": "دخول / إنشاء حساب",
        "userlogin": "دخول / إنشاء حساب",
        "userloginnocreate": "تسجيل الدخول",
        "userlogin-resetpassword-link": "نسيت كلمة مرورك؟",
        "userlogin-helplink2": "المساعدة في الدخول",
        "userlogin-loggedin": "أنت {{GENDER:$1|مسجل|مسجلة}} الدخول مسبقًا باسم $1. {{GENDER:$1|استخدم|استخدمي}} النموذج بالأسفل لتسجيل الدخول بحساب آخر.",
+       "userlogin-reauth": "عليك الدخول مرة أخرى لتؤكد أنك {{GENDER:$1|$1}}.",
        "userlogin-createanother": "إنشاء حساب آخر",
        "createacct-emailrequired": "عنوان البريد الإلكتروني",
        "createacct-emailoptional": "البريد الإلكتروني (اختياري)",
        "createacct-reason-ph": "لماذا تقوم بإنشاء حساب آخر",
        "createacct-submit": "افتح الحساب",
        "createacct-another-submit": "أنشئ حسابا",
+       "createacct-continue-submit": "مواصلة إنشاء الحساب",
+       "createacct-another-continue-submit": "مواصلة إنشاء الحساب",
        "createacct-benefit-heading": "{{SITENAME}} موقع يساهم فيه أشخاص مثلك.",
        "createacct-benefit-body1": "{{PLURAL:$1|تحريرا|تحريرات}}",
        "createacct-benefit-body2": "{{PLURAL:$1|صفحة}}",
        "rightslogtext": "هذا سجل بالتغييرات في صلاحيات المستخدمين.",
        "action-read": "قراءة هذه الصفحة",
        "action-edit": "تعديل هذه الصفحة",
-       "action-createpage": "إنشاء الصفحات",
+       "action-createpage": "إنشاء هذه الصفحة",
        "action-createtalk": "إنشاء صفحات النقاش",
        "action-createaccount": "إنشاء حساب المستخدم هذا",
        "action-autocreateaccount": "تلقائيا إنشاء هذا الحساب مستخدم خارجي",
        "searchsuggest-containing": "يحتوي...",
        "api-error-badaccess-groups": "لا يسمح لك بتحميل الملفات إلى هذه الويكي.",
        "api-error-badtoken": "خطأ داخلي: رمز مميز غير صحيح.",
+       "api-error-blocked": "لقد منعت من التحرير.",
        "api-error-copyuploaddisabled": "تم تعطيل تحميل من رابط على هذا الخادم.",
        "api-error-duplicate": "هناك {{PLURAL:$1|هو ملف آخر|كذلك$2 بعض الملفات الأخرى}} مسبقاً على الموقع بنفس المضمون.",
        "api-error-duplicate-archive": "هناك {{PLURAL:$1|كان ملف آخر |كذلك بعض الملفات الأخرى}} مسبقاً على الموقع بنفس المضمون، ولكن {{PLURAL:$1|أنه تم | إجراء}} الحذف لها.",
        "special-characters-group-ipa": "صوتية دولية",
        "special-characters-group-symbols": "رموز",
        "special-characters-group-greek": "يونانية",
+       "special-characters-group-greekextended": "يونانية موسعة",
        "special-characters-group-cyrillic": "كيريلية",
        "special-characters-group-arabic": "عربية",
        "special-characters-group-arabicextended": "عربية موسعة",
        "log-action-filter-rights-rights": "تغيير يدوي",
        "log-action-filter-upload-upload": "رفع جديد",
        "log-action-filter-upload-overwrite": "إعادة الرفع",
+       "authmanager-create-disabled": "إنشاء الحسابات معطل.",
+       "authmanager-create-from-login": "لإنشاء حساب، برجاء ملء الحقول أدناه.",
        "authmanager-email-label": "البريد الإلكتروني",
        "authmanager-email-help": "عنوان البريد الإلكتروني",
        "authmanager-realname-label": "الاسم الحقيقي",
index 58f5124..7d270e5 100644 (file)
        "resetpass-abort-generic": "رمز دَییشدیرمک، بیر اوزانتی ایله یاریدا کسیلیب‌دیر.",
        "resetpass-expired": "گیریش رمزینیز بایات اولوب.لطفا گیریشینیز اوچون یئنی بیر گیریش رمزی سئچین.",
        "resetpass-expired-soft": "گیریش رمزینیز بایات اولوب و یئنی دن تنظیم اولونمالی دیر. لوطفا ایندی بیر یئنی رمز سئچیب یا دا یئنی دن تنظیم ائتمک اوچون سونرالیقدا\"{{int:authprovider-resetpass-skip-label}}\" دویمه سین باسین.",
-       "resetpass-validity-soft": "گیریش رمزینیز دوز دئیییل:$1\nلوطفا ایندی بیر یئنی گیریش رمزی سئچین یا دا\"{{int:authprovider-resetpass-skip-label}}\" باسین کی سونرا اونو یئنی دن سئچرسیز.",
+       "resetpass-validity-soft": "گیریش رمزینیز دۆز دئییل:$1\nلۆطفاً ایندی یئنی بیر گیریش رمزی سئچین یا دا داها سوْنرا اوْنو یئنیدن سئچمه‌نیز اۆچون \"{{int:authprovider-resetpass-skip-label}}\" اۆستونده کیلیک ائدین.",
        "passwordreset": "رمزی یئنی‌له",
        "passwordreset-text-one": "رمزینیزی صیفیرلاماق اوچون بو فورمو باشا چاتدیرین.",
        "passwordreset-text-many": "{{PLURAL:$1|رمزینیزی صیفیرلاماق اوچون، بیرینی دولدورون.}}",
        "mergehistory-comment": "[[:$1]]، [[:$2]] ایله بیرلشدیریلدی: $3",
        "mergehistory-same-destination": "قایناق و مقصد صحیفه‌لر، بیر اولانمازلار",
        "mergehistory-reason": "ندن",
-       "mergelog": "بیرلشدیرمه سیاهی‌سی",
+       "mergelog": "بیرلشدیرمه ژورنالی",
        "revertmerge": "آيیر",
-       "mergelogpagetext": "آشاغدی‌دا، ان سون صحیفه بیرلشدیریلمه‌لری گؤستریلیر.",
+       "mergelogpagetext": "آشاغیدا، ان سون صفحه گئچمیشلری بیرلشدیریلمه‌لری سیرالانیر.",
        "history-title": "«$1»-ین گئچمیشی",
        "difference-title": "«$1» نوسخه‌لری‌نین آراسینداکی فرقلری",
        "difference-title-multipage": "«$1» و «$2» صحیفه‌لرین آراسینداکی فرقلر",
        "preferences": "ترجیحلر",
        "mypreferences": "ترجیحلر",
        "prefs-edits": "دَییشمه‌لرین سایی:",
-       "prefsnologintext2": "خواهیش اولونور تنظیملرینیزی دَییشمک اوچون گیریش ائدین.",
+       "prefsnologintext2": "لوطفا تنظیملرینیزی دَییشمک اوچون گیریش ائدین.",
        "prefs-skin": "قابیق",
        "skin-preview": "اؤن‌گؤستریش",
        "datedefault": "سئچیم‌سیز",
        "upload-permitted": "{{PLURAL:$2|تیپ|تیپ لر}} مجاز پرونده لر: $1.",
        "upload-preferred": "ترجیح وئریلن فايل تیپلری{{PLURAL:$2|تیپ|تیپ لر}}: $1.",
        "upload-prohibited": "ایجازه وئریلمه‌ين فايل تیپلری{{PLURAL:$2|تیپ|تیپ لر}}: $1.",
-       "uploadlogpage": "یوکلمه قئیدلری",
+       "uploadlogpage": "یۆکله‌مه‌لر ژورنالی",
        "uploadlogpagetext": "آشاغیدا ان سوْن یوکله‌نیلن فایللارین لیستی گؤستریلیر.\nداها گؤرونوش‌لو یوْخلاماق اوچون، [[Special:NewFiles|یئنی فایللار قالری‌سینه]] باخین.",
        "filename": "فایل آدی",
        "filedesc": "قیساسی",
        "showhideselectedlogentries": "گؤستریش/گیزلمه سییاهه ده سئچیلمیش‌لر اوچون",
        "allpages": "بوتون صفحه‌لر",
        "nextpage": "سونراکی صفحه‌‌ ($1)",
-       "prevpage": "اولکی صحیفه ($1)",
+       "prevpage": "قاباقکی صفحه ($1)",
        "allpagesfrom": "بو حرفله باشلایان صفحه‌لری گؤستر:",
        "allpagesto": "بو حرفله قورتولان صفحه لری گؤستر:",
        "allarticles": "بوتون صفحه‌لر",
        "actioncomplete": "چالیشما سوناچاتدی",
        "actionfailed": "چالیشما اوغورسوز اولدو",
        "deletedtext": "\"$1\" سیلیندی.\nسونونجو سیلینمه‌لره باخ: $2.",
-       "dellogpage": "سیلمه قئیدی",
-       "dellogpagetext": "اÙ\86 Ø³Ù\88Ù\86 Ø³Û\8cÙ\84Û\8cÙ\86Ù\85Û\8cØ´ ØµØ­Û\8cÙ\81Ù\87â\80\8cÙ\84رÛ\8cÙ\86 Ø³Û\8cاÙ\87Û\8câ\80\8cسÛ\8c.",
+       "dellogpage": "سیلمه ژورنالی",
+       "dellogpagetext": "بÙ\88Ø\8c Ø§Ù\86 Ø³Ù\88Ù\86 Ø³Û\8cÙ\84Û\8cÙ\86Ù\85Û\8cØ´ ØµÙ\81Ø­Ù\87â\80\8cÙ\84رÛ\8cÙ\86 Ù\84Û\8cستÛ\8cدÛ\8cر.",
        "deletionlog": "سیلمه سییاهه‌سی",
        "reverted": "داها اوولکی وئرسیا برپا ائدیلدی",
        "deletecomment": "ندن:",
        "changecontentmodel-reason-label": "ندن:",
        "logentry-contentmodel-change-revertlink": "قایتار",
        "logentry-contentmodel-change-revert": "قایتار",
-       "protectlogpage": "قوروما قئیدلری",
+       "protectlogpage": "قوروما ژورنالی",
        "protectlogtext": "آشاغی‌داکی، صحیفه قوروما‌لارینا دییشیک‌لیک‌لرین بیر سیاهی‌سی‌دیر.\nحال-حاضردا تطبیق اولونان صحیفه قوروما‌لاری اوچون [[Special:ProtectedPages| قوروما آلتینا آلینمیش صحیفه‌لر سیاهی‌سینا]] باخا بیلرسینیز.",
        "protectedarticle": "«[[$1]]» قوْروندو",
        "modifiedarticleprotection": "\"[[$1]]\" صحیفه‌سی اوچون محافظه سویه‌سی دییشیلدی",
        "protect-locked-dblock": "وئریلن‌لر بازاسی کیلیدلی اولدوغو اوچون محافظه سویه‌سی دییشیله بیلمز.\n'$1 صحیفه‌سینده حال-حاضردا ائده بیلجیینیز عملیات‌لار بون‌لاردیر:",
        "protect-locked-access": "سیزین حسابینیزین محافظه سویه‌سینی دییشمه‌یه ایختیاری یوخ‌دور.\n'$1 صحیفه‌سینده حال-حاضردا ائده بیلجیینیز عملیات‌لار بون‌لاردیر:",
        "protect-cascadeon": "بو صحیفه محافظه‌لی‌دیر، چونکی بو صفحه {{PLURAL:$1|باشقا بیر}} صفحه‌دن کاسکاد محافظه ائدیلمیش‌دیر. سیز بو صفحه‌نین محافظه سویه‌سینی دییشدیره بیلرسینیز، بو کاسکاد محافظه‌یه تأثیر ائتمه‌یه‌جک.",
-       "protect-default": "بÙ\88تÙ\88Ù\86 Ø§Û\8cستÛ\8cÙ\81ادÙ\87â\80\8cÚ\86Û\8câ\80\8cلره ایجازه وئر",
+       "protect-default": "بÙ\88تÙ\88Ù\86 Ø§Û\8cØ´Ù\84دÙ\86لره ایجازه وئر",
        "protect-fallback": "یالنیز «$1» ایجازه‌سی اولان ایستیفاده‌چیلره ایجازه وئر",
        "protect-level-autoconfirmed": "تکجه اوْتوماتیک تأیید اوْلموش ایشلدن‌لره ایجازه وئر",
        "protect-level-sysop": "یالنیز ایداره‌چیلره ایجازه وئر",
        "blocklist-target": "هدف",
        "blocklist-expiry": "قورتولما تاریخی",
        "blocklist-by": "باغلایان ایداره‌چی",
-       "blocklist-params": "بلوک پارامئترلری",
+       "blocklist-params": "باغلاما معیارلاری",
        "blocklist-reason": "نَدَن‌لیک",
        "ipblocklist-submit": "آختار",
        "ipblocklist-localblock": "يئرلی بلوک",
        "import-options-wrong": "{{PLURAL:$2|جزئیات| جزئیات}} یانلیش: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "وئریلن کؤک صحیفه‌‌سی اعتبارسیز آددیر.",
        "import-rootpage-nosubpage": "آد فضا سی  \"$1\" آنا باسئ ٔآلت صحیفه اوچون اجازه وئرمیر.",
-       "importlogpage": "Ú\86Û\8cخارÛ\8cÙ\84Ù\85ا Ú¯Ù\88Ù\86دÙ\87â\80\8cÙ\84Û\8cÚ¯ی",
+       "importlogpage": "Ú\86Û\8cخارÛ\8cÙ\84Ù\85ا Ú\98Ù\88رÙ\86اÙ\84ی",
        "importlogpagetext": "آیری ویکیلردن، دَییشیکلیک گئچمیشلریله بیرلیک‌ده گتیریلمیش صفحه‌لر.",
        "import-logentry-upload-detail": "{{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
        "import-logentry-interwiki-detail": "$2-دن {{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
        "pageinfo-not-current": "تأسفله بو بیلگیلری اسکی نوسخه‌لره وئرمک اولانماز بیر ایش‌دیر.",
        "pageinfo-header-basic": "اصلی ایطلاعات",
        "pageinfo-header-edits": "دَییشدیرمه گئچمیشی",
-       "pageinfo-header-restrictions": "صفحه دن محافظت ائله مک",
+       "pageinfo-header-restrictions": "صفحه قوْروماسی",
        "pageinfo-header-properties": "صفحه خصوصیتلری",
        "pageinfo-display-title": "گؤستریلن باشلیق",
        "pageinfo-default-sort": "فرض ائدیلن سیرالاما آچاری",
        "pageinfo-robot-policy": "بوتلارلا ایندِکسلنیر",
        "pageinfo-robot-index": "ایجازه‌لی",
        "pageinfo-robot-noindex": "ایجازه‌سیز",
-       "pageinfo-watchers": "صحیفه‌نین تاماشا‌چی سایی",
+       "pageinfo-watchers": "صفحه‌نین تاماشا‌چی سایی",
        "pageinfo-few-watchers": "$1-دن آز {{PLURAL:$1|ایزله‌ین}}",
        "pageinfo-redirects-name": "بو صحیفه‌یه یول‌لاندیرما سایی‌سی",
        "pageinfo-subpages-name": "بو صحیفه‌نین آلت‌صحیفه‌لری",
        "pageinfo-subpages-value": "$1 ({{PLURAL:$2|بیر|$2}} یول‌لاندیرما؛ {{PLURAL:$3|بیر|$3}} قِیری-یول‌لاندیرما)",
-       "pageinfo-firstuser": "صحیفنی یارا‌دان",
-       "pageinfo-firsttime": "صحیفه‌نین یارانما تاریخی",
+       "pageinfo-firstuser": "صفحه‌نی یارا‌دان",
+       "pageinfo-firsttime": "صفحه‌نین یارانما تاریخی",
        "pageinfo-lastuser": "سونونجو دییشدیرن",
        "pageinfo-lasttime": "سونونجو دییشدیر‌نین تاریخی",
        "pageinfo-edits": "دییشدیر‌لرین سایی",
        "pageinfo-authors": "فرق‌لی مؤلف‌لرین سایی",
-       "pageinfo-recent-edits": "سÙ\88Ù\86 Ø²Ø§Ù\85اÙ\86Ù\84ارداکÛ\8c ØªÙ\86زÛ\8cÙ\85Ù\84Ù\87â\80\8cÙ\85Ù\87â\80\8cÙ\84ر (سون $1)",
-       "pageinfo-recent-authors": "فرقلی يازارلارین سون سايی",
+       "pageinfo-recent-edits": "سÙ\88Ù\86 Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\85Ù\87â\80\8cÙ\84ر Ø³Ø§Û\8cÛ\8c (سون $1)",
+       "pageinfo-recent-authors": "فرقلی يازانلارین سون سايی",
        "pageinfo-magic-words": "سیحیرلی {{PLURAL:$1|بیر|$1}} سؤزجوک ($1)",
        "pageinfo-hidden-categories": "گیزلی {{PLURAL:$1|بؤلمه|بؤلمه‌لر}} ($1)",
        "pageinfo-templates": "ایشله‌دیلمیش {{PLURAL:$1|بیر|$1}} شابلون ($1)",
        "markedaspatrollederror-noautopatrol": "اؤز دییشیک‌لیک‌لرینیزی یوخلاییب ایشاره‌له‌یه بیلمزسینیز.",
        "markedaspatrollednotify": "$1-اوستونده ديَیشیکلیک قئيد ائدیلمیشدیر، نئجه کی پاترول کئشیگی چکدی.",
        "markedaspatrollederrornotify": "دولانماق برچسبی مووفقیت سیز اولدو",
-       "patrol-log-page": "پاترول گونده‌لیگی",
-       "patrol-log-header": "بÙ\88 Û\8cÙ\88Ø®Ù\84اÙ\86Ù\85Û\8cØ´ Ø¯Û\8cÛ\8cØ´Û\8cÚ©â\80\8cÙ\84Û\8cÚ©â\80\8cÙ\84رÛ\8cÙ\86 Ú¯Ù\88Ù\86دÙ\87â\80\8cÙ\84Û\8cÚ¯ی‌دیر.",
+       "patrol-log-page": "دولانما ژورنالی",
+       "patrol-log-header": "بÙ\88 Û\8cÙ\88Ø®Ù\84اÙ\86Ù\85Û\8cØ´ Ø¯Û\8cÛ\8cØ´Û\8cÚ©â\80\8cÙ\84Û\8cÚ©â\80\8cÙ\84رÛ\8cÙ\86 Ú\98Ù\88رÙ\86اÙ\84ی‌دیر.",
        "log-show-hide-patrol": "$1 پاترول گونده‌لیگی",
        "log-show-hide-tag": "اِتیکت ژورنالی $1",
        "deletedrevision": "کؤهنه نوسخه لری سیلیندی $1.",
        "confirm-unwatch-button": "اولدو",
        "confirm-unwatch-top": "بو صفحه‌نی ایزله‌دیگینیز صفحه‌لردن قالدیریلدی",
        "quotation-marks": "«$1»",
-       "imgmultipageprev": "&larr; اولکی صحیفه‌‌",
+       "imgmultipageprev": "&rarr; قاباقکی صفحه‌‌",
        "imgmultipagenext": "سونراکی صفحه‌‌ &larr;",
        "imgmultigo": "گئت!",
        "imgmultigoto": "$1 صحیفه‌‌يه گئت",
        "table_pager_next": "سوْنراکی صفحه",
        "table_pager_prev": "قاباقکی صفحه",
        "table_pager_first": "بیرینجی صفحه‌‌",
-       "table_pager_last": "سون صحیفه‌‌",
+       "table_pager_last": "سون صفحه‌‌",
        "table_pager_limit": "صحیفه‌‌ده $1 مؤوقئ سرگیله",
        "table_pager_limit_label": "هر صفحه‌ده اولان موردلر سایی‌سی",
        "table_pager_limit_submit": "گئت",
        "revdelete-restricted": "ایداره‌چیلره محدودیت قویدو",
        "revdelete-unrestricted": "ایداره‌چیلرین محدودیتلرینی گؤتوردو",
        "logentry-block-block": "$1 {{GENDER:$4|$3}}-نی {{GENDER:$2|باغلادی}}. قۇرتارماق تاریخی: $5 $6",
-       "logentry-block-unblock": "$1 {{GENDER:$4|$3}}-نین {{GENDER:$2|بلوکلاماغینی قالدیردی}}",
+       "logentry-block-unblock": "$1 {{GENDER:$4|$3}}-نین {{GENDER:$2|باغلانماغینی گؤتوردو}}",
        "logentry-import-upload": "$1 $3-نی فایل یوکله‌مه یولو ایله {{GENDER:$2|ایچری گتیردی}}",
        "logentry-import-upload-details": "$1 $3-نی فایل یوکله‌مه یولو ایله {{GENDER:$2|ایچری گتیردی}} ($4 {{PLURAL:$4|نوسخه}})",
        "logentry-import-interwiki-details": "$1 $3-نی $5-دن {{GENDER:$2|ایچری گتیردی}} ($4 {{PLURAL:$4|نوسخه}})",
+       "logentry-merge-merge": "$1  $3  ایله  $4-نی {{GENDER:$2| بیرلشدیردی}} ($5-جن نوسخه)",
        "logentry-move-move": "$1، $3 صفحه‌سینی $4-ه {{GENDER:$2|آپاردی}}",
        "logentry-move-move-noredirect": "$1، $3 صفحه‌سینی، یوْل‌لاندیرما قوْیماماق‌لا، $4-ه {{GENDER:$2|آپاردی}}",
        "logentry-move-move_redir": "$1، $3 صفحه‌سینی، $4-ده یوْل‌لاندیرما اۆستونه {{GENDER:$2|آپاردی}}",
        "logentry-rights-rights-legacy": "$1، $3-ین قروپ عوضولوگونو {{GENDER:$2|دَییشدیردی}}",
        "logentry-rights-autopromote": "$1-ین مقامی اوتوماتیک $4-دن $5-ه {{GENDER:$2|آرتیریلدی}}",
        "logentry-upload-upload": "$1 $3 را {{GENDER:$2|یوکلندیردی}}",
+       "log-name-managetags": "اِتیکت ایداره گئچمیشی",
        "log-name-tag": "اِتیکت ژورنالی",
        "rightsnone": "(هئچ)",
        "revdelete-summary": "دَییشدیرمه قیساسی",
        "mw-widgets-dateinput-no-date": "تاریخ سئچیلمه‌ییب",
        "mw-widgets-titleinput-description-new-page": "صفحه هله‌لیک یوخدور",
        "mw-widgets-titleinput-description-redirect": "$1-ه داشی",
+       "log-action-filter-block": "باغلانما نوعو:",
+       "log-action-filter-managetags": "ایداره نوعو:",
        "log-action-filter-move": "حرکت نوعو:",
        "log-action-filter-upload": "یۆکله‌مه نوعو",
+       "log-action-filter-managetags-create": "اِتیکت یاراتما",
+       "log-action-filter-managetags-delete": "اِتیکت سیلمه",
+       "log-action-filter-managetags-activate": "اِتیکت چالیشدیرما",
+       "log-action-filter-managetags-deactivate": "اِتیکت دَییشدیرمه",
        "log-action-filter-upload-upload": "یئنی یۆکله‌مه",
        "log-action-filter-upload-overwrite": "یئنیدن یۆکله‌مه"
 }
index f74e328..5910819 100644 (file)
        "passwordreset-nosuchcaller": "Аўтар выкліку не існуе: $1",
        "passwordreset-ignored": "Скіданьне паролю не адбылося. Магчыма, ня быў наладжаны пастаўшчык?",
        "passwordreset-invalideamil": "Няслушны адрас электроннай пошты",
+       "passwordreset-nodata": "Не былі пададзеныя ні імя ўдзельніка, ні адрас электроннай пошты",
        "changeemail": "Зьмяніць або выдаліць адрас электроннай пошты",
        "changeemail-header": "Запоўніце гэтую форму, каб зьмяніць ваш адрас электроннай пошты. Калі вы жадаеце выдаліць адрас электроннай пошты, далучаны да вашага рахунку, пакіньце поле новага адрасу электроннай пошты пустым пры запаўненьні формы.",
        "changeemail-passwordrequired": "Вам трэба будзе ўвесьці ваш пароль, каб пацьвердзіць гэтую зьмену.",
        "action-read": "чытаньне гэтай старонкі",
        "action-edit": "рэдагаваньне гэтай старонкі",
        "action-createpage": "стварэньне гэтай старонкі",
-       "action-createtalk": "стварэньне старонак абмеркаваньняў",
+       "action-createtalk": "стварэньне гэтай старонкі абмеркаваньня",
        "action-createaccount": "стварэньне гэтага рахунку ўдзельніка",
        "action-autocreateaccount": "аўтаматычнае стварэньне гэтага рахунку вонкавага ўдзельніка",
        "action-history": "прагляд гісторыі гэтай старонкі",
        "action-managechangetags": "стварэньне і (дэ)актывацыю метак",
        "action-applychangetags": "дадаваньне метак пры рэдагаваньні",
        "action-changetags": "дадаваньне і выдаленьне адвольных метак да асобных вэрсіяў і запісаў у журнале падзеяў",
+       "action-deletechangetags": "выдаленьне метак з базы зьвестак",
        "nchanges": "$1 {{PLURAL:$1|зьмена|зьмены|зьменаў}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з апошняга візыту}}",
        "enhancedrc-history": "гісторыя",
        "upload-http-error": "Узьнікла памылка HTTP: $1",
        "upload-copy-upload-invalid-domain": "Капіяваньне загрузак не дазволенае ў гэтым дамэне.",
        "upload-foreign-cant-upload": "Гэтая вікі не наладжаная для загрузкі файлаў у запытанае вонкавае сховішча файлаў.",
+       "upload-foreign-cant-load-config": "Не атрымалася загрузіць канфігурацыю для загрузкі файлаў у вонкавае сховішча.",
        "upload-dialog-title": "Загрузка файла",
        "upload-dialog-button-cancel": "Адмяніць",
        "upload-dialog-button-done": "Зроблена",
index f9da6ea..0655819 100644 (file)
@@ -15,7 +15,7 @@
                ]
        },
        "tog-underline": "КӀел сиз хьакха хьажорган:",
-       "tog-hideminor": "Ð\9aÑ\8aайладаÑ\85а ÐºÐ¸Ð³Ð¸Ð¹Ñ\80а Ð½Ð¸Ñ\81даÑ\80Ñ\88 Ð¾Ñ\86 Ð¼Ð¾Ð³Ó\80ама ÐºÐµÑ\80ла Ñ\85ийÑ\86амеÑ\85Ñ\8c",
+       "tog-hideminor": "Ð\9aÑ\8aайладаÑ\85а Ð¶Ð¸Ð¼Ð° Ð½Ð¸Ñ\81даÑ\80Ñ\88 ÐºÐµÑ\80ла Ð¼Ð¾Ð³Ó\80ам Ñ\8eкÑ\8aаÑ\80а",
        "tog-hidepatrolled": "Къайладаха гӀаролладина нисдарш оц могӀама керла нисдаршкахь",
        "tog-newpageshidepatrolled": "Къайлаяха гӀароллайина агӀонаш оьцу могӀама керла агӀонашкахь",
        "tog-hidecategorization": "Къайлаяха агӀонийн категореш",
        "sp-contributions-username": "IP-адрес я декъашхочун цӀе:",
        "sp-contributions-toponly": "Гайта тӀаьххьарлера хийцамаш",
        "sp-contributions-newonly": "АгӀонаш кхоллар бен ма гайта",
+       "sp-contributions-hideminor": "Къайладаха жима нисдарш",
        "sp-contributions-submit": "Лахар",
        "whatlinkshere": "Кхуза хьажоргаш",
        "whatlinkshere-title": "«$1» тӀе хьажоргаш йолу агӀонаш",
index 9588f8f..4ce8b80 100644 (file)
        "group": "Skupina:",
        "group-user": "Uživatelé",
        "group-autoconfirmed": "Automaticky schválení uživatelé",
-       "group-bot": "Boti",
+       "group-bot": "Roboti",
        "group-sysop": "Správci",
        "group-bureaucrat": "Byrokraté",
        "group-suppress": "Utajovatelé",
        "group-all": "(všichni)",
        "group-user-member": "{{GENDER:$1|uživatel|uživatelka|uživatel}}",
        "group-autoconfirmed-member": "automaticky {{GENDER:$1|schválený uživatel|schválená uživatelka|schválený uživatel}}",
-       "group-bot-member": "{{GENDER:$1|bot|botka|bot}}",
+       "group-bot-member": "{{GENDER:$1|robot|robotka}}",
        "group-sysop-member": "{{GENDER:$1|správce|správkyně|správce}}",
        "group-bureaucrat-member": "{{GENDER:$1|byrokrat|byrokratka|byrokrat}}",
        "group-suppress-member": "{{GENDER:$1|utajovatel|utajovatelka|utajovatel}}",
        "recentchanges-feed-description": "Na tomto kanále sledujte poslední změny na {{grammar:6sg|{{SITENAME}}}}.",
        "recentchanges-label-newpage": "Touto editací byla založena nová stránka",
        "recentchanges-label-minor": "Toto je malá editace",
-       "recentchanges-label-bot": "Tuto editaci provedl bot",
+       "recentchanges-label-bot": "Tuto editaci provedl robot",
        "recentchanges-label-unpatrolled": "Tato změna dosud nebyla prověřena",
        "recentchanges-label-plusminus": "Velikost stránky se změnila o tolik bajtů",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
        "noscript.css": "/* Zde uvedené CSS bude ovlivňovat uživatele s vypnutým JavaScriptem */",
        "group-autoconfirmed.css": "/* Zde uvedené CSS bude ovlivňovat pouze automaticky schválené uživatele */",
        "group-user.css": "/* Zde uvedené CSS bude ovlivňovat pouze registrované uživatele */",
-       "group-bot.css": "/* Zde uvedené CSS bude ovlivňovat pouze boty */",
+       "group-bot.css": "/* Zde uvedené CSS bude ovlivňovat pouze roboty */",
        "group-sysop.css": "/* Zde uvedené CSS bude ovlivňovat pouze správce */",
        "group-bureaucrat.css": "/* Zde uvedené CSS bude ovlivňovat pouze byrokraty */",
        "common.js": "/* Zde uvedený JavaScript bude použit pro všechny uživatele při načtení každé stránky. */",
        "group-autoconfirmed.js": "/* Zde uvedený JavaScript bude použit pouze pro automaticky schválené uživatele */",
        "group-user.js": "/* Zde uvedený JavaScript bude použit pouze pro registrované uživatele */",
-       "group-bot.js": "/* Zde uvedený JavaScript bude použit pouze pro boty */",
+       "group-bot.js": "/* Zde uvedený JavaScript bude použit pouze pro roboty */",
        "group-sysop.js": "/* Zde uvedený JavaScript bude použit pouze pro správce */",
        "group-bureaucrat.js": "/* Zde uvedený JavaScript bude použit pouze pro byrokraty */",
        "anonymous": "{{PLURAL:$1|anonymního uživatele|anonymních uživatelů}} {{GRAMMAR:2sg|{{SITENAME}}}}",
index 50534b4..666ff1e 100644 (file)
@@ -96,7 +96,7 @@
        "tog-hidepatrolled": "Kontrollierte Änderungen in den „Letzten Änderungen“ ausblenden",
        "tog-newpageshidepatrolled": "Kontrollierte Seiten bei den „Neuen Seiten“ ausblenden",
        "tog-hidecategorization": "Kategorisierungen von Seiten ausblenden",
-       "tog-extendwatchlist": "In der Beobachtungsliste alle und nicht nur die aktuellsten Änderungen anzeigen",
+       "tog-extendwatchlist": "Alle und nicht nur die aktuellsten Änderungen in der Beobachtungsliste anzeigen",
        "tog-usenewrc": "Änderungen auf „Letzte Änderungen“ und der Beobachtungsliste nach Seite gruppieren",
        "tog-numberheadings": "Überschriften automatisch nummerieren",
        "tog-showtoolbar": "Bearbeiten-Werkzeugleiste anzeigen",
        "tog-watchdefault": "Selbst geänderte Seiten und Dateien automatisch beobachten",
        "tog-watchmoves": "Selbst verschobene Seiten und Dateien automatisch beobachten",
        "tog-watchdeletion": "Selbst gelöschte Seiten und Dateien automatisch beobachten",
-       "tog-watchuploads": "Neue hochgeladene Dateien zu meiner Beobachtungsliste hinzufügen",
+       "tog-watchuploads": "Selbst hochgeladene Dateien automatisch beobachten",
        "tog-watchrollback": "Seiten, bei denen ich eine Zurücksetzung durchgeführt habe, automatisch beobachten",
        "tog-minordefault": "Eigene Änderungen standardmäßig als geringfügig markieren",
        "tog-previewontop": "Vorschau oberhalb des Bearbeitungsfensters anzeigen",
        "tog-watchlisthidebots": "Bearbeitungen durch Bots in der Beobachtungsliste ausblenden",
        "tog-watchlisthideminor": "Kleine Bearbeitungen in der Beobachtungsliste ausblenden",
        "tog-watchlisthideliu": "Bearbeitungen angemeldeter Benutzer in der Beobachtungsliste ausblenden",
-       "tog-watchlistreloadautomatically": "Die Beobachtungsliste automatisch neu laden, wenn ein Filter geändert wurde (erfordert JavaScript)",
+       "tog-watchlistreloadautomatically": "Sofern ein Filter geändert wurde, die Beobachtungsliste automatisch neu laden (erfordert JavaScript)",
        "tog-watchlisthideanons": "Bearbeitungen anonymer Benutzer (IP-Adressen) in der Beobachtungsliste ausblenden",
        "tog-watchlisthidepatrolled": "Kontrollierte Änderungen in der Beobachtungsliste ausblenden",
-       "tog-watchlisthidecategorization": "Kategorisierungen von Seiten ausblenden",
+       "tog-watchlisthidecategorization": "Kategorisierungen von Seiten in der Beobachtungsliste ausblenden",
        "tog-ccmeonemails": "Schicke mir Kopien der E-Mails, die ich anderen Benutzern sende",
        "tog-diffonly": "Beim Versionsvergleich nur die Unterschiede und nicht die vollständige Seite anzeigen",
        "tog-showhiddencats": "Versteckte Kategorien anzeigen",
index b66ac81..6ef0621 100644 (file)
        "categorypage": "Pela kategoriya bıasne",
        "viewtalkpage": "Werênayışi bıvêne",
        "otherlanguages": "Zıwananê binan de",
-       "redirectedfrom": "(Pele da $1 ra heteneyê)",
+       "redirectedfrom": "($1 ra kırışı yê)",
        "redirectpagesub": "Pela berdışi",
        "redirectto": "Beno hetê:",
        "lastmodifiedat": "Ena pele tewr peyên roca $2, $1 de biya rocaniye.",
        "mergelog": "Qeydé zew kerdışi",
        "revertmerge": "Abırnê",
        "mergelogpagetext": "Cêr de yew liste esta ke mocnena ra, raya tewr peyêne kamci pela tarixi be a bine ra şanawa pê.",
-       "history-title": "Tarixê çımraviyarnayışê \"$1\"",
+       "history-title": "Revizyona pela \"$1\"",
        "difference-title": "Pela \"$1\" ferqê çım ra viyarnayışan",
        "difference-title-multipage": "Ferkê pelan dê \"$1\" u \"$2\"",
        "difference-multipage": "(Ferqê pelan)",
        "powersearch-ns": "Cayanê nameyan de cıgeyrayış:",
        "powersearch-togglelabel": "Kontrol ke:",
        "powersearch-toggleall": "Pêro",
-       "powersearch-togglenone": "Çıniyo",
+       "powersearch-togglenone": "Qet",
        "powersearch-remember": "Cıgeyrayışanê newe tepyayan de biya xo viri",
        "search-external": "Cıgeyrayışê teberi",
        "searchdisabled": "{{SITENAME}} no keyepel de cıgerayiş muweqqet bıryayo. no benatê de şıma pê Google eşkeni zerreyê {{SITENAME}} de cıgerayiş bıkeri.",
        "apihelp": "Peştiya APIyi",
        "apihelp-no-such-module": "Modulê \"$1\" çıniyo.",
        "apisandbox": "API qumdor",
+       "apisandbox-fullscreen": "Panela hera kerdışi",
        "apisandbox-submit": "Bıwazê",
        "apisandbox-reset": "Bestere",
        "apisandbox-retry": "Fına",
        "apisandbox-dynamic-parameters": "Parametreya debyayi",
        "apisandbox-dynamic-parameters-add-label": "Parametre dek:",
        "apisandbox-dynamic-parameters-add-placeholder": "Nmaey parametrey",
+       "apisandbox-submit-invalid-fields-title": "Tay çiy ters şı",
        "apisandbox-results": "Neticey",
        "apisandbox-sending-request": "API waştış rışêno...",
        "apisandbox-request-url-label": "URL waştış:",
        "namespace_association": "Heruna nameyanê elaqedaran",
        "tooltip-namespace_association": "Herunda canemiya elekeyın nışan kerdışi sero qıse kerdışi yana zerre dekerdışi rê ena dora tesdiqi nışan kerê",
        "blanknamespace": "(Ser)",
-       "contributions": "Dekerdışê {{GENDER:$1|karber}}i",
+       "contributions": "İştiraqê {{GENDER:$1|karber}}i",
        "contributions-title": "Dekerdenê karber de $1",
        "mycontris": "İştıraqi",
        "anoncontribs": "İştıraqi",
        "file-info-png-frames": "$1 {{PLURAL:$1|çerçeve|çerçeveyi}}",
        "file-no-thumb-animation": "'''Not: Dılet tekniko limit, gırd agozneya resm de qıckek de animasyoni miyan dı nêbo.'''",
        "file-no-thumb-animation-gif": "'''Not: Dılet tekniko limit, gırd agozneya resm de qıckek de  GIF imaci de animasyon do nêbo.'''",
-       "newimages": "Galeriye Dosyan dê newan",
+       "newimages": "Galeriya dosyanê neweyan",
        "imagelisttext": "Cêr de yew listeyê '''$1''' esto {{PLURAL:$1|dosya|dosyayi}} veçiniya $2.",
        "newimages-summary": "Ena pela xasi dosyayi ke peni de bar biyayeyi mocnane.",
        "newimages-legend": "Avrêc",
        "exif-scenecapturetype-1": "Manzara",
        "exif-scenecapturetype-2": "Portre",
        "exif-scenecapturetype-3": "şew-antış",
-       "exif-gaincontrol-0": "Çıniyo",
+       "exif-gaincontrol-0": "Qet yew",
        "exif-gaincontrol-1": "Low gain up",
        "exif-gaincontrol-2": "High gain up",
        "exif-gaincontrol-3": "Low gain down",
        "autosumm-replace": "Maqale pê '$1' vuriya",
        "autoredircomment": "heteneya [[$1]]",
        "autosumm-new": "Pela vıraziyê, '$1' bıvinê",
-       "size-bytes": "$1 B",
+       "size-bytes": "$1 {{PLURAL:$1|bayt|bayti}}",
        "size-kilobytes": "$1 KB",
        "size-megabytes": "$1 MB",
        "size-gigabytes": "$1 GB",
index 1de7b23..9ef95f3 100644 (file)
        "minoredit": "This is a minor edit",
        "watchthis": "Watch this page",
        "savearticle": "Save page",
+       "savechanges": "Save changes",
        "publishpage": "Publish page",
+       "publishchanges": "Publish changes",
        "preview": "Preview",
        "showpreview": "Show preview",
        "showdiff": "Show changes",
index c11a673..c7272ed 100644 (file)
@@ -54,7 +54,8 @@
                        "Freshman404",
                        "Hamisun",
                        "Matma Rex",
-                       "4nn1l2"
+                       "4nn1l2",
+                       "Namo"
                ]
        },
        "tog-underline": "خط کشیدن زیر پیوندها:",
        "rightslogtext": "این سیاههٔ تغییرات اختیارات کاربر است.",
        "action-read": "خواندن این صفحه",
        "action-edit": "ویرایش این صفحه",
-       "action-createpage": "ایجاد صفحه",
-       "action-createtalk": "اÛ\8cجاد ØµÙ\81Ø­Ù\87â\80\8cÙ\87اÛ\8c بحث",
+       "action-createpage": "اÛ\8cجاد Ø§Û\8cÙ\86 ØµÙ\81Ø­Ù\87",
+       "action-createtalk": "اÛ\8cجاد Ø§Û\8cÙ\86 ØµÙ\81Ø­Ù\87 بحث",
        "action-createaccount": "ایجاد این حساب کاربری",
        "action-autocreateaccount": "حساب کاربری خارجی به صورت خودکار ساخته شد",
        "action-history": "مشاهده تاریخچه این صفحه",
        "log-action-filter-suppress-reblock": "مخفی‌سازی کاربر با بستن مجدد",
        "log-action-filter-upload-upload": "بارگذاری جدید",
        "log-action-filter-upload-overwrite": "بارگذاری دوباره",
+       "authmanager-create-disabled": "قابلیت ایجاد حساب غیرفعال است",
+       "authmanager-authplugin-setpass-failed-title": "تغییر گذرواژه ناموفق بود",
+       "authmanager-authplugin-setpass-bad-domain": "دامنه نامعتبر است.",
+       "authmanager-autocreate-noperm": "ایجاد حساب خودکار مجاز نیست.",
+       "authmanager-autocreate-exception": "ایجاد حساب کاربری به خاطر خطاهای قبلی به طور موقت غیرفعال است.",
+       "authmanager-userdoesnotexist": "حساب کاربری «$1» ثبت نشده‌است.",
        "authmanager-email-label": "ایمیل",
        "authmanager-email-help": "آدرس ایمیل",
        "authmanager-realname-label": "نام واقعی",
        "authmanager-realname-help": "نام واقعی کاربر",
        "authmanager-provider-temporarypassword": "گذرواژهٔ موقت",
        "authprovider-resetpass-skip-label": "رها کردن",
+       "specialpage-securitylevel-not-allowed-title": "مجاز نیست",
        "cannotauth-not-allowed-title": "اجازه داده نشد",
        "cannotauth-not-allowed": "شما برای دسترسی به این صفحه مجاز نیستید",
        "changecredentials": "تغییر اعتبارنامه‌ها",
index 61da651..0588688 100644 (file)
        "tog-diffonly": "Ne pas afficher le contenu des pages sous les diffs",
        "tog-showhiddencats": "Afficher les catégories cachées",
        "tog-norollbackdiff": "Ne pas afficher le diff après avoir révoqué",
-       "tog-useeditwarning": "M’avertir quand je quitte une page de modification sans publier les changements",
-       "tog-prefershttps": "Conserver une connexion sécurisée une fois connecté(e)",
+       "tog-useeditwarning": "M’avertir quand je quitte une page en cours de modification sans avoir sauvegardé",
+       "tog-prefershttps": "Toujours utiliser une connexion sécurisée pour se connecter",
        "underline-always": "Toujours",
        "underline-never": "Jamais",
        "underline-default": "Valeur par défaut du thème ou du navigateur",
index 34c55e4..5fbcb34 100644 (file)
        "rightslogtext": "Este é un rexistro dos cambios nos permisos de usuario.",
        "action-read": "ler esta páxina",
        "action-edit": "editar esta páxina",
-       "action-createpage": "crear páxinas",
-       "action-createtalk": "crear páxinas de conversa",
+       "action-createpage": "crear esta páxina",
+       "action-createtalk": "crear esta páxina de conversa",
        "action-createaccount": "crear esta conta de usuario",
        "action-autocreateaccount": "crear automaticamente esta conta de usuario externa",
        "action-history": "ver o historial desta páxina",
index 020e6be..9650318 100644 (file)
        "views": "𐍃𐌹𐌿𐌽𐌴𐌹𐍃",
        "toolbox": "𐍃𐌰𐍂𐍅𐌰𐌽𐍃",
        "otherlanguages": "𐌰𐌽𐌸𐌰𐍂𐌰𐌹𐌼 𐍂𐌰𐌶𐌳𐍉𐌼",
-       "redirectedfrom": "(ð\90\8c½ð\90\8c°ð\90\8c¿ð\90\8c¸ð\90\8c¾ð\90\8c°ð\90\8c½ ð\90\8d\86ð\90\8d\82ð\90\8c°ð\90\8c¼ð\90\8c¹ð\90\8d\83 $1)",
+       "redirectedfrom": "(ð\90\8c¹ð\90\8d\83 ð\90\8d\84ð\90\8c¹ð\90\8c¿ð\90\8c·ð\90\8c°ð\90\8c½ð\90\8d\83\90\8d\84ð\90\8c¹ð\90\8c¿ð\90\8c·ð\90\8c°ð\90\8c½ð\90\8c° ð\90\8c·ð\90\8c¹ð\90\8c³ð\90\8d\82ð\90\8c´ ð\90\8d\86ð\90\8d\82ð\90\8c°ð\90\8c¼ $1)",
        "redirectpagesub": "𐍄𐌰𐌹𐌺𐌾𐌰𐍃𐌴𐌹𐌳𐍉",
        "lastmodifiedat": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌸𐌰𐍄𐌰 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄 𐌹𐌽𐌼𐌰𐌹𐌳𐌹𐌸𐍃 𐍅𐌰𐍃 ($1) ($2).",
        "jumpto": "𐌲𐌰𐌲𐌲 𐌳𐌿:",
        "portal": "𐌱𐌰𐌿𐍂𐌲𐍃 𐌲𐌰𐍅𐌹",
        "portal-url": "Project:𐌱𐌰𐌿𐍂𐌲𐍃 𐌲𐌰𐍅𐌹",
        "privacy": "𐌲𐌰𐍂𐌴𐌳𐌴𐌹𐌽𐍉𐍃 𐍃𐌿𐌽𐌳𐍂𐍉𐍅𐌹𐍃𐌰𐌽𐌰",
-       "privacypage": "Project:ð\90\8c²ð\90\8c°ð\90\8d\82ð\90\8c´𐌳𐌴𐌹𐌽𐍉𐍃 𐍃𐌿𐌽𐌳𐍂𐍉𐍅𐌹𐍃𐌰𐌽𐌰",
+       "privacypage": "Project:ð\90\8c²ð\90\8c°ð\90\8d\82ð\90\8c°ð\90\8c¹𐌳𐌴𐌹𐌽𐍉𐍃 𐍃𐌿𐌽𐌳𐍂𐍉𐍅𐌹𐍃𐌰𐌽𐌰",
        "retrievedfrom": "𐌲𐌰𐌽𐌿𐌼𐌰𐌽 𐍆𐍂𐌰𐌼 \"$1\"",
        "youhavenewmessages": "𐌸𐌿 𐌷𐌰𐌱𐌹𐍃 $1 ($2).",
        "editsection": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹",
        "editold": "𐌼𐌰𐌹𐌳𐌾𐌰𐌽",
-       "editlink": "ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c¾ð\90\8c°ð\90\8c½",
+       "editlink": "ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹",
        "viewsourcelink": "𐍃𐌰𐌹𐍈 𐌱𐍂𐌿𐌽𐌽𐌰𐌽",
        "editsectionhint": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹 𐌳𐌰𐌹𐌻: $1",
        "toc": "𐌹𐌽𐌽𐌰𐌽𐌰",
        "userlogin-yourpassword": "𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
        "userlogin-yourpassword-ph": "𐌼𐌴𐌻𐌰𐌹𐍃 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳 𐌸𐌴𐌹𐌽",
        "createacct-yourpassword-ph": "𐌼𐌴𐌻𐌰𐌹𐍃 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
+       "createacct-yourpasswordagain": "𐌲𐌰𐍃𐌹𐌲𐌻𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
        "createacct-yourpasswordagain-ph": "𐌼𐌴𐌻𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳 𐌰𐍆𐍄𐍂𐌰",
+       "userlogin-remembermypassword": "𐌲𐌰𐍆𐌰𐍃𐍄 𐌼𐌹𐌺 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰𐌽𐌰/𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰",
        "login": "Atgaggan",
        "nav-login-createaccount": "𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽 / 𐌲𐌰𐌻𐌰𐌽𐌲𐌾𐌰𐌽 𐌽𐌹𐌿𐍄𐌰𐌽𐌳𐌹𐍃",
        "userlogin": "Atgaggan / gaskapjan niutandis",
        "userloginnocreate": "𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽",
        "logout": "𐌻𐌴𐌹𐌸𐌰𐌽",
        "userlogout": "𐌻𐌴𐌹𐌸𐌰𐌽",
+       "userlogin-noaccount": "𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽?",
+       "userlogin-joinproject": "𐌲𐌰𐌼𐌰𐌹𐌽𐌴𐌹 {{SITENAME}}",
        "nologinlink": "Gaskapjan þein niutandis",
        "createaccount": "𐌲𐌰𐌻𐌰𐌲𐌾𐌰𐌽 𐌽𐌹𐌿𐍄𐌰𐌽𐌳𐌹𐍃",
        "gotaccount": "Habiþ þu niutandis? '''$1'''",
        "gotaccountlink": "Atgaggan",
        "userlogin-resetpassword-link": "𐌿𐍆𐌰𐍂𐌼𐌿𐌽𐌽𐍉𐌳𐌴𐍃 𐌸𐌴𐌹𐌽𐌰𐌼𐌼𐌰 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳𐌰?",
+       "userlogin-helplink2": "𐌷𐌹𐌻𐍀𐌰 𐌼𐌹𐌸 𐌰𐍄𐌲𐌰𐌲𐌲𐌰",
+       "createacct-emailoptional": "𐌴-𐌱𐍉𐌺𐍉𐍃 (𐌼𐌰𐌷𐍄𐌴𐌹𐌲𐍉𐍃)",
        "createacct-email-ph": "𐌼𐌴𐌻𐌴𐌹 𐌸𐌴𐌹𐌽𐍉𐍃 𐌴-𐌱𐍉𐌺𐍉𐍃",
        "createaccountreason": "𐍆𐌰𐌹𐍂𐌹𐌽𐌰:",
        "createacct-reason": "𐍆𐌰𐌹𐍂𐌹𐌽𐌰",
        "createacct-submit": "𐍃𐌺𐌰𐍀𐌴𐌹 𐌸𐌴𐌹𐌽𐌰 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽",
        "createacct-benefit-heading": "{{SITENAME}} 𐍄𐌰𐍅𐌹𐌸 𐌹𐍃𐍄 𐍆𐍂𐌰𐌼 𐌼𐌰𐌽𐌽𐌰𐌼 𐍃𐍅𐌴 𐌸𐌿𐌺.",
+       "createacct-benefit-body1": "{{PLURAL:$1|𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃|𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|𐌻𐌰𐌿𐍆𐍃|𐌻𐌰𐌿𐌱𐍉𐍃}}",
        "loginlanguagelabel": "Razda: $1",
        "pt-login": "𐌰𐍄𐌲𐌰𐌲𐌲",
+       "pt-login-button": "𐌰𐍄𐌲𐌰𐌲𐌲",
        "pt-createaccount": "𐍃𐌺𐌰𐍀𐌴𐌹 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽",
        "passwordreset": "𐌰𐍆𐍄𐍂𐌰 𐍃𐌰𐍄𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
        "bold_sample": "𐌰𐌱𐍂𐍃 𐌱𐍉𐌺𐌰",
        "savearticle": "𐌲𐌰𐍆𐌰𐍃𐍄 𐌻𐌰𐌿𐍆",
        "preview": "𐍆𐌰𐌿𐍂𐍃𐌰𐌹𐍈𐌰 𐍃𐌴𐌹𐌳𐍉",
        "showpreview": "𐍅𐌹𐍄𐌰𐌽 𐍆𐌰𐌿𐍂𐍃𐌰𐌹𐍈𐌰",
-       "showdiff": "ð\90\8d\85ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½ ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´𐌹𐌽𐍃",
+       "showdiff": "ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c¿ð\90\8c²ð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c¹ð\90\8c½𐌹𐌽𐍃",
        "loginreqlink": "𐌰𐍄𐌲𐌰𐌲𐌲",
        "newarticle": "(Niu)",
        "newarticletext": "𐌻𐌰𐌹𐍃𐍄𐌹𐌳𐌴𐍃 𐌲𐌰𐍅𐌹𐍃 𐌳𐌿 𐌻𐌰𐌿𐌱𐌰 𐍃𐌰𐌴𐌹 𐌽𐌹𐍃𐍄. 𐌳𐌿 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆, 𐌰𐌽 𐌰𐍃𐍄𐍉𐌳𐌴𐌹 𐌼𐌴𐌻𐌾𐌰𐌽 𐌹𐌽 𐌰𐍂𐌺𐌰𐌹 𐌿𐍆 (𐍃𐌰𐌹𐍈 [$1 𐌷𐌹𐌻𐍀𐌰𐌻𐌰𐌿𐍆] 𐌼𐌰𐌽𐌰𐌲𐌹𐌶𐌹𐌽 𐌺𐌿𐌽𐌸𐌾𐌰). 𐌾𐌰𐌱𐌰𐌹 𐌹𐍃 𐌷𐌴𐍂 𐌹𐌽 𐌰𐌹𐍂𐌶𐌴𐌹𐌽𐍃, 𐌲𐌰𐌲𐌲 𐌳𐌿 <𐍃𐍄𐍂𐍉𐌽𐌲>𐌹𐌱𐌿𐌺𐌰𐌷𐌰𐌿𐌱𐌹𐌳𐌹𐌻𐍉𐌽.",
        "updated": "(Nuwisan)",
        "previewnote": "'''𐍃𐌰𐌷 𐌹𐍃𐍄 𐍆𐌰𐌿𐍂𐍃𐌰𐌹𐍈𐌰. 𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐌲𐌰𐌼𐌴𐌻𐌾𐌹𐌸 𐌽𐌹 𐌰𐍆 𐌸𐌹𐌶𐍉𐍃 𐍃𐌴𐌹𐌳𐍉𐍃!'''",
        "editing": "𐌼𐌰𐌹𐌳𐌾𐌰𐌽 𐌰𐍆 $1",
+       "creating": "𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐍃/𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐌴𐌹 $1",
        "editingsection": "𐌼𐌰𐌹𐌳𐌾𐌰𐌽 𐌰𐍆 $1 (𐍆𐌴𐍂𐌰)",
        "editingcomment": "𐌼𐌰𐌹𐌳𐌾𐌰𐌽 𐌰𐍆 $1 (𐍂𐍉𐌳𐌾𐌰𐍆𐌴𐍂𐌰)",
        "yourdiff": "𐌼𐌹𐍃𐍃𐌰𐌻𐌴𐌹𐌺𐍉𐍃",
        "template-semiprotected": "(halb-gabaírgjan)",
        "hiddencategories": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌹𐍃𐍄 𐌲𐌰𐌳𐌰𐌹𐌻𐌰 {{PLURAL:$1|1 𐌰𐌽𐌰𐌻𐌰𐌿𐌲𐌽𐌹𐍃 𐌺𐌿𐌽𐌾𐌹𐍃|$1 𐌰𐌽𐌰𐌻𐌰𐌿𐌲𐌽𐌰𐌹𐌶𐌴 𐌺𐌿𐌽𐌾𐌴}}:‎",
        "permissionserrorstext-withaction": "𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃 𐌰𐌽𐌳𐌻𐌴𐍄 𐌳𐌿 $2, 𐌹𐌽 {{PLURAL:$1|𐌹𐍆𐍄𐌿𐌼𐌰𐌹𐌶𐍉𐍃 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃|𐌹𐍆𐍄𐌿𐌼𐌰𐌹𐌶𐍉 𐍅𐌰𐌹𐌷𐍄𐌴}}:",
+       "moveddeleted-notice": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌿𐍃𐌽𐌿𐌼𐌰𐌽𐍃 𐌹𐍃𐍄. 𐌿𐍃𐌽𐌿𐌼𐍄𐍃 𐌾𐌰𐌷 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌹𐌽𐍃 𐌼𐌹𐌸𐍃𐌰𐍄𐌴𐌹𐌽𐌰𐌹𐍃 𐌿𐍆 𐍃𐌹𐌽𐌳 𐌿𐍃𐍄𐌰𐌹𐌺𐌽𐌴𐌹𐌽𐌰𐌹.",
        "post-expand-template-inclusion-warning": "'''𐌷𐍅𐍉𐍄𐌾𐌰𐌽𐌳𐍃:''' 𐍆𐌰𐌿𐍂𐌰𐌼𐌴𐌻𐌴𐌹𐌽𐍃 𐍃𐌹𐌽𐌳 𐌿𐍆𐌰𐍂𐌼𐌹𐌺𐌹𐌻𐍃. 𐍃𐌿𐌼𐍃 𐍆𐌰𐌿𐍂𐌴𐌼𐌴𐌻𐌴𐌹𐌽𐍉𐍃 𐌽𐌹 𐌼𐌰𐌲 𐍅𐌹𐍃𐌰𐌽 𐌸𐌰𐍂",
        "post-expand-template-inclusion-category": "𐍃𐌴𐌹𐌳𐍉𐌽𐍃 𐌸𐌰𐍂 𐍆𐌰𐌿𐍂𐌰𐌼𐌴𐌻𐌴𐌹𐌽𐍃 𐍃𐌹𐌽𐌳 𐌿𐍆𐌰𐍂𐌼𐌹𐌺𐌹𐌻𐍃",
+       "viewpagelogs": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌹𐌽𐌹𐌽𐍃 𐌸𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰",
        "currentrev": "𐌽𐌿 𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃",
        "currentrev-asof": "𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐌰 𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍃 𐍆𐍂𐌰𐌼 $1",
        "revisionasof": "𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍃 𐍆𐍂𐌰𐌼 $1",
        "histfirst": "𐍆𐌰𐌿𐍂𐌸𐌹𐍃",
        "histlast": "𐍃𐍀𐌴𐌳𐌿𐌼𐌹𐍃𐍄𐍃",
        "history-feed-item-nocomment": "$1 at $2",
+       "rev-delundel": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹 𐌰𐌽𐌰𐍃𐌹𐌿𐌽",
        "revdel-restore": "𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌹𐍃 𐌰𐌽𐌰𐍃𐌹𐌿𐌽𐌼𐌰𐌷𐍄𐌴𐌹𐌲𐍃",
        "revertmerge": "𐌿𐌽𐌲𐌰𐍄𐌹𐌻𐍉𐍃",
        "history-title": "𐌰𐍆𐍄𐍂𐌰𐍃𐌹𐌿𐌽𐌹𐍃𐍀𐌹𐌻𐌻 𐌻𐌰𐌿𐌱𐌹𐍃 \"$1\"",
+       "difference-title": "𐌲𐌰𐍃𐌺𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐌼𐌹𐌸 𐌰𐍆𐍄𐍂𐌰𐍃𐌹𐌿𐌽𐍉𐌼 𐌻𐌰𐌿𐌱𐌹𐍃 \"$1\"",
        "lineno": "𐍃𐍄𐍂𐌹𐌺𐍃 $1:",
        "editundo": "𐍃𐌺𐌰𐍀𐌴𐌹 𐌰𐍆𐍄𐍂𐌰",
        "diff-multi-sameuser": "({{PLURAL:$1|𐌰𐌹𐌽𐌰 𐌼𐌹𐌳𐌿𐌼𐌰𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍃|$1 𐌼𐌹𐌳𐌿𐌼𐌰 𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍉𐍃}} 𐍆𐍂𐌰𐌼 𐍃𐌰𐌼𐌹𐌽 𐌱𐍂𐌿𐌺𐌾𐌹𐌽 𐌽𐌹 𐌰𐍄𐌰𐌿𐌲𐌹𐌳𐌰/𐌰𐍄𐌰𐌿𐌲𐌹𐌳𐍉𐍃)",
        "viewprevnext": "𐍃𐌹𐌿𐌽𐌴𐌹𐍃 ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-new": "<strong>𐍃𐌺𐌰𐍀𐌴𐌹 𐌻𐌰𐌿𐍆 \"[[:$1]]\" 𐌰𐌽𐌰 𐌸𐌹𐌶𐌰𐌹 𐍅𐌹𐌺𐌹!</strong> {{{{PLURAL:$2|0=|𐍃𐌰𐌹 𐌾𐌰𐌷 𐌻𐌰𐌿𐍆 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰 𐌸𐌴𐌹𐌽𐌰𐌹 𐍃𐍉𐌺𐌴𐌹𐌽𐌰𐌹.|𐍃𐌰𐌹 𐌾𐌰𐌷 𐍄𐍉𐌾𐌰 𐍃𐍉𐌺𐌴𐌹𐌽𐌰𐌹𐍃 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰.}}",
        "searchprofile-articles": "𐌷𐌰𐌱𐌰𐌽𐌳𐌰𐌽𐍃 𐌻𐌰𐌿𐌱𐍉𐍃",
-       "searchprofile-images": "ð\90\8c¼ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8c²ð\90\8d\83ð\90\8c¼ð\90\8c´ð\90\8c³ð\90\8c¹𐌰",
+       "searchprofile-images": "ð\90\8c¼ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8c²ð\90\8c¼ð\90\8c´ð\90\8c³ð\90\8c¾𐌰",
        "searchprofile-everything": "𐌰𐌻𐌻",
        "searchprofile-advanced": "𐍆𐌰𐌹𐍂𐍂𐌰𐍆𐍂𐌰𐌼𐌰",
-       "searchprofile-articles-tooltip": "𐍃𐍉𐌺 𐌹𐌽𐌽𐌰 $1",
+       "searchprofile-articles-tooltip": "𐍃𐍉𐌺𐌴𐌹 𐌹𐌽 $1",
        "searchprofile-images-tooltip": "𐍃𐍉𐌺𐌾𐌹𐍃 𐍆𐌴𐌹𐌻𐌰𐌽𐍃",
-       "searchprofile-everything-tooltip": "ð\90\8d\83ð\90\8d\89ð\90\8cºð\90\8c¾ð\90\8c¹ð\90\8d\83 ð\90\8c°ð\90\8c»ð\90\8c» ð\90\8c²ð\90\8c°ð\90\8c·ð\90\8c°ð\90\8c±ð\90\8c¾ð\90\8d\89ð\90\8d\83 (ð\90\8c¸ð\90\8c¹ð\90\8c¶ð\90\8c´ð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8d\83ð\90\8d\84 ð\90\8d\83ð\90\8c´ð\90\8c¹ð\90\8c³ð\90\8d\89ð\90\8c½ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c¾ð\90\8c¹𐍃)",
-       "searchprofile-advanced-tooltip": "ð\90\8d\83ð\90\8cºð\90\8d\89ð\90\8c¾ð\90\8c¹ð\90\8d\83 ð\90\8c¹ð\90\8c½ð\90\8c½ð\90\8c° ð\90\8d\83ð\90\8c¹ð\90\8c»ð\90\8c±ð\90\8c°ð\90\8d\83ð\90\8cºð\90\8c°ð\90\8d\80ð\90\8c¹ð\90\8c¸ð\90\8d\83 ð\90\8c½ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8c½ð\90\8d\83ð\90\8d\84ð\90\8c°ð\90\8c¸",
+       "searchprofile-everything-tooltip": "ð\90\8d\83ð\90\8d\89ð\90\8cºð\90\8c´ð\90\8c¹ ð\90\8c°ð\90\8c»ð\90\8c» ð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° (ð\90\8c¾ð\90\8c°ð\90\8c· ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c¾ð\90\8c°ð\90\8c»ð\90\8c°ð\90\8c¿ð\90\8c±ð\90\8c°ð\90\8c½𐍃)",
+       "searchprofile-advanced-tooltip": "ð\90\8d\83ð\90\8d\89ð\90\8cºð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8c½ ð\90\8c±ð\90\8c¹ð\90\8c¿ð\90\8c·ð\90\8d\84ð\90\8c°ð\90\8c¹ð\90\8c¼ ð\90\8c½ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8d\82ð\90\8c¿ð\90\8c¼ð\90\8c°ð\90\8c¼",
        "search-result-size": "$1 ({{PLURAL:$2|•𐌰• 𐍅𐌰𐌿𐍂𐌳|•$2• 𐍅𐌰𐌿𐍂𐌳𐌰}})",
        "search-redirect": "(𐌰𐍆𐍄𐍂𐌰𐍅𐌴𐌹𐍄𐍃 𐍆𐍂𐌰𐌼 𐌸𐌰𐌼𐌼𐌰 $1)",
        "search-section": "(𐍆𐌴𐍂𐌰 $1)",
        "recentchanges": "𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
        "recentchanges-summary": "𐌰𐍆𐌰𐍂𐌻𐌰𐌹𐍃𐍄𐌴𐌹 𐌸𐌰𐌹𐌼 𐌰𐌽𐌳𐍅𐌰𐌹𐍂𐌸𐌹𐍃𐍄𐍉𐌼 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐌼 𐌳𐌿 𐍅𐌹𐌺𐌾𐌰 𐌰𐌽𐌰 𐌸𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰.",
        "recentchanges-label-newpage": "𐍃𐍉 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐌲𐌰𐍃𐌺𐍉𐍀 𐌽𐌹𐌿𐌾𐌰𐌽𐌰 𐌻𐌰𐌿𐍆",
-       "recentchanges-label-minor": "ð\90\8d\83ð\90\8d\89 ð\90\8c¹ð\90\8d\83ð\90\8d\84 ð\90\8c»ð\90\8c´ð\90\8c¹ð\90\8d\84ð\90\8c¹ð\90\8c»ð\90\8c° 𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃",
+       "recentchanges-label-minor": "ð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° ð\90\8c¹ð\90\8d\83ð\90\8d\84 ð\90\8c»ð\90\8c´ð\90\8c¹ð\90\8d\84ð\90\8c¹ð\90\8c»ð\90\8c° ð\90\8c¹ð\90\8c½𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃",
        "recentchanges-label-bot": "𐍃𐍉 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐍄𐍉𐌾𐌰𐌳𐌰 𐍅𐌰𐍂𐌸 𐍆𐍂𐌰𐌼 𐌼𐌰𐌷𐍄𐌹𐍃𐌺𐌰𐌻𐌺𐌰 (𐌱𐌰𐌿𐍄)",
        "recentchanges-label-plusminus": "𐌻𐌰𐌿𐌱𐌰𐌼𐌹𐌺𐌹𐌻𐌴𐌹 𐌹𐌽𐌼𐌰𐌹𐌳𐌹𐌳𐌰 𐌼𐌹𐌸 𐌸𐌹𐌶𐌰𐌹 𐍂𐌰𐌸𐌾𐍉𐌽 𐌱𐌹𐍄𐍉",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (𐍃𐌰𐌹𐍈 𐌾𐌰𐌷[[Special:NewPages|𐍅𐌹𐌺𐍉 𐌽𐌹𐌿𐌾𐌰𐌹𐌶𐌴 𐌻𐌰𐌿𐌱𐌴]])",
        "rclistfrom": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌽𐌹𐌿𐌾𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌹𐌽𐍃 𐌰𐌽𐌰𐍃𐍄𐍉𐌳𐌾𐌰𐌽𐌳𐌴𐌹𐌽𐍃 𐍆𐍂𐌰𐌼 $2, $3",
-       "rcshowhideminor": "$1 lietila máideins",
+       "rcshowhideminor": "$1 𐌼𐌹𐌽𐌽𐌹𐌶𐌴𐌹𐌽𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
        "rcshowhideminor-show": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹",
        "rcshowhideminor-hide": "𐌰𐍆𐍆𐌹𐌻𐌷",
-       "rcshowhidebots": "$1 bota",
+       "rcshowhidebots": "$1 𐌼𐌰𐌷𐍄𐌹𐍃𐌺𐌰𐌻𐌺𐍉𐍃",
        "rcshowhidebots-show": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹",
-       "rcshowhideliu": "$1 𐌰𐌽𐌰𐌼𐌴𐌻𐌹𐌳𐌰 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃",
+       "rcshowhidebots-hide": "𐌰𐍆𐍆𐌹𐌻𐌷",
+       "rcshowhideliu": "$1 𐌰𐌽𐌰𐌼𐌴𐌻𐌹𐌳𐌰𐌹 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃",
        "rcshowhideliu-hide": "𐌰𐍆𐍆𐌹𐌻𐌷",
-       "rcshowhideanons": "$1 gasteis",
+       "rcshowhideanons": "$1 𐌰𐍆𐍆𐌹𐌻𐌷𐌰𐌽𐌰𐌹 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃",
        "rcshowhideanons-show": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹",
        "rcshowhideanons-hide": "𐌰𐍆𐍆𐌹𐌻𐌷",
-       "rcshowhidemine": "$1 mein máideins",
+       "rcshowhidemine": "$1 𐌼𐌴𐌹𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
        "rcshowhidemine-show": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹",
        "rcshowhidemine-hide": "𐌰𐍆𐍆𐌹𐌻𐌷",
        "rclinks": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍉𐍃 $1 𐌹𐌽𐌼𐌰𐌹𐌳𐌹𐌽𐌹𐌽𐍃 𐌹𐌽 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐌰𐌹𐌼 $2 𐌳𐌰𐌲𐌰𐌼 <br />$3",
        "minoreditletter": "l",
        "newpageletter": "N",
        "boteditletter": "b",
-       "recentchangeslinked": "ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8c»ð\90\8c¹ð\90\8c´ð\90\8cº𐍃",
+       "recentchangeslinked": "ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8c³ð\90\8c°ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89𐍃",
        "recentchangeslinked-feed": "Máideinlieks",
        "recentchangeslinked-toolbox": "𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌻𐌹𐌴𐌺𐍃",
+       "recentchangeslinked-title": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐍉𐍃 𐌼𐌹𐌸 \"$1\"",
        "recentchangeslinked-summary": "𐍃𐍉 𐌹𐍃𐍄 𐌻𐌴𐌹𐍃𐍄𐌰 𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌴 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍃 𐍃𐌺𐍉𐍀 𐌰𐌽𐌰 𐍃𐌴𐌹𐌳𐍉𐌽𐍃 𐌻𐌴𐌹𐌽𐌺𐍉𐌽𐌳 𐌿𐍃 𐌿𐍃𐍃𐌹𐌽𐌳𐌰𐌹 𐍃𐌴𐌹𐌳𐍉𐌽 (𐌰𐌹𐌸𐌸𐌰𐌿 𐌻𐌹𐌸𐌰𐌿𐍃 𐌿𐍃𐍃𐌹𐌽𐌳𐌰𐌹𐌶𐍉𐍃 𐌷𐌰𐌽𐍃𐍉𐍃). 𐍃𐌴𐌹𐌳𐍉𐌽𐍃 [[Special:Watchlist|𐍅𐌹𐍄𐌰𐌽𐌳𐌻𐌴𐌹𐍃𐍄𐍉𐍃 𐌸𐌴𐌹𐌽𐍉𐍃]] 𐍃𐌹𐌽𐌳 '''𐌳𐌹𐌲𐍂𐍃𐍄𐌰𐍆𐍃'''.",
        "recentchangeslinked-page": "𐌻𐌰𐌿𐌱𐌰𐌽𐌰𐌼𐍉:",
        "recentchangeslinked-to": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐌻𐌰𐌿𐌱𐌴 𐌸𐌰𐌹𐌴𐌹 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐌰𐌹 𐌳𐌿 𐌲𐌹𐌱𐌰𐌽𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰.",
        "filehist": "𐍆𐌴𐌹𐌻𐌰𐌽𐍃 𐌰𐌹𐍂𐌹𐍃",
        "filehist-help": "𐌰𐍄𐍄𐌴𐌺 𐌳𐌰𐌲/𐌼𐌴𐌻 𐌳𐌿 𐌰𐍄𐌰𐌿𐌲𐌾𐌰𐌽 𐌳𐌰𐍄𐌰 𐍃𐍅𐌰𐍃𐍅𐌴 𐌲𐌰𐌱𐌰𐌹𐍂𐌷𐍄𐌹𐌳𐌰 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐌼𐌴𐌻𐌰.",
        "filehist-current": "𐌽𐌿",
-       "filehist-datetime": "ð\90\8d\88ð\90\8c´ð\90\8c¹ð\90\8c»ð\90\8c°",
+       "filehist-datetime": "ð\90\8c¼ð\90\8c´ð\90\8c»",
        "filehist-thumb": "𐌻𐌴𐌹𐍄𐌹𐌻𐌰 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃",
        "filehist-thumbtext": "𐌻𐌴𐌹𐍄𐌹𐌻𐌰𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌿𐍃𐌼𐌴𐍂𐌾𐌰 𐌹𐌽 $1",
        "filehist-user": "𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃/𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐌹",
        "nextpage": "𐌹𐍆𐍄𐌿𐌼𐌰 𐍃𐌴𐌹𐌳𐍉 ($1)",
        "prevpage": "𐌰𐍆𐍄𐌿𐌼𐌰 𐍃𐌴𐌹𐌳𐍉 ($1)",
        "allarticles": "𐌰𐌻𐌻𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
-       "allpagessubmit": "ð\90\8c°ð\90\8d\86ð\90\8c²ð\90\8c°ð\90\8c²ð\90\8c²ð\90\8c°ð\90\8c½",
+       "allpagessubmit": "ð\90\8c²ð\90\8c°ð\90\8c²ð\90\8c²",
        "categories": "𐌺𐌿𐌽𐌾𐌰",
        "linksearch-ns": "𐍃𐌴𐌹𐌳𐍉𐍆𐌴𐍂𐌰:",
        "emailuser": "𐍃𐌰𐌽𐌳𐌾𐌰𐌽 𐌸𐍉 𐌽𐌹𐌿𐍄𐌰𐌽𐌳 𐌱𐍉𐌺𐍉𐌼",
        "deleteotherreason": "𐌰𐌽𐌸𐌰𐍂/𐌼𐌰𐌹𐍃 𐌼𐌹𐍄𐍉𐌽𐍃:",
        "deletereasonotherlist": "𐌰𐌽𐌸𐌰𐍂 𐌼𐌹𐍄𐍉𐌽𐍃",
        "rollbacklink": "𐌰𐍆𐍅𐌰𐌻𐍅𐌴𐌹",
+       "rollbacklinkcount": "𐌰𐍆𐍅𐌰𐌻𐍅𐌴𐌹 $1 {{PLURAL:$1|𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽|𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌹𐌽𐍃}}",
        "protectlogpage": "Log af Baírgjan",
        "prot_1movedto2": "[[$1]] skiubiþ du [[$2]]",
        "protect-level-sysop": "𐍃𐌴𐌹𐌳𐍉𐍆𐌰𐌸𐍃 𐌰𐌹𐌽𐌰𐌷𐌰",
        "undeletelink": "𐍃𐌰𐌹𐍈𐌰𐌽/𐌰𐍆𐍄𐍂𐌰𐌲𐌰𐍃𐌰𐍄𐌾𐌰𐌽",
        "undeleteviewlink": "𐍃𐌰𐌹𐍈𐌹𐍃",
        "undelete-search-submit": "Sokeiþ",
-       "namespace": "ð\90\8d\83ð\90\8c´ð\90\8c¹ð\90\8c³ð\90\8d\89ð\90\8d\86ð\90\8c´ð\90\8d\82ð\90\8c°:",
+       "namespace": "ð\90\8c½ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8d\82ð\90\8c¿ð\90\8c¼:",
        "invert": "Afwandjan kustus",
        "blanknamespace": "(𐍆𐍂𐌿𐌼𐌹𐍃𐍄𐍃)",
        "contributions": "𐌱𐌹𐌰𐌿𐌺𐌰𐌹𐌽𐌴𐌹𐍃 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐌹𐍃 {{{{GENDER:$1|User}}",
        "whatlinkshere": "𐌰𐌻𐌻𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃 𐌸𐌰𐌹𐌴𐌹 𐌱𐍂𐌹𐌲𐌲𐌰𐌽𐌳 𐌸𐌿𐌺 𐌷𐌹𐌳𐍂𐌴",
        "whatlinkshere-title": "𐌻𐌰𐌿𐌱𐍉𐍃 𐌸𐌰𐌹𐌴𐌹 𐍄𐌰𐌹𐌺𐌽𐌾𐌰𐌽𐌳 𐌳𐌿 \"$1\"",
        "whatlinkshere-page": "𐌻𐌰𐌿𐍆𐍃:",
+       "linkshere": "𐌹𐍆𐍄𐌿𐌼𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃 𐌱𐍂𐌹𐌲𐌲𐌰𐌽𐌳 𐌸𐌿𐌺  <strong>[[:$1]]</strong>:",
        "isredirect": "𐍄𐌰𐌹𐌺𐌾𐌰𐍃𐌴𐌹𐌳𐍉",
        "istemplate": "ináukan",
        "whatlinkshere-prev": "{{PLURAL:$1|aftuma|aftumans $1}}",
        "ipboptions": "𐌱 𐌰𐍅𐍂𐌰:2 hours, 𐌰 𐌳𐌰𐌲𐍃:1 day, 𐌲 𐌳𐌰𐌲𐍉𐍃:3 days, 𐌰 𐍅𐌹𐌺𐍉:1 week, 𐌱 𐍅𐌹𐌺𐍉𐌽𐍃:2 weeks, 𐌰 𐌼𐌴𐌽𐍉𐌸𐍃:1 month, 𐌲 𐌼𐌴𐌽𐍉𐌸𐍉𐍃:3 months, 𐌵 𐌼𐌴𐌽𐍉𐌸𐍉𐍃:6 months, 𐌰 𐌾𐌴𐍂:1 year, 𐌹𐌽𐌿𐍄𐍂𐌹𐌲𐌲𐌴𐌽𐌳𐌴𐌹𐍃:infinite",
        "ipblocklist-submit": "Sokeiþ",
        "infiniteblock": "ajukduþs",
-       "blocklink": "ð\90\8d\85ð\90\8c°ð\90\8d\82ð\90\8c²ð\90\8c¾ð\90\8c°ð\90\8c½",
+       "blocklink": "ð\90\8d\86ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c°ð\90\8c¼ð\90\8c¼ð\90\8c´ð\90\8c¹",
        "unblocklink": "𐍅𐌰𐌽𐌳𐌾𐌰𐌽",
        "change-blocklink": "𐌲𐌰𐌼𐌰𐌹𐌳𐌾𐌰𐌽 𐍅𐌰𐌿𐍂𐌾𐌰𐍅𐌰𐌳𐌳𐌾𐌿𐍃",
        "contribslink": "𐌱𐌹𐌰𐌿𐌺𐌰𐌹𐌽𐌴𐌹𐍃",
        "tooltip-pt-createaccount": "𐌱𐌰𐍄𐌹𐌶𐍉 𐌹𐍃𐍄 𐌸𐌿𐍃 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽, 𐌹𐌸 𐍃𐌺𐌿𐌻𐌳 𐌽𐌹𐍃𐍄",
        "tooltip-ca-talk": "𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌹 𐌱𐌹 𐌷𐌰𐌱𐌰𐌽𐌳𐌰𐌽 𐌻𐌰𐌿𐍆",
        "tooltip-ca-edit": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆",
-       "tooltip-ca-addsection": "ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8d\83ð\90\8d\84ð\90\8d\89ð\90\8c³ð\90\8c¾ð\90\8c¹ð\90\8d\83 𐌽𐌹𐌿𐌾𐌰 𐌳𐌰𐌹𐌻",
+       "tooltip-ca-addsection": "ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8d\83ð\90\8d\84ð\90\8d\89ð\90\8c³ð\90\8c´ð\90\8c¹ 𐌽𐌹𐌿𐌾𐌰 𐌳𐌰𐌹𐌻",
        "tooltip-ca-viewsource": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌷𐌰𐌱𐌰𐌹𐌸 𐌼𐌿𐌽𐌳. 𐌼𐌰𐌲𐍄 𐌸𐌹𐍃 𐌻𐌰𐌿𐌱𐌹𐍃 𐌼𐌿𐌽𐌳 𐍃𐌰𐌹𐍈𐌰𐌽.",
        "tooltip-ca-history": "𐌰𐍆𐍄𐌿𐌼𐍉𐍃 𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍉𐍃 𐌸𐌹𐍃 𐌻𐌰𐌿𐌱𐌹𐍃",
        "tooltip-ca-protect": "𐌱𐌰𐌹𐍂𐌲𐌰 𐌸𐍉 𐍃𐌴𐌹𐌳𐍉",
        "tooltip-n-mainpage": "𐌲𐌰𐍅𐌴𐌹𐍃 𐌷𐌰𐌿𐌱𐌹𐌳𐌰𐌻𐌰𐌿𐌱𐌹𐍃",
        "tooltip-n-mainpage-description": "𐌲𐌰𐍅𐌴𐌹𐍃 𐌷𐌰𐌿𐌱𐌹𐌳𐌰𐌻𐌰𐌿𐌱𐌹𐍃",
        "tooltip-n-portal": "𐌱𐌹 𐍆𐌰𐌿𐍂𐌰𐍅𐌰𐌿𐍂𐍀𐌰, 𐍈𐌰 𐌼𐌰𐌲𐍄 𐍄𐌰𐌿𐌾𐌰𐌽, 𐍈𐌰𐍂 𐌱𐌹𐌲𐌹𐍄𐌹𐍃 𐍅𐌰𐌹𐌷𐍄𐌹𐌽𐍃",
-       "tooltip-n-currentevents": "ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8c¸ð\90\8c¹ð\90\8d\83 ð\90\8c±ð\90\8c°ð\90\8cºð\90\8c²ð\90\8c°ð\90\8c·ð\90\8c°ð\90\8c±ð\90\8c¹ ð\90\8c±ð\90\8c¹ ð\90\8c½ð\90\8c¹ð\90\8c¿ð\90\8c¾ð\90\8c°ð\90\8c½ð\90\8d\83 ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8c·ð\90\8d\84ð\90\8c°𐌽𐍃",
+       "tooltip-n-currentevents": "ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8d\84 ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8d\83 ð\90\8cºð\90\8c¿ð\90\8c½ð\90\8c¸ð\90\8c¹ ð\90\8c±ð\90\8c¹ ð\90\8c½ð\90\8c¹ð\90\8c¿ð\90\8c¾ð\90\8d\89ð\90\8d\83 ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c¸ð\90\8c°ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8c·ð\90\8d\84ð\90\8c¹𐌽𐍃",
        "tooltip-n-recentchanges": "𐍅𐌹𐌺𐍉 𐌰𐌽𐌳𐍅𐌰𐌹𐍂𐌸𐌰𐌹𐌶𐍉 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐍅𐌹𐌺𐌾𐌰",
        "tooltip-n-randompage": "𐌿𐍃𐌱𐍂𐌹𐌲𐌲 𐌸𐌿𐍃 𐌿𐌽𐌺𐌿𐌽𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆",
        "tooltip-n-help": "𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌳𐌿 𐌱𐌹𐌲𐌹𐍄𐌰𐌽",
        "tooltip-ca-nstab-main": "𐍃𐌰𐌹𐍈 𐌷𐌰𐌱𐌰𐌽𐌳𐌰𐌽 𐌻𐌰𐌿𐍆",
        "tooltip-ca-nstab-user": "𐍃𐌰𐌹𐍈 𐌱𐍂𐌿𐌺𐌾𐌰𐌻𐌰𐌿𐍆",
        "tooltip-ca-nstab-special": "𐍃𐌰 𐌹𐍃𐍄 𐌿𐍃𐍃𐌹𐌽𐌳𐍃 𐌻𐌰𐌿𐍆𐍃 𐌾𐌰𐌷 𐌽𐌹 𐌼𐌰𐌲 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌳𐌰.",
+       "tooltip-ca-nstab-project": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐍆𐌰𐌿𐍂𐌰𐍅𐌰𐌿𐍂𐍀𐌰𐌻𐌰𐌿𐍆",
        "tooltip-ca-nstab-image": "𐍃𐌰𐌹𐍈𐌰𐌽 𐌸𐍉 𐍆𐌴𐌹𐌻𐌰𐍃𐌴𐌹𐌳𐍉𐌽",
        "tooltip-ca-nstab-template": "𐍃𐌰𐌹𐍈𐌹𐍃 𐍆𐌰𐌿𐍂𐌰𐌼𐌴𐌻𐌴𐌹𐌽",
        "tooltip-ca-nstab-category": "𐍃𐌰𐌹𐍈 𐌺𐌿𐌽𐌾𐌰𐌻𐌰𐌿𐍆",
-       "tooltip-save": "ð\90\8d\83ð\90\8cºð\90\8d\82ð\90\8c´ð\90\8c¹ð\90\8c±ð\90\8c°ð\90\8c¹ð\90\8c½ ð\90\8c¸ð\90\8c´ð\90\8c¹ð\90\8c½ ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´𐌹𐌽𐍃",
+       "tooltip-save": "ð\90\8c²ð\90\8c°ð\90\8d\86ð\90\8c°ð\90\8d\83ð\90\8d\84 ð\90\8c¸ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½𐌹𐌽𐍃",
        "tooltip-preview": "𐍆𐌰𐌿𐍂𐍃𐌰𐍈𐌹𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌴𐌹𐌽𐍉𐍃 𐌸𐌴𐌹𐌽𐌰, 𐌱𐌹𐌳𐌾𐌰 𐌸𐌿𐌺 𐌱𐍂𐌿𐌺𐌾𐌰𐌽 𐌸𐌰𐍄𐌰 𐍆𐌰𐌿𐍂𐌰 𐌼𐌴𐌻𐌾𐌹𐍃!",
-       "tooltip-diff": "ð\90\8d\83ð\90\8c°ð\90\8c¹ð\90\8d\88ð\90\8c¹ð\90\8c¸ ð\90\8c¸ð\90\8c°ð\90\8c½ð\90\8c¶ð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c¾ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c´ð\90\8c¹ ð\90\8c±ð\90\8d\82ð\90\8c¿ð\90\8cºð\90\8c¾ð\90\8c¹ð\90\8d\83 ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8c°ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c¹ð\90\8c½ð\90\8d\83",
+       "tooltip-diff": "ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c¿ð\90\8c²ð\90\8c´ð\90\8c¹ ð\90\8d\88ð\90\8c¹ð\90\8c»ð\90\8c´ð\90\8c¹ð\90\8cºð\90\8d\89ð\90\8d\83 ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8c¹ð\90\8c½ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8d\83 ð\90\8c³ð\90\8c¿ ð\90\8c¸ð\90\8c°ð\90\8c¹ð\90\8c¼ ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8d\89ð\90\8c¼",
        "tooltip-rollback": "\"𐌰𐍆𐍅𐌰𐌻𐍅𐌾𐌰𐌽\" 𐌱𐌰𐌺𐌼𐌰𐌹𐌳𐌾𐌹𐌸 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌴𐌹𐌽𐍃 𐌱𐌹 𐌸𐌹𐌶𐌰𐌹 𐍃𐌴𐌹𐌳𐍉𐌽 𐍅𐌹𐍃𐌰𐌽 𐍃𐍉 𐌲𐌰𐌼𐌰𐌳𐌾𐌴𐌹 𐌼𐌰𐌽𐌰𐌲𐌹𐍃𐍄𐍃 𐌽𐌹𐌿𐌾𐌰 𐌲𐌹𐌱𐌰𐌾𐌹𐌽𐍃 𐍆𐍂𐌰𐌼 𐌰𐌹𐌽𐍃 𐌺𐌻𐌹𐌺",
        "tooltip-undo": "\"𐌽𐌹𐌿𐍃𐌺𐌰𐍀𐌾𐌰𐌽\" 𐌱𐌰𐌺𐌼𐌰𐌹𐌳𐌾𐌹𐌸 𐌹𐌽𐌼𐌰𐌹𐌳𐌲𐌴𐌹𐌽𐍃 𐌾𐌰𐌷 𐌿𐍃𐌻𐌿𐌺𐍉𐌸 𐌼𐌰𐌹𐌳𐌾𐌰𐍆𐍉𐍂𐌼𐍉𐌽 𐍃𐍅𐌴 𐍆𐌰𐌿𐍂𐍃𐌰𐌹𐍈𐌰 𐌷𐌹𐍅𐌾𐌰. 𐌸𐌰𐍄𐌰 𐌻𐌴𐍄 𐌰𐌽𐌰𐌿𐌺𐌰𐌽𐌰𐌽 𐍃𐌰𐌿𐌸𐌰 𐌹𐌽 𐌹𐌽𐌽𐌰𐌷𐌰𐌻𐌳𐌰𐌰𐌽𐌲𐌰𐌱𐌰.",
        "tooltip-summary": "𐌰𐍄𐌲𐌰𐌲𐌲𐌹𐍃 𐌹𐌽𐌽𐌰𐌷𐌰𐌻𐌳𐌰𐌰𐌽𐌲𐌰𐌱𐌰 𐌼𐌰𐌿𐍂𐌲𐌾𐌰",
        "metadata": "𐌿𐍆𐌰𐍂𐌳𐌰𐍄𐌰",
        "exif-colorspace": "𐍆𐌰𐍂𐍅𐌰𐍂𐌿𐌼",
        "exif-orientation-1": "𐌱𐌹 𐌱𐌹𐌿𐌷𐍄𐌾𐌰",
-       "namespacesall": "𐌰𐌻𐌻𐌹𐍃",
+       "namespacesall": "𐌰𐌻𐌻",
        "monthsall": "𐌰𐌻𐌻𐌹𐍃",
        "imgmultigo": "Afgaggan!",
        "table_pager_limit_submit": "Affgaggan",
        "tags-deactivate-reason": "𐌳𐌿𐌸𐌸𐌴:",
        "tags-deactivate-submit": "𐌿𐌽𐌲𐌰𐌵𐌹𐌿𐌴𐌹",
        "logentry-delete-delete": "$1 {{GENDER:$2|𐌿𐍃𐌽𐌰𐌼}} 𐌻𐌰𐌿𐍆 $3",
+       "logentry-move-move": "$1 {{GENDER:$2|𐌼𐌹𐌸𐍃𐌰𐍄𐌹𐌳𐌰}} 𐌻𐌰𐌿𐍆𐍃 $3 𐌳𐌿 $4",
        "logentry-newusers-create": "𐌱𐍂𐌿𐌺𐌾𐌰𐌺𐌰𐍅𐍄𐍃𐌾𐍉 $1 𐍅𐌰𐍃 {{{{GENDER:$2|𐌲𐌰𐍃𐌺𐌰𐍀𐌰𐌽𐌰}}",
        "rightsnone": "(ni áinshun)",
        "searchsuggest-search": "𐍃𐍉𐌺𐌴𐌹"
index 18d80d8..53efc71 100644 (file)
        "page_last": "אחרון",
        "histlegend": "בחירת גרסאות להשוואה: סמנו את תיבות האפשרויות של הגרסאות המיועדות להשוואה, והקישו על Enter או על הכפתור למטה.<br />\nמקרא: '''({{int:cur}})''' = השוואה עם הגרסה הנוכחית, '''({{int:last}})''' = השוואה עם הגרסה הקודמת, '''{{int:minoreditletter}}''' = שינוי משני.",
        "history-fieldset-title": "חיפוש בהיסטוריית הדף",
-       "history-show-deleted": "רק ×\9e×\97×\95ק×\95ת",
+       "history-show-deleted": "ער×\99×\9b×\95ת ×\9e×\95סתר×\95ת ×\91×\9c×\91×\93",
        "histfirst": "הישנות ביותר",
        "histlast": "החדשות ביותר",
        "historysize": "({{PLURAL:$1|בייט אחד|$1 בייטים}})",
        "ipblocklist-localblock": "חסימה מקומית",
        "ipblocklist-otherblocks": "{{PLURAL:$1|חסימה אחרת|חסימות אחרות}}",
        "infiniteblock": "ללא הגבלת זמן",
-       "expiringblock": "החסימה פוקעת ב{{GRAMMAR:תחילית|$1}} בשעה $2",
+       "expiringblock": "החסימה פוקעת ב־$1 בשעה $2",
        "anononlyblock": "משתמשים אנונימיים בלבד",
        "noautoblockblock": "חסימה אוטומטית מבוטלת",
        "createaccountblock": "יצירת חשבונות נחסמה",
index 41f08db..3b74420 100644 (file)
        "rightslogtext": "以下は利用者権限の変更記録です。",
        "action-read": "このページの閲覧",
        "action-edit": "このページの編集",
-       "action-createpage": "ページの作成",
-       "action-createtalk": "議論ページの作成",
+       "action-createpage": "ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81®ä½\9cæ\88\90",
+       "action-createtalk": "この議論ページの作成",
        "action-createaccount": "この利用者アカウントの作成",
        "action-autocreateaccount": "この外部利用者アカウントを自動的に作成",
        "action-history": "このページの履歴の閲覧",
        "action-managechangetags": "タグの作成、有効化および無効化",
        "action-applychangetags": "自分の編集にタグを適用する",
        "action-changetags": "個々の版および記録項目への任意のタグの追加と除去",
+       "action-deletechangetags": "データベースからタグの削除",
        "nchanges": "$1 {{PLURAL:$1|回の変更}}",
        "enhancedrc-since-last-visit": "最終閲覧以降 $1 {{PLURAL:$1|件}}",
        "enhancedrc-history": "履歴",
        "upload-too-many-redirects": "そのURLに含まれるリダイレクトが多すぎます",
        "upload-http-error": "HTTP エラー発生: $1",
        "upload-copy-upload-invalid-domain": "このドメインからのアップロードは許可されていません。",
+       "upload-foreign-cant-upload": "このウィキでは、要求された外部ファイルリポジトリにファイルをアップロードできるように設定されていません。",
        "upload-foreign-cant-load-config": "外部ファイルリポジトリへのファイルアップロードに使用される設定の読み込みに失敗しました。",
+       "upload-dialog-disabled": "このウィキでは、このダイアログを使用するファイルのアップロードが無効にされています。",
        "upload-dialog-title": "ファイルをアップロード",
        "upload-dialog-button-cancel": "中止",
        "upload-dialog-button-done": "完了",
        "uploadstash-badtoken": "操作を実行できませんでした。編集するための認証の期限切れが原因である可能性があります。再度試してください。",
        "uploadstash-errclear": "ファイルの消去に失敗しました。",
        "uploadstash-refresh": "ファイルの一覧を更新",
+       "uploadstash-thumbnail": "サムネイルを表示",
        "invalid-chunk-offset": "無効なチャンクオフセット",
        "img-auth-accessdenied": "アクセスが拒否されました",
        "img-auth-nopathinfo": "PATH_INFO が見つかりません。\nサーバーが、この情報を渡すように構成されていません。\nCGI ベースであるため、img_auth に対応できない可能性もあります。\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization をご覧ください。",
        "apisandbox-dynamic-parameters-add-placeholder": "引数名",
        "apisandbox-dynamic-error-exists": "引数名 \"$1\" は既に存在しています。",
        "apisandbox-deprecated-parameters": "廃止予定の引数",
+       "apisandbox-fetch-token": "トークンを自動入力します",
        "apisandbox-submit-invalid-fields-title": "いくつかの欄が不正です。",
        "apisandbox-submit-invalid-fields-message": "印が付いている欄を訂正し、再試行してください。",
        "apisandbox-results": "結果",
        "trackingcategories-msg": "追跡用カテゴリ",
        "trackingcategories-name": "メッセージ名",
        "trackingcategories-desc": "カテゴリに入る基準",
+       "restricted-displaytitle-ignored": "ページ名の表示が無視されているページ",
        "restricted-displaytitle-ignored-desc": "実際のページ名と等価でないために <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> が無視されているページ。",
        "noindex-category-desc": "このページは <code><nowiki>__NOINDEX__</nowiki></code> というマジックワードを含んでおり、その印が有効になっている名前空間にあるため、ロボットによる収集の対象になりません。",
        "index-category-desc": "このページは <code><nowiki>__INDEX__</nowiki></code> というマジックワードを含んでおり、その印が有効になっている名前空間にあるため、通常と異なり、ロボットによる収集の対象になります。",
        "exif-copyrighted-false": "著作権情報未設定",
        "exif-photometricinterpretation-0": "黒と白 (白が0)",
        "exif-photometricinterpretation-1": "黒と白(黒が0)",
+       "exif-photometricinterpretation-9": "CIE L*a*b* (ICC エンコード)",
+       "exif-photometricinterpretation-10": "CIE L*a*b* (ITU エンコード)",
+       "exif-photometricinterpretation-32803": "カラーフィルター配列",
+       "exif-photometricinterpretation-34892": "リニア RAW",
        "exif-unknowndate": "不明な日付",
        "exif-orientation-1": "通常",
        "exif-orientation-2": "左右反転",
index 133550a..7507a63 100644 (file)
        "upload-too-many-redirects": "URL ngandhut kakèhan pengalihan",
        "upload-http-error": "Ana kasalahan HTTP: $1",
        "upload-copy-upload-invalid-domain": "Unggahan salinan ora sumadhiya nèng domain iki.",
+       "upload-form-label-infoform-categories": "Kategori",
        "backend-fail-stream": "Ora bisa milikaké berkas \"$1\".",
        "backend-fail-backup": "Ora bisa nyadangaké berkas \"$1\".",
        "backend-fail-notexists": "Berkas $1 ora ana.",
        "pageinfo-recent-edits": "Cacahé suntingan saiki (ing $1 kapungkur)",
        "pageinfo-recent-authors": "Cacahé panganggit sing bédha-bédha saiki",
        "pageinfo-magic-words": "{{PLURAL:$1|Tembung|Tembung}} mujarab ($1)",
-       "pageinfo-hidden-categories": "{{PLURAL:$1|Katégori|Katégori}} kadhelikaké ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|Kategori}} ndhelik ($1)",
        "pageinfo-templates": "{{PLURAL:$1|Templat|Templat}} yang ditransklusi ($1)",
        "pageinfo-transclusions": "{{PLURAL:$1|Kaca|Kaca}} sing ditransklusi ing ($1)",
        "pageinfo-toolboxlink": "Katerangan kaca",
index 505d417..d80554d 100644 (file)
        "usermessage-summary": "Жүйе хабарламасы қалдырылуда.",
        "usermessage-editor": "Жүйе мессенжері",
        "watchlist": "Бақылау тізімі",
-       "mywatchlist": "Бақылау тізімі",
+       "mywatchlist": "Бақылау тізімім",
        "watchlistfor2": "$1 ($2) бақылау тізімі",
        "nowatchlist": "Бақылау тізіміңізде еш дана жоқ",
        "watchlistanontext": "Бақылау тізіміңіздегі даналарды қарау не өңдеу үшін кіріңіз.",
index 81b674c..befc978 100644 (file)
        "userpage-userdoesnotexist": "\"$1\" 사용자 계정은 등록되어 있지 않습니다.\n이 문서를 만들거나 편집하기 전에 계정이 존재하는지 확인해주세요.",
        "userpage-userdoesnotexist-view": "\"$1\" 사용자 계정은 등록되어 있지 않습니다.",
        "blocked-notice-logextract": "이 사용자는 현재 차단되어 있습니다.\n해당 사용자의 최근 차단 기록을 참조하십시오:",
-       "clearyourcache": "<strong>참고:</strong> 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.\n* <strong>파이어폭스 / 사파리</strong>: <em>Shift</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em> 또는 <em>Ctrl-R</em> 을 입력 (Mac에서는 <em>⌘-R</em>)\n* <strong>구글 크롬</strong>: <em>Ctrl-Shift-R</em>키를 입력 (Mac에서는 <em>⌘-Shift-R</em>)\n* <strong>인터넷 익스플로러</strong>: <em>Ctrl</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em>를 입력.\n* <strong>오페라:</strong> <em>메뉴 → 설정</em>(맥의 경우 <em>오페라 → 환경 설정</em>)으로 이동한 다음 <em>개인 정보 보호 및 보안 → 검색 데이터 지우기 → 캐시한 이미지 및 파일</em>을 누름.",
+       "clearyourcache": "<strong>참고:</strong> 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.\n* <strong>파이어폭스 / 사파리</strong>: <em>Shift</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em> 또는 <em>Ctrl-R</em>을 입력 (Mac에서는 <em>⌘-R</em>)\n* <strong>구글 크롬</strong>: <em>Ctrl-Shift-R</em>키를 입력 (Mac에서는 <em>⌘-Shift-R</em>)\n* <strong>인터넷 익스플로러</strong>: <em>Ctrl</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em>를 입력.\n* <strong>오페라:</strong> <em>메뉴 → 설정</em>(맥의 경우 <em>오페라 → 환경 설정</em>)으로 이동한 다음 <em>개인 정보 보호 및 보안 → 검색 데이터 지우기 → 캐시한 이미지 및 파일</em>을 누름.",
        "usercssyoucanpreview": "'''안내''': CSS 문서를 저장하기 전에 \"{{int:showpreview}}\" 기능을 통해 작동을 확인해주세요.",
        "userjsyoucanpreview": "'''안내''': 자바스크립트 문서를 저장하기 전에 \"{{int:showpreview}}\" 기능을 통해 작동을 확인해주세요.",
        "usercsspreview": "'''사용자 CSS의 미리 보기입니다.'''\n'''아직 저장하지 않았습니다!'''",
        "last": "이전",
        "page_first": "처음",
        "page_last": "마지막",
-       "histlegend": "비교하려는 판을 선택한 다음 엔터나 아래의 버튼을 누르세요.<br />\n설명: <strong>({{int:cur}})</strong> = 최신 판과 비교, <strong>({{int:last}})</strong> = 이전 판과 비교, <strong>{{int:minoreditletter}}</strong>= 사소한 편집",
+       "histlegend": "차이 선택: 비교하려는 판의 라디오 상자를 선택한 다음 엔터나 아래의 버튼을 누르세요.<br />\n설명: <strong>({{int:cur}})</strong> = 최신 판과 비교, <strong>({{int:last}})</strong> = 이전 판과 비교, <strong>{{int:minoreditletter}}</strong>= 사소한 편집",
        "history-fieldset-title": "역사 찾아보기",
        "history-show-deleted": "삭제된 것만",
        "histfirst": "오래됨",
        "uploadstash-thumbnail": "섬네일 보기",
        "invalid-chunk-offset": "청크 오프셋이 잘못되었습니다.",
        "img-auth-accessdenied": "접근이 거부됨",
-       "img-auth-nopathinfo": "PATH_INFO를 잃었습니다.\n서버가 이 정보를 받을 수 있도록 설정되어 있지 않습니다.\n이러한 경우는 서버가 CGI 기반이고 img_auth를 지원하지 않을 때 나타날 수 있습니다.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization 을 참조하십시오.",
+       "img-auth-nopathinfo": "PATH_INFO를 잃었습니다.\n서버가 이 정보를 받을 수 있도록 설정되어 있지 않습니다.\n이러한 경우는 서버가 CGI 기반이고 img_auth를 지원하지 않을 때 나타날 수 있습니다.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization을 참조하십시오.",
        "img-auth-notindir": "요청한 경로가 설정한 올리기 디렉터리에 없습니다.",
        "img-auth-badtitle": "\"$1\"에서 올바른 제목을 만들 수 없습니다.",
        "img-auth-nologinnWL": "로그인하지 않았으며 \"$1\" 파일은 화이트리스트에 존재하지 않습니다.",
        "linksearch-ns": "이름공간:",
        "linksearch-ok": "검색",
        "linksearch-text": "\"*.wikipedia.org\"와 같이 와일드 카드를 사용할 수 있습니다.\n적어도 \"*.org\"와 같이 최상위 도메인을 입력해야 합니다.<br />\n지원하는 {{PLURAL:$2|프로토콜}}: $1 (프로토콜을 지정하지 않을 때 기본값은 http://)",
-       "linksearch-line": "$2에서 $1 을 링크하고 있습니다.",
+       "linksearch-line": "$1가 $2에서 링크됩니다.",
        "linksearch-error": "와일드카드는 주소의 처음 부분에만 사용될 수 있습니다.",
        "listusersfrom": "다음으로 시작하는 사용자 보기:",
        "listusers-submit": "보기",
        "enotif_body_intro_moved": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|이동하였으며}} 현재 판은 $3 에서 볼 수 있습니다.",
        "enotif_body_intro_restored": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|되살렸으며}} 현재 판은 $3 에서 볼 수 있습니다.",
        "enotif_body_intro_changed": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|바꾸었으며}} 현재 판은 $3 에서 볼 수 있습니다.",
-       "enotif_lastvisited": "마지막으로 방문한 뒤 생긴 모든 바뀜을 보려면 $1 을 보세요.",
-       "enotif_lastdiff": "이 바뀜을 보려면 $1 을 보세요.",
+       "enotif_lastvisited": "마지막으로 방문한 뒤 생긴 모든 바뀜을 보려면 $1을 보세요.",
+       "enotif_lastdiff": "이 바뀜을 보려면 $1을 보세요.",
        "enotif_anon_editor": "익명 사용자 $1",
        "enotif_body": "$WATCHINGUSERNAME님,\n\n$PAGEINTRO $NEWPAGE\n\n편집 요약: $PAGESUMMARY $PAGEMINOREDIT\n\n다음을 통해 편집자와 대화를 할 수 있습니다:\n이메일: $PAGEEDITOR_EMAIL\n위키: $PAGEEDITOR_WIKI\n\n로그인한 상태에서 이 문서를 열기 전에는 다른 알림 이메일을 더 이상 보내지 않습니다. 모든 주시 문서의 알림 딱지를 초기화할 수도 있습니다.\n\n{{SITENAME}} 알림 시스템\n\n--\n이메일 알림 설정을 바꾸시려면 이곳을 방문해주세요:\n{{canonicalurl:{{#special:Preferences}}}}\n\n주시문서 설정을 바꾸려면 다음을 사용하세요:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\n주시문서에서 이 문서를 지우려면 이곳을 방문해주세요:\n$UNWATCHURL\n\n피드백 및 추가 도움 얻기:\n$HELPPAGE",
        "created": "만들었",
        "ipbnounblockself": "자기 스스로를 차단 해제할 수 없습니다.",
        "lockdb": "데이터베이스 잠그기",
        "unlockdb": "데이터베이스 잠금 해제",
-       "lockdbtext": "데이터베이스를 잠그면 모든 사용자의 편집, 환경 설정 바꾸기, 주시문서 편집 등 데이터베이스를 요구하는 모든 기능이 정지됩니다.\n정말로 잠가야 하는지를 다시 한번 확인해주세요. 관리 작업이 끝난 뒤에는 데이터베이스 잠금을 풀어야 합니다.",
-       "unlockdbtext": "데이터베이스를 잠금 해제하면 모든 사용자의 편집, 환경 설정 바꾸기, 주시문서 편집 등 데이터베이스를 요구하는 모든 기능이 복구됩니다.\n정말로 잠금을 해제하려는지를 다시 한번 확인해주세요.",
+       "lockdbtext": "데이터베이스를 잠그면 모든 사용자의 편집, 환경 설정 바꾸기, 주시문서 편집 등 데이터베이스에서 변경을 요구하는 모든 기능이 정지됩니다.\n정말로 잠가야 하는지 확인해 주세요. 관리 작업이 끝난 뒤에는 데이터베이스 잠금을 풀어야 합니다.",
+       "unlockdbtext": "데이터베이스를 잠금 해제하면 모든 사용자의 편집, 환경 설정 바꾸기, 주시문서 편집 등 데이터베이스에서 변경을 요구하는 모든 기능이 복구됩니다.\n정말로 잠금을 해제해야 하는지 확인해주세요.",
        "lockconfirm": "네, 데이터베이스를 잠급니다.",
        "unlockconfirm": "네, 데이터베이스를 잠금 해제합니다.",
        "lockbtn": "데이터베이스 잠그기",
index 60955d2..2548e45 100644 (file)
        "last": "met dovör",
        "page_first": "Aanfang",
        "page_last": "Engk",
-       "histlegend": "Heh kanns De Versione för et Verjliiche ußsöke: Dun met dä Knöpp di zweij markiere,\nzwesche dänne De de Ungerscheid jezeich krije wells, dann dröck „<b style=\"padding:2px; background-color:#ddd;\ncolor:black\">{{int:compareselectedversions}}</b>“ udder „<b style=\"padding:2px; background-color:#ddd;\ncolor:black\">{{int:visualcomparison}}</b>“ udder „<b style=\"padding:2px; background-color:#ddd;\ncolor:black\">{{int:wikicodecomparison}}</b>“ met Dinge Taste, oder klick op ein vun dä Knöpp övver oder unger de Liss.<br />\nVerklierung:\n({{int:cur}}) = donn met de neuste Väsjohn verjliche,\n({{int:last}}) = donn met de Väsjohn ein doför verjliche,\n<b>M</b> = en klein <b>M</b>ini-Änderong,\nDattum+Uhrzigg = don de Version fun dämm Daach un dä Zigg aanzeije.",
+       "histlegend": "Heh kanns De Väsiohne för et Verjliische ußsöhke: Dun met dä Knöpp di zweij makehre, zwesche dänne De de Ungerscheid jezeich krije wells, dann dröck „<b style=\"padding:2px; background-color:#ddd; color:black\">{{int:compareselectedversions}}</b>“ udder klek op ein vun dä Knöpp övver udder onger dä Less.<br />\nVerklierung:\n({{int:cur}}) = donn met de neuste Väsjohn verjliische,\n({{int:last}}) = donn met de Väsjohn ein doför verjliische,\n<b>{{int:minoreditletter}}</b> = en klein <strong>M</strong>ini-Änderong,\nDattum+Uhrzigg = don de Väsjohn fun dämm Daach un dä Zigg aanzeije.",
        "history-fieldset-title": "Wat uß de Verjangeheit ußwähle?",
        "history-show-deleted": "blohß fottjeschmeße Versione",
        "histfirst": "de Ählste",
        "difference-title-multipage": "Ongerscheide zwesche dä Sigge „$1“ un „$2“",
        "difference-multipage": "(Ongerscheide zwesche Sigge)",
        "lineno": "Reih $1:",
-       "compareselectedversions": "Dun de markehte Väsjohn verjliiche",
+       "compareselectedversions": "Dun de markehte Väsjohne verjliiche",
        "showhideselectedversions": "De ußjewählte Versione aanzeije udder vershteiche",
        "editundo": "De läzde Änderong zeröck nämme",
        "diff-empty": "(Keine Ongerscheid)",
index 293c1c6..4f3f53a 100644 (file)
        "nstab-template": "Şablón",
        "nstab-help": "Ayudo",
        "nstab-category": "Kategoría",
+       "mainpage-nstab": "La Primera Hoja",
        "nosuchaction": "No egziste esa aksyon",
        "nosuchactiontext": "La aksyon espesefikada por el URL es invalido.\nEs posivle ke el URL fue eskrito mal, o ke segite un enlase inkorrecto.\nTambiem puede indikar un yerro en la programa uzado por {{SITENAME}}.",
        "nosuchspecialpage": "No ay tala hoja especial",
        "accountcreated": "Cuento creado",
        "accountcreatedtext": "El kuento de usuario para [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) fue kreado.",
        "loginlanguagelabel": "Lingua: $1",
+       "pt-login": "Entrar",
+       "pt-createaccount": "Criar un cuento",
        "changepassword": "Trocar el kóddiche",
        "resetpass_header": "Kambiar kontrasenya del kuento",
        "oldpassword": "Kóddiche viejo:",
index 1d88838..f7332f9 100644 (file)
        "resetpass-submit-loggedin": "कूटशब्द बदली",
        "resetpass-submit-cancel": "रद्द करी",
        "resetpass-wrong-oldpass": "अमान्य अस्थायी वा अखुनका कूटशब्द।\nअहाँ पहिनहिये सफलतासँ कूटशब्द बदलि लेने छी वा एकटा नव अस्थायी कूटशब्द लेल आग्रह केने छी।",
-       "resetpass-recycled": "रà¥\80सà¥\87à¤\9f à¤\95रà¤\8fà¤\95à¥\87 à¤²à¥\87ल à¤¨à¤¯à¤¾à¤\81 à¤\95à¥\82à¤\9fशबà¥\8dदमà¥\87 à¤\95à¥\83पया à¤\85पन à¤µà¤°à¥\8dतमान à¤\95à¥\82à¤\9fशबà¥\8dद à¤¨à¥\88 à¤¦ à¤\95à¥\87 à¤¨à¤¯à¤¾à¤\81 à¤\95à¥\82à¤\9fशबà¥\8dद à¤¦à¥\87ल à¤\9cाà¤\89।",
+       "resetpass-recycled": "रिसà¥\87à¤\9f à¤\95रà¥\88à¤\95 à¤²à¥\87ल à¤¨à¤¯à¤¾à¤\81 à¤\95à¥\82à¤\9fशबà¥\8dदमà¥\87 à¤\95à¥\83पया à¤\85पन à¤µà¤°à¥\8dतमान à¤\95à¥\82à¤\9fशबà¥\8dद à¤¨à¥\88 à¤¦ à¤¨à¤¯à¤¾à¤\81 à¤\95à¥\82à¤\9fशबà¥\8dद à¤¦à¥\87ल à¤\9cाà¤\8f।",
        "resetpass-temp-emailed": "अहाँ अखन एकटा अस्थायी ई-पत्र कोड सँ सम्प्रवेशित केनए छी। सम्प्रवेश पूर्ण करए के लेल अहाँ के एतए नयाँ कूटशब्द राखए पडत:",
        "resetpass-temp-password": "तात्कालिक कूटशब्द:",
        "resetpass-abort-generic": "कूटशब्दमे बदलाव कोनो एक्सटेंशनद्वारा रोकल गएल अछि।",
        "emailccme": "हमर सन्देशक द्वितीयक हमर ई-पत्रपर पठाबी",
        "emailccsubject": "अहाँक संदेशक द्वितीयक $1: $2",
        "emailsent": "ई-पत्र पठेलौं",
-       "emailsenttext": "à¤\85हाà¤\81à¤\95 à¤\88-पतà¥\8dर à¤¸à¤\82देश पठाएल गेल।",
+       "emailsenttext": "à¤\85हाà¤\81à¤\95 à¤\88-पतà¥\8dर à¤¸à¤¨à¥\8dदेश पठाएल गेल।",
        "emailuserfooter": "ई ई-मेल $1 {{GENDER:$1|द्वारा}} {{GENDER:$2|$2}}क भेजल गेल छल जेकर लेल \"{{int:emailuser}}\" कार्यके {{SITENAME}} पर प्रयोगमे लाबल गेल छल।",
        "usermessage-summary": "प्रणाली सन्देश छोडि रहल अछि।",
        "usermessage-editor": "प्रणालीक दूत",
        "export-pagelinks": "लागिबला पन्ना सभकेँ एतेक तह धरि राखू:",
        "allmessages": "प्रणालीक सन्देश",
        "allmessagesname": "नाम",
-       "allmessagesdefault": "पà¥\82रà¥\8dवनिरà¥\8dधारित à¤¸à¤\82देश पाठ",
-       "allmessagescurrent": "à¤\85à¤\96à¥\81नà¤\95ा à¤¸à¤\82देश पाठ",
+       "allmessagesdefault": "पà¥\82रà¥\8dवनिरà¥\8dधारित à¤¸à¤¨à¥\8dदेश पाठ",
+       "allmessagescurrent": "à¤\85à¤\96à¥\81नà¤\95ा à¤¸à¤¨à¥\8dदेश पाठ",
        "allmessagestext": "ई मिडियाविकी नामस्थानमे उपलब्ध संस्थागत सन्देशक सूची छी।\nकृपा कऽ देखी [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation MediaWiki Localisation] आ [https://translatewiki.net translatewiki.net] जँ अहाँ मिडियाविकीक स्थानिकीकरणक मूलक अनुवादमे योगदान करए चाहै छी।",
        "allmessagesnotsupportedDB": "ई पन्ना प्रयोगमे नै आनल जा सकैए कारण '''$wgUseDatabaseMessages''' अशक्त कएल अछि।",
        "allmessages-filter-legend": "चलनी",
        "tooltip-ca-nstab-special": "ई एकटा विशिष्ट पन्ना छी, आ अहाँ एकरा सम्पादित नै कऽ सकै छी",
        "tooltip-ca-nstab-project": "परियोजना पन्ना देखू",
        "tooltip-ca-nstab-image": "सञ्चिकाक पृष्ठ देखी",
-       "tooltip-ca-nstab-mediawiki": "पà¥\8dरणालà¥\80à¤\95 à¤¸à¤\82दà¥\87श à¤¦à¥\87à¤\96à¥\82",
+       "tooltip-ca-nstab-mediawiki": "पà¥\8dरणालà¥\80à¤\95 à¤¸à¤¨à¥\8dदà¥\87श à¤¦à¥\87à¤\96à¥\80",
        "tooltip-ca-nstab-template": "नमूना देखू",
        "tooltip-ca-nstab-help": "सहायता पृष्ठ देखू",
        "tooltip-ca-nstab-category": "संवर्ग पन्ना देखू",
        "feedback-error1": "त्रुटि: नै पहचानल गेल परिणाम एपीआईसँ",
        "feedback-error2": "त्रुटि: संपादन विफल भेल",
        "feedback-error3": "त्रुटि:एपीआईसँग कोनो प्रतिक्रिया नै",
-       "feedback-message": "सà¤\82देश:",
+       "feedback-message": "सनà¥\8dदेश:",
        "feedback-subject": "विषय:",
        "feedback-submit": "दिअ",
        "feedback-thanks-title": "धन्यवाद!",
index f76875c..38d5b17 100644 (file)
        "changepassword-success": "Вашата лозинка е сменета!",
        "changepassword-throttled": "Имате премногу обиди за најава за кратко време.\nПочекајте $1 пред да се обидете повторно.",
        "botpasswords": "Ботовски лозинки",
+       "botpasswords-disabled": "Ботовските лозинки се оневозможени.",
+       "botpasswords-no-central-id": "За да користите ботовски лозинки, мора да сте најавени со централизирана сметка.",
        "botpasswords-existing": "Постоечки ботовски лозинки",
        "botpasswords-createnew": "Направи нова ботовска лозинка",
        "botpasswords-editexisting": "Измени постоечка ботовска лозинка",
        "botpasswords-label-delete": "Избриши",
        "botpasswords-label-resetpassword": "Ставете нова лозинка",
        "botpasswords-label-grants": "Применливи доделувања:",
+       "botpasswords-help-grants": "Секое доделување дава пристап до список до наведени права што веќе ги има корисничката сметка. Повеќе ќе најдете на [[Special:ListGrants|табелата со доделувања]].",
        "botpasswords-label-restrictions": "Ограничувања на употребата:",
        "botpasswords-label-grants-column": "Доделено",
        "botpasswords-bad-appid": "Името на ботот „$1“ е неважечко.",
        "botpasswords-no-provider": "BotPasswordsSessionProvider  е недостапен.",
        "botpasswords-restriction-failed": "Не можете да се најавите поради ограничувањата за лозинки на ботови.",
        "botpasswords-invalid-name": "Укажаното корисничко име не го содржи одделувачот ботовска лозинка („$1“).",
+       "botpasswords-not-exist": "Корисникот „$1“ нема ботовска лозинка „$2“.",
        "resetpass_forbidden": "Лозинките не може да се менуваат",
+       "resetpass_forbidden-reason": "Лозинките не може да се менуваат: $1",
        "resetpass-no-info": "Мора да сте најавени ако сакате да имате директен пристап до оваа страница.",
        "resetpass-submit-loggedin": "Смени лозинка",
        "resetpass-submit-cancel": "Откажи",
        "log-action-filter-managetags-activate": "Активирање на ознаки",
        "log-action-filter-managetags-deactivate": "Деактивирање на ознаки",
        "log-action-filter-move-move": "Преместување без запис врз пренасочувања",
+       "log-action-filter-upload-upload": "Ново подигање",
+       "log-action-filter-upload-overwrite": "Преподигање",
        "authmanager-authplugin-setpass-bad-domain": "Неважечки домен.",
        "authmanager-autocreate-noperm": "Автоматското создавање на сметки не е дозволено.",
        "authmanager-autocreate-exception": "Автоматското создавање на сметки е привремено оневозможено поради претходни грешки.",
index 17c6f6b..4d45b82 100644 (file)
        "rightslogtext": "ही सदस्य अधिकारांमधील बदलांची नोंद आहे.",
        "action-read": "हे पान वाचा",
        "action-edit": "हे पान संपादित करा",
-       "action-createpage": "लà¥\87à¤\96 à¤¬à¤¨à¤µा",
-       "action-createtalk": "à¤\9aरà¥\8dà¤\9aा à¤ªà¥\83षà¥\8dठà¥\87 तयार करा",
+       "action-createpage": "हà¥\87 à¤ªà¤¾à¤¨ à¤¤à¤¯à¤¾à¤° à¤\95रा",
+       "action-createtalk": "हà¥\87 à¤\9aरà¥\8dà¤\9aा à¤ªà¥\83षà¥\8dठ तयार करा",
        "action-createaccount": "हे सदस्यखाते तयार करा",
        "action-autocreateaccount": "हे बाह्य सदस्य खाते आपोआप तयार करा",
        "action-history": "या पानाचा इतिहास बघा.",
index 06b9626..c381250 100644 (file)
        "trackingcategories-msg": "Sporingskategori",
        "trackingcategories-name": "Beskjednavn",
        "trackingcategories-desc": "Kategori-inklusjonskriterium",
+       "restricted-displaytitle-ignored": "Sider med ignorerte visningstitler",
        "noindex-category-desc": "Denne siden indekseres ikke av roboter fordi den er merket med det magiske ordet <code><nowiki>__NOINDEX__</nowiki></code> og er i navnerom der dette flagget tillates.",
        "index-category-desc": "Denne siden er påført det magiske ordet <code><nowiki>__INDEX__</nowiki></code> (og er i et navnerom hvor flagget er tillatt), og vil derfor bli indeksert av roboter selv når det normalt ikke ville skjedd.",
        "post-expand-template-inclusion-category-desc": "Sidestørrelsen er større enn <code>$wgMaxArticleSize</code> etter at alle maler er utvidet, så noen maler ble ikke utvidet.",
index eb0b980..bfdff5a 100644 (file)
        "timezone-local": "Lokale tijd",
        "duplicate-defaultsort": "'''Waarschuwing:''' de standaardsortering \"$2\" krijgt voorrang voor de sortering \"$1\".",
        "duplicate-displaytitle": "<strong>Waarschuwing:</strong>Titelweergave \"$2\" overschrijft eerdere titelweergave \"$1\".",
-       "restricted-displaytitle": "<strong>Waarschuwing:</strong> weergegeven paginanaam \"$1\" is genegeerd omdat deze niet overeenkomt met de werkelijke paginanaam.",
+       "restricted-displaytitle": "<strong>Waarschuwing:</strong> Titelweergave \"$1\" werd genegeerd omdat deze niet overeenkomt met de werkelijke paginatitel.",
        "invalid-indicator-name": "<strong>Fout:</strong> de eigenschap <code>name</code> van de paginastatusindicators mag niet leeg zijn.",
        "version": "Versie",
        "version-extensions": "Geïnstalleerde uitbreidingen",
index b94ec8d..a2c40da 100644 (file)
        "authmanager-create-disabled": "Utworzenie konta jest wyłączone.",
        "authmanager-create-from-login": "Aby utworzyć konto, wypełnij poniższe pola.",
        "authmanager-authplugin-setpass-failed-title": "Zmiana hasła nie powiodła się",
+       "authmanager-authplugin-setpass-failed-message": "Wtyczka do uwierzytelniania uniemożliwiła zmianę hasła.",
+       "authmanager-authplugin-create-fail": "Wtyczka do uwierzytelniania uniemożliwiła utworzenie konta.",
        "authmanager-authplugin-setpass-denied": "Wtyczka uwierzytelniania nie zezwala na zmianę haseł.",
        "authmanager-authplugin-setpass-bad-domain": "Niepoprawna domena.",
        "authmanager-autocreate-noperm": "Automatyczne tworzenie konta jest niedozwolone.",
index e66e333..b5a4525 100644 (file)
        "tog-ccmeonemails": "Enviar-me cópias das mensagens por correio electrónico que eu enviar a outros utilizadores",
        "tog-diffonly": "Não mostrar o conteúdo da página ao comparar duas edições",
        "tog-showhiddencats": "Mostrar categorias ocultas",
-       "tog-norollbackdiff": "Omitir diferenças depois de reverter edições em bloco",
+       "tog-norollbackdiff": "Ocultar diferenças depois de reverter edições em bloco",
        "tog-useeditwarning": "Avisar-me ao abandonar uma página editada sem gravar as alterações.",
        "tog-prefershttps": "Usar sempre uma ligação segura enquanto tiver sessão iniciada",
        "underline-always": "Sempre",
index db50cd2..2fc6327 100644 (file)
        "subject": "Used as label for input box in the EditPage page.\n\nSee also:\n* {{msg-mw|Summary}}\n{{Identical|Subject}}",
        "minoredit": "Text above Save page button in editor\n\nSee also:\n* {{msg-mw|Minoredit}}\n* {{msg-mw|Accesskey-minoredit}}\n* {{msg-mw|Tooltip-minoredit}}",
        "watchthis": "Text of checkbox above {{msg-mw|Showpreview}} button in editor.\n\nSee also:\n* {{msg-mw|Watchthis}}\n* {{msg-mw|Accesskey-watch}}\n* {{msg-mw|Tooltip-watch}}\n{{Identical|Watch this page}}",
-       "savearticle": "Text on the Save page button. See also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons.\n\nSee also:\n* {{msg-mw|Savearticle}}\n* {{msg-mw|Accesskey-save}}\n* {{msg-mw|Tooltip-save}}\n{{Identical|Save page}}",
-       "publishpage": "Text on the button to save the changes to the page. It should be an action which is short and makes clear that the effect is immediate and public.\n\nSee also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons.\n\nNote: This i18n is being introduced in advance of usage to provide extra time for translators.\n\nSee also:\n* {{msg-mw|Accesskey-publish}}\n* {{msg-mw|Tooltip-publish}}\n{{Identical|Publish page}}",
+       "savearticle": "Text on the button to create a new page. It should be an action which is short and makes clear that the effect is immediate and public.\n\nSee also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons, and {{msg-mw|savechanges}} for the label for the button when the page is being modified.\n\nSee also:\n* {{msg-mw|Accesskey-publish}}\n* {{msg-mw|Tooltip-publish}}\n{{Identical|Save page}}",
+       "savechanges": "Text on the button to save the changes to an existing page. It should be an action which is short and makes clear that the effect is immediate and public.\n\nSee also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons, and {{msg-mw|savearticle}} for the label for the button when the page is being modified.\n\nNote: This i18n is being introduced in advance of usage to provide extra time for translators.\n\nSee also:\n* {{msg-mw|Accesskey-publish}}\n* {{msg-mw|Tooltip-publish}}\n{{Identical|Save changes}}",
+       "publishpage": "Text on the button to create a new page on a public wiki. It should be an action which is short and makes clear that the effect is immediate and public.\n\nSee also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons, and {{msg-mw|publishchanges}} for the label for the button when the page is being modified.\n\nNote: This i18n is being introduced in advance of usage to provide extra time for translators.\n\nSee also:\n* {{msg-mw|Accesskey-publish}}\n* {{msg-mw|Tooltip-publish}}\n{{Identical|Publish page}}",
+       "publishchanges": "Text on the button to save the changes to an existing page on a public wiki. It should be an action which is short and makes clear that the effect is immediate and public.\n\nSee also {{msg-mw|showpreview}} and {{msg-mw|showdiff}} for the other buttons, and {{msg-mw|publishchanges}} for the label for the button when the page is being created.\n\nNote: This i18n is being introduced in advance of usage to provide extra time for translators.\n\nSee also:\n* {{msg-mw|Accesskey-publish}}\n* {{msg-mw|Tooltip-publish}}\n{{Identical|Publish changes}}",
        "preview": "The title of the Preview page shown after clicking the \"Show preview\" button in the edit page. Since this is a heading, it should probably be translated as a noun and not as a verb.\n\n{{Identical|Preview}}",
        "showpreview": "The text of the button to preview the page you are editing. See also {{msg-mw|showdiff}} and {{msg-mw|savearticle}} for the other buttons.\n\nSee also:\n* {{msg-mw|Showpreview}}\n* {{msg-mw|Accesskey-preview}}\n* {{msg-mw|Tooltip-preview}}\n{{Identical|Show preview}}",
        "showdiff": "Button below the edit page. See also {{msg-mw|Showpreview}} and {{msg-mw|Savearticle}} for the other buttons.\n\nSee also:\n* {{msg-mw|Showdiff}}\n* {{msg-mw|Accesskey-diff}}\n* {{msg-mw|Tooltip-diff}}\n{{Identical|Show change}}",
index cb50ba6..b6ee480 100644 (file)
        "login-security": "Подтвердите свою личность",
        "nav-login-createaccount": "Представиться / зарегистрироваться",
        "userlogin": "Представиться или зарегистрироваться",
-       "userloginnocreate": "Ð\9fÑ\80едÑ\81Ñ\82авиÑ\82Ñ\8cÑ\81Ñ\8f",
+       "userloginnocreate": "Ð\92ойÑ\82и",
        "logout": "Завершение сеанса",
        "userlogout": "Завершение сеанса",
        "notloggedin": "Вы не представились системе",
        "passwordreset": "Сброс пароля",
        "passwordreset-text-one": "Заполните эту форму, чтобы сбросить свой пароль.",
        "passwordreset-text-many": "{{PLURAL:$1|Заполните одно из полей для получения временного пароля по электронной почте.}}",
-       "passwordreset-disabled": "СбÑ\80оÑ\81Ñ\8b Ð¿Ð°Ñ\80олÑ\8f Ð¾Ñ\82клÑ\8eÑ\87енÑ\8b Ð½Ð° Ñ\8dÑ\82ой Ð²Ð¸ÐºÐ¸.",
+       "passwordreset-disabled": "СбÑ\80оÑ\81Ñ\8b Ð¿Ð°Ñ\80олÑ\8f Ð² Ñ\8dÑ\82ой Ð²Ð¸ÐºÐ¸ Ð¾Ñ\82клÑ\8eÑ\87енÑ\8b.",
        "passwordreset-emaildisabled": "Функции электронной почты отключены в этой вики.",
        "passwordreset-username": "Имя участника:",
        "passwordreset-domain": "Домен:",
        "nosuchsectiontitle": "Невозможно найти раздел",
        "nosuchsectiontext": "Вы пытаетесь редактировать несуществующий раздел.\nВозможно, он был перемещён или удалён, пока вы просматривали эту страницу.",
        "loginreqtitle": "Требуется авторизация",
-       "loginreqlink": "пÑ\80едÑ\81Ñ\82авиÑ\82Ñ\8cÑ\81Ñ\8f",
+       "loginreqlink": "войÑ\82и",
        "loginreqpagetext": "Вы должны $1, чтобы просмотреть другие страницы.",
        "accmailtitle": "Пароль выслан",
        "accmailtext": "Сгенерированный случайным образом пароль для [[User talk:$1|$1]] выслан на адрес $2.\n\nПосле авторизации можно будет сменить пароль для этой учётной записи на ''[[Special:ChangePassword|специальной странице смены пароля]]''.",
        "userrights-groupsmember-auto": "Неявно состоит в группах:",
        "userrights-groups-help": "Вы можете изменить группы, в которые входит этот участник.\n* Если около названия группы стоит отметка, значит участник входит в эту группу.\n* Если отметка не стоит — участник не относится к соответствующей группе.\n* Знак * отмечает, что вы не сможете удалить участника из группы, если добавите его в неё, или наоборот.",
        "userrights-reason": "Причина:",
-       "userrights-no-interwiki": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ñ\80азÑ\80еÑ\88ениÑ\8f Ð¸Ð·Ð¼ÐµÐ½Ñ\8fÑ\82Ñ\8c Ð¿Ñ\80ава Ñ\83Ñ\87аÑ\81Ñ\82ников Ð½Ð° других вики.",
+       "userrights-no-interwiki": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ñ\80азÑ\80еÑ\88ениÑ\8f Ð¸Ð·Ð¼ÐµÐ½Ñ\8fÑ\82Ñ\8c Ð¿Ñ\80ава Ñ\83Ñ\87аÑ\81Ñ\82ников Ð² других вики.",
        "userrights-nodatabase": "База данных $1 не существует или расположена не локально.",
        "userrights-nologin": "Вы должны [[Special:UserLogin|представиться системе]] с учётной записи администратора, чтобы присваивать права участникам.",
        "userrights-notallowed": "У вас нет разрешения добавлять и удалять права участников.",
        "upload-copy-upload-invalid-domain": "Копирование загрузок не доступно в этом домене.",
        "upload-foreign-cant-upload": "Эта вики не настроена для загрузки файлов на запрошенный сторонний файловый репозиторий.",
        "upload-foreign-cant-load-config": "Не удалось загрузить конфигурацию загрузки файлов на внешнее хранилище файлов.",
-       "upload-dialog-disabled": "Ð\97агÑ\80Ñ\83зка Ñ\84айлов Ñ\81 Ð¿Ð¾Ð¼Ð¾Ñ\89Ñ\8cÑ\8e Ñ\8dÑ\82ого Ð´Ð¸Ð°Ð»Ð¾Ð³Ð¾Ð²Ð¾Ð³Ð¾ Ð¾ÐºÐ½Ð° Ð¾Ñ\82клÑ\8eÑ\87ена Ð² Ð´Ð°Ð½Ð½Ð¾Ð¹ Ð²Ð¸ÐºÐ¸.",
+       "upload-dialog-disabled": "Ð\9dа Ñ\8dÑ\82ом Ð²Ð¸ÐºÐ¸-Ñ\81айÑ\82е Ð¾Ñ\82клÑ\8eÑ\87ена Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñ\81Ñ\82Ñ\8c Ð·Ð°Ð³Ñ\80Ñ\83зки Ñ\84айлов Ñ\81 Ð¿Ð¾Ð¼Ð¾Ñ\89Ñ\8cÑ\8e Ñ\8dÑ\82ого Ð´Ð¸Ð°Ð»Ð¾Ð³Ð¾Ð²Ð¾Ð³Ð¾ Ð¾ÐºÐ½Ð°.",
        "upload-dialog-title": "Загрузить файл",
        "upload-dialog-button-cancel": "Отменить",
        "upload-dialog-button-done": "Готово",
        "authmanager-realname-help": "Настоящее имя участника",
        "authmanager-provider-password": "Аутентификация на основе пароля",
        "authmanager-provider-temporarypassword": "Временный пароль",
+       "authprovider-confirmlink-option": "$1 ($2)",
        "authprovider-confirmlink-request-label": "Учётные записи, которые должны быть связаны",
        "authprovider-confirmlink-success-line": "$1: успешно связан.",
-       "authprovider-confirmlink-failed": "Связывание учётных записей не полностью удалось: $1",
+       "authprovider-confirmlink-failed-line": "$1: $2",
+       "authprovider-confirmlink-failed": "Удалось связать не все учётные записи: $1",
        "authprovider-resetpass-skip-label": "Пропустить",
        "authprovider-resetpass-skip-help": "Пропустить сброс пароля.",
        "authform-newtoken": "Отсутствует токен. $1",
index b0163b6..75e3238 100644 (file)
        "welcomeuser": "Johar",
        "welcomecreation-msg": "Amaḱ ekaunṭ do̠ jhićena. Amaḱ pạsindko bodol alom hiṛińa.",
        "yourname": "Beoboharicaḱ ńutum",
+       "userlogin-yourname": "Beoharićaḱ ńutum:",
        "userlogin-yourname-ph": "Amaḱ beohar ńutum emme.",
        "yourpassword": "Uku namber",
+       "userlogin-yourpassword": "Uku nambar",
+       "userlogin-yourpassword-ph": "Amaḱ uku nambar emme",
+       "createacct-yourpassword-ph": "Uku namber emme",
        "yourpasswordagain": "Arhõ oku namber olme",
+       "createacct-yourpasswordagain": "Uku nambar sãyãḱme",
+       "createacct-yourpasswordagain-ph": "Uku nambar arhõ emme",
        "remembermypassword": "Mitṭen khon bạṛti khata reaḱ cạbi disạ dohoḱma (Jạsti $1 {{PLURAL:$1 din reaḱ din reaḱ}} lạgit)",
+       "userlogin-remembermypassword": "Bolo thirege dohokạńme",
        "yourdomainname": "Amaḱ ḍomen:",
        "externaldberror": "Hoe daṛeyaḱa jahan bahre reaḱ jacaeaḱ ḍaṭabes vul hoeakana se amaḱ bahre reaḱ ekaunṭ do nahaḱ halot aguire ạidạri bạnuḱa.",
        "login": "bolok' duar",
        "logout": "Bahre oḍoń",
        "userlogout": "Bahre oḍoń",
        "notloggedin": "Bhitri baṅ bolokana",
+       "userlogin-noaccount": "Cet́ accountge banuḱtama?",
        "userlogin-joinproject": "Seledoḱ {{SITENAME}}",
        "nologin": "Amaḱ do cet́ wikipeḍiare ekaunṭ banuḱtama? Ado '''$1'''",
        "nologinlink": "account tear me",
        "gotaccount": "Amaḱ do cet́ miṭten ekaunṭ tear menaḱtama? $1 tearmẽ.",
        "gotaccountlink": "Bhitri bolon",
        "userlogin-resetlink": "Amaḱ boloḱ talam cạbi sanamem hiṛińkeda?",
+       "userlogin-resetpassword-link": "Amaḱ uku nambarem hiṛiń akada?",
+       "userlogin-helplink2": "Bolon khạtir go̠ṛo̠",
+       "createacct-emailoptional": "Email ṭhikana (iccha lekate)",
+       "createacct-email-ph": "Amaḱ e-mail ṭhikana emme",
        "createaccountmail": "E-mail hotete",
        "createaccountreason": "Karon",
+       "createacct-submit": "Amaḱ account tearme",
+       "createacct-benefit-heading": "{{SITENAME}} am lekan hoṛ hotete tear akan.",
+       "createacct-benefit-body1": "{{PLURAL:$1|joṛao|joṛaoko}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|sakam|sakamko}}",
+       "createacct-benefit-body3": "nahaḱ {{PLURAL:$1|kamiạ|kạmiako}}",
        "badretype": "Am do okaṭaḱ oku nambarkom em keda ona do baṅ milạolena.",
        "userexists": "Laṛcaṛicaḱ ńutum em hoyena ona do beohar hoyakana.\nDayakatet́ eṭagaḱ ńutum bachaome.",
        "loginerror": "Bhitri bolok do vulgea",
        "login-abort-generic": "Amaḱ bhitri boloḱ do baṅ hoylena - batena.",
        "loginlanguagelabel": "Pạrsi: $1",
        "pt-login": "Bolok' duar",
+       "pt-login-button": "Bolon",
        "pt-createaccount": "Ṭhai benaome",
+       "pt-userlogout": "Bahre oḍoń",
        "user-mail-no-addy": "Jahan e-mail ṭhikana bạgi kate e-mail kul kurumuṭu hoena.",
        "changepassword": "Uku nombor bodolme",
        "resetpass_header": "Ekaunṭ oku namber bodol",
        "currentrev": "Mucạt nãwã aroe",
        "currentrev-asof": "Mucạt nãwã aroy",
        "revisionasof": "Nãwã aro sakam $1 leka",
-       "revision-info": "Ńel/pańja ruạṛ $1 khon $2",
+       "revision-info": "Revision as of $1 by {{GENDER:$6|$2}}$7",
        "previousrevision": "Pạhilaḱ paṛhao ruạṛ",
        "nextrevision": "nãwate n'el ruar",
        "currentrevisionlink": "Nitoḱaḱ nãwa aroy",
        "powersearch-toggleall": "Sanamaḱ",
        "powersearch-togglenone": "Okaṭaḱ hõ baṅ",
        "preferences": "Pạsindko",
-       "mypreferences": "Iñaḱ pạsindko",
+       "mypreferences": "Pạsindko",
        "prefs-skin": "Harta",
        "skin-preview": "Ńel, Unuduḱ",
        "datedefault": "Pạsind banuḱa",
        "emailmessage": "Mesag",
        "emailsend": "Kulmẽ",
        "watchlist": "Inak' n'el ko",
-       "mywatchlist": "Iñak jagarna tạlikạ",
+       "mywatchlist": "Ńeloḱgoḱ tạlika",
        "watchlistfor2": "$1 ($2) lạ̣gitte",
        "watch": "Ńelme",
        "unwatch": "bang nelok' a",
        "contributions": "{{GENDER:$1|Beoharićaḱ }} Kạmiko",
        "contributions-title": "$1 Beoharićaḱ kạmiko",
        "mycontris": "Ińaḱ kạmiko",
+       "anoncontribs": "Ińaḱ kạmiko",
        "contribsub2": "$1 ($2) lạgitte",
        "uctop": "(coṭ utạr)",
        "month": "Cando khon (ar etohopreaḱ)",
        "whatlinkshere-prev": "{{PLURAL:$1 Laha reaḱ Laha reaḱ$1ṭen}}",
        "whatlinkshere-next": "{{PLURAL:$1 |Laha renaḱ | Laha renaḱko $1}}",
        "whatlinkshere-links": "Joṛaoko",
-       "whatlinkshere-hideredirs": "$1 acurgeya",
-       "whatlinkshere-hidetrans": "$1 ṭarnskuleson uduḱme",
-       "whatlinkshere-hidelinks": "$1 joṛao",
+       "whatlinkshere-hideredirs": "$1 arhõ unuduḱ",
+       "whatlinkshere-hidetrans": "Selet́ $1",
+       "whatlinkshere-hidelinks": "$1 joṛaoko",
        "whatlinkshere-hideimages": "$1 Chubi joṛaoko",
        "whatlinkshere-filters": "Sapha",
        "block": "Beoharić esedem",
        "thumbnail-more": "Lạṭui mẽ",
        "thumbnail_error": "Benawakan unuduḱ kạṭuṕ do baṅ ṭhika: $1",
        "import-upload-filename": "Rẽt ńutum",
-       "tooltip-pt-userpage": "Amaḱ́ bebo̠harić sakam",
-       "tooltip-pt-mytalk": "Amaḱ katha ro̠ṛrenaḱ́ sakam",
-       "tooltip-pt-preferences": "Amaḱ pạsindko",
+       "tooltip-pt-userpage": "{{GENDER:|am beoharićaḱ}} sakam",
+       "tooltip-pt-mytalk": "{{GENDER:|Amaḱ}} ro̠ṛreaḱ́ sakam",
+       "tooltip-pt-preferences": "{{GENDER:|Amaḱ}} pạsindko",
        "tooltip-pt-watchlist": "Sakam tạlika okaṭak̕katet́ am do nãwã aroy lạgitem ńeleḱkan",
-       "tooltip-pt-mycontris": "Amaḱ kạmi reaḱ tạlika",
+       "tooltip-pt-mycontris": "Mit́ṭen lisṭ {{GENDER:|amaḱ}} kạmiko reaḱ",
        "tooltip-pt-login": "Am do boloḱ lagit́te udgạoiń emamkana; Nonḍe boloḱ unạḱ jarur do bań kana",
        "tooltip-pt-logout": "O̠nḍo̠ńme",
        "tooltip-pt-createaccount": "Am do mit́ṭen hisạb jhić katet́ boloniń metamkana, tobe joborjosti katet́ do baṅ.",
        "watchlisttools-view": "Jońgṛao bodolaḱko ńel",
        "watchlisttools-edit": "Ńelok tạlika ńel ar joṛao",
        "watchlisttools-raw": "Baṇ purạo akan ńelok tạlika purạomẽ",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_galmarao}}:$1|talk]])",
        "duplicate-defaultsort": "'''Sontoroḱmẽ:''' ḍifolṭ sajao reaḱ cạbi: $2 lahare ḍifolṭ sajao reaḱ sakam: ''$1'' e bae luturaḱ kana.",
        "specialpages": "Osokayteaḱ sakamko",
        "external_image_whitelist": "#Noa sakam do cet leka menaḱa oṅkage dohoemẽ\n#Sanam okte re jạhiren kuṭrạ latar re (khạli hạtiń //talare) bạisạomẽ\n#Noako do bahre reaḱ (hotlinked) chubi reaḱ URL saõte milạo hoyoḱa\n#Okako milạḱa, onako do chubi lekate udugoḱa, baṅkhan do khali chubi joṛao udugoḱa\n#Noa layen reaḱ ehoṕre # menaḱa ona layenko menko hisapte beohar hoyoḱka\n#Noa do kas-baṅ rimjhạoaḱge\n#Noa dag cetanre regex kuṭrạ bạsạomẽ. Noa layen cetleka menaḱa oṅkage dohoemẽ</pre>",
index 15e0e8d..ed486ad 100644 (file)
        "upload-http-error": "Ett HTTP-fel uppstod: $1",
        "upload-copy-upload-invalid-domain": "Uppladdning av kopior är inte tillgängligt från denna domän.",
        "upload-foreign-cant-upload": "Denna wiki är inte konfigurerad för att ladda upp filer till det begärda externa filförvaret.",
-       "upload-foreign-cant-load-config": "Misslyckades att läsa in filuppladdningskonfigurationen för det externa filförvaret.",
+       "upload-foreign-cant-load-config": "Misslyckades att läsa in konfigurationen för filuppladdningar till det externa filförvaret.",
        "upload-dialog-disabled": "Filuppladdningar med denna dialogruta har inaktiverats på denna wiki.",
        "upload-dialog-title": "Ladda upp fil",
        "upload-dialog-button-cancel": "Avbryt",
index 05f5cb8..1ccc6f6 100644 (file)
        "tooltip-summary": "مختصر خلاصہ درج کریں",
        "anonymous": "{{SITENAME}} گمنام صارف",
        "others": "دیگر",
+       "pageinfo-visiting-watchers": "تعداد ناظرین جنہوں نے حالیہ ترامیم کا مشاہدہ کیا",
+       "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
        "pageinfo-toolboxlink": "معلومات صفحہ",
        "markaspatrolledtext": "اس صفحہ کو بطور مراجعت شدہ نشان زد کریں",
        "deletedrevision": "حذف شدہ پرانی ترمیم $1۔",
index ae4051c..f2c9f0c 100644 (file)
        "rightslogtext": "Đây là nhật trình lưu những thay đổi đối với các quyền hạn thành viên.",
        "action-read": "đọc trang này",
        "action-edit": "sửa đổi trang này",
-       "action-createpage": "tạo trang",
-       "action-createtalk": "tạo trang thảo luận",
+       "action-createpage": "tạo trang này",
+       "action-createtalk": "tạo trang thảo luận này",
        "action-createaccount": "mở tài khoản này",
        "action-autocreateaccount": "tự động tạo tài khoản người dùng bên ngoài này",
        "action-history": "xem lịch sử của trang này",
        "upload-http-error": "Xảy ra lỗi HTTP: $1",
        "upload-copy-upload-invalid-domain": "Không có sẵn các bản sao tải lên tại tên miền này.",
        "upload-foreign-cant-upload": "Wiki này không được thiết lập để tải tập tin lên kho tập tin chung bên ngoài được chỉ định.",
+       "upload-foreign-cant-load-config": "Thất bại khi tải cấu hình cho tập tin tải lên kho tập tin bên ngoài.",
+       "upload-dialog-disabled": "Chức năng tải lên tập tin qua hộp thoại này bị tắt trong wiki này.",
        "upload-dialog-title": "Tải tập tin lên",
        "upload-dialog-button-cancel": "Hủy bỏ",
        "upload-dialog-button-done": "Xong",
        "pageinfo-category-files": "Số tập tin",
        "markaspatrolleddiff": "Đánh dấu tuần tra",
        "markaspatrolledtext": "Đánh dấu tuần tra trang này",
-       "markaspatrolledtext-file": "Đánh dấu phiên bản fiel này là đã kiểm tra",
+       "markaspatrolledtext-file": "Đánh dấu đã tuần tra phiên bản file này",
        "markedaspatrolled": "Đã đánh dấu tuần tra",
        "markedaspatrolledtext": "Phiên bản được chọn của [[:$1]] đã được đánh dấu tuần tra.",
        "rcpatroldisabled": "“Thay đổi gần đây” của các trang tuần tra không bật",
        "linkaccounts-success-text": "Đã liên kết tài khoản.",
        "linkaccounts-submit": "Liên kết tài khoản",
        "unlinkaccounts": "Gỡ liên kết tài khoản",
-       "unlinkaccounts-success": "Đã gỡ liên kết tài khoản."
+       "unlinkaccounts-success": "Đã gỡ liên kết tài khoản.",
+       "authenticationdatachange-ignored": "Tác vụ thay đổi dữ liệu xác thực không được xử lý. Có lẽ nhà cung cấp chưa được cấu hình?"
 }
index c289493..268138e 100644 (file)
        "passwordreset-emailsentusername": "טאמער איז פאראן אן ע־פאסט אדרעס פארקניפט מיט דעם באניצער־נאמען, וועט מען שיקן א פאסווארט צוריקשטעלן ע-פּאָסט.",
        "passwordreset-emailsent-capture": "מען האט געשיקט א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן.",
        "passwordreset-emailerror-capture": "מען האט געשאפן א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן, אבער שיקן צום {{GENDER:$2|באניצער}}איז דורכגעפאלן: $1",
+       "passwordreset-nocaller": "מען דארף פֿארזארגן א רופֿער",
        "passwordreset-invalideamil": "אומגילטיקער ע־פאסט אדרעס",
        "changeemail": "ענדערן אדער אראפנעמען ע-פּאָסט אַדרעס",
        "changeemail-header": "דערגאַנצט די פֿאָרעם צו ענדערן אייער ע-פּאָסט אַדרעס .\nטאמער ווילט איר אראפנעמען די צוארדנונג פון איינעם פון אייערע ע־פאסט אדרעסן פו אייער קאנטע, לאזט ליידיג דעם נייעם ע־פאסט אדרעס ווען איר גיט איין די פֿארעם.",
        "special-characters-title-endash": "ען טירע",
        "special-characters-title-emdash": "עם טירע",
        "special-characters-title-minus": "מינוס",
+       "mw-widgets-titleinput-description-new-page": "דער בלאט עקזיסטירט נאך נישט",
        "log-action-filter-upload": "טיפ ארויפֿלאד:",
        "log-action-filter-all": "אַלע",
        "log-action-filter-delete-delete": "אויסמעקן בלאט",
index 3271fd6..db3af92 100644 (file)
@@ -41,6 +41,7 @@ class BackupDumper extends Maintenance {
        public $revEndId = 0;
        public $dumpUploads = false;
        public $dumpUploadFileContents = false;
+       public $orderRevs = false;
 
        protected $reportingInterval = 100;
        protected $pageCount = 0;
@@ -271,7 +272,7 @@ class BackupDumper extends Maintenance {
                } elseif ( is_null( $this->pages ) ) {
                        # Page dumps: all or by page ID range
                        if ( $this->startId || $this->endId ) {
-                               $exporter->pagesByRange( $this->startId, $this->endId );
+                               $exporter->pagesByRange( $this->startId, $this->endId, $this->orderRevs );
                        } elseif ( $this->revStartId || $this->revEndId ) {
                                $exporter->revsByRange( $this->revStartId, $this->revEndId );
                        } else {
index 7b92f89..4f625fc 100644 (file)
@@ -62,10 +62,6 @@ class CopyFileBackend extends Maintenance {
 
                $rateFile = $this->getOption( 'ratefile' );
 
-               if ( $this->hasOption( 'utf8only' ) && !extension_loaded( 'mbstring' ) ) {
-                       $this->error( "Cannot check for UTF-8, mbstring extension missing.", 1 ); // die
-               }
-
                foreach ( $containers as $container ) {
                        if ( $subDir != '' ) {
                                $backendRel = "$container/$subDir";
index d4255a0..9bf1222 100644 (file)
@@ -50,6 +50,8 @@ TEXT
                $this->addOption( 'stable', 'Dump stable versions of pages' );
                $this->addOption( 'revrange', 'Dump range of revisions specified by revstart and ' .
                        'revend parameters' );
+               $this->addOption( 'orderrevs', 'Dump revisions in ascending revision order ' .
+                       '(implies dump of a range of pages)' );
                $this->addOption( 'pagelist',
                        'Dump only pages included in the file', false, true );
                // Options
@@ -127,6 +129,7 @@ TEXT
                $this->skipFooter = $this->hasOption( 'skip-footer' );
                $this->dumpUploads = $this->hasOption( 'uploads' );
                $this->dumpUploadFileContents = $this->hasOption( 'include-files' );
+               $this->orderRevs = $this->hasOption( 'orderrevs' );
        }
 }
 
index e31e77e..7f4e132 100644 (file)
@@ -2352,7 +2352,5 @@ return [
        ],
 
        /* OOjs UI */
-       // WARNING: OOjs-UI is NOT TESTED with older browsers and is likely to break
-       // if loaded in browsers that don't support ES5
        // @see ResourcesOOUI.php
 ];
index 400a432..ac7f6c6 100644 (file)
@@ -8,7 +8,8 @@
                        "Kenrick95",
                        "McDutchie",
                        "Rv77ax",
-                       "William Surya Permana"
+                       "William Surya Permana",
+                       "Rachmat.Wahidi"
                ]
        },
        "ooui-outline-control-move-down": "Pindahkan butir ke bawah",
@@ -23,6 +24,8 @@
        "ooui-dialog-process-dismiss": "Tutup",
        "ooui-dialog-process-retry": "Coba lagi",
        "ooui-dialog-process-continue": "Lanjutkan",
+       "ooui-selectfile-button-select": "Pilih berkas",
        "ooui-selectfile-not-supported": "Peilihan berkas tidak didukung",
-       "ooui-selectfile-placeholder": "Tidak ada berkas yang terpilih"
+       "ooui-selectfile-placeholder": "Tidak ada berkas yang terpilih",
+       "ooui-selectfile-dragdrop-placeholder": "Letakkan berkas di sini"
 }
diff --git a/resources/lib/oojs-ui/i18n/inh.json b/resources/lib/oojs-ui/i18n/inh.json
new file mode 100644 (file)
index 0000000..a897ed5
--- /dev/null
@@ -0,0 +1,24 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Adam-Yourist",
+                       "ElizaMag"
+               ]
+       },
+       "ooui-outline-control-move-down": "Элемент Iолохеяккха",
+       "ooui-outline-control-move-up": "Элемент Iолакхеяккха",
+       "ooui-outline-control-remove": "ДIаяккха пункт",
+       "ooui-toolbar-more": "Кхы а",
+       "ooui-toolgroup-expand": "Дукха",
+       "ooui-toolgroup-collapse": "КӀезига",
+       "ooui-dialog-message-accept": "ОК",
+       "ooui-dialog-message-reject": "Эшац",
+       "ooui-dialog-process-error": "Харцахь хилар цхьа хIама",
+       "ooui-dialog-process-dismiss": "ДIакъовла",
+       "ooui-dialog-process-retry": "Кхы цкъа де гIорта",
+       "ooui-dialog-process-continue": "ДIаьхде",
+       "ooui-selectfile-button-select": "Файл хьахаржа",
+       "ooui-selectfile-not-supported": "Файл харжа вIаштаь дац",
+       "ooui-selectfile-placeholder": "Файл хержа яц",
+       "ooui-selectfile-dragdrop-placeholder": "Хьадехьаяккха файл укхаз"
+}
index 1024d2a..8da8ef1 100644 (file)
@@ -8,18 +8,20 @@
                        "Meursault2004"
                ]
        },
-       "ooui-outline-control-move-down": "Pindhahaken butir mangandhap",
-       "ooui-outline-control-move-up": "Pindhah kara menyang dhuwur",
-       "ooui-outline-control-remove": "Busak kara",
-       "ooui-toolbar-more": "Manèh",
-       "ooui-toolgroup-expand": "Manèh",
-       "ooui-toolgroup-collapse": "Suda",
-       "ooui-dialog-message-accept": "Oké",
-       "ooui-dialog-message-reject": "Batal",
+       "ooui-outline-control-move-down": "Lih barang mangisor",
+       "ooui-outline-control-move-up": "Lih barang mandhuwur",
+       "ooui-outline-control-remove": "Buwang barang",
+       "ooui-toolbar-more": "Liyané",
+       "ooui-toolgroup-expand": "Liyané",
+       "ooui-toolgroup-collapse": "Sacukupé",
+       "ooui-dialog-message-accept": "Ha'a",
+       "ooui-dialog-message-reject": "Wurungaké",
        "ooui-dialog-process-error": "Ana sing klèru",
        "ooui-dialog-process-dismiss": "Tutup",
        "ooui-dialog-process-retry": "Jajal manèh",
-       "ooui-dialog-process-continue": "Tutug",
-       "ooui-selectfile-not-supported": "Pilihan berkas ora disokong",
-       "ooui-selectfile-placeholder": "Ora ana berkas sing kapilih"
+       "ooui-dialog-process-continue": "Banjuraké",
+       "ooui-selectfile-button-select": "Pilih barkas",
+       "ooui-selectfile-not-supported": "Barkas pilihan ora disengkuyung",
+       "ooui-selectfile-placeholder": "Ora ana barkas sing dipilih",
+       "ooui-selectfile-dragdrop-placeholder": "Dèkèk barkas ing kéné"
 }
index 94b4687..9954744 100644 (file)
@@ -12,5 +12,6 @@
        "ooui-dialog-message-reject": "Betal bike",
        "ooui-dialog-process-retry": "Dîsa hewl bide",
        "ooui-dialog-process-continue": "Bidomîne",
+       "ooui-selectfile-button-select": "Dosyeyekê hilbijêre",
        "ooui-selectfile-placeholder": "Ti dosye nehatiye hilbijartin"
 }
diff --git a/resources/lib/oojs-ui/i18n/pnb.json b/resources/lib/oojs-ui/i18n/pnb.json
new file mode 100644 (file)
index 0000000..8bd4be8
--- /dev/null
@@ -0,0 +1,21 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Saanvel"
+               ]
+       },
+       "ooui-outline-control-move-down": "شیہ تھلے کرو",
+       "ooui-outline-control-move-up": "شیہ اتے کرو",
+       "ooui-outline-control-remove": "شیہ مٹاؤ",
+       "ooui-toolbar-more": "ہور",
+       "ooui-toolgroup-expand": "ہور",
+       "ooui-dialog-message-accept": "OK",
+       "ooui-dialog-message-reject": "مکاؤ",
+       "ooui-dialog-process-error": "کوئی رپھڑ پے گیا اے۔",
+       "ooui-dialog-process-dismiss": "مکاؤ",
+       "ooui-dialog-process-retry": "فیر کرو",
+       "ooui-dialog-process-continue": "چلاؤ",
+       "ooui-selectfile-button-select": "فائل چنو",
+       "ooui-selectfile-placeholder": "کوئی فائل نئی چنی ہوئی",
+       "ooui-selectfile-dragdrop-placeholder": "فائل ایتھے پاؤ"
+}
index 510e468..598acbc 100644 (file)
@@ -2,7 +2,8 @@
        "@metadata": {
                "authors": [
                        "Lloffiwr",
-                       "Muddyb Blast Producer"
+                       "Muddyb Blast Producer",
+                       "Muddyb"
                ]
        },
        "ooui-outline-control-move-down": "Sogeza kipengee chini",
index 537a9d7..026bbb3 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
index fdfd3e4..fa5ec0a 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-element-hidden {
        display: none !important;
 .oo-ui-textInputWidget textarea {
        display: block;
        width: 100%;
-       resize: none;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
 .oo-ui-textInputWidget textarea {
        overflow: auto;
+       resize: none;
 }
 .oo-ui-textInputWidget [type="search"] {
        -webkit-appearance: textfield;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
+       cursor: default;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
 .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
        position: absolute;
 }
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
+       cursor: pointer;
+}
 .oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
        z-index: 1;
        width: 100%;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
-       cursor: pointer;
-}
 .oo-ui-dropdownWidget:last-child {
        margin-right: 0;
 }
index 05f3838..03380b2 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -67,7 +67,6 @@
 .oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button {
        padding-left: 0.25em;
        padding-right: 0.25em;
-       color: #333333;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
        box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
        border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #ffffff;
+       box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
        color: #ffffff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
        border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #ffffff;
+       box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
        color: #ffffff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
        border-color: #d11d13;
-       box-shadow: inset 0 0 0 1px #ffffff;
+       box-shadow: inset 0 0 0 1px #d11d13, inset 0 0 0 2px #ffffff;
 }
 .oo-ui-clippableElement-clippable {
        -webkit-box-sizing: border-box;
 .oo-ui-textInputWidget textarea {
        display: block;
        width: 100%;
-       resize: none;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
 .oo-ui-textInputWidget textarea {
        overflow: auto;
+       resize: none;
 }
 .oo-ui-textInputWidget [type="search"] {
        -webkit-appearance: textfield;
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
        padding: 0.5em;
-       line-height: 1.275em;
        margin: 0;
        font-size: inherit;
        font-family: inherit;
        color: #000000;
        border: 1px solid #cccccc;
        border-radius: 2px;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
+}
+.oo-ui-textInputWidget textarea {
+       line-height: 1.275em;
 }
 .oo-ui-textInputWidget .oo-ui-pendingElement-pending {
        background-color: transparent;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
+       cursor: default;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
 .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon {
        position: absolute;
 }
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
+       cursor: pointer;
+}
 .oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
        z-index: 1;
        width: 100%;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
-       cursor: pointer;
-}
 .oo-ui-dropdownWidget:last-child {
        margin-right: 0;
 }
index 6836553..e230a49 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
@@ -2219,7 +2219,7 @@ OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) {
  * @chainable
  */
 OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
-       var i, len, item, event, events, currentIndex,
+       var i, len, item, itemEvent, events, currentIndex,
                itemElements = [];
 
        for ( i = 0, len = items.length; i < len; i++ ) {
@@ -2237,8 +2237,8 @@ OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
                // Add the item
                if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
                        events = {};
-                       for ( event in this.aggregateItemEvents ) {
-                               events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
+                       for ( itemEvent in this.aggregateItemEvents ) {
+                               events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
                        }
                        item.connect( this, events );
                }
@@ -2271,22 +2271,19 @@ OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
  * @chainable
  */
 OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
-       var i, len, item, index, remove, itemEvent;
+       var i, len, item, index, events, itemEvent;
 
        // Remove specific items
        for ( i = 0, len = items.length; i < len; i++ ) {
                item = items[ i ];
                index = this.items.indexOf( item );
                if ( index !== -1 ) {
-                       if (
-                               item.connect && item.disconnect &&
-                               !$.isEmptyObject( this.aggregateItemEvents )
-                       ) {
-                               remove = {};
-                               if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
-                                       remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
+                       if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
+                               events = {};
+                               for ( itemEvent in this.aggregateItemEvents ) {
+                                       events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
                                }
-                               item.disconnect( this, remove );
+                               item.disconnect( this, events );
                        }
                        item.setElementGroup( null );
                        this.items.splice( index, 1 );
index 750ab66..15d4d44 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
index 3f1910e..cb5eeda 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index dd59a23..09730d4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 3b1e15d..97a8fc9 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
index 0767ab7..68156c7 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
@@ -17,7 +17,6 @@
        cursor:         grab;
 }
 .oo-ui-draggableElement-handle:active {
-       cursor: move;
        cursor: url(images/grabbing.cur );
        cursor: -webkit-grabbing;
        cursor:    -moz-grabbing;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
-       position: absolute;
-       top: 0;
-       bottom: 0;
-       right: 0;
-       left: 0;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-}
-.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow {
-       display: none;
-}
 .oo-ui-toggleSwitchWidget:last-child {
        margin-right: 0;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
        opacity: 0.5;
 }
+.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
+.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
+       border-color: #aaaaaa;
+}
 .oo-ui-toggleSwitchWidget-grip {
        top: 0.25em;
        left: 0.25em;
        background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
        -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
 }
-.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
-.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
-       border-color: #aaaaaa;
-}
-.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
+.oo-ui-toggleSwitchWidget-glow {
+       position: absolute;
+       top: 0;
+       bottom: 0;
+       right: 0;
+       left: 0;
        border-radius: 1em;
        box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
        -webkit-transition: opacity 250ms ease;
        background-image:    -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
        background-image:         linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
        -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
-}
-.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-glow {
-       opacity: 1;
-}
-.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
-       left: 2.25em;
-       margin-left: -2px;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
 .oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow {
-       display: block;
        opacity: 0;
 }
 .oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-grip {
        left: 0.25em;
        margin-left: 0;
 }
+.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-glow {
+       opacity: 1;
+}
+.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
+       left: 2.25em;
+       margin-left: -2px;
+}
 .oo-ui-progressBarWidget {
        max-width: 50em;
        background-color: #ffffff;
 .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
        position: absolute;
 }
+.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
+       cursor: default;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
 .oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
        z-index: 2;
 }
        display: block;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
index 82d0b5f..048e732 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
@@ -17,7 +17,6 @@
        cursor:         grab;
 }
 .oo-ui-draggableElement-handle:active {
-       cursor: move;
        cursor: url(images/grabbing.cur );
        cursor: -webkit-grabbing;
        cursor:    -moz-grabbing;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-toggleSwitchWidget .oo-ui-toggleSwitchWidget-glow {
-       position: absolute;
-       top: 0;
-       bottom: 0;
-       right: 0;
-       left: 0;
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
-}
-.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-glow {
-       display: none;
-}
 .oo-ui-toggleSwitchWidget:last-child {
        margin-right: 0;
 }
        content: "";
        display: block;
        position: absolute;
-       top: 0;
-       left: 0;
-       bottom: 0;
-       right: 0;
+       top: 1px;
+       left: 1px;
+       bottom: 1px;
+       right: 1px;
        border: 1px solid transparent;
        border-radius: 1em;
        z-index: 1;
+       -webkit-transition: border-color 100ms;
+          -moz-transition: border-color 100ms;
+               transition: border-color 100ms;
 }
 .oo-ui-toggleSwitchWidget-grip {
        top: 0.35em;
+       min-width: 16px;
        width: 1.2em;
+       min-height: 16px;
        height: 1.2em;
        border-radius: 1.2em;
        background-color: #555555;
 .oo-ui-toggleSwitchWidget-glow {
        display: none;
 }
-.oo-ui-toggleSwitchWidget.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
-       left: 1.9em;
-       margin-left: -2px;
-}
 .oo-ui-toggleSwitchWidget.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-grip {
        left: 0.4em;
        margin-left: 0;
 }
+.oo-ui-toggleSwitchWidget.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
+       left: 1.9em;
+       margin-left: -2px;
+}
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on {
        background-color: #347bff;
        border-color: #347bff;
 .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
        position: absolute;
 }
+.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
+       cursor: default;
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
 .oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
        z-index: 2;
 }
        display: block;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
index cc010a6..c4486d3 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
@@ -374,18 +374,22 @@ OO.ui.mixin.DraggableGroupElement.prototype.reorder = function ( item, newIndex
  * @param {OO.ui.mixin.DraggableElement} item Dragged item
  */
 OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) {
-       this.dragItem = item;
-       this.$element.on( 'dragover', this.onDragOver.bind( this ) );
-       this.$element.addClass( 'oo-ui-draggableGroupElement-dragging' );
+       if ( this.dragItem !== item ) {
+               this.dragItem = item;
+               this.$element.on( 'dragover', this.onDragOver.bind( this ) );
+               this.$element.addClass( 'oo-ui-draggableGroupElement-dragging' );
+       }
 };
 
 /**
  * Unset the current dragged item
  */
 OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () {
-       this.dragItem = null;
-       this.$element.off( 'dragover' );
-       this.$element.removeClass( 'oo-ui-draggableGroupElement-dragging' );
+       if ( this.dragItem ) {
+               this.dragItem = null;
+               this.$element.off( 'dragover' );
+               this.$element.removeClass( 'oo-ui-draggableGroupElement-dragging' );
+       }
 };
 
 /**
@@ -1770,11 +1774,13 @@ OO.ui.BookletLayout.prototype.onStackLayoutVisibleItemChange = function ( page )
 OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) {
        var layout = this;
        if ( !this.scrolling && page ) {
-               page.scrollElementIntoView( { complete: function () {
-                       if ( layout.autoFocus ) {
-                               layout.focus();
+               page.scrollElementIntoView( {
+                       complete: function () {
+                               if ( layout.autoFocus ) {
+                                       layout.focus();
+                               }
                        }
-               } );
+               } );
        }
 };
 
@@ -2278,11 +2284,13 @@ OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
 OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
        var layout = this;
        if ( card ) {
-               card.scrollElementIntoView( { complete: function () {
-                       if ( layout.autoFocus ) {
-                               layout.focus();
+               card.scrollElementIntoView( {
+                       complete: function () {
+                               if ( layout.autoFocus ) {
+                                       layout.focus();
+                               }
                        }
-               } );
+               } );
        }
 };
 
index de80299..b55dd25 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
@@ -64,8 +64,7 @@
        bottom: 0;
 }
 .oo-ui-dialog-content > .oo-ui-window-foot {
-       overflow: hidden;
-       z-index: 1;
+       z-index: 3;
        bottom: 0;
 }
 .oo-ui-dialog-content > .oo-ui-window-body {
index 5aaedc1..727e874 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:56Z
+ * Date: 2016-06-29T13:27:11Z
  */
 .oo-ui-window {
        background: transparent;
        bottom: 0;
 }
 .oo-ui-dialog-content > .oo-ui-window-foot {
-       overflow: hidden;
-       z-index: 1;
+       z-index: 3;
        bottom: 0;
 }
-.oo-ui-dialog-content > .oo-ui-window-body {
-       outline: 1px solid #aaaaaa;
-}
 .oo-ui-messageDialog-actions-horizontal {
        display: table;
        table-layout: fixed;
@@ -98,6 +94,9 @@
        display: inline;
        white-space: nowrap;
 }
+.oo-ui-messageDialog-content > .oo-ui-window-foot {
+       outline: 1px solid #aaaaaa;
+}
 .oo-ui-messageDialog-title,
 .oo-ui-messageDialog-message {
        display: block;
        border-bottom-width: 0;
 }
 .oo-ui-messageDialog-actions .oo-ui-actionWidget {
-       height: 3.4em;
+       min-height: 3.4em;
        margin-right: 0;
 }
 .oo-ui-messageDialog-actions .oo-ui-actionWidget:last-child {
 }
 .oo-ui-messageDialog-actions .oo-ui-actionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
        text-align: center;
-       line-height: 3.4em;
+       line-height: 3.4;
 }
 .oo-ui-messageDialog-actions .oo-ui-actionWidget:hover {
        background-color: rgba(0, 0, 0, 0.05);
index 17bab01..8234b6d 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.4
+ * OOjs UI v0.17.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-05-31T21:50:52Z
+ * Date: 2016-06-29T13:27:08Z
  */
 ( function ( OO ) {
 
@@ -3114,7 +3114,11 @@ OO.ui.ProcessDialog.prototype.initialize = function () {
                .append( this.$errors );
        this.$navigation
                .addClass( 'oo-ui-processDialog-navigation' )
-               .append( this.$safeActions, this.$location, this.$primaryActions );
+               // Note: Order of appends below is important. These are in the order
+               // we want tab to go through them. Display-order is handled entirely
+               // by CSS absolute-positioning. As such, primary actions like "done"
+               // should go first.
+               .append( this.$primaryActions, this.$location, this.$safeActions );
        this.$head.append( this.$navigation );
        this.$foot.append( this.$otherActions );
 };
index 519e39b..6ff2e01 100644 (file)
@@ -113,18 +113,19 @@ function getAccessKeyLabel( element ) {
  * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
  */
 function updateTooltipOnElement( element, titleElement ) {
-       var array = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ),
-               regexp = new RegExp( $.map( array, mw.RegExp.escape ).join( '.*?' ) + '$' ),
-               oldTitle = titleElement.title,
-               rawTitle = oldTitle.replace( regexp, '' ),
-               newTitle = rawTitle,
-               accessKeyLabel = getAccessKeyLabel( element );
-
-       // don't add a title if the element didn't have one before
+       var oldTitle, parts, regexp, newTitle, accessKeyLabel;
+
+       oldTitle = titleElement.title;
        if ( !oldTitle ) {
+               // don't add a title if the element didn't have one before
                return;
        }
 
+       parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' );
+       regexp = new RegExp( $.map( parts, mw.RegExp.escape ).join( '.*?' ) + '$' );
+       newTitle = oldTitle.replace( regexp, '' );
+       accessKeyLabel = getAccessKeyLabel( element );
+
        if ( accessKeyLabel ) {
                // Should be build the same as in Linker::titleAttrib
                newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel );
index bdb5ce8..501e898 100644 (file)
                                // If this is not a custom case, do the default: wrap the
                                // contents and add the toggle link. Different elements are
                                // treated differently.
+
                                if ( $collapsible.is( 'table' ) ) {
 
                                        // If the table has a caption, collapse to the caption
                                                }
                                        }
 
+                               } else if ( $collapsible.parent().is( 'li' ) &&
+                                       $collapsible.parent().children( '.mw-collapsible' ).size() === 1
+                               ) {
+                                       // special case of one collapsible in <li> tag
+                                       $toggleLink = buildDefaultToggleLink();
+                                       $collapsible.before( $toggleLink );
                                } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
                                        // The toggle-link will be in the first list-item
                                        $firstItem = $collapsible.find( 'li:first' );
index 38e5a1f..884ecb6 100644 (file)
                                        } else if ( selected.is( '.suggestions-special' ) ) {
                                                if ( typeof context.config.special.select === 'function' ) {
                                                        // Allow the callback to decide whether to prevent default or not
-                                                       if ( context.config.special.select.call( selected, context.data.$textbox ) === true ) {
+                                                       if ( context.config.special.select.call( selected, context.data.$textbox, 'keyboard' ) === true ) {
                                                                preventDefault = false;
                                                        }
                                                }
                                        } else {
                                                if ( typeof context.config.result.select === 'function' ) {
                                                        // Allow the callback to decide whether to prevent default or not
-                                                       if ( context.config.result.select.call( selected, context.data.$textbox ) === true ) {
+                                                       if ( context.config.result.select.call( selected, context.data.$textbox, 'keyboard' ) === true ) {
                                                                preventDefault = false;
                                                        }
                                                }
                                                                if ( $result.get( 0 ) !== $other.get( 0 ) ) {
                                                                        return;
                                                                }
+                                                               $.suggestions.highlight( context, $result, true );
+                                                               if ( typeof context.config.result.select === 'function' ) {
+                                                                       context.config.result.select.call( $result, context.data.$textbox, 'mouse' );
+                                                               }
                                                                // Don't interfere with special clicks (e.g. to open in new tab)
                                                                if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
-                                                                       $.suggestions.highlight( context, $result, true );
-                                                                       if ( typeof context.config.result.select === 'function' ) {
-                                                                               context.config.result.select.call( $result, context.data.$textbox );
-                                                                       }
                                                                        // This will hide the link we're just clicking on, which causes problems
                                                                        // when done synchronously in at least Firefox 3.6 (bug 62858).
                                                                        setTimeout( function () {
                                                                if ( $special.get( 0 ) !== $other.get( 0 ) ) {
                                                                        return;
                                                                }
+                                                               if ( typeof context.config.special.select === 'function' ) {
+                                                                       context.config.special.select.call( $special, context.data.$textbox, 'mouse' );
+                                                               }
                                                                // Don't interfere with special clicks (e.g. to open in new tab)
                                                                if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
-                                                                       if ( typeof context.config.special.select === 'function' ) {
-                                                                               context.config.special.select.call( $special, context.data.$textbox );
-                                                                       }
                                                                        // This will hide the link we're just clicking on, which causes problems
                                                                        // when done synchronously in at least Firefox 3.6 (bug 62858).
                                                                        setTimeout( function () {
                                                $.suggestions.keypress( e, context, context.data.keypressed );
                                        } )
                                        .keyup( function ( e ) {
-                                               // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a
-                                               // keypress in between, solve it
-                                               if ( context.data.keypressedCount === 0 ) {
+                                               // The keypress event is fired when a key is pressed down and that key normally
+                                               // produces a character value. We also want to handle some keys that don't
+                                               // produce a character value so we also attach to the keydown/keyup events.
+                                               // List of codes sourced from
+                                               // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
+                                               var allowed = [
+                                                       40, // up arrow
+                                                       38, // down arrow
+                                                       27, // escape
+                                                       13, // enter
+                                                       46, // delete
+                                                       8   // backspace
+                                               ];
+                                               if ( context.data.keypressedCount === 0
+                                                       && e.which === context.data.keypressed
+                                                       && $.inArray( e.which, allowed ) !== -1
+                                               ) {
                                                        $.suggestions.keypress( e, context, context.data.keypressed );
                                                }
                                        } )
index 269db8e..5b08f95 100644 (file)
@@ -1,5 +1,23 @@
 /* Basic styles for the history page */
 
+#pagehistory .history-user {
+       margin-left: 0.4em;
+       margin-right: 0.2em;
+}
+
+#pagehistory li {
+       border: 1px solid #fff;
+}
+
+#pagehistory li.selected {
+       background-color: #f9f9f9;
+       border: 1px dashed #aaa;
+}
+
+.mw-history-revisionactions {
+       float: right;
+}
+
 .updatedmarker {
        background-color: #b7f430;
 }
index 99982e3..d5520a1 100644 (file)
@@ -2,32 +2,32 @@
  * File description page
  */
 
-div.mw-filepage-resolutioninfo {
+.mw-filepage-resolutioninfo {
        font-size: smaller;
 }
 
 /*
  * File histories
  */
-h2#filehistory {
+#filehistory {
        clear: both;
 }
 
-table.filehistory th,
-table.filehistory td {
+.filehistory th,
+.filehistory td {
        vertical-align: top;
 }
 
-table.filehistory th {
+.filehistory th {
        text-align: left;
 }
 
-table.filehistory td.mw-imagepage-filesize,
-table.filehistory th.mw-imagepage-filesize {
+.filehistory td.mw-imagepage-filesize,
+.filehistory th.mw-imagepage-filesize {
        white-space: nowrap;
 }
 
-table.filehistory td.filehistory-selected {
+.filehistory td.filehistory-selected {
        font-weight: bold;
 }
 
@@ -44,7 +44,7 @@ table.filehistory td.filehistory-selected {
 /*
  * filetoc
  */
-ul#filetoc {
+#filetoc {
        text-align: center;
        border: 1px solid #aaa;
        background-color: #f9f9f9;
@@ -68,3 +68,54 @@ ul#filetoc {
 #shared-image-conflict {
        font-style: italic;
 }
+
+/*
+ * Classes for Exif data display
+ */
+.mw_metadata {
+       font-size: 0.8em;
+       margin-left: 0.5em;
+       margin-bottom: 0.5em;
+       width: 400px;
+}
+
+.mw_metadata caption {
+       font-weight: bold;
+}
+
+.mw_metadata th {
+       font-weight: normal;
+       text-align: center;
+}
+
+.mw_metadata td {
+       padding: 0.1em;
+}
+
+.mw_metadata {
+       border: none;
+       border-collapse: collapse;
+}
+
+.mw_metadata td,
+.mw_metadata th {
+       border: 1px solid #aaa;
+       padding-left: 5px;
+       padding-right: 5px;
+}
+
+.mw_metadata th {
+       background-color: #f9f9f9;
+}
+
+.mw_metadata td {
+       background-color: #fcfcfc;
+}
+
+.mw_metadata ul.metadata-langlist {
+       list-style-type: none;
+       list-style-image: none;
+       padding-right: 5px;
+       padding-left: 5px;
+       margin: 0;
+}
index 4ff403a..a5508c6 100644 (file)
@@ -284,31 +284,11 @@ p.mw-delete-editreasons {
        text-align: right;
 }
 
-/* Page history styling */
-
 /* The auto-generated edit comments */
 .autocomment {
        color: #808080;
 }
 
-#pagehistory .history-user {
-       margin-left: 0.4em;
-       margin-right: 0.2em;
-}
-
-#pagehistory li {
-       border: 1px solid #fff;
-}
-
-#pagehistory li.selected {
-       background-color: #f9f9f9;
-       border: 1px dashed #aaa;
-}
-
-.mw-history-revisionactions {
-       float: right;
-}
-
 /** Generic minor/bot/newpage styling (recent changes) */
 .newpage,
 .minoredit,
@@ -520,55 +500,6 @@ table.wikitable > caption {
        background-color: #eef;
 }
 
-/* Classes for Exif data display */
-table.mw_metadata {
-       font-size: 0.8em;
-       margin-left: 0.5em;
-       margin-bottom: 0.5em;
-       width: 400px;
-}
-
-table.mw_metadata caption {
-       font-weight: bold;
-}
-
-table.mw_metadata th {
-       font-weight: normal;
-       text-align: center;
-}
-
-table.mw_metadata td {
-       padding: 0.1em;
-}
-
-table.mw_metadata {
-       border: none;
-       border-collapse: collapse;
-}
-
-table.mw_metadata td,
-table.mw_metadata th {
-       border: 1px solid #aaa;
-       padding-left: 5px;
-       padding-right: 5px;
-}
-
-table.mw_metadata th {
-       background-color: #f9f9f9;
-}
-
-table.mw_metadata td {
-       background-color: #fcfcfc;
-}
-
-table.mw_metadata ul.metadata-langlist {
-       list-style-type: none;
-       list-style-image: none;
-       padding-right: 5px;
-       padding-left: 5px;
-       margin: 0;
-}
-
 /* Correct directionality when page dir is different from site/user dir */
 .mw-content-ltr ul,
 .mw-content-rtl .mw-content-ltr ul {
index 12825de..f29897c 100644 (file)
@@ -15,7 +15,6 @@
        cursor: pointer;
        vertical-align: bottom;
        line-height: normal;
-
        font-weight: normal;
 
        & > input[type="checkbox"],
        background: @bgColor;
 
        &:hover {
-               // The inner bottom bevel should match the active background color.
                background-color: @highlightColor;
        }
 
        &:focus {
                border-color: @colorWhite;
-               box-shadow: 0 0 0 1px @highlightColor;
+               box-shadow: inset 0 0 0 1px @bgColor, inset 0 0 0 2px @colorWhite;
+               outline-width: 0;
 
-               outline: none;
-               // remove outline in Firefox
+               // Remove the inner border and padding in Firefox.
                &::-moz-focus-inner {
                        border-color: transparent;
+                       padding: 0;
                }
        }
 
        &:active,
        &.is-on,
        &.mw-ui-checked {
-               background: @activeColor;
+               background-color: @activeColor;
                box-shadow: none;
        }
 }
@@ -95,7 +94,7 @@
        text-shadow: 0 1px rgba(0, 0, 0, .1);
 
        &:disabled {
-               background: @colorGray13;
+               background-color: @colorGray13;
                border-color: @colorGray13;
 
                // make sure disabled buttons don't have hover and active states
 
        &:hover,
        &:focus {
-               background: transparent;
+               background-color: transparent;
                color: @textColor;
        }
 
index f8d283a..76fee23 100644 (file)
        }
 
        &:focus {
-               box-shadow: inset 0 0 0 2px @colorProgressive;
-               // Color being used to match inset shadow, not semantic reasons
                border-color: @colorProgressive;
-               // Remove focus glow on input[type="search"]
+               box-shadow: inset 0 0 0 1px @colorProgressive;
                outline: 0;
        }
 
index df03679..d816335 100755 (executable)
         *  used (header or content).
         */
        mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
+               // The parent constructors will detach this from the DOM, and won't
+               // be reattached until after this function is completed. As such
+               // grab a handle here. If no config.$input is passed tracking of
+               // form submissions won't work.
+               var $form = config.$input ? config.$input.closest( 'form' ) : $();
+
                config = $.extend( {
                        type: 'search',
                        icon: 'search',
@@ -36,6 +42,7 @@
                // Initialization
                this.$element.addClass( 'mw-widget-searchInputWidget' );
                this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' );
+               this.lastLookupItems = [];
                if ( !config.pushPending ) {
                        this.pushPending = false;
                }
                        this.performSearchOnClick = config.performSearchOnClick;
                }
                this.setLookupsDisabled( !this.suggestions );
+
+               $form.on( 'submit', function () {
+                       mw.track( 'mw.widgets.SearchInputWidget', {
+                               action: 'submit-form',
+                               numberOfResults: this.lastLookupItems.length,
+                               $form: $form,
+                               inputLocation: this.dataLocation || 'header',
+                               index: this.lastLookupItems.indexOf(
+                                       this.$input.val()
+                               )
+                       } );
+               }.bind( this ) );
        };
 
        /* Setup */
        /**
         * @inheritdoc
         */
-       mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function ( item ) {
-               var items;
-
-               // get items which was suggested before the input changes
-               items = this.lookupMenu.items;
-
+       mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function () {
                mw.widgets.SearchInputWidget.parent.prototype.onLookupMenuItemChoose.apply( this, arguments );
 
-               mw.track( 'mw.widgets.SearchInputWidget', {
-                       action: 'click-result',
-                       numberOfResults: items.length,
-                       clickIndex: items.indexOf( item ) + 1
-               } );
-
                if ( this.performSearchOnClick ) {
                        this.$element.closest( 'form' ).submit();
                }
        };
 
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function () {
+               var items = mw.widgets.SearchInputWidget.parent.prototype.getLookupMenuOptionsFromData.apply(
+                       this, arguments
+               );
+
+               this.lastLookupItems = items.map( function ( item ) {
+                       return item.data;
+               } );
+
+               return items;
+       };
+
 }( jQuery, mediaWiki ) );
index 60276cd..bb3a913 100644 (file)
                /**
                 * API helper to grab a csrf token.
                 *
-                * @return {jQuery.Promise}
-                * @return {Function} return.done
-                * @return {string} return.done.token Received token.
+                * @return {jQuery.Promise} Received token.
                 */
                getEditToken: function () {
                        return this.getToken( 'csrf' );
                },
 
+               /**
+                * Create a new page.
+                *
+                * Example:
+                *
+                *     new mw.Api().create( 'Sandbox',
+                *         { summary: 'Load sand particles.' },
+                *         'Sand.'
+                *     );
+                *
+                * @since 1.28
+                * @param {mw.Title|string} title Page title
+                * @param {Object} params Edit API parameters
+                * @param {string} params.summary Edit summary
+                * @param {string} content
+                * @return {jQuery.Promise} API response
+                */
+               create: function ( title, params, content ) {
+                       return this.postWithEditToken( $.extend( {
+                               action: 'edit',
+                               title: String( title ),
+                               text: content,
+                               formatversion: '2',
+
+                               // Protect against errors and conflicts
+                               assert: mw.user.isAnon() ? undefined : 'user',
+                               createonly: true
+                       }, params ) ).then( function ( data ) {
+                               return data.edit;
+                       } );
+               },
+
+               /**
+                * Edit an existing page.
+                *
+                * To create a new page, use #create() instead.
+                *
+                * Simple transformation:
+                *
+                *     new mw.Api()
+                *         .edit( 'Sandbox', function ( revision ) {
+                *             return revision.content.replace( 'foo', 'bar' );
+                *         } )
+                *         .then( function () {
+                *             console.log( 'Saved! ');
+                *         } );
+                *
+                * Set save parameters by returning an object instead of a string:
+                *
+                *     new mw.Api().edit(
+                *         'Sandbox',
+                *         function ( revision ) {
+                *             return {
+                *                 text: revision.content.replace( 'foo', 'bar' ),
+                *                 summary: 'Replace "foo" with "bar".',
+                *                 assert: 'bot',
+                *                 minor: true
+                *             };
+                *         }
+                *     )
+                *     .then( function () {
+                *         console.log( 'Saved! ');
+                *     } );
+                *
+                * Transform asynchronously by returning a promise.
+                *
+                *     new mw.Api()
+                *         .edit( 'Sandbox', function ( revision ) {
+                *             return Spelling
+                *                 .corrections( revision.content )
+                *                 .then( function ( report ) {
+                *                     return {
+                *                         text: report.output,
+                *                         summary: report.changelog
+                *                     };
+                *                 } );
+                *         } )
+                *         .then( function () {
+                *             console.log( 'Saved! ');
+                *         } );
+                *
+                * @since 1.28
+                * @param {mw.Title|string} title Page title
+                * @param {Function} transform Callback that prepares the edit
+                * @param {Object} transform.revision Current revision
+                * @param {string} transform.revision.content Current revision content
+                * @param {string|Object|jQuery.Promise} transform.return New content, object with edit
+                *  API parameters, or promise providing one of those.
+                * @return {jQuery.Promise} Edit API response
+                */
+               edit: function ( title, transform ) {
+                       var basetimestamp, curtimestamp,
+                               api = this;
+                       return api.get( {
+                                       action: 'query',
+                                       prop: 'revisions',
+                                       rvprop: [ 'content', 'timestamp' ],
+                                       titles: String( title ),
+                                       formatversion: '2',
+                                       curtimestamp: true
+                               } )
+                               .then( function ( data ) {
+                                       var page, revision;
+                                       if ( !data.query || !data.query.pages ) {
+                                               return $.Deferred().reject( 'unknown' );
+                                       }
+                                       page = data.query.pages[ 0 ];
+                                       if ( !page || page.missing ) {
+                                               return $.Deferred().reject( 'nocreate-missing' );
+                                       }
+                                       revision = page.revisions[ 0 ];
+                                       basetimestamp = revision.timestamp;
+                                       curtimestamp = data.curtimestamp;
+                                       return transform( {
+                                               timestamp: revision.timestamp,
+                                               content: revision.content
+                                       } );
+                               } )
+                               .then( function ( params ) {
+                                       var editParams = typeof params === 'object' ? params : { text: String( params ) };
+                                       return api.postWithEditToken( $.extend( {
+                                               action: 'edit',
+                                               title: title,
+                                               formatversion: '2',
+
+                                               // Protect against errors and conflicts
+                                               assert: mw.user.isAnon() ? undefined : 'user',
+                                               basetimestamp: basetimestamp,
+                                               starttimestamp: curtimestamp,
+                                               nocreate: true
+                                       }, editParams ) );
+                               } )
+                               .then( function ( data ) {
+                                       return data.edit;
+                               } );
+               },
+
                /**
                 * Post a new section to the page.
                 *
index 40c5595..8d42b98 100644 (file)
                },
 
                /**
-                * Add (does not replace) parameters for `N$` placeholder values.
+                * Add (does not replace) parameters for `$N` placeholder values.
                 *
                 * @param {Array} parameters
                 * @chainable
index 2d603bf..7c7aca3 100644 (file)
                        mw.track( 'mediawiki.searchSuggest', {
                                action: 'render-one',
                                formData: formData,
-                               index: context.config.suggestions.indexOf( text ) + 1
+                               index: context.config.suggestions.indexOf( text )
                        } );
 
                        // this is the container <div>, jQueryfied
                }
 
                // The function used when the user makes a selection
-               function selectFunction( $input ) {
+               function selectFunction( $input, source ) {
                        var context = $input.data( 'suggestionsContext' ),
                                text = $input.val();
 
-                       mw.track( 'mediawiki.searchSuggest', {
-                               action: 'click-result',
-                               numberOfResults: context.config.suggestions.length,
-                               clickIndex: context.config.suggestions.indexOf( text ) + 1
-                       } );
+                       // Selecting via keyboard triggers a form submission. That will fire
+                       // the submit-form event in addition to this click-result event.
+                       if ( source !== 'keyboard' ) {
+                               mw.track( 'mediawiki.searchSuggest', {
+                                       action: 'click-result',
+                                       numberOfResults: context.config.suggestions.length,
+                                       index: context.config.suggestions.indexOf( text )
+                               } );
+                       }
 
                        // allow the form to be submitted
                        return true;
                        // linkParams object is modified and reused
                        formData.linkParams[ formData.textParam ] = query;
 
+                       mw.track( 'mediawiki.searchSuggest', {
+                               action: 'render-one',
+                               formData: formData,
+                               index: context.config.suggestions.indexOf( query )
+                       } );
+
                        if ( $el.children().length === 0 ) {
                                $el
                                        .append(
                        },
                        special: {
                                render: specialRenderFunction,
-                               select: function ( $input ) {
-                                       $input.closest( 'form' )
-                                               .append( $( '<input type="hidden" name="fulltext" value="1"/>' ) );
+                               select: function ( $input, source ) {
+                                       var context = $input.data( 'suggestionsContext' ),
+                                               text = $input.val();
+                                       if ( source === 'mouse' ) {
+                                               // mouse click won't trigger form submission, so we need to send a click event
+                                               mw.track( 'mediawiki.searchSuggest', {
+                                                       action: 'click-result',
+                                                       numberOfResults: context.config.suggestions.length,
+                                                       index: context.config.suggestions.indexOf( text )
+                                               } );
+                                       } else {
+                                               $input.closest( 'form' )
+                                                       .append( $( '<input type="hidden" name="fulltext" value="1"/>' ) );
+                                       }
                                        return true; // allow the form to be submitted
                                }
                        },
                                        action: 'submit-form',
                                        numberOfResults: context.config.suggestions.length,
                                        $form: context.config.$region.closest( 'form' ),
-                                       inputLocation: getInputLocation( context )
+                                       inputLocation: getInputLocation( context ),
+                                       index: context.config.suggestions.indexOf(
+                                               context.data.$textbox.val()
+                                       )
                                } );
                        } )
                        // If the form includes any fallback fulltext search buttons, remove them
index 2e059d7..be9ccaf 100644 (file)
@@ -11013,7 +11013,7 @@ int keyword - non-existing message
 !! wikitext
 {{int:var}}
 !! html
-<p>&lt;var&gt;
+<p>⧼var⧽
 </p>
 !! end
 
index 0e13721..d20344d 100644 (file)
@@ -315,6 +315,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', 'LBFactory' ],
                        'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
                        'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
+                       'WatchedItemQueryService' => [ 'WatchedItemQueryService', WatchedItemQueryService::class ],
                        'GenderCache' => [ 'GenderCache', GenderCache::class ],
                        'LinkCache' => [ 'LinkCache', LinkCache::class ],
                        'LinkRenderer' => [ 'LinkRenderer', LinkRenderer::class ],
index 8aa1361..4c689ab 100644 (file)
@@ -223,13 +223,13 @@ class MessageTest extends MediaWikiLangTestCase {
         */
        public function testToStringKey() {
                $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() );
-               $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->text() );
-               $this->assertEquals( '<i<dont>exist-evar>', wfMessage( 'i<dont>exist-evar' )->text() );
-               $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->plain() );
-               $this->assertEquals( '<i<dont>exist-evar>', wfMessage( 'i<dont>exist-evar' )->plain() );
-               $this->assertEquals( '&lt;i-dont-exist-evar&gt;', wfMessage( 'i-dont-exist-evar' )->escaped() );
+               $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->text() );
+               $this->assertEquals( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->text() );
+               $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->plain() );
+               $this->assertEquals( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->plain() );
+               $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->escaped() );
                $this->assertEquals(
-                       '&lt;i&lt;dont&gt;exist-evar&gt;',
+                       '⧼i&lt;dont&gt;exist-evar⧽',
                        wfMessage( 'i<dont>exist-evar' )->escaped()
                );
        }
@@ -237,8 +237,10 @@ class MessageTest extends MediaWikiLangTestCase {
        public static function provideToString() {
                return [
                        [ 'mainpage', 'Main Page' ],
-                       [ 'i-dont-exist-evar', '<i-dont-exist-evar>' ],
-                       [ 'i-dont-exist-evar', '&lt;i-dont-exist-evar&gt;', 'escaped' ],
+                       [ 'i-dont-exist-evar', '⧼i-dont-exist-evar⧽' ],
+                       [ 'i-dont-exist-evar', '⧼i-dont-exist-evar⧽', 'escaped' ],
+                       [ 'script>alert(1)</script', '⧼script&gt;alert(1)&lt;/script⧽', 'escaped' ],
+                       [ 'script>alert(1)</script', '⧼script&gt;alert(1)&lt;/script⧽' ],
                ];
        }
 
@@ -589,6 +591,10 @@ class MessageTest extends MediaWikiLangTestCase {
        public function testNewFromSpecifier( $value, $expectedText ) {
                $message = Message::newFromSpecifier( $value );
                $this->assertInstanceOf( Message::class, $message );
+               if ( $value instanceof Message ) {
+                       $this->assertInstanceOf( get_class( $value ), $message );
+                       $this->assertEquals( $value, $message );
+               }
                $this->assertSame( $expectedText, $message->text() );
        }
 
@@ -602,6 +608,7 @@ class MessageTest extends MediaWikiLangTestCase {
                        'array' => [ [ 'youhavenewmessages', 'foo', 'bar' ], 'You have foo (bar).' ],
                        'Message' => [ new Message( 'youhavenewmessages', [ 'foo', 'bar' ] ), 'You have foo (bar).' ],
                        'RawMessage' => [ new RawMessage( 'foo ($1)', [ 'bar' ] ), 'foo (bar)' ],
+                       'ApiMessage' => [ new ApiMessage( [ 'mainpage' ], 'code', [ 'data' ] ), 'Main Page' ],
                        'MessageSpecifier' => [ $messageSpecifier, 'Main Page' ],
                        'nested RawMessage' => [ [ new RawMessage( 'foo ($1)', [ 'bar' ] ) ], 'foo (bar)' ],
                ];
index 782fab0..474a481 100644 (file)
@@ -376,9 +376,9 @@ class StatusTest extends MediaWikiLangTestCase {
                $status->warning( 'fooBar!' );
                $testCases['1StringWarning'] = [
                        $status,
-                       "<fooBar!>",
+                       "⧼fooBar!⧽",
                        "(wrap-short: (fooBar!))",
-                       "<p>&lt;fooBar!&gt;\n</p>",
+                       "<p>⧼fooBar!⧽\n</p>",
                        "<p>(wrap-short: (fooBar!))\n</p>",
                ];
 
@@ -387,9 +387,9 @@ class StatusTest extends MediaWikiLangTestCase {
                $status->warning( 'fooBar2!' );
                $testCases['2StringWarnings'] = [
                        $status,
-                       "* <fooBar!>\n* <fooBar2!>\n",
+                       "* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
                        "(wrap-long: * (fooBar!)\n* (fooBar2!)\n)",
-                       "<ul><li> &lt;fooBar!&gt;</li>\n<li> &lt;fooBar2!&gt;</li></ul>\n",
+                       "<ul><li> ⧼fooBar!⧽</li>\n<li> ⧼fooBar2!⧽</li></ul>\n",
                        "<p>(wrap-long: * (fooBar!)\n</p>\n<ul><li> (fooBar2!)</li></ul>\n<p>)\n</p>",
                ];
 
@@ -397,9 +397,9 @@ class StatusTest extends MediaWikiLangTestCase {
                $status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
                $testCases['1MessageWarning'] = [
                        $status,
-                       "<fooBar!>",
+                       "⧼fooBar!⧽",
                        "(wrap-short: (fooBar!: foo, bar))",
-                       "<p>&lt;fooBar!&gt;\n</p>",
+                       "<p>⧼fooBar!⧽\n</p>",
                        "<p>(wrap-short: (fooBar!: foo, bar))\n</p>",
                ];
 
@@ -408,9 +408,9 @@ class StatusTest extends MediaWikiLangTestCase {
                $status->warning( new Message( 'fooBar2!' ) );
                $testCases['2MessageWarnings'] = [
                        $status,
-                       "* <fooBar!>\n* <fooBar2!>\n",
+                       "* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
                        "(wrap-long: * (fooBar!: foo, bar)\n* (fooBar2!)\n)",
-                       "<ul><li> &lt;fooBar!&gt;</li>\n<li> &lt;fooBar2!&gt;</li></ul>\n",
+                       "<ul><li> ⧼fooBar!⧽</li>\n<li> ⧼fooBar2!⧽</li></ul>\n",
                        "<p>(wrap-long: * (fooBar!: foo, bar)\n</p>\n<ul><li> (fooBar2!)</li></ul>\n<p>)\n</p>",
                ];
 
diff --git a/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php b/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
new file mode 100644 (file)
index 0000000..b63a1f4
--- /dev/null
@@ -0,0 +1,1013 @@
+<?php
+
+/**
+ * @covers WatchedItemQueryService
+ */
+class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|DatabaseBase
+        */
+       private function getMockDb() {
+               $mock = $this->getMockBuilder( DatabaseBase::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $mock->expects( $this->any() )
+                       ->method( 'makeList' )
+                       ->with(
+                               $this->isType( 'array' ),
+                               $this->isType( 'int' )
+                       )
+                       ->will( $this->returnCallback( function( $a, $conj ) {
+                               $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
+                               return join( $sqlConj, array_map( function( $s ) {
+                                       return '(' . $s . ')';
+                               }, $a
+                               ) );
+                       } ) );
+
+               $mock->expects( $this->any() )
+                       ->method( 'addQuotes' )
+                       ->will( $this->returnCallback( function( $value ) {
+                               return "'$value'";
+                       } ) );
+
+               $mock->expects( $this->any() )
+                       ->method( 'timestamp' )
+                       ->will( $this->returnArgument( 0 ) );
+
+               $mock->expects( $this->any() )
+                       ->method( 'bitAnd' )
+                       ->willReturnCallback( function( $a, $b ) {
+                               return "($a & $b)";
+                       } );
+
+               return $mock;
+       }
+
+       /**
+        * @param $mockDb
+        * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
+        */
+       private function getMockLoadBalancer( $mockDb ) {
+               $mock = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getConnection' )
+                       ->with( DB_SLAVE )
+                       ->will( $this->returnValue( $mockDb ) );
+               return $mock;
+       }
+
+       /**
+        * @param int $id
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockNonAnonUserWithId( $id ) {
+               $mock = $this->getMock( User::class );
+               $mock->expects( $this->any() )
+                       ->method( 'isAnon' )
+                       ->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getId' )
+                       ->will( $this->returnValue( $id ) );
+               return $mock;
+       }
+
+       /**
+        * @param int $id
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockUnrestrictedNonAnonUserWithId( $id ) {
+               $mock = $this->getMockNonAnonUserWithId( $id );
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowed' )
+                       ->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowedAny' )
+                       ->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )
+                       ->method( 'useRCPatrol' )
+                       ->will( $this->returnValue( true ) );
+               return $mock;
+       }
+
+       /**
+        * @param int $id
+        * @param string $notAllowedAction
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction ) {
+               $mock = $this->getMockNonAnonUserWithId( $id );
+
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowed' )
+                       ->will( $this->returnCallback( function( $action ) use ( $notAllowedAction ) {
+                               return $action !== $notAllowedAction;
+                       } ) );
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowedAny' )
+                       ->will( $this->returnCallback( function() use ( $notAllowedAction ) {
+                               $actions = func_get_args();
+                               return !in_array( $notAllowedAction, $actions );
+                       } ) );
+
+               return $mock;
+       }
+
+       /**
+        * @param int $id
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockNonAnonUserWithIdAndNoPatrolRights( $id ) {
+               $mock = $this->getMockNonAnonUserWithId( $id );
+
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowed' )
+                       ->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )
+                       ->method( 'isAllowedAny' )
+                       ->will( $this->returnValue( true ) );
+
+               $mock->expects( $this->any() )
+                       ->method( 'useRCPatrol' )
+                       ->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )
+                       ->method( 'useNPPatrol' )
+                       ->will( $this->returnValue( false ) );
+
+               return $mock;
+       }
+
+       private function getFakeRow( array $rowValues ) {
+               $fakeRow = new stdClass();
+               foreach ( $rowValues as $valueName => $value ) {
+                       $fakeRow->$valueName = $value;
+               }
+               return $fakeRow;
+       }
+
+       public function testGetWatchedItemsWithRecentChangeInfo() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               [
+                                       'rc_id',
+                                       'rc_namespace',
+                                       'rc_title',
+                                       'rc_timestamp',
+                                       'rc_type',
+                                       'rc_deleted',
+                                       'wl_notificationtimestamp',
+                                       'rc_cur_id',
+                                       'rc_this_oldid',
+                                       'rc_last_oldid',
+                               ],
+                               [
+                                       'wl_user' => 1,
+                                       '(rc_this_oldid=page_latest) OR (rc_type=3)',
+                               ],
+                               $this->isType( 'string' ),
+                               [],
+                               [
+                                       'watchlist' => [
+                                               'INNER JOIN',
+                                               [
+                                                       'wl_namespace=rc_namespace',
+                                                       'wl_title=rc_title'
+                                               ]
+                                       ],
+                                       'page' => [
+                                               'LEFT JOIN',
+                                               'rc_cur_id=page_id',
+                                       ],
+                               ]
+                       )
+                       ->will( $this->returnValue( [
+                               $this->getFakeRow( [
+                                       'rc_id' => 1,
+                                       'rc_namespace' => 0,
+                                       'rc_title' => 'Foo1',
+                                       'rc_timestamp' => '20151212010101',
+                                       'rc_type' => RC_NEW,
+                                       'rc_deleted' => 0,
+                                       'wl_notificationtimestamp' => '20151212010101',
+                               ] ),
+                               $this->getFakeRow( [
+                                       'rc_id' => 2,
+                                       'rc_namespace' => 1,
+                                       'rc_title' => 'Foo2',
+                                       'rc_timestamp' => '20151212010102',
+                                       'rc_type' => RC_NEW,
+                                       'rc_deleted' => 0,
+                                       'wl_notificationtimestamp' => null,
+                               ] ),
+                       ] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user );
+
+               $this->assertInternalType( 'array', $items );
+               $this->assertCount( 2, $items );
+
+               foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
+                       $this->assertInstanceOf( WatchedItem::class, $watchedItem );
+                       $this->assertInternalType( 'array', $recentChangeInfo );
+               }
+
+               $this->assertEquals(
+                       new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
+                       $items[0][0]
+               );
+               $this->assertEquals(
+                       [
+                               'rc_id' => 1,
+                               'rc_namespace' => 0,
+                               'rc_title' => 'Foo1',
+                               'rc_timestamp' => '20151212010101',
+                               'rc_type' => RC_NEW,
+                               'rc_deleted' => 0,
+                       ],
+                       $items[0][1]
+               );
+
+               $this->assertEquals(
+                       new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
+                       $items[1][0]
+               );
+               $this->assertEquals(
+                       [
+                               'rc_id' => 2,
+                               'rc_namespace' => 1,
+                               'rc_title' => 'Foo2',
+                               'rc_timestamp' => '20151212010102',
+                               'rc_type' => RC_NEW,
+                               'rc_deleted' => 0,
+                       ],
+                       $items[1][1]
+               );
+       }
+
+       public function getWatchedItemsWithRecentChangeInfoOptionsProvider() {
+               return [
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
+                               [ 'rc_type', 'rc_minor', 'rc_bot' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
+                               [ 'rc_user_text' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
+                               [ 'rc_user' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
+                               [ 'rc_comment' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
+                               [ 'rc_patrolled', 'rc_log_type' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
+                               [ 'rc_old_len', 'rc_new_len' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
+                               [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'namespaceIds' => [ 0, 1 ] ],
+                               [],
+                               [ 'wl_namespace' => [ 0, 1 ] ],
+                               [],
+                       ],
+                       [
+                               [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
+                               [],
+                               [ 'wl_namespace' => [ 0, 1 ] ],
+                               [],
+                       ],
+                       [
+                               [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
+                               [],
+                               [ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
+                               [],
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
+                               [],
+                               [],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
+                               [],
+                               [],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
+                               [],
+                               [ "rc_timestamp <= '20151212010101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
+                               [],
+                               [ "rc_timestamp >= '20151212010101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                       ],
+                       [
+                               [
+                                       'dir' => WatchedItemQueryService::DIR_OLDER,
+                                       'start' => '20151212020101',
+                                       'end' => '20151212010101'
+                               ],
+                               [],
+                               [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
+                               [],
+                               [ "rc_timestamp >= '20151212010101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
+                               [],
+                               [ "rc_timestamp <= '20151212010101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                       ],
+                       [
+                               [
+                                       'dir' => WatchedItemQueryService::DIR_NEWER,
+                                       'start' => '20151212010101',
+                                       'end' => '20151212020101'
+                               ],
+                               [],
+                               [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                       ],
+                       [
+                               [ 'limit' => 10 ],
+                               [],
+                               [],
+                               [ 'LIMIT' => 10 ],
+                       ],
+                       [
+                               [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
+                               [],
+                               [],
+                               [ 'LIMIT' => 10 ],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
+                               [],
+                               [ 'rc_minor != 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
+                               [],
+                               [ 'rc_minor = 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
+                               [],
+                               [ 'rc_bot != 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
+                               [],
+                               [ 'rc_bot = 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
+                               [],
+                               [ 'rc_user = 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
+                               [],
+                               [ 'rc_user != 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
+                               [],
+                               [ 'rc_patrolled != 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ],
+                               [],
+                               [ 'rc_patrolled = 0' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
+                               [],
+                               [ 'rc_timestamp >= wl_notificationtimestamp' ],
+                               [],
+                       ],
+                       [
+                               [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
+                               [],
+                               [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
+                               [],
+                       ],
+                       [
+                               [ 'onlyByUser' => 'SomeOtherUser' ],
+                               [],
+                               [ 'rc_user_text' => 'SomeOtherUser' ],
+                               [],
+                       ],
+                       [
+                               [ 'notByUser' => 'SomeOtherUser' ],
+                               [],
+                               [ "rc_user_text != 'SomeOtherUser'" ],
+                               [],
+                       ],
+                       [
+                               [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_OLDER ],
+                               [],
+                               [
+                                       "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
+                               ],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                       ],
+                       [
+                               [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_NEWER ],
+                               [],
+                               [
+                                       "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
+                               ],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                       ],
+                       [
+                               [
+                                       'startFrom' => [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
+                                       'dir' => WatchedItemQueryService::DIR_OLDER
+                               ],
+                               [],
+                               [
+                                       "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
+                               ],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider getWatchedItemsWithRecentChangeInfoOptionsProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
+               array $options,
+               array $expectedExtraFields,
+               array $expectedExtraConds,
+               array $expectedDbOptions
+       ) {
+               $expectedFields = array_merge(
+                       [
+                               'rc_id',
+                               'rc_namespace',
+                               'rc_title',
+                               'rc_timestamp',
+                               'rc_type',
+                               'rc_deleted',
+                               'wl_notificationtimestamp',
+
+                               'rc_cur_id',
+                               'rc_this_oldid',
+                               'rc_last_oldid',
+                       ],
+                       $expectedExtraFields
+               );
+               $expectedConds = array_merge(
+                       [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
+                       $expectedExtraConds
+               );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               $expectedFields,
+                               $expectedConds,
+                               $this->isType( 'string' ),
+                               $expectedDbOptions,
+                               [
+                                       'watchlist' => [
+                                               'INNER JOIN',
+                                               [
+                                                       'wl_namespace=rc_namespace',
+                                                       'wl_title=rc_title'
+                                               ]
+                                       ],
+                                       'page' => [
+                                               'LEFT JOIN',
+                                               'rc_cur_id=page_id',
+                                       ],
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function filterPatrolledOptionProvider() {
+               return [
+                       [ WatchedItemQueryService::FILTER_PATROLLED ],
+                       [ WatchedItemQueryService::FILTER_NOT_PATROLLED ],
+               ];
+       }
+
+       /**
+        * @dataProvider filterPatrolledOptionProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights(
+               $filtersOption
+       ) {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               $this->isType( 'array' ),
+                               [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
+                               $this->isType( 'string' ),
+                               $this->isType( 'array' ),
+                               $this->isType( 'array' )
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo(
+                       $user,
+                       [ 'filters' => [ $filtersOption ] ]
+               );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function mysqlIndexOptimizationProvider() {
+               return [
+                       [
+                               'mysql',
+                               [],
+                               [ "rc_timestamp > ''" ],
+                       ],
+                       [
+                               'mysql',
+                               [ 'start' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
+                               [ "rc_timestamp <= '20151212010101'" ],
+                       ],
+                       [
+                               'mysql',
+                               [ 'end' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
+                               [ "rc_timestamp >= '20151212010101'" ],
+                       ],
+                       [
+                               'postgres',
+                               [],
+                               [],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider mysqlIndexOptimizationProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization(
+               $dbType,
+               array $options,
+               array $expectedExtraConds
+       ) {
+               $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
+               $conds = array_merge( $commonConds, $expectedExtraConds );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               $this->isType( 'array' ),
+                               $conds,
+                               $this->isType( 'string' ),
+                               $this->isType( 'array' ),
+                               $this->isType( 'array' )
+                       )
+                       ->will( $this->returnValue( [] ) );
+               $mockDb->expects( $this->any() )
+                       ->method( 'getType' )
+                       ->will( $this->returnValue( $dbType ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function userPermissionRelatedExtraChecksProvider() {
+               return [
+                       [
+                               [],
+                               'deletedhistory',
+                               [
+                                       '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
+                                               LogPage::DELETED_ACTION . ')'
+                               ],
+                       ],
+                       [
+                               [],
+                               'suppressrevision',
+                               [
+                                       '(rc_type != ' . RC_LOG . ') OR (' .
+                                               '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
+                                               ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
+                               ],
+                       ],
+                       [
+                               [],
+                               'viewsuppressed',
+                               [
+                                       '(rc_type != ' . RC_LOG . ') OR (' .
+                                               '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
+                                               ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
+                               ],
+                       ],
+                       [
+                               [ 'onlyByUser' => 'SomeOtherUser' ],
+                               'deletedhistory',
+                               [
+                                       'rc_user_text' => 'SomeOtherUser',
+                                       '(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
+                                       '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
+                                               LogPage::DELETED_ACTION . ')'
+                               ],
+                       ],
+                       [
+                               [ 'onlyByUser' => 'SomeOtherUser' ],
+                               'suppressrevision',
+                               [
+                                       'rc_user_text' => 'SomeOtherUser',
+                                       '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
+                                               ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
+                                       '(rc_type != ' . RC_LOG . ') OR (' .
+                                               '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
+                                               ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
+                               ],
+                       ],
+                       [
+                               [ 'onlyByUser' => 'SomeOtherUser' ],
+                               'viewsuppressed',
+                               [
+                                       'rc_user_text' => 'SomeOtherUser',
+                                       '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
+                                               ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
+                                       '(rc_type != ' . RC_LOG . ') OR (' .
+                                               '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
+                                               ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
+                               ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider userPermissionRelatedExtraChecksProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
+               array $options,
+               $notAllowedAction,
+               array $expectedExtraConds
+       ) {
+               $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
+               $conds = array_merge( $commonConds, $expectedExtraConds );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               $this->isType( 'array' ),
+                               $conds,
+                               $this->isType( 'string' ),
+                               $this->isType( 'array' ),
+                               $this->isType( 'array' )
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist' ],
+                               [
+                                       'rc_id',
+                                       'rc_namespace',
+                                       'rc_title',
+                                       'rc_timestamp',
+                                       'rc_type',
+                                       'rc_deleted',
+                                       'wl_notificationtimestamp',
+
+                                       'rc_cur_id',
+                                       'rc_this_oldid',
+                                       'rc_last_oldid',
+                               ],
+                               [ 'wl_user' => 1, ],
+                               $this->isType( 'string' ),
+                               [],
+                               [
+                                       'watchlist' => [
+                                               'INNER JOIN',
+                                               [
+                                                       'wl_namespace=rc_namespace',
+                                                       'wl_title=rc_title'
+                                               ]
+                                       ],
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider() {
+               return [
+                       [
+                               [ 'rcTypes' => [ 1337 ] ],
+                               'Bad value for parameter $options[\'rcTypes\']',
+                       ],
+                       [
+                               [ 'rcTypes' => [ 'edit' ] ],
+                               'Bad value for parameter $options[\'rcTypes\']',
+                       ],
+                       [
+                               [ 'rcTypes' => [ RC_EDIT, 1337 ] ],
+                               'Bad value for parameter $options[\'rcTypes\']',
+                       ],
+                       [
+                               [ 'dir' => 'foo' ],
+                               'Bad value for parameter $options[\'dir\']',
+                       ],
+                       [
+                               [ 'start' => '20151212010101' ],
+                               'Bad value for parameter $options[\'dir\']: must be provided',
+                       ],
+                       [
+                               [ 'end' => '20151212010101' ],
+                               'Bad value for parameter $options[\'dir\']: must be provided',
+                       ],
+                       [
+                               [ 'startFrom' => [ '20151212010101', 123 ] ],
+                               'Bad value for parameter $options[\'dir\']: must be provided',
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => '20151212010101' ],
+                               'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
+                       ],
+                       [
+                               [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => [ '20151212010101' ] ],
+                               'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
+                       ],
+                       [
+                               [
+                                       'dir' => WatchedItemQueryService::DIR_OLDER,
+                                       'startFrom' => [ '20151212010101', 123, 'foo' ]
+                               ],
+                               'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
+                       ],
+                       [
+                               [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
+                               'Bad value for parameter $options[\'watchlistOwnerToken\']',
+                       ],
+                       [
+                               [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
+                               'Bad value for parameter $options[\'watchlistOwner\']',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions(
+               array $options,
+               $expectedInExceptionMessage
+       ) {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( $this->anything() );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
+               $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
+       }
+
+       public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist', 'page' ],
+                               [
+                                       'rc_id',
+                                       'rc_namespace',
+                                       'rc_title',
+                                       'rc_timestamp',
+                                       'rc_type',
+                                       'rc_deleted',
+                                       'wl_notificationtimestamp',
+                                       'rc_cur_id',
+                               ],
+                               [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
+                               $this->isType( 'string' ),
+                               [],
+                               [
+                                       'watchlist' => [
+                                               'INNER JOIN',
+                                               [
+                                                       'wl_namespace=rc_namespace',
+                                                       'wl_title=rc_title'
+                                               ]
+                                       ],
+                                       'page' => [
+                                               'LEFT JOIN',
+                                               'rc_cur_id=page_id',
+                                       ],
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo(
+                       $user,
+                       [ 'usedInGenerator' => true ]
+               );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'recentchanges', 'watchlist' ],
+                               [
+                                       'rc_id',
+                                       'rc_namespace',
+                                       'rc_title',
+                                       'rc_timestamp',
+                                       'rc_type',
+                                       'rc_deleted',
+                                       'wl_notificationtimestamp',
+                                       'rc_this_oldid',
+                               ],
+                               [ 'wl_user' => 1 ],
+                               $this->isType( 'string' ),
+                               [],
+                               [
+                                       'watchlist' => [
+                                               'INNER JOIN',
+                                               [
+                                                       'wl_namespace=rc_namespace',
+                                                       'wl_title=rc_title'
+                                               ]
+                                       ],
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo(
+                       $user,
+                       [ 'usedInGenerator' => true, 'allRevisions' => true, ]
+               );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               $this->isType( 'array' ),
+                               $this->isType( 'array' ),
+                               [
+                                       'wl_user' => 2,
+                                       '(rc_this_oldid=page_latest) OR (rc_type=3)',
+                               ],
+                               $this->isType( 'string' ),
+                               $this->isType( 'array' ),
+                               $this->isType( 'array' )
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+               $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
+               $otherUser->expects( $this->once() )
+                       ->method( 'getOption' )
+                       ->with( 'watchlisttoken' )
+                       ->willReturn( '0123456789abcdef' );
+
+               $items = $queryService->getWatchedItemsWithRecentChangeInfo(
+                       $user,
+                       [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
+               );
+
+               $this->assertEmpty( $items );
+       }
+
+       public function invalidWatchlistTokenProvider() {
+               return [
+                       [ 'wrongToken' ],
+                       [ '' ],
+               ];
+       }
+
+       /**
+        * @dataProvider invalidWatchlistTokenProvider
+        */
+       public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken( $token ) {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( $this->anything() );
+
+               $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+               $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
+               $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
+               $otherUser->expects( $this->once() )
+                       ->method( 'getOption' )
+                       ->with( 'watchlisttoken' )
+                       ->willReturn( '0123456789abcdef' );
+
+               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $queryService->getWatchedItemsWithRecentChangeInfo(
+                       $user,
+                       [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
+               );
+       }
+
+}
index 898b58e..eaeb3ae 100644 (file)
@@ -821,8 +821,13 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
                );
                $this->watchPages( $user, [ $target ] );
 
-               $resultMinor = $this->doListWatchlistRequest( [ 'wlshow' => 'minor', 'wlprop' => 'flags' ] );
-               $resultNotMinor = $this->doListWatchlistRequest( [ 'wlshow' => '!minor', 'wlprop' => 'flags' ] );
+               $resultMinor = $this->doListWatchlistRequest( [
+                       'wlshow' => WatchedItemQueryService::FILTER_MINOR,
+                       'wlprop' => 'flags'
+               ] );
+               $resultNotMinor = $this->doListWatchlistRequest( [
+                       'wlshow' => WatchedItemQueryService::FILTER_NOT_MINOR, 'wlprop' => 'flags'
+               ] );
 
                $this->assertArraySubsetsEqual(
                        $this->getItemsFromApiResponse( $resultMinor ),
@@ -845,8 +850,12 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
                );
                $this->watchPages( $user, [ $target ] );
 
-               $resultBot = $this->doListWatchlistRequest( [ 'wlshow' => 'bot' ] );
-               $resultNotBot = $this->doListWatchlistRequest( [ 'wlshow' => '!bot' ] );
+               $resultBot = $this->doListWatchlistRequest( [
+                       'wlshow' => WatchedItemQueryService::FILTER_BOT
+               ] );
+               $resultNotBot = $this->doListWatchlistRequest( [
+                       'wlshow' => WatchedItemQueryService::FILTER_NOT_BOT
+               ] );
 
                $this->assertArraySubsetsEqual(
                        $this->getItemsFromApiResponse( $resultBot ),
@@ -870,11 +879,11 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
 
                $resultAnon = $this->doListWatchlistRequest( [
                        'wlprop' => 'user',
-                       'wlshow' => 'anon'
+                       'wlshow' => WatchedItemQueryService::FILTER_ANON
                ] );
                $resultNotAnon = $this->doListWatchlistRequest( [
                        'wlprop' => 'user',
-                       'wlshow' => '!anon'
+                       'wlshow' => WatchedItemQueryService::FILTER_NOT_ANON
                ] );
 
                $this->assertArraySubsetsEqual(
@@ -914,11 +923,11 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
 
                $resultUnread = $this->doListWatchlistRequest( [
                        'wlprop' => 'notificationtimestamp|title',
-                       'wlshow' => 'unread'
+                       'wlshow' => WatchedItemQueryService::FILTER_UNREAD
                ] );
                $resultNotUnread = $this->doListWatchlistRequest( [
                        'wlprop' => 'notificationtimestamp|title',
-                       'wlshow' => '!unread'
+                       'wlshow' => WatchedItemQueryService::FILTER_NOT_UNREAD
                ] );
 
                $this->assertEquals(
@@ -951,11 +960,11 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
 
                $resultPatrolled = $this->doListWatchlistRequest( [
                        'wlprop' => 'patrol',
-                       'wlshow' => 'patrolled'
+                       'wlshow' => WatchedItemQueryService::FILTER_PATROLLED
                ], $user );
                $resultNotPatrolled = $this->doListWatchlistRequest( [
                        'wlprop' => 'patrol',
-                       'wlshow' => '!patrolled'
+                       'wlshow' => WatchedItemQueryService::FILTER_NOT_PATROLLED
                ], $user );
 
                $this->assertEquals(
index 68ce640..d6249bb 100644 (file)
@@ -55,6 +55,9 @@ class KafkaHandlerTest extends MediaWikiTestCase {
                $produce->expects( $this->once() )
                        ->method( 'setMessages' )
                        ->with( $expect, $this->anything(), $this->anything() );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
 
                $handler = new KafkaHandler( $produce, $options );
                $handler->handle( [
@@ -86,6 +89,9 @@ class KafkaHandlerTest extends MediaWikiTestCase {
                $produce->expects( $this->any() )
                        ->method( 'getAvailablePartitions' )
                        ->will( $this->throwException( new \Kafka\Exception ) );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
 
                if ( $expectException ) {
                        $this->setExpectedException( 'Kafka\Exception' );
@@ -144,6 +150,9 @@ class KafkaHandlerTest extends MediaWikiTestCase {
                        ->will( $this->returnValue( [ 'A' ] ) );
                $mockMethod = $produce->expects( $this->exactly( 2 ) )
                        ->method( 'setMessages' );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
                // evil hax
                \TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher =
                        new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
@@ -178,6 +187,9 @@ class KafkaHandlerTest extends MediaWikiTestCase {
                $produce->expects( $this->once() )
                        ->method( 'setMessages' )
                        ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
 
                $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' );
                $formatter->expects( $this->any() )
index a2d76e0..95f28c8 100644 (file)
@@ -84,6 +84,7 @@ return [
                        'tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js',
                        'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js',
                        'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js',
+                       'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js',
                        'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js',
                        'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js',
                        'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js',
diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.edit.test.js
new file mode 100644 (file)
index 0000000..f83f66c
--- /dev/null
@@ -0,0 +1,153 @@
+( function ( mw, $ ) {
+       QUnit.module( 'mediawiki.api.edit', QUnit.newMwEnvironment( {
+               setup: function () {
+                       this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
+               }
+       } ) );
+
+       QUnit.test( 'edit( title, transform String )', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       if ( /query.+titles=Sandbox/.test( req.url ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       curtimestamp: '2016-01-02T12:00:00Z',
+                                       query: {
+                                               pages: [ {
+                                                       pageid: 1,
+                                                       ns: 0,
+                                                       title:  'Sandbox',
+                                                       revisions: [ {
+                                                               timestamp: '2016-01-01T12:00:00Z',
+                                                               contentformat: 'text/x-wiki',
+                                                               contentmodel: 'wikitext',
+                                                               content: 'Sand.'
+                                                       } ]
+                                               } ]
+                                       }
+                               } ) );
+                       }
+                       if ( /edit.+basetimestamp=2016-01-01.+starttimestamp=2016-01-02.+text=Box%2E/.test( req.requestBody ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       edit: {
+                                               result: 'Success',
+                                               oldrevid: 11,
+                                               newrevid: 13,
+                                               newtimestamp: '2016-01-03T12:00:00Z'
+                                       }
+                               } ) );
+                       }
+               } );
+
+               return new mw.Api()
+                       .edit( 'Sandbox', function ( revision ) {
+                               return revision.content.replace( 'Sand', 'Box' );
+                       } )
+                       .then( function ( edit ) {
+                               assert.equal( edit.newrevid, 13 );
+                       } );
+       } );
+
+       QUnit.test( 'edit( title, transform Promise )', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       if ( /query.+titles=Async/.test( req.url ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       curtimestamp: '2016-02-02T12:00:00Z',
+                                       query: {
+                                               pages: [ {
+                                                       pageid: 4,
+                                                       ns: 0,
+                                                       title:  'Async',
+                                                       revisions: [ {
+                                                               timestamp: '2016-02-01T12:00:00Z',
+                                                               contentformat: 'text/x-wiki',
+                                                               contentmodel: 'wikitext',
+                                                               content: 'Async.'
+                                                       } ]
+                                               } ]
+                                       }
+                               } ) );
+                       }
+                       if ( /edit.+basetimestamp=2016-02-01.+starttimestamp=2016-02-02.+text=Promise%2E/.test( req.requestBody ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       edit: {
+                                               result: 'Success',
+                                               oldrevid: 21,
+                                               newrevid: 23,
+                                               newtimestamp: '2016-02-03T12:00:00Z'
+                                       }
+                               } ) );
+                       }
+               } );
+
+               return new mw.Api()
+                       .edit( 'Async', function ( revision ) {
+                               return $.Deferred().resolve( revision.content.replace( 'Async', 'Promise' ) );
+                       } )
+                       .then( function ( edit ) {
+                               assert.equal( edit.newrevid, 23 );
+                       } );
+       } );
+
+       QUnit.test( 'edit( title, transform Object )', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       if ( /query.+titles=Param/.test( req.url ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       curtimestamp: '2016-03-02T12:00:00Z',
+                                       query: {
+                                               pages: [ {
+                                                       pageid: 3,
+                                                       ns: 0,
+                                                       title:  'Param',
+                                                       revisions: [ {
+                                                               timestamp: '2016-03-01T12:00:00Z',
+                                                               contentformat: 'text/x-wiki',
+                                                               contentmodel: 'wikitext',
+                                                               content: '...'
+                                                       } ]
+                                               } ]
+                                       }
+                               } ) );
+                       }
+                       if ( /edit.+basetimestamp=2016-03-01.+starttimestamp=2016-03-02.+text=Content&summary=Sum/.test( req.requestBody ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       edit: {
+                                               result: 'Success',
+                                               oldrevid: 31,
+                                               newrevid: 33,
+                                               newtimestamp: '2016-03-03T12:00:00Z'
+                                       }
+                               } ) );
+                       }
+               } );
+
+               return new mw.Api()
+                       .edit( 'Param', function () {
+                               return { text: 'Content', summary: 'Sum' };
+                       } )
+                       .then( function ( edit ) {
+                               assert.equal( edit.newrevid, 33 );
+                       } );
+       } );
+
+       QUnit.test( 'create( title, content )', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       if ( /edit.+text=Sand/.test( req.requestBody ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify( {
+                                       edit: {
+                                               'new': true,
+                                               result: 'Success',
+                                               newrevid: 41,
+                                               newtimestamp: '2016-04-01T12:00:00Z'
+                                       }
+                               } ) );
+                       }
+               } );
+
+               return new mw.Api()
+                       .create( 'Sandbox', { summary: 'Load sand particles.' }, 'Sand.' )
+                       .then( function ( page ) {
+                               assert.equal( page.newrevid, 41 );
+                       } );
+       } );
+
+}( mediaWiki, jQuery ) );