Merge "Drop call of deprecated IE8-support method"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 12 Oct 2017 17:35:08 +0000 (17:35 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 12 Oct 2017 17:35:08 +0000 (17:35 +0000)
64 files changed:
RELEASE-NOTES-1.31
includes/CategoryViewer.php
includes/CommentStore.php
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/Revision.php
includes/api/ApiQueryCategories.php
includes/api/ApiQueryImages.php
includes/api/ApiQueryLinks.php
includes/api/i18n/uk.json
includes/api/i18n/zh-hans.json
includes/content/TextContent.php
includes/installer/PostgresUpdater.php
includes/installer/i18n/eu.json
includes/installer/i18n/zh-hant.json
includes/shell/Command.php
includes/shell/Result.php
includes/shell/Shell.php
includes/specials/SpecialUnblock.php
languages/i18n/af.json
languages/i18n/ba.json
languages/i18n/cdo.json
languages/i18n/ckb.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/fi.json
languages/i18n/hr.json
languages/i18n/ia.json
languages/i18n/ml.json
languages/i18n/mwl.json
languages/i18n/qqq.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/tg-cyrl.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/messages/MessagesUr.php
maintenance/postgres/archives/patch-add-3d.sql [deleted file]
phpcs.xml
resources/Resources.php
resources/src/mediawiki.action/mediawiki.action.edit.styles.less
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.UriProcessor.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuHeaderWidget.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuHeaderWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ItemMenuOptionWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MenuSelectWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.TagItemWidget.js
tests/common/TestsAutoLoader.php
tests/phpunit/includes/CommentStoreTest.php
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php [deleted file]
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/RevisionTestModifyableContent.php [new file with mode: 0644]
tests/phpunit/includes/RevisionTestModifyableContentHandler.php [new file with mode: 0644]
tests/phpunit/includes/shell/CommandTest.php
tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js
tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js

index 3fd1fc8..57cbec4 100644 (file)
@@ -6,7 +6,9 @@ MediaWiki 1.31 is an alpha-quality branch and is not recommended for use in
 production.
 
 === Configuration changes in 1.31 ===
-* …
+* $wgEnableAPI and $wgEnableWriteAPI are now deprecated and will be removed in
+  a future version. The API is now considered to be stable, secure and
+  essential.
 
 === New features in 1.31 ===
 * …
index 9d692d7..f36c758 100644 (file)
@@ -629,7 +629,7 @@ class CategoryViewer extends ContextSource {
         * @return string HTML
         */
        private function pagingLinks( $first, $last, $type = '' ) {
-               $prevLink = $this->msg( 'prev-page' )->text();
+               $prevLink = $this->msg( 'prev-page' )->escaped();
 
                $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( $first != '' ) {
@@ -638,13 +638,13 @@ class CategoryViewer extends ContextSource {
                        unset( $prevQuery["{$type}from"] );
                        $prevLink = $linkRenderer->makeKnownLink(
                                $this->addFragmentToTitle( $this->title, $type ),
-                               $prevLink,
+                               new HtmlArmor( $prevLink ),
                                [],
                                $prevQuery
                        );
                }
 
-               $nextLink = $this->msg( 'next-page' )->text();
+               $nextLink = $this->msg( 'next-page' )->escaped();
 
                if ( $last != '' ) {
                        $lastQuery = $this->query;
@@ -652,7 +652,7 @@ class CategoryViewer extends ContextSource {
                        unset( $lastQuery["{$type}until"] );
                        $nextLink = $linkRenderer->makeKnownLink(
                                $this->addFragmentToTitle( $this->title, $type ),
-                               $nextLink,
+                               new HtmlArmor( $nextLink ),
                                [],
                                $lastQuery
                        );
index b8a31e6..0d679d3 100644 (file)
@@ -29,10 +29,24 @@ use Wikimedia\Rdbms\IDatabase;
  */
 class CommentStore {
 
-       /** Maximum length of a comment. Longer comments will be truncated. */
+       /**
+        * Maximum length of a comment in UTF-8 characters. Longer comments will be truncated.
+        * @note This must be at least 255 and not greater than floor( MAX_COMMENT_LENGTH / 4 ).
+        */
+       const COMMENT_CHARACTER_LIMIT = 1000;
+
+       /**
+        * Maximum length of a comment in bytes. Longer comments will be truncated.
+        * @note This value is determined by the size of the underlying database field,
+        *  currently BLOB in MySQL/MariaDB.
+        */
        const MAX_COMMENT_LENGTH = 65535;
 
-       /** Maximum length of serialized data. Longer data will result in an exception. */
+       /**
+        * Maximum length of serialized data in bytes. Longer data will result in an exception.
+        * @note This value is determined by the size of the underlying database field,
+        *  currently BLOB in MySQL/MariaDB.
+        */
        const MAX_DATA_LENGTH = 65535;
 
        /**
@@ -371,6 +385,15 @@ class CommentStore {
 
                # Truncate comment in a Unicode-sensitive manner
                $comment->text = $this->lang->truncate( $comment->text, self::MAX_COMMENT_LENGTH );
+               if ( mb_strlen( $comment->text, 'UTF-8' ) > self::COMMENT_CHARACTER_LIMIT ) {
+                       $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this->lang )->escaped();
+                       if ( mb_strlen( $ellipsis ) >= self::COMMENT_CHARACTER_LIMIT ) {
+                               // WTF?
+                               $ellipsis = '...';
+                       }
+                       $maxLength = self::COMMENT_CHARACTER_LIMIT - mb_strlen( $ellipsis, 'UTF-8' );
+                       $comment->text = mb_substr( $comment->text, 0, $maxLength, 'UTF-8' ) . $ellipsis;
+               }
 
                if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
                        $dbData = $comment->data;
index 780976a..bd944d2 100644 (file)
@@ -7953,6 +7953,8 @@ $wgExemptFromUserRobotsControl = null;
  * machine-readable data via api.php
  *
  * See https://www.mediawiki.org/wiki/API
+ *
+ * @deprecated since 1.31
  */
 $wgEnableAPI = true;
 
@@ -7960,6 +7962,8 @@ $wgEnableAPI = true;
  * Allow the API to be used to perform write operations
  * (page edits, rollback, etc.) when an authorised user
  * accesses it
+ *
+ * @deprecated since 1.31
  */
 $wgEnableWriteAPI = true;
 
index 069e1be..d53e98d 100644 (file)
@@ -2259,6 +2259,7 @@ function wfEscapeShellArg( /*...*/ ) {
  * @deprecated since 1.30 use MediaWiki\Shell::isDisabled()
  */
 function wfShellExecDisabled() {
+       wfDeprecated( __FUNCTION__, '1.30' );
        return Shell::isDisabled() ? 'disabled' : false;
 }
 
index bcfbe63..dd3ee78 100644 (file)
@@ -571,168 +571,184 @@ class Revision implements IDBAccessObject {
         * @throws MWException
         * @access private
         */
-       function __construct( $row ) {
+       public function __construct( $row ) {
                if ( is_object( $row ) ) {
-                       $this->mId = intval( $row->rev_id );
-                       $this->mPage = intval( $row->rev_page );
-                       $this->mTextId = intval( $row->rev_text_id );
-                       $this->mComment = CommentStore::newKey( 'rev_comment' )
-                               // Legacy because $row probably came from self::selectFields()
-                               ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
-                       $this->mUser = intval( $row->rev_user );
-                       $this->mMinorEdit = intval( $row->rev_minor_edit );
-                       $this->mTimestamp = $row->rev_timestamp;
-                       $this->mDeleted = intval( $row->rev_deleted );
-
-                       if ( !isset( $row->rev_parent_id ) ) {
-                               $this->mParentId = null;
-                       } else {
-                               $this->mParentId = intval( $row->rev_parent_id );
-                       }
-
-                       if ( !isset( $row->rev_len ) ) {
-                               $this->mSize = null;
-                       } else {
-                               $this->mSize = intval( $row->rev_len );
-                       }
-
-                       if ( !isset( $row->rev_sha1 ) ) {
-                               $this->mSha1 = null;
-                       } else {
-                               $this->mSha1 = $row->rev_sha1;
-                       }
+                       $this->constructFromDbRowObject( $row );
+               } elseif ( is_array( $row ) ) {
+                       $this->constructFromRowArray( $row );
+               } else {
+                       throw new MWException( 'Revision constructor passed invalid row format.' );
+               }
+               $this->mUnpatrolled = null;
+       }
 
-                       if ( isset( $row->page_latest ) ) {
-                               $this->mCurrent = ( $row->rev_id == $row->page_latest );
-                               $this->mTitle = Title::newFromRow( $row );
-                       } else {
-                               $this->mCurrent = false;
-                               $this->mTitle = null;
-                       }
+       /**
+        * @param object $row
+        */
+       private function constructFromDbRowObject( $row ) {
+               $this->mId = intval( $row->rev_id );
+               $this->mPage = intval( $row->rev_page );
+               $this->mTextId = intval( $row->rev_text_id );
+               $this->mComment = CommentStore::newKey( 'rev_comment' )
+                       // Legacy because $row probably came from self::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
+               $this->mUser = intval( $row->rev_user );
+               $this->mMinorEdit = intval( $row->rev_minor_edit );
+               $this->mTimestamp = $row->rev_timestamp;
+               $this->mDeleted = intval( $row->rev_deleted );
+
+               if ( !isset( $row->rev_parent_id ) ) {
+                       $this->mParentId = null;
+               } else {
+                       $this->mParentId = intval( $row->rev_parent_id );
+               }
 
-                       if ( !isset( $row->rev_content_model ) ) {
-                               $this->mContentModel = null; # determine on demand if needed
-                       } else {
-                               $this->mContentModel = strval( $row->rev_content_model );
-                       }
+               if ( !isset( $row->rev_len ) ) {
+                       $this->mSize = null;
+               } else {
+                       $this->mSize = intval( $row->rev_len );
+               }
 
-                       if ( !isset( $row->rev_content_format ) ) {
-                               $this->mContentFormat = null; # determine on demand if needed
-                       } else {
-                               $this->mContentFormat = strval( $row->rev_content_format );
-                       }
+               if ( !isset( $row->rev_sha1 ) ) {
+                       $this->mSha1 = null;
+               } else {
+                       $this->mSha1 = $row->rev_sha1;
+               }
 
-                       // Lazy extraction...
-                       $this->mText = null;
-                       if ( isset( $row->old_text ) ) {
-                               $this->mTextRow = $row;
-                       } else {
-                               // 'text' table row entry will be lazy-loaded
-                               $this->mTextRow = null;
-                       }
+               if ( isset( $row->page_latest ) ) {
+                       $this->mCurrent = ( $row->rev_id == $row->page_latest );
+                       $this->mTitle = Title::newFromRow( $row );
+               } else {
+                       $this->mCurrent = false;
+                       $this->mTitle = null;
+               }
 
-                       // Use user_name for users and rev_user_text for IPs...
-                       $this->mUserText = null; // lazy load if left null
-                       if ( $this->mUser == 0 ) {
-                               $this->mUserText = $row->rev_user_text; // IP user
-                       } elseif ( isset( $row->user_name ) ) {
-                               $this->mUserText = $row->user_name; // logged-in user
-                       }
-                       $this->mOrigUserText = $row->rev_user_text;
-               } elseif ( is_array( $row ) ) {
-                       // Build a new revision to be saved...
-                       global $wgUser; // ugh
-
-                       # if we have a content object, use it to set the model and type
-                       if ( !empty( $row['content'] ) ) {
-                               // @todo when is that set? test with external store setup! check out insertOn() [dk]
-                               if ( !empty( $row['text_id'] ) ) {
-                                       throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
-                                               "can't serialize content object" );
-                               }
+               if ( !isset( $row->rev_content_model ) ) {
+                       $this->mContentModel = null; # determine on demand if needed
+               } else {
+                       $this->mContentModel = strval( $row->rev_content_model );
+               }
 
-                               $row['content_model'] = $row['content']->getModel();
-                               # note: mContentFormat is initializes later accordingly
-                               # note: content is serialized later in this method!
-                               # also set text to null?
-                       }
+               if ( !isset( $row->rev_content_format ) ) {
+                       $this->mContentFormat = null; # determine on demand if needed
+               } else {
+                       $this->mContentFormat = strval( $row->rev_content_format );
+               }
 
-                       $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
-                       $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
-                       $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
-                       $this->mUserText = isset( $row['user_text'] )
-                               ? strval( $row['user_text'] ) : $wgUser->getName();
-                       $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
-                       $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
-                       $this->mTimestamp = isset( $row['timestamp'] )
-                               ? strval( $row['timestamp'] ) : wfTimestampNow();
-                       $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
-                       $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
-                       $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
-                       $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
-
-                       $this->mContentModel = isset( $row['content_model'] )
-                               ? strval( $row['content_model'] ) : null;
-                       $this->mContentFormat = isset( $row['content_format'] )
-                               ? strval( $row['content_format'] ) : null;
-
-                       // Enforce spacing trimming on supplied text
-                       $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
-                       $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+               // Lazy extraction...
+               $this->mText = null;
+               if ( isset( $row->old_text ) ) {
+                       $this->mTextRow = $row;
+               } else {
+                       // 'text' table row entry will be lazy-loaded
                        $this->mTextRow = null;
+               }
 
-                       $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
-
-                       // if we have a Content object, override mText and mContentModel
-                       if ( !empty( $row['content'] ) ) {
-                               if ( !( $row['content'] instanceof Content ) ) {
-                                       throw new MWException( '`content` field must contain a Content object.' );
-                               }
-
-                               $handler = $this->getContentHandler();
-                               $this->mContent = $row['content'];
+               // Use user_name for users and rev_user_text for IPs...
+               $this->mUserText = null; // lazy load if left null
+               if ( $this->mUser == 0 ) {
+                       $this->mUserText = $row->rev_user_text; // IP user
+               } elseif ( isset( $row->user_name ) ) {
+                       $this->mUserText = $row->user_name; // logged-in user
+               }
+               $this->mOrigUserText = $row->rev_user_text;
+       }
 
-                               $this->mContentModel = $this->mContent->getModel();
-                               $this->mContentHandler = null;
+       /**
+        * @param array $row
+        *
+        * @throws MWException
+        */
+       private function constructFromRowArray( array $row ) {
+               // Build a new revision to be saved...
+               global $wgUser; // ugh
 
-                               $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
-                       } elseif ( $this->mText !== null ) {
-                               $handler = $this->getContentHandler();
-                               $this->mContent = $handler->unserializeContent( $this->mText );
+               # if we have a content object, use it to set the model and type
+               if ( !empty( $row['content'] ) ) {
+                       if ( !( $row['content'] instanceof Content ) ) {
+                               throw new MWException( '`content` field must contain a Content object.' );
                        }
 
-                       // If we have a Title object, make sure it is consistent with mPage.
-                       if ( $this->mTitle && $this->mTitle->exists() ) {
-                               if ( $this->mPage === null ) {
-                                       // if the page ID wasn't known, set it now
-                                       $this->mPage = $this->mTitle->getArticleID();
-                               } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
-                                       // Got different page IDs. This may be legit (e.g. during undeletion),
-                                       // but it seems worth mentioning it in the log.
-                                       wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
-                                               $this->mTitle->getArticleID() . " provided by the Title object." );
-                               }
+                       // @todo when is that set? test with external store setup! check out insertOn() [dk]
+                       if ( !empty( $row['text_id'] ) ) {
+                               throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
+                                       "can't serialize content object" );
                        }
 
-                       $this->mCurrent = false;
+                       $row['content_model'] = $row['content']->getModel();
+                       # note: mContentFormat is initializes later accordingly
+                       # note: content is serialized later in this method!
+                       # also set text to null?
+               }
+
+               $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
+               $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
+               $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
+               $this->mUserText = isset( $row['user_text'] )
+                       ? strval( $row['user_text'] ) : $wgUser->getName();
+               $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
+               $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
+               $this->mTimestamp = isset( $row['timestamp'] )
+                       ? strval( $row['timestamp'] ) : wfTimestampNow();
+               $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
+               $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
+               $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+               $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+
+               $this->mContentModel = isset( $row['content_model'] )
+                       ? strval( $row['content_model'] ) : null;
+               $this->mContentFormat = isset( $row['content_format'] )
+                       ? strval( $row['content_format'] ) : null;
+
+               // Enforce spacing trimming on supplied text
+               $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
+               $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+               $this->mTextRow = null;
+
+               $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+               // if we have a Content object, override mText and mContentModel
+               if ( !empty( $row['content'] ) ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $row['content'];
 
-                       // If we still have no length, see it we have the text to figure it out
-                       if ( !$this->mSize && $this->mContent !== null ) {
-                               $this->mSize = $this->mContent->getSize();
-                       }
+                       $this->mContentModel = $this->mContent->getModel();
+                       $this->mContentHandler = null;
 
-                       // Same for sha1
-                       if ( $this->mSha1 === null ) {
-                               $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+                       $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+               } elseif ( $this->mText !== null ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $handler->unserializeContent( $this->mText );
+               }
+
+               // If we have a Title object, make sure it is consistent with mPage.
+               if ( $this->mTitle && $this->mTitle->exists() ) {
+                       if ( $this->mPage === null ) {
+                               // if the page ID wasn't known, set it now
+                               $this->mPage = $this->mTitle->getArticleID();
+                       } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
+                               // Got different page IDs. This may be legit (e.g. during undeletion),
+                               // but it seems worth mentioning it in the log.
+                               wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
+                                       $this->mTitle->getArticleID() . " provided by the Title object." );
                        }
+               }
 
-                       // force lazy init
-                       $this->getContentModel();
-                       $this->getContentFormat();
-               } else {
-                       throw new MWException( 'Revision constructor passed invalid row format.' );
+               $this->mCurrent = false;
+
+               // If we still have no length, see it we have the text to figure it out
+               if ( !$this->mSize && $this->mContent !== null ) {
+                       $this->mSize = $this->mContent->getSize();
                }
-               $this->mUnpatrolled = null;
+
+               // Same for sha1
+               if ( $this->mSha1 === null ) {
+                       $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+               }
+
+               // force lazy init
+               $this->getContentModel();
+               $this->getContentFormat();
        }
 
        /**
index c4428d5..f728dc5 100644 (file)
@@ -69,7 +69,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
 
                $this->addTables( 'categorylinks' );
                $this->addWhereFld( 'cl_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
-               if ( !is_null( $params['categories'] ) ) {
+               if ( $params['categories'] ) {
                        $cats = [];
                        foreach ( $params['categories'] as $cat ) {
                                $title = Title::newFromText( $cat );
@@ -79,6 +79,10 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
                                        $cats[] = $title->getDBkey();
                                }
                        }
+                       if ( !$cats ) {
+                               // No titles so no results
+                               return;
+                       }
                        $this->addWhereFld( 'cl_to', $cats );
                }
 
index 0086c58..01a54de 100644 (file)
@@ -85,7 +85,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
                }
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
 
-               if ( !is_null( $params['images'] ) ) {
+               if ( $params['images'] ) {
                        $images = [];
                        foreach ( $params['images'] as $img ) {
                                $title = Title::newFromText( $img );
@@ -95,6 +95,10 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
                                        $images[] = $title->getDBkey();
                                }
                        }
+                       if ( !$images ) {
+                               // No titles so no results
+                               return;
+                       }
                        $this->addWhereFld( 'il_to', $images );
                }
 
index 4b34091..2f75e76 100644 (file)
@@ -89,7 +89,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
                $this->addWhereFld( $this->prefix . '_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
                $this->addWhereFld( $this->prefix . '_namespace', $params['namespace'] );
 
-               if ( !is_null( $params[$this->titlesParam] ) ) {
+               if ( $params[$this->titlesParam] ) {
                        $lb = new LinkBatch;
                        foreach ( $params[$this->titlesParam] as $t ) {
                                $title = Title::newFromText( $t );
@@ -102,6 +102,9 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
                        $cond = $lb->constructSet( $this->prefix, $this->getDB() );
                        if ( $cond ) {
                                $this->addWhere( $cond );
+                       } else {
+                               // No titles so no results
+                               return;
                        }
                }
 
index 2fcd915..ca0b0a8 100644 (file)
@@ -12,7 +12,8 @@
                        "Andriykopanytsia",
                        "Максим Підліснюк",
                        "AS",
-                       "Umherirrender"
+                       "Umherirrender",
+                       "Choomaq"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Документація]]\n* [[mw:Special:MyLanguage/API:FAQ|ЧаПи]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Список розсилки]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Оголошення API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Баґи і запити]\n</div>\n<strong>Статус:</strong> Усі функції, вказані на цій сторінці, мають працювати, але API далі перебуває в активній розробці і може змінитися у будь-який момент. Підпишіться на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ список розсилки mediawiki-api-announce], щоб помічати оновлення.\n\n<strong>Хибні запити:</strong> Коли до API надсилаються хибні запити, буде відіслано HTTP-шапку з ключем «MediaWiki-API-Error», а тоді і значення шапки, і код помилки, надіслані назад, будуть встановлені з тим же значенням. Більше інформації див. на [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Тестування:</strong> Для зручності тестування запитів API, див. [[Special:ApiSandbox]].",
        "apihelp-tokens-summary": "Отримати жетони для дій пов'язаних зі зміною даних.",
        "apihelp-tokens-extended-description": "Цей модуль застарів на користь [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
        "apihelp-tokens-param-type": "Які типи жетонів запитати.",
-       "apihelp-tokens-example-edit": "Отримати жетон редагування (за замовчуванням).",
-       "apihelp-tokens-example-emailmove": "Отримати жетон електронної пошти та жетон перейменування.",
+       "apihelp-tokens-example-edit": "Отримати токен редагування (за замовчуванням).",
+       "apihelp-tokens-example-emailmove": "Отримати токен електронної пошти та токен перейменування.",
        "apihelp-unblock-summary": "Розблокувати користувача.",
        "apihelp-unblock-param-id": "Ідентифікатор блоку чи розблокування (отриманий через <kbd>list=blocks</kbd>). Не може бути використано разом із <var>$1user</var> або <var>$1userid</var>.",
        "apihelp-unblock-param-user": "Ім'я користувача, IP-адреса чи IP-діапазон до розблокування. Не може бути використано разом із <var>$1id</var> або <var>$1userid</var>.",
index 5daadb1..0084717 100644 (file)
@@ -22,7 +22,8 @@
                        "損齋",
                        "Myy730",
                        "D41D8CD98F",
-                       "Umherirrender"
+                       "Umherirrender",
+                       "NeverBehave"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>本页所展示的所有特性都应正常工作,但是API仍在开发当中,将会随时变化。请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。",
        "apiwarn-notfile": "“$1”不是文件。",
        "apiwarn-nothumb-noimagehandler": "不能创建缩略图,因为$1没有关联的图片处理器。",
        "apiwarn-parse-nocontentmodel": "<var>title</var>或<var>contentmodel</var>未提供,假设$1。",
-       "apiwarn-parse-revidwithouttext": "<var>revid</var>在没有<var>text</var>的情况下被使用,并且请求了已解析页面的属性。您是想用<var>oldid</var>而不是<var>revid</var>么?",
+       "apiwarn-parse-revidwithouttext": "<var>revid</var>在没有<var>text</var>的情况下被使用,并且请求了已解析的页面属性。您是想用<var>oldid</var>而不是<var>revid</var>么?",
        "apiwarn-parse-titlewithouttext": "<var>title</var>在没有<var>text</var>的情况下被使用,并且请求了已解析页面的属性。您是想用<var>page</var>而不是<var>title</var>么?",
        "apiwarn-redirectsandrevids": "重定向解决方案不能与<var>revids</var>参数一起使用。任何<var>revids</var>所指向的重定向都未被解决。",
        "apiwarn-tokennotallowed": "操作“$1”不允许当前用户使用。",
index 5f585bc..e5a9f90 100644 (file)
  */
 class TextContent extends AbstractContent {
 
+       /**
+        * @var string
+        */
+       protected $mText;
+
        /**
         * @param string $text
         * @param string $model_id
index 07aeb13..0475fe4 100644 (file)
@@ -454,7 +454,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addPgIndex', 'user_groups', 'user_groups_expiry', '( ug_expiry )' ],
 
                        // 1.30
-                       [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
+                       [ 'addPgEnumValue', 'media_type', '3D' ],
                        [ 'setDefault', 'revision', 'rev_comment', '' ],
                        [ 'changeNullableField', 'revision', 'rev_comment', 'NOT NULL', true ],
                        [ 'setDefault', 'archive', 'ar_comment', '' ],
@@ -838,6 +838,46 @@ END;
                }
        }
 
+       /**
+        * Add a value to an existing PostgreSQL enum type
+        * @since 1.31
+        * @param string $type Type name. Must be in the core schema.
+        * @param string $value Value to add.
+        */
+       public function addPgEnumValue( $type, $value ) {
+               $row = $this->db->selectRow(
+                       [
+                               't' => 'pg_catalog.pg_type',
+                               'n' => 'pg_catalog.pg_namespace',
+                               'e' => 'pg_catalog.pg_enum',
+                       ],
+                       [ 't.typname', 't.typtype', 'e.enumlabel' ],
+                       [
+                               't.typname' => $type,
+                               'n.nspname' => $this->db->getCoreSchema(),
+                       ],
+                       __METHOD__,
+                       [],
+                       [
+                               'n' => [ 'JOIN', 't.typnamespace = n.oid' ],
+                               'e' => [ 'LEFT JOIN', [ 'e.enumtypid = t.oid', 'e.enumlabel' => $value ] ],
+                       ]
+               );
+
+               if ( !$row ) {
+                       $this->output( "...Type $type does not exist, skipping modify enum.\n" );
+               } elseif ( $row->typtype !== 'e' ) {
+                       $this->output( "...Type $type does not seem to be an enum, skipping modify enum.\n" );
+               } elseif ( $row->enumlabel === $value ) {
+                       $this->output( "...Enum type $type already contains value '$value'.\n" );
+               } else {
+                       $this->output( "...Adding value '$value' to enum type $type.\n" );
+                       $etype = $this->db->addIdentifierQuotes( $type );
+                       $evalue = $this->db->addQuotes( $value );
+                       $this->db->query( "ALTER TYPE $etype ADD VALUE $evalue" );
+               }
+       }
+
        protected function dropFkey( $table, $field ) {
                $fi = $this->db->fieldInfo( $table, $field );
                if ( is_null( $fi ) ) {
index eda62d4..f367abd 100644 (file)
@@ -14,7 +14,9 @@
        "config-localsettings-upgrade": "<code>LocalSettings.php</code> fitxategi bat detektatu da.\nInstalazioa eguneratzeko, mesedez, sar ezazu <code>$wgUpgradeKey</code> balioa beheko koadroan.\n<code>LocalSettings.php</code> fitxategian aurkituko duzu.",
        "config-localsettings-cli-upgrade": "<code>LocalSettings.php</code> fitxategi bat detektatu da.\nInstalazioa eguneratzeko, exekuta ezazu <code>update.php</code>, mesedez",
        "config-localsettings-key": "Eguneratze-gakoa:",
-       "config-upgrade-key-missing": "Detektatu egin dagoeneko MediaWiki instalatu dagoela.\n\nInstalazio hau gaurkotzeko, jarri hurrengo lerroa behekoaldean <code> LocalSettings.php </code>\n\n$1",
+       "config-localsettings-badkey": "Sartu duzun eguneratze-gakoa ez da zuzena.",
+       "config-upgrade-key-missing": "Detektatu egin da dagoeneko MediaWiki instalatu dagoela.\n\nInstalazio hau gaurkotzeko, jarri hurrengo lerroa behekoaldean <code> LocalSettings.php </code>\n\n$1",
+       "config-localsettings-incomplete": "Existitzen den <code>LocalSettings.php</code> bukatu gabe dagoela ematen du.\n$1 aldagaia ez dago finkatuta.\nMesedez, aldatu <code>LocalSettings.php</code>, aldagaia aldatzeko eta gero klikatu {{int:Config-continue}}\".",
        "config-session-error": "Saio hasierako errorea: $1",
        "config-session-expired": "Saioren informazio galdu egin dela ematen du.\nSaioak konfiguratutak daude $1 -eko iraupenerako.\nHau handitu ahal duzu <code>code>session.gc_maxlifetime</code> jartzen  php.ini -n.\n\nBerrabiatu instalazio prozesua.",
        "config-no-session": "Saioren informazio galdu egin da!\nEgiaztatu zure php.ini eta ziurtatu <code>session.save_path</code> egoki zaion direktorioan kokatu dagoela.",
@@ -49,6 +51,7 @@
        "config-apcu": "[http://www.php.net/apcu APCu] instalatuta dago",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] instalatuta dago",
        "config-diff3-bad": "GNU diff3 ez da aurkitu.",
+       "config-git": "Git bertsio-kontrol software aurkitu da: <code>$1</code>",
        "config-git-bad": "Git bertsio-kontrol software ez da aurkitu.",
        "config-no-uri": "<strong>Errore:</strong> Ezin izan da zehaztu URI. Instalazio geldiarazi egin da.",
        "config-no-cli-uri": "<strong>Oharra</strong>. Ez da zehaztu <code>--scriptpath</code>, erabiltzen estandar <code>$1</code> .",
        "config-db-host-oracle": "Datu-baseko TNS:",
        "config-db-wiki-settings": "Wiki hau identifikatu",
        "config-db-name": "Datu-base izena:",
+       "config-db-name-help": "Aukeratu zure Wikia identifikatzen duen izena.\nEzin dira espazioak eabili.\n\nErabiltzen ari bazara web hosting partekatua, hostin-eko hornitzaileak emango dizu datu-basearen izen espezifikoa edo kontrol panel baten bitzrtez zure datu-basea sortzea utziko dizu.",
        "config-db-name-oracle": "Datu-baseko eskema:",
        "config-db-username": "Datu-base lankide izena:",
        "config-db-password": "Datu-base pasahitza:",
+       "config-db-install-username": "Sartu erabiliko duzun erabiltzaile izena datu-basearikn konektzatzeko instalazio prozesuaren bitartean.\nHau ez da MediaWikiaren erabiltzaile izanea; hau da datu-basearen erabiltzaile izena da.",
        "config-db-install-password": "Sartu erabiliko den pasahitza datu-basea konektatzeko instalazio prozesuan zehar.\n\nHau ez da MediaWikiaren pasahitza; hau da zure datu-basearen pasahitza.",
        "config-db-account-lock": "Operazio arruntentan erabili erabiltzaile izena eta pasahitza berdina",
        "config-db-wiki-account": "Operazio arruntentaten erabili erabiltzaile kontua.",
@@ -71,6 +76,7 @@
        "config-db-schema": "MediaWikirako eskema:",
        "config-db-schema-help": "Patroi hau normalean egokia da. Bakarrik aldatu beharrezkoa bada.",
        "config-pg-test-error": "Ezin da datu-basearekin konektatu <strong>$1</strong>: $2",
+       "config-sqlite-dir": "SQLite -eko informazioaren direktorioa:",
        "config-type-mysql": "MySQL (edo bateragarria)",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-header-mssql": "Microsoft SQL Server-en ezarpenak",
        "config-invalid-db-type": "Datu-base mota baliogabea.",
        "config-db-sys-user-exists-oracle": "$1 erabiltzaile kontua dagoeneko existitzen da. SYSDBA kontu berri bat sortzeko erabili daiteke soilik!",
+       "config-sqlite-mkdir-error": "Arazo bat sortu da datuen direktorioa sortzerakoan \"$1\".\nLokalizazio egiaztatu eta berriro saiatu.",
+       "config-sqlite-dir-unwritable": "Ezin izan da \"$1\" direktoriora idatzi.\nAldatu baimenak web-serbidoreak idatzi ahal izateko, eta berriro saiatu.",
+       "config-sqlite-connection-error": "$1.\n\nDatu direktorioa eta datu-basea egiaztatu eta berriro saiatu.",
        "config-sqlite-readonly": "Ezin da idatzi <code>$1</code> fitxategian.",
+       "config-sqlite-cant-create-db": "Ezin izan da <code>$1</code> datu-basearen artxiboa sortu.",
+       "config-upgrade-done-no-regenerate": "Eguneratze prozesua amaitu egin da.\n\nHasi ahal zara [ $1 wikia arabiltzen]",
        "config-regenerate": "Birsortu LocalSettings.php →",
+       "config-db-web-account": "Datu-basearen kontua web sarbiderako.",
        "config-db-web-account-same": "Instalazioan erabili duzun kontu berdina erabili.",
+       "config-db-web-create": "Kontua sortu oraindik ez bada existitzen.",
+       "config-db-web-no-create-privs": "Zehaztu duzun kontuak ez dauka pribilegio nahikoak kontu bat sortzeko.\nZehaztu duzun kontua existitu behar da.",
        "config-mysql-innodb": "InnoDB",
        "config-mysql-myisam": "MyISAM",
        "config-mysql-binary": "Bitarra",
        "config-mysql-utf8": "UTF-8",
+       "config-mssql-windowsauth": "Windows-eko Autentifikazioa.",
        "config-site-name": "Wikiaren izena:",
+       "config-site-name-blank": "Aukeratu webgunearen izena.",
        "config-project-namespace": "Proiektuaren izen-tartea:",
        "config-ns-generic": "Proiektua",
+       "config-ns-site-name": "Wiki izenaren berdina: $1",
        "config-ns-other": "Bestelakoa (zehaztu)",
        "config-ns-other-default": "MyWiki",
        "config-admin-box": "Administratzaile kontua",
        "config-admin-name": "Zure erabiltzaile-izena:",
        "config-admin-password": "Pasahitza:",
        "config-admin-password-confirm": "Pasahitza berriz:",
+       "config-admin-name-blank": "Sartu administratzaile kontua.",
+       "config-admin-name-invalid": "<nowiki>$1</nowiki> erabiltzaile izena baliogabea da.\nZehaztu erabiltzaile izen desberdin bat.",
        "config-admin-password-blank": "Sartu pasahitza administratzaile kontuarentzako.",
        "config-admin-password-mismatch": "Sartutako bi pasahitzak ez datoz bat.",
        "config-admin-email": "E-posta helbidea:",
+       "config-admin-email-help": "Sartu email bat baimena emateko mezuak jasotzeko, pasahitza aldatzeko and orrien aldaketeei buruz berri edukitzeko.\nHutsik utzi ahal duzu.",
        "config-admin-error-bademail": "Helbide elektroniko okerra idatzi duzu.",
+       "config-almost-done": "Ia amaitu duzu!\nFalta den konfigurazioa saltatu ahal duzu eta zuzenean wikia instalatu.",
        "config-optional-continue": "Galdera gehiago egin.",
        "config-optional-skip": "Aspertuta nago, wikia instalatu bakarrik.",
        "config-profile-wiki": "Wikia ireki",
        "config-license-pd": "Domeinu Askea",
        "config-license-cc-choose": "Aukeratu Creative Commons lizentzia pertsonalizatua",
        "config-email-settings": "E-posta hobespenak",
+       "config-enable-email": "Aktibatu irteerako emaila.",
+       "config-email-auth": "Aktibatu emailaren autentifikazioa.",
+       "config-email-auth-help": "Aukera hau aktibatuta badago, erabiltzaileak konfirmatu behar du bere emaila, sortzerakoen edota aldetzerakoan bidali zaion linka erabiltzen.\n\nBakarrik kautotaku emailak gai izango dira beste erabiltzaileen emailak jasotzeko edota jakinarazpen emailak aldatzeko.\n\nAukera hau hautatzea <strong>gomendagarria</strong> da Wiki publikoentzat, emaileen erramintien abusua dela eta.",
        "config-email-sender": "Itzuli helbide elektronikoa:",
        "config-upload-settings": "Irudi eta fitxategi igoerak",
        "config-upload-enable": "Fitxategi igoera gaitu",
        "config-instantcommons": "Instant Commons gaitu",
        "config-cc-again": "Berriz aukeratu...",
        "config-advanced-settings": "Konfigurazio aurreratua",
+       "config-memcache-badip": "Sartu duzu Memcached-eko baliogabeko IP  bat: $1",
        "config-extensions": "Luzapenak",
        "config-skins": "Itxurak",
        "config-install-step-done": "egina",
        "config-install-stats": "Estatistikak hasten",
        "config-install-keys": "Gako sekretuak sortzen",
        "config-install-sysop": "Administratzaile kontua sortzen",
+       "config-install-subscribe-fail": "Ezin izan da mediawiki-announce -ra harpidetu: $1",
        "config-install-done-path": "<strong>Zorionak!</strong>\nMediaWiki instalatu duzu.\n\nInstalatzaileak sortu egin du <code>LocalSettings.php</code>\nZure konfigurazio guztia dauka.\n\nDeskargatu egin behar duzu eta jarri <code>$4</code> -ean . Deskarga automakikoki hasiko da.\n\nEz badizu deskargatzeko aukerarik eman, edo kantzalatu egin baduzu, hurrengo linkean klikatu berrabiatzeko:\n\n$3\n\n<strong>Oharra:</strong> Instalazio prozesuatik ateratzen bazara konfigurazio artxikoa deskargatu barik, gero ez da egongo eskuragarri.\n\nBehin hori eginda, <strong>[$2 enter your wiki]</strong> ahal duzu.",
        "config-download-localsettings": "Jaitsi <code>LocalSettings.php</code>",
        "config-help": "Laguntza",
index ab365ec..96f0ef1 100644 (file)
@@ -19,7 +19,8 @@
                        "Suchichi02",
                        "Winstonyin",
                        "Wehwei",
-                       "Wwycheuk"
+                       "Wwycheuk",
+                       "蘭斯特"
                ]
        },
        "config-desc": "MediaWiki 安裝程式",
        "config-help-tooltip": "點選以展開",
        "config-nofile": "查無檔案 \"$1\",是否已被刪除?",
        "config-extension-link": "您是否了解您的 Wiki 支援 [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 擴充套件]?\n\n\n您可以瀏覽 [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 擴充套件分類] 或 [https://www.mediawiki.org/wiki/Extension_Matrix 擴充套件資料表] 以取得相關的資訊。",
+       "config-skins-screenshots": "$1 (螢幕截圖: $2)",
+       "config-screenshot": "螢幕截圖",
        "mainpagetext": "<strong>已安裝 MediaWiki。</strong>",
        "mainpagedocfooter": "有關使用wiki的訊息,請參閱[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 使用者指南]。\n\n== 新手入門 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 系統設定]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki常見問題]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki郵寄清單]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 將MediaWiki翻譯至您的語言]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 了解如何在您的wiki上防禦破壞]"
 }
index 4e0c0ec..fb2d787 100644 (file)
@@ -265,7 +265,7 @@ class Command {
                $desc = [
                        0 => [ 'file', 'php://stdin', 'r' ],
                        1 => [ 'pipe', 'w' ],
-                       2 => [ 'file', 'php://stderr', 'w' ],
+                       2 => [ 'pipe', 'w' ],
                ];
                if ( $useLogPipe ) {
                        $desc[3] = [ 'pipe', 'w' ];
@@ -278,6 +278,7 @@ class Command {
                        throw new ProcOpenError();
                }
                $outBuffer = $logBuffer = '';
+               $errBuffer = null;
                $emptyArray = [];
                $status = false;
                $logMsg = false;
@@ -352,6 +353,9 @@ class Command {
                                } elseif ( $fd == 1 ) {
                                        // From stdout
                                        $outBuffer .= $block;
+                               } elseif ( $fd == 2 ) {
+                                       // From stderr
+                                       $errBuffer .= $block;
                                } elseif ( $fd == 3 ) {
                                        // From log FD
                                        $logBuffer .= $block;
@@ -402,6 +406,6 @@ class Command {
                        $this->logger->warning( "$logMsg: {command}", [ 'command' => $cmd ] );
                }
 
-               return new Result( $retval, $outBuffer );
+               return new Result( $retval, $outBuffer, $errBuffer );
        }
 }
index c1429df..1e18210 100644 (file)
@@ -32,13 +32,17 @@ class Result {
        /** @var string */
        private $stdout;
 
+       /** @var string|null */
+       private $stderr;
+
        /**
         * @param int $exitCode
         * @param string $stdout
         */
-       public function __construct( $exitCode, $stdout ) {
+       public function __construct( $exitCode, $stdout, $stderr = null ) {
                $this->exitCode = $exitCode;
                $this->stdout = $stdout;
+               $this->stderr = $stderr;
        }
 
        /**
@@ -58,4 +62,14 @@ class Result {
        public function getStdout() {
                return $this->stdout;
        }
+
+       /**
+        * Returns stderr of the process or null if the Command was configured to add stderr to stdout
+        * with includeStderr( true )
+        *
+        * @return string|null
+        */
+       public function getStderr() {
+               return $this->stderr;
+       }
 }
index f2c96ae..e21d762 100644 (file)
@@ -38,6 +38,7 @@ use MediaWiki\MediaWikiServices;
  *
  *  ... = $result->getExitCode();
  *  ... = $result->getStdout();
+ *  ... = $result->getStderr();
  */
 class Shell {
 
index 56b6cc3..0da9b7a 100644 (file)
@@ -57,7 +57,7 @@ class SpecialUnblock extends SpecialPage {
 
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'unblockip' ) );
-               $out->addModules( [ 'mediawiki.special' ] );
+               $out->addModules( [ 'mediawiki.special', 'mediawiki.userSuggest' ] );
 
                $form = HTMLForm::factory( 'ooui', $this->getFields(), $this->getContext() );
                $form->setWrapperLegendMsg( 'unblockip' );
@@ -87,12 +87,12 @@ class SpecialUnblock extends SpecialPage {
        protected function getFields() {
                $fields = [
                        'Target' => [
-                               'type' => 'user',
+                               'type' => 'text',
                                'label-message' => 'ipaddressorusername',
                                'autofocus' => true,
                                'size' => '45',
                                'required' => true,
-                               'ipallowed' => true,
+                               'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
                        ],
                        'Name' => [
                                'type' => 'info',
index 898fc11..12d89de 100644 (file)
        "rcfilters-filter-user-experience-level-newcomer-label": "Nuwelinge",
        "rcfilters-filter-user-experience-level-newcomer-description": "Minder as 10 wysigings en 4 dae van aktiwiteit.",
        "rcfilters-filter-user-experience-level-learner-label": "Leerlinge",
+       "rcfilters-filter-user-experience-level-learner-description": "Redigeerders met meer ervaring as \"nuwelinge\", maar minder as \"ervare gebruikers\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Ervare gebruikers",
+       "rcfilters-filter-user-experience-level-experienced-description": "Meer as 500 wysigings en 30 dae van aktiwiteit.",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Wysigings met behulp van geoutomatiseerde hulpmiddele.",
        "rcfilters-filter-humans-label": "Menslik (nie 'n bot)",
        "rcfilters-filter-minor-label": "Klein wysigings",
        "rcfilters-filter-minor-description": "Wysigings wat deur die outeur as klein gemerk is.",
        "rcfilters-filter-major-label": "Groot wysigings",
-       "rcfilters-filter-major-description": "Wysigings wat nie as klein gekerm is nie.",
+       "rcfilters-filter-major-description": "Wysigings wat nie as klein gemerk is nie.",
        "rcfilters-filtergroup-watchlist": "Dopgehoude bladsye",
-       "rcfilters-filter-watchlist-watched-label": "Op die dophoulys",
+       "rcfilters-filter-watchlist-watched-label": "In dophoulys",
        "rcfilters-filter-watchlist-watched-description": "Wysigings aan bladsye op u dophoulys.",
        "rcfilters-filter-watchlist-watchednew-label": "Nuwe dophoulys-wysigings",
+       "rcfilters-filter-watchlist-watchednew-description": "Wysigings aan bladsye op u dophoulys wat u nog nie besoek het nie.",
        "rcfilters-filter-watchlist-notwatched-label": "Nie op dophoulys",
+       "rcfilters-filter-watchlist-notwatched-description": "Alles behalwe wysigings aan bladsye op u dophoulys.",
        "rcfilters-filtergroup-watchlistactivity": "Dophoulys-bedrywighede",
        "rcfilters-filtergroup-changetype": "Soort wysiging",
        "rcfilters-filter-pageedits-label": "Bladsywysigings",
index c48c952..0a78d13 100644 (file)
        "rcfilters-activefilters": "Әүҙем фильтрҙар",
        "rcfilters-advancedfilters": "Киңәйтелгән фильтрҙар",
        "rcfilters-limit-title": "Күрһәтеү өсөн үҙгәртеүҙәр",
-       "rcfilters-limit-shownum": "Һуңғы {{PLURAL:$1 үҙгәреште}} күрһәтергә",
+       "rcfilters-limit-shownum": "Һуңғы {{PLURAL:$1}} үҙгәреште күрһәтергә",
        "rcfilters-days-title": "Аҙаҡҡы көндәр",
        "rcfilters-hours-title": "Аҙаҡҡы сәғәттәр",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|көн}}",
index 7d07a83..f2b3cdd 100644 (file)
@@ -53,7 +53,7 @@
        "tog-watchlisthideanons": "共匿名其用戶其編輯趁監視單𡅏囥起咯",
        "tog-watchlisthidepatrolled": "共巡查其編輯趁監視單𡅏囥起咯",
        "tog-watchlisthidecategorization": "Káung kī hiĕk gì lôi-biék",
-       "tog-ccmeonemails": "共我發乞其他用戶其電子郵件其備份發乞我",
+       "tog-ccmeonemails": "共我發乞其他用戶其電子郵件其備份發乞我",
        "tog-diffonly": "伓使敆下底其顯示𣍐蜀様其地方顯示頁面內容",
        "tog-showhiddencats": "㪗藏類別",
        "tog-norollbackdiff": "Cék-hèng huòi-gūng ī-hâiu ng-sāi hiēng-sê chă-biék",
        "filenotfound": "討𣍐著文件「$1」。",
        "unexpected": "伓是卜挃其值:「$1」=「$2」。",
        "formerror": "綻去:𣍐使提交表單。",
-       "badarticleerror": "不允許敆茲蜀萆做茲蜀種行為。",
-       "cannotdelete": "ç\84¡è\83½è\80\90å\88ªæ\8e\89é \81é\9d¢æ\88\96è\80\85æ\96\87件ã\80\8c$1ã\80\8dã\80\82\nå\8f¯è\83½è\8c²å·²ç¶\93å\85±別儂刪掉咯了。",
+       "badarticleerror": "𣍐使敆茲蜀萆頁面做茲蜀種行為。",
+       "cannotdelete": "ç\84¡è\83½è\80\90å\88ªæ\8e\89é\80\99é \81é\9d¢æ\88\96è\80\85æ\96\87件ã\80\8c$1ã\80\8dã\80\82\nå\8f¯è\83½å\9a½å·²ç¶\93ä¹\9e別儂刪掉咯了。",
        "cannotdelete-title": "無辦法刪掉頁面「$1」",
        "delete-hook-aborted": "刪除乞鉤子拍斷咯。\n無給出解釋。",
        "no-null-revision": "𣍐使敆頁面$1𡅏新建空操作。",
        "exception-nologin-text": "起動汝登錄以後再訪問茲蜀頁,或者做茲蜀萆操作。",
        "exception-nologin-text-manual": "起動汝$1,以後才會使訪問茲蜀頁,或者做茲蜀萆行為。",
        "virus-badscanner": "呆其配置:𣍐仈倛病毒掃描器:<em>$1</em>",
-       "virus-scanfailed": "掃描失敗(代碼$1)",
+       "virus-scanfailed": "掃描𣍐來(代碼$1)",
        "virus-unknownscanner": "𣍐八其反病毒:",
        "logouttext": "<strong>汝現在已經躒出了。</strong>\n\n注意有其頁面可能會繼續顯示汝未躒出辰候其樣式,除開汝清理瀏覽器緩存。",
        "welcomeuser": "歡迎,$1!",
        "resetpass-submit-cancel": "取消",
        "resetpass-temp-password": "臨時密碼:",
        "passwordreset": "重置密碼",
-       "passwordreset-text-one": "完成茲隻表單,通過電批寄臨時密碼其方法來重新設定汝其密碼。",
+       "passwordreset-text-one": "完成茲隻表單,使電批寄臨時密碼來重新設定汝其密碼。",
        "passwordreset-username": "用戶名:",
        "passwordreset-domain": "域名:",
        "passwordreset-email": "電批地址:",
        "blockedtitle": "用戶乞封鎖了",
        "blockednoreason": "無掏出原因",
        "whitelistedittext": "汝必須$1乍會使修改頁面。",
-       "loginreqtitle": "需要登錄",
+       "loginreqtitle": "汝著登錄",
        "loginreqlink": "láuk-diē",
        "loginreqpagetext": "起動汝$1以後再看其它頁面。",
        "accmailtitle": "密碼寄出了",
        "note": "<strong>注意:</strong>",
        "previewnote": "'''記定茲若是蜀萆預覽。'''\n汝其改變固𡅏未保存!",
        "continue-editing": "繼續修改",
-       "editing": "修改 $1",
+       "editing": "修改$1",
        "creating": "創建$1",
        "editingsection": "修改$1(段)",
        "editingcomment": "修改$1(新其蜀部分)",
index 44117f4..a4e8205 100644 (file)
        "rcfilters-activefilters": "پاڵێوەرە چالاکەکان",
        "rcfilters-advancedfilters": "پاڵوێنە پێشکەوتووەکان",
        "rcfilters-limit-shownum": "پیشاندانی دوایین $1 دەستکاری",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|ڕۆژ}}",
        "rcfilters-quickfilters": "پاڵوێنە پاشەکەوتکراوەکان",
        "rcfilters-savedqueries-defaultlabel": "پاڵوێنە پاشەکەوتکراوەکان",
        "rcfilters-clear-all-filters": "ھەموو فیلتەرەکان بسڕەوە",
index 4a2e3f5..a2dfd7b 100644 (file)
@@ -84,7 +84,7 @@
        "tog-shownumberswatching": "Εμφάνιση του αριθμού των συνδεδεμένων χρηστών",
        "tog-oldsig": "Η τρέχουσα υπογραφή σας:",
        "tog-fancysig": "Μεταχείριση υπογραφής ως κώδικα wiki (χωρίς αυτόματο σύνδεσμο)",
-       "tog-uselivepreview": "ΧÏ\81ήÏ\83η Ï\80Ï\81οεÏ\80ιÏ\83κÏ\8cÏ\80ηÏ\83ηÏ\82 Ï\83ε Î¶Ï\89νÏ\84ανÏ\8c Ï\87Ï\81Ï\8cνο",
+       "tog-uselivepreview": "Î\95μÏ\86άνιÏ\83ηÏ\82 Ï\80Ï\81οεÏ\80ιÏ\83κÏ\8cÏ\80ηÏ\83ηÏ\82 Ï\87Ï\89Ï\81ίÏ\82 ÎµÏ\80αναÏ\86Ï\8cÏ\81Ï\84Ï\89Ï\83η Ï\84ηÏ\82 Ï\83ελίδαÏ\82",
        "tog-forceeditsummary": "Να ειδοποιούμαι κατά την εισαγωγή κενής σύνοψης επεξεργασίας",
        "tog-watchlisthideown": "Απόκρυψη των επεξεργασιών μου από τη λίστα παρακολούθησης",
        "tog-watchlisthidebots": "Απόκρυψη των επεξεργασιών των bot από τη λίστα παρακολούθησης",
        "contentmodelediterror": "Δεν μπορείτε να επεξεργαστείτε αυτή την αναθεώρηση, γιατί το περιεχόμενό του μοντέλου του  είναι <code>$1</code>, το οποίο διαφέρει από το τρέχον περιεχόμενο μοντέλο της σελίδας <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Προειδοποίηση: Ξαναδημιουργείτε μια σελίδα που είχε προηγουμένως διαγραφεί.'''\n\nΘα πρέπει να σκεφτείτε σοβαρά αν είναι σωστό να συνεχίσετε να επεξεργάζεστε αυτή τη σελίδα.\nΟι καταγραφές διαγραφών και μετακινήσεων παρέχονται εδώ για διευκόλυνση:",
        "moveddeleted-notice": "Αυτή η σελίδα έχει διαγραφεί.\nΤο αρχείο καταγραφών διαγραφών και μετακινήσεων της σελίδας παρέχεται παρακάτω για αναφορά.",
-       "moveddeleted-notice-recent": "ΣÏ\85γγνÏ\8eμη, Î· Ï\83ελίδα Î­Ï\87ει Î´Î¹Î±Î³Ï\81αÏ\86εί Ï\80Ï\81Ï\8cÏ\83Ï\86αÏ\84α (μέÏ\83α Ï\83Ï\84ιÏ\82 Ï\84ελεÏ\85Ï\84αίεÏ\82 24 Ï\8eÏ\81εÏ\82).\nÎ\97 Î´Î¹Î±Î³Ï\81αÏ\86ή ÎºÎ±Î¹ Î¼ÎµÏ\84ακίνηÏ\83η Ï\84οÏ\85 Î±Ï\81Ï\87είοÏ\85 ÎºÎ±Ï\84αγÏ\81αÏ\86ής της σελίδας παρέχεται παρακάτω για αναφορά.",
+       "moveddeleted-notice-recent": "ΣÏ\85γγνÏ\8eμη, Î· Ï\83ελίδα Î­Ï\87ει Î´Î¹Î±Î³Ï\81αÏ\86εί Ï\80Ï\81Ï\8cÏ\83Ï\86αÏ\84α (μέÏ\83α Ï\83Ï\84ιÏ\82 Ï\84ελεÏ\85Ï\84αίεÏ\82 24 Ï\8eÏ\81εÏ\82).\nΤο Î±Ï\81Ï\87είο ÎºÎ±Ï\84αγÏ\81αÏ\86ήÏ\82 Î´Î¹Î±Î³Ï\81αÏ\86ήÏ\82, Ï\80Ï\81οÏ\83Ï\84αÏ\83ίαÏ\82, ÎºÎ±Î¹ Î¼ÎµÏ\84ακίνηÏ\83ης της σελίδας παρέχεται παρακάτω για αναφορά.",
        "log-fulllog": "Εμφάνιση πλήρους αρχείου",
        "edit-hook-aborted": "Η επεξεργασία ματαιώθηκε από το hook.\nΔεν έδωσε εξήγηση.",
        "edit-gone-missing": "Δεν ήταν εφικτό να ενημερωθεί η σελίδα.\nΦαίνεται πως έχει διαγραφεί.",
        "search-file-match": "(ταιριάζει με το περιεχόμενο του αρχείου)",
        "search-suggest": "Μήπως εννοούσατε: $1",
        "search-rewritten": "Εμφανίζονται αποτελέσματα για $1. Εναλλακτικά αναζητήστε για $2.",
-       "search-interwiki-caption": "Αδελφικά εγχειρήματα",
+       "search-interwiki-caption": "Αποτελέσματα από αδελφικά εγχειρήματα",
        "search-interwiki-default": "$1 αποτελέσματα:",
        "search-interwiki-more": "(περισσότερα)",
        "search-interwiki-more-results": "περισσότερα αποτελέσματα",
        "youremail": "Διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "username": "{{GENDER:$1|Όνομα χρήστη}}:",
        "prefs-memberingroups": "{{GENDER:$2|Μέλος}} της {{PLURAL:$1|ομάδας|ομάδων}}:",
-       "group-membership-link-with-expiry": "$1 (μέÏ\87Ï\81ι Ï\84ιÏ\82 $3 Ï\83Ï\84ιÏ\82 $4)",
+       "group-membership-link-with-expiry": "$1 (έÏ\89Ï\82 $2)",
        "prefs-registration": "Χρόνος εγγραφής:",
        "yourrealname": "Πραγματικό όνομα:",
        "yourlanguage": "Γλώσσα:",
        "userrights-nodatabase": "Η βάση δεδομένων $1 δεν υπάρχει ή δεν είναι τοπική.",
        "userrights-changeable-col": "Ομάδες που μπορείτε να αλλάξετε",
        "userrights-unchangeable-col": "Ομάδες που δεν μπορείτε να αλλάξετε",
-       "userrights-expiry-current": "Λήγει στις $2 στις $3",
+       "userrights-expiry-current": "Λήγει στις $1",
        "userrights-expiry-none": "Δεν λήγει",
        "userrights-expiry": "Λήγει:",
        "userrights-expiry-existing": "Υπάρχουσα ώρα λήξης: $3, $2",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (δείτε [[Special:NewPages|κατάλογος νέων σελίδων]])",
        "recentchanges-legend-plusminus": "(<em>±123</em>)",
        "recentchanges-submit": "Προβολή",
+       "rcfilters-other-review-tools": "<strong>Άλλα εργαλεία επιθεώρησης</strong>",
+       "rcfilters-group-results-by-page": "Ομαδοποίηση αποτελεσμάτων ανά σελίδα",
+       "rcfilters-grouping-title": "Ομαδοποίηση",
        "rcfilters-activefilters": "Ενεργά φίλτρα",
        "rcfilters-advancedfilters": "Σύνθετα Φίλτρα",
        "rcfilters-limit-title": "Αλλαγές για εμφάνιση",
+       "rcfilters-limit-shownum": "Εμφάνιση {{{{PLURAL:$1|τελευταίας επεξεργασίας|τελευταίων $1 επεξεργασιών}}",
        "rcfilters-days-title": "Πρόσφατες ημέρες",
        "rcfilters-hours-title": "Πρόσφατες ώρες",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|μέρα|μέρες}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|ώρα|ώρες}}",
+       "rcfilters-highlighted-filters-list": "Επισημασμένες: $1",
        "rcfilters-quickfilters": "Αποθηκευμένα φίλτρα",
        "rcfilters-savedqueries-defaultlabel": "Αποθηκευμένα φίλτρα",
        "rcfilters-savedqueries-rename": "Μετονομασία",
        "rcfilters-savedqueries-setdefault": "Ορισμός ως προεπιλογή",
        "rcfilters-savedqueries-remove": "Αφαίρεση",
        "rcfilters-savedqueries-new-name-label": "Όνομα",
+       "rcfilters-savedqueries-apply-label": "Δημιουργία φίλτρου",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "Δημιουργία προεπιλεγμένου φίλτρου",
        "rcfilters-savedqueries-cancel-label": "Ακύρωση",
+       "rcfilters-savedqueries-add-new-title": "Αποθήκευση τρεχουσών ρυθμίσεων φίλτρων",
        "rcfilters-restore-default-filters": "Επαναφορά προεπιλεγμένων φίλτρων",
        "rcfilters-clear-all-filters": "Εκκαθάριση όλων των φίλτρων",
+       "rcfilters-show-new-changes": "Προβολή νεότερων αλλαγών",
        "rcfilters-search-placeholder": "Φιλτράρισμα πρόσφατων αλλαγών (περιηγηθείτε ή αρχίστε να πληκτρολογείτε)",
        "rcfilters-invalid-filter": "Μη έγκυρο φίλτρο",
        "rcfilters-empty-filter": "Χωρίς ενεργά φίλτρα. Εμφανίζονται όλες οι συνεισφορές.",
        "rcfilters-filterlist-title": "Φίλτρα",
-       "rcfilters-filterlist-whatsthis": "Τι ÎµÎ¯Î½Î±Î¹ Î±Ï\85Ï\84Ï\8c;",
-       "rcfilters-filterlist-feedbacklink": "Î\94Ï\8eÏ\83Ï\84ε Î±Î½Î±Ï\84Ï\81οÏ\86οδÏ\8cÏ\84ηÏ\83η Î³Î¹Î± Ï\84α Î½Î­Î± Ï\86ίλÏ\84Ï\81α (beta)",
+       "rcfilters-filterlist-whatsthis": "ΠÏ\89Ï\82 Î»ÎµÎ¹Ï\84οÏ\85Ï\81γοÏ\8dν Î±Ï\85Ï\84ά;",
+       "rcfilters-filterlist-feedbacklink": "ΠείÏ\84ε Î¼Î±Ï\82 Ï\84ι Ï\83κέÏ\86Ï\84εÏ\83Ï\84ε Î³Î¹Î± Î±Ï\85Ï\84ά Ï\84α (νέα) ÎµÏ\81γαλεία Ï\86ιλÏ\84Ï\81αÏ\81ίÏ\83μαÏ\84οÏ\82",
        "rcfilters-highlightbutton-title": "Επισήμανση αποτελεσμάτων",
        "rcfilters-highlightmenu-title": "Επιλέξτε ένα χρώμα",
        "rcfilters-highlightmenu-help": "Επιλέξτε ένα χρώμα για να επισημάνετε αυτή την ιδιότητα",
        "rcfilters-filter-editsbyself-description": "Οι δικές σας συνεισφορές.",
        "rcfilters-filter-editsbyother-label": "Αλλαγές από άλλους",
        "rcfilters-filter-editsbyother-description": "Όλες οι αλλαγές εκτός από τις δικές σας.",
-       "rcfilters-filtergroup-userExpLevel": "Επίπεδο εμπειρίας (για εγγεγραμμένους χρήστες μόνο)",
+       "rcfilters-filtergroup-userExpLevel": "Εγγραφή χρήστη και εμπειρία",
        "rcfilters-filter-user-experience-level-registered-label": "Εγγεγραμμένοι",
        "rcfilters-filter-user-experience-level-registered-description": "Συνδεδεμένοι συντάκτες.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Μη εγγεγραμμένοι",
        "rcfilters-filter-user-experience-level-unregistered-description": "Συντάκτες που δεν είναι συνδεδεμένοι.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Νεοφερμένοι",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Î\9bιγότερες από 10 επεξεργασίες και 4 ημέρες δραστηριότητας.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Î\95γγεγÏ\81αμμένοι Ï\83Ï\85νÏ\84άκÏ\84εÏ\82 Î¼Îµ Î»ιγότερες από 10 επεξεργασίες και 4 ημέρες δραστηριότητας.",
        "rcfilters-filter-user-experience-level-learner-label": "Μαθητευόμενοι",
-       "rcfilters-filter-user-experience-level-learner-description": "ΠεÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81εÏ\82 Î·Î¼Î­Ï\81εÏ\82 Î´Ï\81αÏ\83Ï\84ηÏ\81ιÏ\8cÏ\84ηÏ\84αÏ\82 ÎºÎ±Î¹ ÎµÏ\80εξεÏ\81γαÏ\83ίεÏ\82 Î±Ï\80Ï\8c Ï\84οÏ\85Ï\82 Â«Î½ÎµÎ¿Ï\86εÏ\81μένοÏ\85Ï\82» Î±Î»Î»Î¬ Î»Î¹Î³Ï\8cÏ\84εÏ\81εÏ\82 Î±Ï\80Ï\8c Ï\84οÏ\85Ï\82 Â«Î­Î¼Ï\80ειÏ\81οÏ\85Ï\82 Ï\87Ï\81ήÏ\83Ï\84εÏ\82».",
+       "rcfilters-filter-user-experience-level-learner-description": "Î\95γγεγÏ\81αμμένοι Ï\83Ï\85νÏ\84άκÏ\84εÏ\82 Î¼Îµ ÎµÎ¼Ï\80ειÏ\81ία Î¼ÎµÏ\84αξÏ\8d Â«Î½ÎµÎ¿Ï\86εÏ\81μένÏ\89ν» ÎºÎ±Î¹ Â«Î­Î¼Ï\80ειÏ\81Ï\89ν Ï\87Ï\81ηÏ\83Ï\84Ï\8eν».",
        "rcfilters-filter-user-experience-level-experienced-label": "Έμπειροι χρήστες",
-       "rcfilters-filter-user-experience-level-experienced-description": "ΠεÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81εÏ\82 Î±Ï\80Ï\8c 30 Î·Î¼Î­Ï\81εÏ\82 Î´Ï\81αÏ\83Ï\84ηÏ\81ιÏ\8cÏ\84ηÏ\84αÏ\82 ÎºÎ±Î¹ 500 Î±Î»Î»Î±Î³Î­ς.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Î\95γγεγÏ\81αμμένοι Ï\83Ï\85νÏ\84άκÏ\84εÏ\82 Î¼Îµ Ï\80εÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81εÏ\82 Î±Ï\80Ï\8c 500 ÎµÏ\80εξεÏ\81γαÏ\83ίεÏ\82 ÎºÎ±Î¹ 30 Î·Î¼Î­Ï\81εÏ\82 Î´Ï\81αÏ\83Ï\84ηÏ\81ιÏ\8cÏ\84ηÏ\84ας.",
        "rcfilters-filtergroup-automated": "Αυτοματοποιημένες συνεισφορές",
        "rcfilters-filter-bots-label": "Ρομπότ",
        "rcfilters-filter-bots-description": "Επεξεργασίες που έγιναν από αυτοματοποιημένα εργαλεία.",
        "rcfilters-filter-humans-label": "Ανθρώπινες (όχι από ρομπότ)",
        "rcfilters-filter-humans-description": "Επεξεργασίες που έγιναν από ανθρώπους συντάκτες.",
+       "rcfilters-filter-patrolled-label": "Ελεγμένες",
        "rcfilters-filtergroup-significance": "Σημαντικότητα",
        "rcfilters-filter-minor-label": "Μικροεπεξεργασίες",
        "rcfilters-filter-minor-description": "Επεξεργασίες που ο συντάκτης χαρακτήρισε ως μικροεπεξεργασίες.",
        "rcfilters-filter-major-description": "Επεξεργασίες μη χαρακτηρισμένες ως μικροεπεξεργασίες.",
        "rcfilters-filtergroup-changetype": "Τύπος αλλαγής",
        "rcfilters-filter-pageedits-label": "Επεξεργασίες σελίδων",
-       "rcfilters-filter-pageedits-description": "Επεξεργασίες σε περιεχόμενο του wiki, συζητήσεις, περιγραφές κατηγοριών....",
+       "rcfilters-filter-pageedits-description": "Επεξεργασίες σε περιεχόμενο του wiki, συζητήσεις, περιγραφές κατηγοριών",
        "rcfilters-filter-newpages-label": "Δημιουργίες σελίδων",
        "rcfilters-filter-newpages-description": "Επεξεργασίες που δημιουργούν νέες σελίδες.",
        "rcfilters-filter-categorization-label": "Αλλαγές κατηγοριών",
        "rcfilters-filter-categorization-description": "Καταγραφές σελίδων που προστίθενται ή αφαιρούνται από κατηγορίες.",
        "rcfilters-filter-logactions-label": "Καταγραφόμενες ενέργειες",
-       "rcfilters-filter-logactions-description": "Διαχειριστικές ενέργειες, δημιουργίες λογαριασμών, διαγραφές σελίδων, ανεβάσματα αρχείων....",
+       "rcfilters-filter-logactions-description": "Διαχειριστικές ενέργειες, δημιουργίες λογαριασμών, διαγραφές σελίδων, ανεβάσματα αρχείων…",
+       "rcfilters-exclude-button-off": "Εξαίρεση επιλεγμένων",
+       "rcfilters-view-advanced-filters-label": "Προχωρημένα φίλτρα",
+       "rcfilters-view-tags": "Επεξεργασίες με ετικέτες",
+       "rcfilters-view-namespaces-tooltip": "Φιλτράρισμα αποτελεσμάτων κατά ονοματοχώρο",
        "rcnotefrom": "Παρακάτω {{PLURAL:$5|είναι η αλλαγή|είναι οι αλλαγές}} από <strong>$3, $4</strong> (έως <strong>$1</strong> που εμφανίζεται).",
        "rclistfrom": "Εμφάνιση νέων αλλαγών αρχίζοντας από τις $3 στις $2",
        "rcshowhideminor": "$1 μικροεπεξεργασιών",
        "version-libraries-description": "Περιγραφή",
        "version-libraries-authors": "Δημιουργοί",
        "redirect": "Ανακατεύθυνση κατά αρχείο, χρήστη, σελίδα, αναγνωριστικό αναθεώρησης ή αρχείο καταγραφής ID",
+       "redirect-summary": "Αυτή η ειδική σελίδα ανακατευθύνει προς ένα αρχείο (αν δοθεί όνομα αρχείου), μια σελίδα (αν δοθεί ID αναθεώρησης ή ID σελίδας), μια σελίδα χρήστη (αν δοθεί αριθμητικό ID χρήστη), ή μια καταχώρηση μητρώου (αν δοθεί ID καταχώρησης). Χρήση: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], or [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Μετάβαση",
        "redirect-lookup": "Αναζήτηση:",
        "redirect-value": "Τιμή:",
index 79b0f60..6bf32b2 100644 (file)
        "rcfilters-view-namespaces-tooltip": "Filter results by namespace",
        "rcfilters-view-tags-tooltip": "Filter results using edit tags",
        "rcfilters-view-return-to-default-tooltip": "Return to main filter menu",
-       "rcfilters-view-tags-help-icon-tooltip": "Learn more about Tagged Edits",
+       "rcfilters-view-tags-help-icon-tooltip": "Learn more about Tagged edits",
        "rcfilters-liveupdates-button": "Live updates",
        "rcfilters-liveupdates-button-title-on": "Turn off live updates",
        "rcfilters-liveupdates-button-title-off": "Display new changes as they happen",
index 293b3de..096d50c 100644 (file)
        "mw-widgets-usersmultiselect-placeholder": "Lisää enemmän...",
        "date-range-from": "Aloituspäivä:",
        "date-range-to": "Päättymispäivä:",
-       "sessionmanager-tie": "!!FYZZ!!Cannot combine multiple request authentication types: $1.",
+       "sessionmanager-tie": "Cannot combine multiple request authentication types: $1.",
        "sessionprovider-generic": "$1 istuntoa",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "istuntoja, joissa on evästeet käytössä",
        "sessionprovider-nocookies": "Evästeet on voitu poistaa käytöstä. Varmista, että sinulla on evästeet käytössä ja yritä sitten uudelleen.",
index 794f946..002df5d 100644 (file)
        "nosuchusershort": "Ne postoji suradnik s imenom \"$1\". Provjerite Vaš unos.",
        "nouserspecified": "Molimo navedite suradničko ime.",
        "login-userblocked": "Ovaj je suradnik blokiran. Prijava nije dopuštena.",
-       "wrongpassword": "Zaporka koju ste unijeli nije ispravna. Pokušajte ponovno.",
+       "wrongpassword": "Zaporka koju ste unijeli nije ispravna. Molimo Vas, pokušajte ponovo.",
        "wrongpasswordempty": "Niste unijeli zaporku. Pokušajte ponovno.",
        "passwordtooshort": "Zaporka mora sadržavati najmanje {{PLURAL:$1|1 znak|$1 znaka|$1 znakova}}.",
        "password-name-match": "Vaša zaporka mora biti različita od Vašeg suradničkog imena.",
        "rcfilters-savedqueries-add-new-title": "Spremi trenutačne postavke filtra",
        "rcfilters-restore-default-filters": "Vrati zadane filtre",
        "rcfilters-clear-all-filters": "Očisti sve filtre",
-       "rcfilters-show-new-changes": "Prikaži najnovije promjene",
+       "rcfilters-show-new-changes": "Vidi najnovije izmjene",
        "rcfilters-search-placeholder": "Filtriraj nedavne promjene (pretražite ili počnite unositi)",
        "rcfilters-invalid-filter": "Filter nije valjan",
        "rcfilters-empty-filter": "Nema aktivnih filtra. Prikazani su svi doprinosi.",
        "revertpage-nouser": "Vraćene izmjene suradnika (suradničko ime uklonjeno) na posljednju inačicu suradnika [[User:$1|$1]]",
        "rollback-success": "uklonjeno uređivanje {{GENDER:$1|suradnika|suradnice}} $1\nvraćeno na posljednju inačicu {{GENDER:$2|suradnika|suradnice}} $2.",
        "sessionfailure-title": "Prekid sesije",
-       "sessionfailure": "Uočili smo problem s Vašom prijavom. Zadnja naredba nije izvršena kako bi se izbjegla zloupotreba. Molimo Vas da se u pregledniku vratite natrag na prethodnu stranicu, ponovno je učitate i zatim pokušate opet.",
+       "sessionfailure": "Izgleda da postoji problem s uspostavom sjednice kod Vašega prijavljivanja; ta radnja otkazana je kao način sprječavanja krađe sjednice. Molimo Vas da se u pregledniku vratite natrag na prethodnu stranicu, ponovo ju učitate i zatim pokušate opet.",
        "changecontentmodel": "Promjena modela sadržaja stranice",
        "changecontentmodel-legend": "Promijeni model sadržaja",
        "changecontentmodel-title-label": "Naziv stranice",
index e676f76..ebf0b28 100644 (file)
        "rcfilters-view-namespaces-tooltip": "Filtrar le resultatos per spatio de nomines",
        "rcfilters-view-tags-tooltip": "Filtrar le resultatos usante etiquettas de version",
        "rcfilters-view-return-to-default-tooltip": "Retornar al menu principal de filtros",
+       "rcfilters-view-tags-help-icon-tooltip": "Leger plus sur le modification con etiquettas",
        "rcfilters-liveupdates-button": "Fluxo continue",
        "rcfilters-liveupdates-button-title-on": "Disactivar actualisation in directo",
        "rcfilters-liveupdates-button-title-off": "Monstrar cambiamentos in tempore real",
        "ipb_blocked_as_range": "Error: Le IP $1 non es blocate directemente e non pote esser disblocate.\nIllo es, nonobstante, blocate como parte del intervallo $2, le qual pote esser disblocate.",
        "ip_range_invalid": "Intervallo de adresses IP invalide.",
        "ip_range_toolarge": "Non es permittite blocar un gamma de adresses IP plus grande que /$1.",
+       "ip_range_exceeded": "Le rango IP excede le rango maxime. Le rango permittite es: /$1.",
+       "ip_range_toolow": "Le rangos IP ha essite effectivemente disactivate.",
        "proxyblocker": "Blocator de proxy",
        "proxyblockreason": "Tu adresse IP ha essite blocate proque illo es un proxy aperte.\nPer favor contacta tu providitor de servicio internet o supporto technic e informa les de iste problema grave de securitate.",
        "sorbsreason": "Tu adresse IP es listate como proxy aperte in le DNSBL usate per {{SITENAME}}.",
index 9011dcc..6f7a688 100644 (file)
        "uploadstash-badtoken": "പ്രവൃത്തി വിജയകരമായിരുന്നില്ല, താങ്കളുടെ തിരുത്തുവാനുള്ള അവകാശങ്ങൾ ചിലപ്പോൾ കാലഹരണപ്പെട്ടിട്ടുണ്ടാകാം. വീണ്ടും ശ്രമിക്കുക.",
        "uploadstash-errclear": "പ്രമാണങ്ങൾ ശൂന്യമാക്കൽ വിജയകരമായിരുന്നില്ല.",
        "uploadstash-refresh": "പ്രമാണങ്ങളുടെ പട്ടിക പുതുക്കുക",
+       "uploadstash-thumbnail": "ലഘുചിത്രം കാണുക",
        "img-auth-accessdenied": "പ്രവേശനമില്ല",
        "img-auth-nopathinfo": "PATH_INFO ലഭ്യമല്ല.\nതാങ്കളുടെ സെർവർ ഈ വിവരം കൈമാറ്റം ചെയ്യാൻ തയ്യാറാക്കിയിട്ടില്ല.\nഅത് img_auth പിന്തുണയില്ലാത്ത സി.ജി.ഐ. അധിഷ്ഠിതമായ ഒന്നായിരിക്കാം.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization കാണുക.",
        "img-auth-notindir": "ആവശ്യപ്പെട്ട പാത അപ്‌‌ലോഡ് ഡയറക്റ്ററിയിൽ സജ്ജീകരിച്ചു നൽകിയിട്ടില്ല.",
index 5398fae..8f29c6e 100644 (file)
        "disclaimers": "Abiso de cuntenido",
        "disclaimerpage": "Project:Abiso giral",
        "edithelp": "Ajuda de eidiçon",
-       "mainpage": "Páigina percipal",
+       "mainpage": "Biquipédia:Páigina percipal",
        "mainpage-description": "Páigina percipal",
        "policy-url": "Project:Políticas",
        "portal": "Portal de la quemunidade",
        "nolinkshere": "Nun eisisten lhigaçones pa '''[[:$1]]'''.",
        "isredirect": "páigina de ancaminamiento",
        "istemplate": "ancluson",
-       "isimage": "lhigaçon d'eimaige",
+       "isimage": "lhigaçon pa l fexeiro",
        "whatlinkshere-prev": "{{PLURAL:$1|pa trás|$1 pa trás}}",
        "whatlinkshere-next": "{{PLURAL:$1|próssimo|próssimos $1}}",
        "whatlinkshere-links": "← lhigaçones",
index 4d6cf93..bb40e25 100644 (file)
        "rcfilters-view-namespaces-tooltip": "Tooltip for the button that loads the namespace view in [[Special:RecentChanges]]",
        "rcfilters-view-tags-tooltip": "Tooltip for the button that loads the tags view in [[Special:RecentChanges]]",
        "rcfilters-view-return-to-default-tooltip": "Tooltip for the button that returns to the default filter view in [[Special:RecentChanges]]",
-       "rcfilters-view-tags-help-icon-tooltip": "Tooltip for the help button that leads user to [[mw:Special:MyLanguage/Help:New_filters_for_edit_review/Advanced_filters#tags|Help page]] for Tagged Edits",
+       "rcfilters-view-tags-help-icon-tooltip": "Tooltip for the help button that leads user to Special:Tags page",
        "rcfilters-liveupdates-button": "Label for the button to enable or disable live updates on [[Special:RecentChanges]]",
        "rcfilters-liveupdates-button-title-on": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is ON.",
        "rcfilters-liveupdates-button-title-off": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is OFF.",
index c345048..33bd974 100644 (file)
        "rcfilters-filter-editsbyself-description": "Vaša lastna urejanja.",
        "rcfilters-filter-editsbyother-label": "Spremembe drugih",
        "rcfilters-filter-editsbyother-description": "Vse spremembe razen vaših.",
-       "rcfilters-filtergroup-userExpLevel": "Registriranost in izkušenost uporabnika",
-       "rcfilters-filter-user-experience-level-registered-label": "Registriran",
+       "rcfilters-filtergroup-userExpLevel": "Prijava in izkušnje uporabnika",
+       "rcfilters-filter-user-experience-level-registered-label": "Prijavljeni",
        "rcfilters-filter-user-experience-level-registered-description": "Prijavljeni uporabniki.",
-       "rcfilters-filter-user-experience-level-unregistered-label": "Neregistriran",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Neprijavljeni",
        "rcfilters-filter-user-experience-level-unregistered-description": "Uporabniki, ki niso prijavljeni.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Novinci",
        "rcfilters-filter-user-experience-level-newcomer-description": "Registrirani uporabniki z manj kot 10 urejanji in 4 dnevi dejavnosti.",
index e02bf41..b6b9c79 100644 (file)
        "privacy": "Integritetspolicy",
        "privacypage": "Project:Integritetspolicy",
        "badaccess": "Behörighetsfel",
-       "badaccess-group0": "Du har inte behörighet att utföra den handling du begärt.",
-       "badaccess-groups": "Den handling du har begärt kan enbart utföras av användare i {{PLURAL:$2|gruppen|en av grupperna}}: $1.",
+       "badaccess-group0": "Du har inte behörighet att utföra den åtgärd du begärt.",
+       "badaccess-groups": "Den åtgärd du har begärt kan enbart utföras av användare i {{PLURAL:$2|gruppen|en av grupperna}}: $1.",
        "versionrequired": "Version $1 av MediaWiki krävs",
-       "versionrequiredtext": "Version $1 av MediaWiki är nödvändig för att använda denna sida. Se [[Special:Version|versionssidan]].",
+       "versionrequiredtext": "Version $1 av MediaWiki krävs för att använda denna sida. Se [[Special:Version|versionssidan]].",
        "ok": "OK",
        "retrievedfrom": "Hämtad från \"$1\"",
        "youhavenewmessages": "{{PLURAL:$3|Du har}} $1 ($2).",
        "nstab-category": "Kategori",
        "mainpage-nstab": "Huvudsida",
        "nosuchaction": "Funktionen finns inte",
-       "nosuchactiontext": "Den handling som specificerats av webbadressen är ogiltig.\nDu kan ha stavat webbadressen fel, eller följt en felaktig länk.\nDet kan även bero på en bugg i mjukvaran som används på {{SITENAME}}.",
+       "nosuchactiontext": "Den åtgärd som specificerats av webbadressen är ogiltig.\nDu kan ha stavat webbadressen fel, eller följt en felaktig länk.\nDet kan även bero på en bugg i mjukvaran som används på {{SITENAME}}.",
        "nosuchspecialpage": "Någon sådan specialsida finns inte",
        "nospecialpagetext": "<strong>Du har begärt en specialsida som inte finns.</strong>\n\nI [[Special:SpecialPages|listan över specialsidor]] kan du se vilka specialsidor som finns.",
        "error": "Fel",
        "directorynotreadableerror": "Katalog \"$1\" är inte läsbar.",
        "filenotfound": "Kunde inte hitta filen \"$1\".",
        "unexpected": "Oväntat värde: \"$1\"=\"$2\".",
-       "formerror": "Fel: Kunde inte sända formulär",
+       "formerror": "Fel: Kunde inte sända formulär.",
        "badarticleerror": "Den åtgärden kan inte utföras på den här sidan.",
        "cannotdelete": "Sidan eller filen \"$1\" kunde inte raderas.\nDen kanske redan har raderats av någon annan.",
        "cannotdelete-title": "Sidan \"$1\" kan inte raderas",
        "title-invalid-characters": "Den begärda sidtiteln innehåller ogiltiga tecken: \"$1\".",
        "title-invalid-relative": "Titeln har relativa sökvägar. Relativa sidtitlar (./, ../) är ogiltiga då de oftast är onåbara när de hanteras av en användares webbläsare.",
        "title-invalid-magic-tilde": "Den begärda sidans titel innehåller ogiltiga magiska tildesekvenser (<nowiki>~~~</nowiki>).",
-       "title-invalid-too-long": "Den begärda sidtiteln är för lång. Det får inte vara längre än $1 {{PLURAL:$1|byte}} i UTF-8-kodning.",
+       "title-invalid-too-long": "Den begärda sidtiteln är för lång. Den får inte vara längre än $1 {{PLURAL:$1|byte}} i UTF-8-kodning.",
        "title-invalid-leading-colon": "Den begärda sidans titel innehåller ett ogiltigt kolon i början.",
        "perfcached": "Följande data är cachad och är möjligtvis inte helt uppdaterad. Maximalt {{PLURAL:$1|ett|$1}} resultat finns {{PLURAL:$1|tillgängligt|tillgängliga}} i cachen.",
        "perfcachedts": "Följande data är cachad och uppdaterades senast $1. Maximalt {{PLURAL:$4|ett|$4}} resultat finns {{PLURAL:$4|tillgängligt|tillgängliga}} i cachen.",
        "protectedpagetext": "Den här sidan har skrivskyddats för att förhindra redigering eller andra åtgärder.",
        "viewsourcetext": "Du kan se och kopiera denna sidas källtext.",
        "viewyourtext": "Du kan se och kopiera källtexten för <strong>dina redigeringar</strong> av denna sida.",
-       "protectedinterface": "Denna sida innehåller text för mjukvarans gränssnitt på denna wiki, och är skrivskyddad för att förebygga missbruk.\nFör att lägga till eller ändra översättningar för alla wikis, var god använd [https://translatewiki.net/ translatewiki.net], lokaliseringsprojektet för MediaWiki.",
+       "protectedinterface": "Denna sida innehåller text för mjukvarans gränssnitt på denna wiki, och är skrivskyddad för att förebygga missbruk.\nFör att lägga till eller ändra översättningar för alla wikier, var god använd [https://translatewiki.net/ translatewiki.net], lokaliseringsprojektet för MediaWiki.",
        "editinginterface": "<strong>Varning:</strong> Du redigerar en sida som används för texten i gränssnittet.\nÄndringar på denna sida kommer att påverka användargränssnittets utseende för andra användare på denna wiki.",
-       "translateinterface": "För att lägga till eller ändra översättningar för alla wikis, använd [https://translatewiki.net/ translatewiki.net], lokaliseringsprojektet för MediaWiki.",
+       "translateinterface": "För att lägga till eller ändra översättningar för alla wikier, använd [https://translatewiki.net/ translatewiki.net], lokaliseringsprojektet för MediaWiki.",
        "cascadeprotected": "Den här sidan har skyddats från redigering eftersom den inkluderas på följande {{PLURAL:$1|sida|sidor}} som skrivskyddats med \"kaskaderande\"-inställningen aktiverad:\n$2",
-       "namespaceprotected": "Du har inte behörighet att redigera sidor i namnrymden '''$1'''.",
+       "namespaceprotected": "Du har inte behörighet att redigera sidor i namnrymden <strong>$1</strong>.",
        "customcssprotected": "Du har inte behörighet att redigera denna CSS-sida eftersom den innehåller en annan användares personliga inställningar.",
        "customjsprotected": "Du har inte behörighet att redigera denna JavaScript-sida eftersom den innehåller en annan användares personliga inställningar.",
-       "mycustomcssprotected": "Du har inte rättigheten att redigera denna CSS-sida.",
-       "mycustomjsprotected": "Du har inte rättigheten att redigera denna JavaScript-sida.",
+       "mycustomcssprotected": "Du har inte behörighet att redigera denna CSS-sida.",
+       "mycustomjsprotected": "Du har inte behörighet att redigera denna JavaScript-sida.",
        "myprivateinfoprotected": "Du har inte behörighet att redigera din privata information.",
        "mypreferencesprotected": "Du har inte behörighet att redigera dina inställningar.",
        "ns-specialprotected": "Specialsidor kan inte redigeras.",
        "exception-nologin": "Inte inloggad",
        "exception-nologin-text": "Var god logga in för att komma åt denna sida eller åtgärd.",
        "exception-nologin-text-manual": "Var god $1 för att få tillgång till denna sida eller åtgärd.",
-       "virus-badscanner": "Dålig konfigurering: okänd virusskanner: ''$1''",
+       "virus-badscanner": "Dålig konfigurering: Okänd virusskanner: <em>$1</em>",
        "virus-scanfailed": "skanning misslyckades (kod $1)",
        "virus-unknownscanner": "okänt antivirusprogram:",
        "logouttext": "<strong>Du är nu utloggad.</strong>\n\nObservera att det, tills du tömmer din webbläsares cache, på vissa sidor kan det se ut som att du fortfarande är inloggad.",
        "userlogin-yourpassword": "Lösenord",
        "userlogin-yourpassword-ph": "Ange ditt lösenord",
        "createacct-yourpassword-ph": "Ange ett lösenord",
-       "yourpasswordagain": "Upprepa lösenord",
+       "yourpasswordagain": "Upprepa lösenord:",
        "createacct-yourpasswordagain": "Bekräfta lösenordet",
        "createacct-yourpasswordagain-ph": "Ange lösenordet igen",
        "userlogin-remembermypassword": "Håll mig inloggad",
        "cannotloginnow-text": "Det går inte att logga in med $1.",
        "cannotcreateaccount-title": "Kan inte skapa konton",
        "cannotcreateaccount-text": "Direkt kontoregistrering är inte aktiverat på denna wiki.",
-       "yourdomainname": "Din domän",
+       "yourdomainname": "Din domän:",
        "password-change-forbidden": "Du kan inte ändra lösenord på denna wiki.",
-       "externaldberror": "Antingen inträffade autentiseringsproblem med en extern databas, eller så får du inte uppdatera ditt externa konto.",
+       "externaldberror": "Antingen inträffade autentiseringsproblem med en databas, eller så får du inte uppdatera ditt externa konto.",
        "login": "Logga in",
        "login-security": "Verifiera din identitet",
        "nav-login-createaccount": "Logga in / skapa konto",
        "userlogin-createanother": "Skapa ett annat konto",
        "createacct-emailrequired": "E-postadress",
        "createacct-emailoptional": "E-postadress (valfritt)",
-       "createacct-email-ph": "Bekräfta din e-postadress",
+       "createacct-email-ph": "Ange din e-postadress",
        "createacct-another-email-ph": "Skriv in e-postadress",
        "createaccountmail": "Använd ett tillfälligt slumpvis valt lösenord och skicka det till den angivna e-postadressen",
        "createaccountmail-help": "Kan användas för att skapa ett konto åt en annan person utan att kunna lösenordet.",
        "createacct-loginerror": "Kontot skapades men du kunde inte loggas in automatiskt. Var god [[Special:UserLogin|logga in manuellt]].",
        "noname": "Du har angett ett ogiltigt användarnamn.",
        "loginsuccesstitle": "Inloggad",
-       "loginsuccess": "'''Du är nu inloggad på {{SITENAME}} som \"$1\".'''",
+       "loginsuccess": "<strong>Du är nu inloggad på {{SITENAME}} som \"$1\".</strong>",
        "nosuchuser": "Det finns ingen användare med namnet \"$1\".\nAnvändarnamn är skiftlägeskänsliga.\nKontrollera din stavning, eller [[Special:CreateAccount|skapa ett nytt konto]].",
        "nosuchusershort": "Det finns ingen användare som heter \"$1\". Kontrollera att du stavat rätt.",
        "nouserspecified": "Du måste ange ett användarnamn.",
        "login-userblocked": "Denna användare är blockerad. Inloggning är inte tillåtet.",
        "wrongpassword": "Lösenordet du angav är felaktigt. Försök igen.",
-       "wrongpasswordempty": "Lösenordet som angavs var blankt. Var god försök igen.",
+       "wrongpasswordempty": "Lösenordet som angavs var tomt. Var god försök igen.",
        "passwordtooshort": "Lösenord måste innehålla minst {{PLURAL:$1|$1 tecken}}.",
        "passwordtoolong": "Lösenord kan inte vara längre än {{PLURAL:$1|1 tecken|$1 tecken}}.",
        "passwordtoopopular": "Vanliga lösenord kan inte användas. Välj ett mer unikt lösenord.",
        "password-login-forbidden": "Användningen av dessa användarnamn och lösenord har förbjudits.",
        "mailmypassword": "Återställ lösenord",
        "passwordremindertitle": "Nytt temporärt lösenord från {{SITENAME}}",
-       "passwordremindertext": "Någon (förmodligen du, från IP-adressen $1) har begärt ett nytt lösenord för {{SITENAME}} ($4). Ett tillfälligt lösenordet för användaren \"$2\" har skapats och satts till \"$3\". Om detta var vad du önskade, behöver du nu logga in och välja ett nytt lösenord. Ditt tillfälliga lösenord går ut om {{PLURAL:$5|ett dygn|$5 dygn}}.\n\nOm denna begäran gjordes av någon annan, eller om du har kommit ihåg ditt lösenord, och inte längre önskar ändra det, kan du ignorera detta meddelande och fortsätta använda ditt gamla lösenord.",
+       "passwordremindertext": "Någon (förmodligen du, från IP-adressen $1) har begärt ett nytt lösenord för {{SITENAME}} ($4). Ett tillfälligt lösenord för användaren \"$2\" har skapats och satts till \"$3\". Om detta var vad du önskade, behöver du nu logga in och välja ett nytt lösenord. Ditt tillfälliga lösenord går ut om {{PLURAL:$5|ett dygn|$5 dygn}}.\n\nOm denna begäran gjordes av någon annan, eller om du har kommit ihåg ditt lösenord, och inte längre önskar ändra det, kan du ignorera detta meddelande och fortsätta använda ditt gamla lösenord.",
        "noemail": "Användaren \"$1\" har inte registrerat någon e-postadress.",
-       "noemailcreate": "Du måste ange en giltig e-postadress",
+       "noemailcreate": "Du måste ange en giltig e-postadress.",
        "passwordsent": "Ett nytt lösenord har skickats till den e-postadress som användaren \"$1\" har registrerat. När du får meddelandet, var god logga in igen.",
        "blocked-mailpassword": "Din IP-adress har blockerats från att redigera. För att förhindra missbruk kan den inte användas för att få ett nytt lösenord.",
        "eauthentsent": "Ett e-postmeddelande för bekräftelse har skickats till den angivna e-postadressen.\nInnan någon annan e-post kan skickas till kontot, måste du följa instruktionerna i e-postmeddelandet för att bekräfta att kontot verkligen är ditt.",
        "mailerror": "Fel vid skickande av e-post: $1",
        "acct_creation_throttle_hit": "Besökare till den här wikin som har använt din IP-adress har skapat {{PLURAL:$1|ett användarkonto|$1 användarkonton}} under det senaste $2, vilket är det maximalt tillåtna inom den tidsperioden.\nSom ett resultat kan besökare som använder den här IP-adressen inte skapa några fler användarkonton just nu.",
        "emailauthenticated": "Din e-postadress bekräftades den $2 kl. $3.",
-       "emailnotauthenticated": "Din e-postadress är ännu inte bekräftad. Ingen e-post kommer att skickas vad gäller det följande funktionerna.",
+       "emailnotauthenticated": "Din e-postadress är ännu inte bekräftad. Ingen e-post kommer att skickas vad gäller de följande funktionerna.",
        "noemailprefs": "Uppge en e-postadress i dina inställningar för att få dessa funktioner att fungera.",
        "emailconfirmlink": "Bekräfta din e-postadress",
        "invalidemailaddress": "E-postadressen kan inte godtas då formatet verkar vara felaktigt.\nSkriv in en adress med korrekt format eller töm fältet.",
-       "cannotchangeemail": "E-post-adresser som är bundna till användarkonton kan inte ändras på denna wiki.",
+       "cannotchangeemail": "E-postadresser kan inte ändras på denna wiki.",
        "emaildisabled": "Denna webbplats kan inte skicka e-post.",
        "accountcreated": "Kontot har skapats",
        "accountcreatedtext": "Användarkontot [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|diskussion]]) har skapats.",
        "createaccount-title": "Konto skapat på {{SITENAME}}",
        "createaccount-text": "Någon har skapat ett konto åt din e-postadress på {{SITENAME}} ($4) med namnet \"$2\" och lösenordet \"$3\". Du bör nu logga in och ändra ditt lösenord.\n\nDu kan ignorera detta meddelande om kontot skapats av misstag.",
-       "login-throttled": "Du har gjort för många misslyckade inloggningsförsök.\nVänta $1 innan du försöker igen.",
+       "login-throttled": "Du har gjort för många nyliga inloggningsförsök.\nVänta $1 innan du försöker igen.",
        "login-abort-generic": "Din inloggning misslyckades - Avbröts",
-       "login-migrated-generic": "Dit konto har migrerats och ditt användarnamn existerar inte längre på denna wiki.",
+       "login-migrated-generic": "Ditt konto har migrerats och ditt användarnamn existerar inte längre på denna wiki.",
        "loginlanguagelabel": "Språk: $1",
        "suspicious-userlogout": "Din begäran om att logga ut nekades eftersom det ser ut som det skickades av en trasig webbläsare eller cachande proxy.",
        "createacct-another-realname-tip": "Riktigt namn behöver inte anges.\nOm du väljer att ange det, kommer det att användas för att tillskriva dig ditt arbete.",
        "pt-login-continue-button": "Fortsätt att logga in",
        "pt-createaccount": "Skapa konto",
        "pt-userlogout": "Logga ut",
-       "php-mail-error-unknown": "Okänt fel i PHP:s mail()-funktion",
+       "php-mail-error-unknown": "Okänt fel i PHP:s mail()-funktion.",
        "user-mail-no-addy": "Försökte skicka e-post utan en e-postadress",
        "user-mail-no-body": "Försökte skicka e-post med tomt eller orimligt kort innehåll.",
        "changepassword": "Byt lösenord",
        "changepassword-success": "Ditt lösenord har ändrats!",
        "changepassword-throttled": "Du har gjort för många inloggningsförsök nyligen.\nVänta $1 innan du försöker igen.",
        "botpasswords": "Botlösenord",
-       "botpasswords-summary": "<em>Botlösenord</em> ger åtkomst till ett användarkonto via API utan att använda kontots primära inloggningsuppgifter. Tillgängliga användarrättigheter när du är loggar in med ett botlösenord kan vara begränsade.\n\nOm du inte vet varför du kanske vill göra detta bör du förmodligen inte göra det. Ingen behöver någonsin be dig att generera ett av dessa och ge det till dem.",
+       "botpasswords-summary": "<em>Botlösenord</em> ger åtkomst till ett användarkonto via API utan att använda kontots primära inloggningsuppgifter. Tillgängliga användarbehörigheter när man är inloggad med ett botlösenord kan vara begränsade.\n\nOm du inte vet varför du skulle vilja göra detta, bör du förmodligen inte göra det. Ingen behöver någonsin be dig att generera ett av dessa och ge det till dem.",
        "botpasswords-disabled": "Botlösenord är inaktiverade.",
        "botpasswords-no-central-id": "För att använda botlösenord måste du vara inloggad på ett centraliserat konto.",
        "botpasswords-existing": "Befintliga botlösenord",
        "botpasswords-label-delete": "Radera",
        "botpasswords-label-resetpassword": "Återställ lösenordet",
        "botpasswords-label-grants": "Tillgängliga beviljanden:",
-       "botpasswords-help-grants": "Beviljande ger åtkomst till användarrättigheter som ditt användarkonto redan har. När ett beviljande aktiveras här ger det inte åtkomst till de användarrättigheter som ditt användarkonto inte annars skulle ha. Se [[Special:ListGrants|tabellen över beviljanden]] för mer information.",
+       "botpasswords-help-grants": "Beviljande ger åtkomst till användarbehörigheter som ditt användarkonto redan har. När ett beviljande aktiveras här ger det inte åtkomst till de användarrättigheter som ditt användarkonto inte annars skulle ha. Se [[Special:ListGrants|tabellen över beviljanden]] för mer information.",
        "botpasswords-label-grants-column": "Beviljas",
        "botpasswords-bad-appid": "Botnamnet \"$1\" är inte giltigt.",
        "botpasswords-insert-failed": "Kunde inte lägga till botnamnet \"$1\". Har det redan lagts till?",
        "botpasswords-updated-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" uppdaterades.",
        "botpasswords-deleted-title": "Botlösenord raderades",
        "botpasswords-deleted-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" raderades.",
-       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em> <br> (För äldre botar som kräver att inloggningsnamnet är detsamma som det eventuella användarnamnet kan du även använda <strong>$3</strong> som användarnamn och <strong>$4</strong> som lösenord.)",
+       "botpasswords-newpassword": "Det nya lösenordet för att logga in med <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta för framtida referens.</em> <br> (För äldre botar som kräver att inloggningsnamnet är detsamma som det eventuella användarnamnet kan du även använda <strong>$3</strong> som användarnamn och <strong>$4</strong> som lösenord.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider är inte tillgänglig.",
        "botpasswords-restriction-failed": "Begränsningar av botlösenord tillåter inte denna inloggning.",
        "botpasswords-invalid-name": "Det angivna användarnamnet innehåller inte separatorn för botlösenord (\"$1\").",
index 9114076..973741b 100644 (file)
        "pool-servererror": "Хидмати ҳисобкунаки ҳафз дастрас нест ($1).",
        "aboutsite": "Дар бораи {{SITENAME}}",
        "aboutpage": "Project:Дар бораи",
-       "copyright": "Мӯҳтаво таҳти иҷозатномаи $1 ва ё дигар дастрас аст.",
+       "copyright": "Муҳтаво таҳти иҷозатномаи $1 ва ё дигар дастрас аст.",
        "copyrightpage": "{{ns:project}}:Copyrights",
        "currentevents": "Рӯйдодҳои кунунӣ",
        "currentevents-url": "Лоиҳа:Рӯйдодҳои кунунӣ",
        "nocookiesfornew": "Ҳисоби корбарӣ сохта нашуд, чун мо манбаъи онро тасдиқ карда натавонистем.\nМутмаин бошед, ки кукиҳои мурургар фаъоланд, ин саҳифро аз нав кушода бори дигар саъй кунед.",
        "noname": "Номи корбари дурустеро шумо пешниҳод накардед.",
        "loginsuccesstitle": "Вуруд бо муваффақият",
-       "loginsuccess": "'''Шумо акнун ба Википедиа ҳамчун \"$1\". вуруд кардед'''",
+       "loginsuccess": "'''Шумо акнун ба Википедия ҳамчун \"$1\". вуруд кардед'''",
        "nosuchuser": "Корбаре бо номи \"$1\" вуҷуд надорад.\nАмали номро барраси кунед, ё [[Special:CreateAccount|ҳисоби ҷадидеро эҷод кунед]].",
        "nosuchusershort": "Ягон корбаре бо номи \"$1\" вуҷуд надорад. Тарзи навишти номро санҷед.",
        "nouserspecified": "Шумо бояд як номи корбарӣ мушаххас кунед.",
        "viewprevnext": "Намоиши ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Саҳифае бо номи \"[[:$1]]\" дар ин вики вуҷуд дорад.'''",
        "searchmenu-new": "'''Эҷоди саҳифаи \"[[:$1]]\" дар ин вики!'''",
-       "searchprofile-articles": "Саҳифаҳои мӯҳтаво",
+       "searchprofile-articles": "Саҳифаҳои муҳтаво",
        "searchprofile-images": "Чандрасонаӣ",
        "searchprofile-everything": "Ҳамачиз",
        "searchprofile-advanced": "Пешрафта",
        "searchprofile-articles-tooltip": "Ҷустуҷӯ дар $1",
        "searchprofile-images-tooltip": "Ҷустуҷӯи парвандаҳо",
-       "searchprofile-everything-tooltip": "Ҷустуҷӯи ҳамаи мӯҳтаво (бо ҳисоби саҳифаҳои баҳс)",
+       "searchprofile-everything-tooltip": "Ҷустуҷӯи ҳамаи муҳтаво (бо ҳисоби саҳифаҳои баҳс)",
        "searchprofile-advanced-tooltip": "Ҷустуҷӯ дар фазоҳои номи дилхоҳ",
        "search-result-size": "$1 ({{PLURAL:$2|1 калима|$2 калима}})",
        "search-redirect": "(тағйири масир $1)",
        "prefs-registration": "Замони сабтином:",
        "yourrealname": "Номи аслӣ:",
        "yourlanguage": "Забон:",
-       "yourvariant": "Навъи забони мӯҳтаво:",
+       "yourvariant": "Навъи забони муҳтаво:",
        "yournick": "Ники шумо:",
        "badsig": "Имзои хом нодуруст аст; барҷасбҳои HTML-ро баррасӣ кунед.",
        "badsiglength": "Тахаллус хеле дароз аст.\nОн бояд камтар аз $1 {{PLURAL:$1|аломат|аломатҳо}} бошад.",
        "enhancedrc-history": "таърих",
        "recentchanges": "Тағйироти охирин",
        "recentchanges-legend": "Ихтиёроти тағйироти охирин",
-       "recentchanges-summary": "Назорати тағйиротҳои навтарин дар Википедиа дар ҳамин саҳифа аст.",
+       "recentchanges-summary": "Назорати тағйиротҳои навтарин дар Википедия дар ҳамин саҳифа аст.",
        "recentchanges-feed-description": "Радёбии охирин тағйироти ин вики дар ин хурд.",
        "recentchanges-label-newpage": "Ин вироиш саҳифаи нав эҷод кард",
        "recentchanges-label-minor": "Ин вироиши ҷузъи аст",
        "randomincategory-submit": "Бирав",
        "randomredirect": "Масири тасодуфӣ",
        "randomredirect-nopages": "Ҳеҷ саҳифаи тағйири масире дар ин фазои ном мавҷуд нест.",
-       "statistics": "Омор/Статистика",
+       "statistics": "Омор",
        "statistics-header-pages": "Саҳифаи омор",
        "statistics-header-edits": "Вироиши омор",
        "statistics-header-users": "Омори корбарон",
        "statistics-header-hooks": "Дигар омор",
-       "statistics-articles": "Саҳифаҳои мӯҳтаво",
+       "statistics-articles": "Саҳифаҳои муҳтаво",
        "statistics-pages": "Саҳифаҳо",
        "statistics-pages-desc": "Тамоми саҳифаҳо дар ин вики-сомона (саҳифаҳои равонакунӣ, баҳсҳо ва ғ.)",
        "statistics-files": "Парвандаҳои боршуда",
        "tooltip-n-mainpage-description": "Бозгашт ба саҳифаи аслӣ",
        "tooltip-n-portal": "Дар бораи лоиҳа, чӣ корҳоро метавонед кард, ва дарёфти чизҳо",
        "tooltip-n-currentevents": "Ёфтани иттилооти пешзамина перомуни рӯйдодҳои кунунӣ",
-       "tooltip-n-recentchanges": "Рӯйхати тағйирот дар Википедиа",
+       "tooltip-n-recentchanges": "Рӯйхати тағйирот дар Википедия",
        "tooltip-n-randompage": "Овардани як саҳифаи тасодуфӣ",
        "tooltip-n-help": "Макон барои дарёфт",
        "tooltip-t-whatlinkshere": "Рӯйхати ҳамаи саҳифаҳое, ки ба ин саҳифа пайванд доранд",
        "tooltip-preferences-save": "Захираи тарҷиҳот",
        "tooltip-summary": "Хулосаи кӯтоҳ ворид кунед",
        "anonymous": "{{PLURAL:$1|корбари|корбарони}} гумномӣ {{SITENAME}}",
-       "siteuser": "Википедиа user $1",
+       "siteuser": "Википедия user $1",
        "lastmodifiedatby": "Ин саҳифа охирин маротиба дар $2, $1 аз тарафи $3 тағйир дода шудааст.",
        "othercontribs": "Дар асоси коре аз тарафи $1.",
        "others": "дигарон",
index bb256be..fae1106 100644 (file)
        "underline-never": "Ніколи",
        "underline-default": "Використовувати налаштування браузера",
        "editfont-style": "Тип шрифту в полі редагування:",
-       "editfont-default": "Шрифт від веб-оглядача",
        "editfont-monospace": "Шрифт зі сталою шириною",
        "editfont-sansserif": "Шрифт без засічок",
        "editfont-serif": "Шрифт із засічками",
        "prefs-advancedwatchlist": "Розширені налаштування",
        "prefs-displayrc": "Налаштування показу",
        "prefs-displaywatchlist": "Налаштування показу",
-       "prefs-tokenwatchlist": "Ð\96еÑ\82он",
+       "prefs-tokenwatchlist": "Токен",
        "prefs-diffs": "Різниці версій",
        "prefs-help-prefershttps": "Цей параметр набуде чинності при вашому наступному вході у систему.",
        "prefswarning-warning": "Ви внесли в свої налаштування зміни, які ще не були збережені.\nЯкщо ви залишите цю сторінку, не натиснувши \"$1\", налаштування не будуть оновлені.",
        "block": "Заблокувати користувача",
        "unblock": "Розблокувати користувача",
        "blockip": "Заблокувати {{GENDER:$1|користувача|користувачку}}",
-       "blockip-legend": "Блокування користувача",
        "blockiptext": "Використовуйте форму нижче, щоб заблокувати можливість редагування зазначеній IP-адресі або користувачу.\nЦе слід робити лише для запобігання порушенням і у відповідності до [[{{MediaWiki:Policy-url}}|правил]].\nОбов'язково заповніть причину нижче, бажано дати інформативну вичерпну інформацію (наприклад, послатися на конкретні правила, дати посилання на редагування користувача, які призвели до блокування). Можна конкретизувати причину блокування на сторінці обговорення користувача.\nВи можете заблокувати діапазони IP-адрес, використовуючи [https://uk.wikipedia.org/wiki/CIDR CIDR]-синтаксис. Максимально допустимий діапазон — /$1 для протоколу IPv4 та /$2 для протоколу IPv6.",
        "ipaddressorusername": "IP-адреса або ім'я користувача:",
        "ipbexpiry": "Термін:",
index 66a5a70..33fe621 100644 (file)
        "mw-widgets-dateinput-no-date": "کسی تاریخ کو منتخب نہیں کیا گیا",
        "mw-widgets-titleinput-description-new-page": "صفحہ ابھی تک موجود نہیں",
        "mw-widgets-titleinput-description-redirect": "$1 کا رجوع مکرر",
+       "mw-widgets-usersmultiselect-placeholder": "مزید شامل کریں۔۔۔",
        "date-range-from": "تاریخ از:",
        "date-range-to": "تا:",
        "sessionmanager-tie": "تصدیقی درخواست کی متعدد قسموں کو یکجا نہیں کیا جا سکتا: $1",
index cac8482..80c8114 100644 (file)
@@ -12,6 +12,7 @@
  * @author Muhammad Shuaib
  * @author Noor2020
  * @author O.bangash
+ * @author Obaid Raza
  * @author Rachitrali
  * @author Reedy
  * @author Tahir mq
@@ -61,80 +62,119 @@ $namespaceAliases = [
 $specialPageAliases = [
        'Activeusers'               => [ 'متحرک_صارفین' ],
        'Allmessages'               => [ 'تمام_پیغامات' ],
+       'AllMyUploads'              => [ 'میرے_تمام_اپلوڈ', 'میری_تمام_فائلیں' ],
        'Allpages'                  => [ 'تمام_صفحات' ],
+       'ApiHelp'                   => [ 'آے_پی_آئی_معاونت' ],
+       'ApiSandbox'                => [ 'اے_پی_آئی_تختہ_مشق' ],
        'Ancientpages'              => [ 'قدیم_صفحات' ],
+       'AutoblockList'             => [ 'فہرست_خودکار_پابندی' ],
        'Badtitle'                  => [ 'خراب_عنوان' ],
        'Blankpage'                 => [ 'خالی_صفحہ' ],
-       'Block'                     => [ 'پابندی', 'آئی_پی_پتہ_پابندی', 'پابندی_بر_صارف' ],
+       'Block'                     => [ 'پابÙ\86دÛ\8c', 'آئÛ\8c_Ù¾Û\8c_پابÙ\86دÛ\8c', 'آئÛ\8c_Ù¾Û\8c_پتÛ\81_پابÙ\86دÛ\8c', 'پابÙ\86دÛ\8c_بر_صارÙ\81' ],
        'Booksources'               => [ 'کتابی_وسائل' ],
+       'BotPasswords'              => [ 'روبہ_کے_پاسورڈ' ],
        'BrokenRedirects'           => [ 'شکستہ_رجوع_مکررات' ],
        'Categories'                => [ 'زمرہ_جات' ],
+       'ChangeContentModel'        => [ 'تبدیلی_مواد_ماڈل' ],
+       'ChangeCredentials'         => [ 'تبدیلی_حساس_معلومات' ],
        'ChangeEmail'               => [ 'ڈاک_تبدیل' ],
-       'ChangePassword'            => [ 'کلمہ_شناخت_تبدیل', 'تنظیم_کلمہ_شناخت' ],
+       'ChangePassword'            => [ 'تبدیلی_پاسورڈ', 'کلمہ_شناخت_تبدیل', 'تنظیم_کلمہ_شناخت' ],
        'ComparePages'              => [ 'موازنہ_صفحات' ],
        'Confirmemail'              => [ 'تصدیق_ڈاک' ],
        'Contributions'             => [ 'شراکتیں' ],
        'CreateAccount'             => [ 'تخلیق_کھاتہ' ],
        'Deadendpages'              => [ 'مردہ_صفحات' ],
        'DeletedContributions'      => [ 'حذف_شدہ_شراکتیں' ],
+       'Diff'                      => [ 'فرق' ],
        'DoubleRedirects'           => [ 'دوہرے_رجوع_مکررات' ],
-       'EditWatchlist'             => [ 'ترمیم_زیر_نظر' ],
-       'Emailuser'                 => [ 'صارف_ڈاک' ],
+       'EditTags'                  => [ 'ترمیم_ٹیگ' ],
+       'EditWatchlist'             => [ 'ترمیم_زیر_نظر_فہرست', 'ترمیم_زیر_نظر' ],
+       'Emailuser'                 => [ 'صارف_ڈاک', 'برقی_ڈاک' ],
+       'ExpandTemplates'           => [ 'توسیع_سانچہ_جات' ],
        'Export'                    => [ 'برآمد', 'برآمدگی' ],
-       'Fewestrevisions'           => [ 'کم_نظر_ثانی_شدہ' ],
+       'Fewestrevisions'           => [ 'کمترین_نسخہ', 'کم_نظر_ثانی_شدہ' ],
        'FileDuplicateSearch'       => [ 'تلاش_دوہری_فائل', 'دہری_ملف_تلاش' ],
        'Filepath'                  => [ 'راہ_فائل', 'راہ_ملف' ],
+       'GoToInterwiki'             => [ 'بین_الویکی_پر_جائیں' ],
        'Import'                    => [ 'درآمد', 'درآمدگی' ],
        'Invalidateemail'           => [ 'ڈاک_تصدیق_منسوخ' ],
        'JavaScriptTest'            => [ 'تجربہ_جاوا_اسکرپٹ' ],
-       'BlockList'                 => [ 'فہرست_ممنوع', 'فہرست_دستور_شبکی_ممنوع' ],
+       'BlockList'                 => [ 'فہرست_ممنوعین', 'فہرست_ممنوع', 'فہرست_دستور_شبکی_ممنوع' ],
        'LinkSearch'                => [ 'تلاش_روابط' ],
+       'LinkAccounts'              => [ 'کھاتے_مربوط_کریں' ],
        'Listadmins'                => [ 'فہرست_منتظمین' ],
        'Listbots'                  => [ 'فہرست_روبہ_جات' ],
        'Listfiles'                 => [ 'فائلوں_کی_فہرست', 'فہرست_تصاویر' ],
        'Listgrouprights'           => [ 'فہرست_اختیارات_گروہ', 'صارفی_گروہ_اختیارات' ],
+       'Listgrants'                => [ 'فہرست_عطیات' ],
        'Listredirects'             => [ 'فہرست_رجوع_مکررات' ],
+       'ListDuplicatedFiles'       => [ 'دوہری_فائلوں_کی_فہرست' ],
        'Listusers'                 => [ 'فہرست_صارفین' ],
+       'Lockdb'                    => [ 'ڈیٹابیس_مقفل' ],
        'Log'                       => [ 'نوشتہ', 'نوشتہ_جات' ],
        'Lonelypages'               => [ 'یتیم_صفحات' ],
        'Longpages'                 => [ 'طویل_صفحات' ],
+       'MediaStatistics'           => [ 'شماریات_میڈیا' ],
        'MergeHistory'              => [ 'ضم_تاریخچہ' ],
+       'MIMEsearch'                => [ 'ایم_آئی_ایم_ای_تلاش' ],
+       'Mostcategories'            => [ 'بیشتر_زمرہ_جات' ],
+       'Mostimages'                => [ 'بیشتر_تصویریں', 'بیشتر_مربوط_تصویریں', 'بیشتر_فائلیں' ],
+       'Mostinterwikis'            => [ 'بیشتر_بین_الویکی_رعابط' ],
+       'Mostlinked'                => [ 'بیشتر_مربوط_صفحات', 'بیشتر_مربوط' ],
+       'Mostlinkedcategories'      => [ 'بیشتر_مربوط_زمرہ_جات', 'بیشتر_مستعمل_زمرہ_جات' ],
+       'Mostlinkedtemplates'       => [ 'بیشتر_مستعمل_صفحات', 'بیشتر_مربوط_سانچے', 'بیشتر_مستعمل_سانچے' ],
+       'Mostrevisions'             => [ 'بیشتر_نسخے' ],
        'Movepage'                  => [ 'منتقلی_صفحہ' ],
        'Mycontributions'           => [ 'میری_شراکتیں', 'میرا_حصہ' ],
+       'MyLanguage'                => [ 'میری_زبان' ],
        'Mypage'                    => [ 'میرا_صفحہ' ],
-       'Mytalk'                    => [ 'میری_گفتگو' ],
-       'Myuploads'                 => [ 'میرے_اپلوڈ', 'میرے_زبراثقالات' ],
-       'Newimages'                 => [ 'جدید_فائلیں', 'جدید_املاف', 'جدید_تصاویر' ],
+       'Mytalk'                    => [ 'میرا_تبادلہ_خیال',  'میری_گفتگو' ],
+       'Myuploads'                 => [ 'میرے_اپلوڈ', 'میرے_زبراثقالات', 'میری_فائلیں' ],
+       'Newimages'                 => [ 'جدید_فائلیں', 'جدید_املاف', 'جدید_تصاویر', 'نئی_فائلیں', 'نئی_املاف', 'نئی_تصاویر', 'نئی_تصویریں', 'جدید_تصویریں' ],
        'Newpages'                  => [ 'جدید_صفحات' ],
+       'PagesWithProp'             => [ 'صفحات_مع_خاصیت', 'صفحات_بلحاظ_خاصیت' ],
+       'PageData'                  => [ 'معلومات_صفحہ' ],
+       'PageLanguage'              => [ 'صفحہ_کی_زبان' ],
+       'PasswordReset'             => [ 'پاسورڈ_کی_ترتیب_نو' ],
        'PermanentLink'             => [ 'مستقل_ربط' ],
        'Preferences'               => [ 'ترجیحات' ],
        'Prefixindex'               => [ 'اشاریہ_سابقہ' ],
        'Protectedpages'            => [ 'محفوظ_صفحات' ],
-       'Protectedtitles'           => [ 'محفوظ_عناوین' ],
-       'Randompage'                => [ 'تصادف', 'تصادفی_مقالہ' ],
-       'Randomredirect'            => [ 'تصادفی_رجوع_مکرر' ],
+       'Protectedtitles'          => [ 'محفوظ_عناوین' ],
+       'Randompage'                => [ 'جستہ', 'جستہ_جستہ', 'تصادف', 'تصادفی_مقالہ' ],
+       'RandomInCategory'          => [ 'جستہ_جستہ_زمرہ' ],
+       'Randomredirect'            => [ 'جستہ_جستہ_رجوع_مکرر', 'تصادفی_رجوع_مکرر' ],
+       'Randomrootpage'            => [ 'جستہ_جستہ_بنیادی_صفحہ' ],
        'Recentchanges'             => [ 'حالیہ_تبدیلیاں' ],
-       'Recentchangeslinked'       => [ 'متعلقہ_تبدیلیاں' ],
-       'Revisiondelete'            => [ 'حذف_نظر_ثانی', 'حذف_اعادہ' ],
+       'Recentchangeslinked'       => [ 'متعلقہ_حالیہ_تبدیلیاں', 'متعلقہ_تبدیلیاں' ],
+       'Redirect'                  => [ 'رجوع_مکرر' ],
+       'RemoveCredentials'         => [ 'حذف_حساس_معلومات' ],
+       'ResetTokens'               => [ 'ٹوکنوں_کی_ترتیب_نو' ],
+       'Revisiondelete'            => [ 'حذف_نسخہ', 'حذف_نظر_ثانی', 'حذف_اعادہ' ],
+       'RunJobs'                   => [ 'تعمیل_امور' ],
        'Search'                    => [ 'تلاش' ],
        'Shortpages'                => [ 'مختصر_صفحات' ],
        'Specialpages'              => [ 'خصوصی_صفحات' ],
        'Statistics'                => [ 'شماریات' ],
        'Tags'                      => [ 'ٹیگ', 'ٹیگز' ],
+       'TrackingCategories'        => [ 'متلاشی_زمرہ_جات' ],
        'Unblock'                   => [ 'پابندی_ختم' ],
        'Uncategorizedcategories'   => [ 'غیر_زمرہ_بند_زمرہ_جات' ],
        'Uncategorizedimages'       => [ 'غیر_زمرہ_بند_فائلیں', 'غیر_زمرہ_بند_املاف', 'غیر_زمرہ_بند_تصاویر' ],
        'Uncategorizedpages'        => [ 'غیر_زمرہ_بند_صفحات' ],
        'Uncategorizedtemplates'    => [ 'غیر_زمرہ_بند_سانچے' ],
        'Undelete'                  => [ 'بحال' ],
+       'UnlinkAccounts'            => [ 'کھاتے_غیر_مربوط_کریں' ],
+       'Unlockdb'                  => [ 'ڈیٹابیس_قفل_کھولیں' ],
        'Unusedcategories'          => [ 'غیر_مستعمل_زمرہ_جات' ],
-       'Unusedimages'              => [ 'غیر_مستعمل_فائلیں', 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر' ],
+       'Unusedimages'              => [ 'غیر_مستعمل_فائلیں', 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر', 'غیر_مستعمل_تصویریں' ],
        'Unusedtemplates'           => [ 'غیر_مستعمل_سانچے' ],
        'Unwatchedpages'            => [ 'نادیدہ_صفحات' ],
-       'Upload'                    => [ 'اپلوڈ', 'زبراثقال' ],
-       'Userlogin'                 => [ 'داخل_نوشتگی' ],
-       'Userlogout'                => [ 'خارج_نوشتگی' ],
-       'Userrights'                => [ 'صارفی_اختیارات' ],
+       'Upload'                    => [ 'اپلوڈ', 'زبراثقال', 'زبر_اثقال' ],
+       'UploadStash'               => [ 'اجتماعی_اپلوڈ' ],
+       'Userlogin'                 => [ 'داخل_ہوں', 'داخل_نوشتگی' ],
+       'Userlogout'                => [ 'خارج_ہوں', 'خارج_نوشتگی' ],
+       'Userrights'                => [ 'اختیارات_صارف', 'صارفی_اختیارات' ],
        'Version'                   => [ 'نسخہ', 'اخراجہ' ],
        'Wantedcategories'          => [ 'مطلوبہ_زمرہ_جات' ],
        'Wantedfiles'               => [ 'مطلوبہ_فائلیں', 'مطلوبہ_املاف' ],
@@ -147,23 +187,174 @@ $specialPageAliases = [
 
 $magicWords = [
        'redirect'                  => [ '0', '#رجوع_مکرر', '#REDIRECT' ],
-       'notoc'                     => [ '0', '_فہرست_نہیں_', '__NOTOC__' ],
+       'notoc'                     => [ '0', '__فہرست_نہیں__', '__نافہرست__', '__NOTOC__' ],
+       'nogallery'                 => [ '0', '__نگارخانہ_نہیں__', '__NOGALLERY__' ],
+       'forcetoc'                  => [ '0', '__بافہرست__', '__FORCETOC__' ],
        'toc'                       => [ '0', '__فہرست__', '__TOC__' ],
-       'noeditsection'             => [ '0', '__ناتحریرقسم__', '__NOEDITSECTION__' ],
+       'noeditsection'             => [ '0', '__ناترمیم_قطعہ__', '__NOEDITSECTION__' ],
+       'currentmonth'              => [ '1', 'موجودہ_مہینہ', 'موجودہ_مہینہ2', 'CURRENTMONTH', 'CURRENTMONTH2' ],
+       'currentmonth1'             => [ '1', 'موجودہ_مہینہ1', 'CURRENTMONTH1' ],
+       'currentmonthname'          => [ '1', 'موجودہ_مہینہ_کا_نام', 'CURRENTMONTHNAME' ],
+       'currentmonthnamegen'       => [ '1', 'موجودہ_مہینہ_کا_نام_اضافت', 'CURRENTMONTHNAMEGEN' ],
+       'currentmonthabbrev'        => [ '1', 'موجودہ_مہینہ_کا_اختصار', 'CURRENTMONTHABBREV' ],
+       'currentday'                => [ '1', 'موجودہ_دن', 'CURRENTDAY' ],
+       'currentday2'               => [ '1', 'موجودہ_دن2', 'CURRENTDAY2' ],
+       'currentdayname'            => [ '1', 'موجودہ_دن_کا_نام', 'CURRENTDAYNAME' ],
+       'currentyear'               => [ '1', 'موجودہ_سال', 'CURRENTYEAR' ],
+       'currenttime'               => [ '1', 'موجودہ_وقت', 'CURRENTTIME' ],
+       'currenthour'               => [ '1', 'موجودہ_گھنٹہ', 'CURRENTHOUR' ],
+       'localmonth'                => [ '1', 'مقامی_مہینہ', 'مقامی_مہینہ2',  'LOCALMONTH', 'LOCALMONTH2' ],
+       'localmonth1'               => [ '1', 'مقامی_مہینہ1', 'LOCALMONTH1' ],
+       'localmonthname'            => [ '1', 'مقامی_مہینہ_کا_نام', 'LOCALMONTHNAME' ],
+       'localmonthnamegen'         => [ '1', 'مقامی_مہینہ_کا_نام_اضافت', 'LOCALMONTHNAMEGEN' ],
+       'localmonthabbrev'          => [ '1', 'مقامی_مہینہ_کا_اختصار', 'LOCALMONTHABBREV' ],
+       'localday'                  => [ '1', 'مقامی_دن', 'LOCALDAY' ],
+       'localday2'                 => [ '1', 'مقامی_دن2', 'LOCALDAY2' ],
+       'localdayname'              => [ '1', 'مقامی_دن_کا_نام', 'LOCALDAYNAME' ],
+       'localyear'                 => [ '1', 'مقامی_سال', 'LOCALYEAR' ],
+       'localtime'                 => [ '1', 'مقامی_وقت', 'LOCALTIME' ],
+       'localhour'                 => [ '1', 'مقامی_گھنٹہ', 'LOCALHOUR' ],
+       'numberofpages'             => [ '1', 'تعداد_صفحات', 'NUMBEROFPAGES' ],
+       'numberofarticles'          => [ '1', 'تعداد_مضامین', 'NUMBEROFARTICLES' ],
+       'numberoffiles'             => [ '1', 'تعداد_فائل', 'NUMBEROFFILES' ],
+       'numberofusers'             => [ '1', 'تعداد_صارفین', 'NUMBEROFUSERS' ],
+       'numberofactiveusers'       => [ '1', 'تعداد_فعال_صارفین', 'NUMBEROFACTIVEUSERS' ],
+       'numberofedits'             => [ '1', 'تعداد_ترامیم', 'NUMBEROFEDITS' ],
        'pagename'                  => [ '1', 'نام_صفحہ', 'PAGENAME' ],
+       'pagenamee'                 => [ '1', 'نام_صفحہ_کوڈ', 'PAGENAMEE' ],
        'namespace'                 => [ '1', 'نام_فضا', 'NAMESPACE' ],
+       'namespacee'                => [ '1', 'نام_فضا_کوڈ', 'NAMESPACEE' ],
+       'namespacenumber'           => [ '1', 'نام_فضا_کا_عدد', 'NAMESPACENUMBER' ],
+       'talkspace'                 => [ '1', 'تبادلہ_خیال_نام_فضا', 'TALKSPACE' ],
+       'talkspacee'                => [ '1', 'تبادلہ_خیال_کوڈ', 'TALKSPACEE' ],
+       'subjectspace'              => [ '1', 'مضمون_نام_فضا',  'SUBJECTSPACE', 'ARTICLESPACE' ],
+       'subjectspacee'             => [ '1', 'مضمون_نام_فضا_کوڈ',  'SUBJECTSPACEE', 'ARTICLESPACEE' ],
+       'fullpagename'              => [ '1', 'صفحہ_کا_مکمل_نام', 'مکمل_نام',  'FULLPAGENAME' ],
+       'fullpagenamee'             => [ '1', 'مکمل_نام_کوڈ', 'FULLPAGENAMEE' ],
+       'subpagename'               => [ '1', 'نام_ذیلی_صفحہ', 'SUBPAGENAME' ],
+       'subpagenamee'              => [ '1', 'ذیلی_صفحہ_کوڈ', 'SUBPAGENAMEE' ],
+       'rootpagename'              => [ '1', 'نام_اصل_صفحہ', 'ROOTPAGENAME' ],
+       'rootpagenamee'             => [ '1', 'اصل_صفحہ_کوڈ', 'ROOTPAGENAMEE' ],
+       'basepagename'              => [ '1', 'نام_بنیادی_صفحہ', 'BASEPAGENAME' ],
+       'basepagenamee'             => [ '1', 'بنیادی_صفحہ_کوڈ', 'BASEPAGENAMEE' ],
+       'talkpagename'              => [ '1', 'نام_تبادلہ_خیال_صفحہ', 'TALKPAGENAME' ],
+       'talkpagenamee'             => [ '1', 'تبادلہ_خیال_صفحہ_کوڈ', 'TALKPAGENAMEE' ],
+       'subjectpagename'           => [ '1', 'نام_صفحہ_مضمون', 'SUBJECTPAGENAME', 'ARTICLEPAGENAME' ],
+       'subjectpagenamee'          => [ '1', 'نام_صفحہ_مضمون_کوڈ', 'SUBJECTPAGENAMEE', 'ARTICLEPAGENAMEE' ],
        'msg'                       => [ '0', 'پیغام:', 'MSG:' ],
-       'subst'                     => [ '0', 'جا:', 'نقل:', 'SUBST:' ],
-       'safesubst'                 => [ '0', 'محفوظ_جا:', 'محفوظ_نقل:', 'SAFESUBST:' ],
+       'subst'                     => [ '0', 'نقل:', 'جا:', 'SUBST:' ],
+       'safesubst'                 => [ '0', 'محفوظ_نقل:', 'محفوظ_جا:', 'SAFESUBST:' ],
+       'msgnw'                     => [ '0', 'خام_پیغام:', 'MSGNW:' ],
        'img_thumbnail'             => [ '1', 'تصغیر', 'thumb', 'thumbnail' ],
+       'img_manualthumb'           => [ '1', 'تصغیر=$1', 'thumbnail=$1', 'thumb=$1' ],
        'img_right'                 => [ '1', 'دائیں', 'right' ],
        'img_left'                  => [ '1', 'بائیں', 'left' ],
-       'img_center'                => [ '1', 'درمیان', 'center', 'centre' ],
-       'sitename'                  => [ '1', 'نام_موقع', 'SITENAME' ],
+       'img_none'                  => [ '1', 'بغیر', 'بدون', 'none' ],
+       'img_width'                 => [ '1', '$1پکسل', '$1پک', '$1px' ],
+       'img_center'                => [ '1', 'وسط', 'center', 'centre' ],
+       'img_framed'                => [ '1', 'چوکھٹا', 'frame', 'framed', 'enframed' ],
+       'img_frameless'             => [ '1', 'بدون_چوکھٹا', 'frameless' ],
+       'img_lang'                  => [ '1', 'زبان=$1', 'lang=$1' ],
+       'img_page'                  => [ '1', 'صفحہ=$1', 'صفحہ_$1', 'page=$1', 'page $1' ],
+       'img_upright'               => [ '1', 'ایستادہ', 'ایستادہ=$1', 'ایستادہ_$1', 'upright', 'upright=$1', 'upright $1' ],
+       'img_border'                => [ '1', 'حدود', 'border' ],
+       'img_baseline'              => [ '1', 'خط_اساسی', 'baseline' ],
+       'img_sub'                   => [ '1', 'زیر', 'sub' ],
+       'img_super'                 => [ '1', 'زبر', 'super', 'sup' ],
+       'img_top'                   => [ '1', 'بالا', 'top' ],
+       'img_text_top'              => [ '1', 'متن-بالا', 'text-top' ],
+       'img_middle'                => [ '1', 'وسط', 'درمیان', 'middle' ],
+       'img_bottom'                => [ '1', 'زیریں', 'bottom' ],
+       'img_text_bottom'           => [ '1', 'متن-زیریں', 'text-bottom' ],
+       'img_link'                  => [ '1', 'ربط=$1', 'لنک=$1', 'link=$1' ],
+       'img_alt'                   => [ '1', 'متبادل=$1', 'alt=$1' ],
+       'img_class'                 => [ '1', 'درجہ=$1', 'class=$1' ],
+       'int'                       => [ '0', 'عالمی:', 'INT:' ],
+       'sitename'                  => [ '1', 'سائٹ_نام', 'نام_سائٹ', 'نام_موقع', 'SITENAME' ],
+       'ns'                        => [ '0', 'نف:', 'NS:' ],
+       'nse'                       => [ '0', 'نفک:', 'NSE:' ],
+       'localurl'                  => [ '0', 'مقامی_ربط:', 'LOCALURL:' ],
+       'localurle'                 => [ '0', 'مقامی_ربط_کوڈ:', 'LOCALURLE:' ],
+       'articlepath'               => [ '0', 'راہ_مضمون', 'ARTICLEPATH' ],
+       'pageid'                    => [ '0', 'شناخت_صفحہ', 'PAGEID' ],
+       'server'                    => [ '0', 'سرور', 'SERVER' ],
+       'servername'                => [ '0', 'نام_سرور', 'SERVERNAME' ],
+       'scriptpath'                => [ '0', 'راہ_اسکرپٹ', 'SCRIPTPATH' ],
+       'stylepath'                 => [ '0', 'راہ_طرز', 'STYLEPATH' ],
        'grammar'                   => [ '0', 'قواعد:', 'GRAMMAR:' ],
-       'gender'                    => [ '0', 'جنس:', 'GENDER:' ],
+       'gender'                    => [ '0', 'صنف:', 'GENDER:' ],
+       'bidi'                      => [ '0', 'بی_ڈی:', 'BIDI:' ],
+       'notitleconvert'            => [ '0', '__منتقلی_عنوان_نہیں__',  '__NOTITLECONVERT__', '__NOTC__' ],
+       'nocontentconvert'          => [ '0', '__منتقلی_مواد_نہیں__', '__NOCONTENTCONVERT__', '__NOCC__' ],
+       'currentweek'               => [ '1', 'موجودہ_ہفتہ', 'CURRENTWEEK' ],
+       'currentdow'                => [ '1', 'ہفتہ_کا_دن', 'CURRENTDOW' ],
+       'localweek'                 => [ '1', 'مقامی_ہفتہ', 'LOCALWEEK' ],
+       'localdow'                  => [ '1', 'مقامی_ہفتہ_کا_دن', 'LOCALDOW' ],
+       'revisionid'                => [ '1', 'شناخت_نسخہ', 'REVISIONID' ],
+       'revisionday'               => [ '1', 'یوم_نسخہ', 'REVISIONDAY' ],
+       'revisionday2'              => [ '1', 'یوم_نسخہ2', 'REVISIONDAY2' ],
+       'revisionmonth'             => [ '1', 'ماہ_نسخہ', 'REVISIONMONTH' ],
+       'revisionmonth1'            => [ '1', 'ماہ_نسخہ1', 'REVISIONMONTH1' ],
+       'revisionyear'              => [ '1', 'سال_نسخہ', 'REVISIONYEAR' ],
+       'revisiontimestamp'         => [ '1', 'مہر_وقت_نسخہ', 'REVISIONTIMESTAMP' ],
+       'revisionuser'              => [ '1', 'صارف_نسخہ', 'REVISIONUSER' ],
+       'revisionsize'              => [ '1', 'حجم_نسخہ', 'REVISIONSIZE' ],
+       'plural'                    => [ '0', 'جمع:', 'PLURAL:' ],
+       'fullurl'                   => [ '0', 'مکمل_ربط:', 'FULLURL:' ],
+       'fullurle'                  => [ '0', 'مکمل_ربط_کوڈ:', 'FULLURLE:' ],
+       'canonicalurl'              => [ '0', 'معیاری_ربط:', 'CANONICALURL:' ],
+       'canonicalurle'             => [ '0', 'معیاری_ربط_کوڈ:', 'CANONICALURLE:' ],
+       'lcfirst'                   => [ '0', 'چھوٹے_حروف_سے_شروع:', 'LCFIRST:' ],
+       'ucfirst'                   => [ '0', 'بڑے_حروف_سے_شروع:', 'UCFIRST:' ],
+       'lc'                        => [ '0', 'چھوٹے_حروف:', 'LC:' ],
+       'uc'                        => [ '0', 'بڑے_حروف:', 'UC:' ],
+       'raw'                       => [ '0', 'خام:', 'RAW:' ],
+       'displaytitle'              => [ '1', 'نمائش_عنوان', 'DISPLAYTITLE' ],
+       'rawsuffix'                 => [ '1', 'آر', 'R' ],
+       'nocommafysuffix'           => [ '0', 'جدا_نہیں', 'NOSEP' ],
+       'newsectionlink'            => [ '1', '__ربط_نیا_قطعہ__', '__NEWSECTIONLINK__' ],
+       'nonewsectionlink'          => [ '1', '__ربط_نیا_قطعہ_نہیں__', '__NONEWSECTIONLINK__' ],
+       'currentversion'            => [ '1', 'موجودہ_نسخہ', 'CURRENTVERSION' ],
+       'urlencode'                 => [ '0', 'ربط_کوڈ:', 'URLENCODE:' ],
+       'anchorencode'              => [ '0', 'اینکر_کوڈ', 'ANCHORENCODE' ],
+       'currenttimestamp'          => [ '1', 'موجودہ_مہر_وقت', 'CURRENTTIMESTAMP' ],
+       'localtimestamp'            => [ '1', 'مقامی_مہر_وقت', 'LOCALTIMESTAMP' ],
+       'directionmark'             => [ '1', 'علامت_جہت', 'DIRECTIONMARK', 'DIRMARK' ],
+       'language'                  => [ '0', '#زبان:', '#LANGUAGE:' ],
+       'contentlanguage'           => [ '1', 'مواد_کی_زبان', 'CONTENTLANGUAGE', 'CONTENTLANG' ],
+       'pagesinnamespace'          => [ '1', 'نام_فضا_میں_صفحات:', 'PAGESINNAMESPACE:', 'PAGESINNS:' ],
+       'numberofadmins'            => [ '1', 'تعداد_منتظمین', 'NUMBEROFADMINS' ],
+       'formatnum'                 => [ '0', 'صیغہ_عدد', 'FORMATNUM' ],
+       'padleft'                   => [ '0', 'بائیں_جوڑیں', 'PADLEFT' ],
+       'padright'                  => [ '0', 'دائیں_جوڑیں', 'PADRIGHT' ],
        'special'                   => [ '0', 'خاص', 'special' ],
-       'speciale'                  => [ '0', 'خاص_عنوان', 'speciale' ],
+       'speciale'                  => [ '0', 'خاص_کوڈ', 'speciale' ],
+       'defaultsort'               => [ '1', 'ابتدائی_ترتیب', 'کلید_ابتدائی_ترتیب', 'ابتدائی_ترتیب_زمرہ', 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ],
+       'filepath'                  => [ '0', 'راہ_فائل:', 'FILEPATH:' ],
+       'tag'                       => [ '0', 'ٹیگ', 'tag' ],
+       'hiddencat'                 => [ '1', '__پوشیدہ_زمرہ__', '__HIDDENCAT__' ],
+       'pagesincategory'           => [ '1', 'زمرے_میں_صفحات', 'PAGESINCATEGORY', 'PAGESINCAT' ],
+       'pagesize'                  => [ '1', 'حجم_صفحہ', 'PAGESIZE' ],
        'index'                     => [ '1', '__اشاریہ__', '__INDEX__' ],
-       'noindex'                   => [ '1', '__نااشاریہ__', '__NOINDEX__' ],
+       'noindex'                   => [ '1', '__اشاریہ_نہیں__', '__NOINDEX__' ],
+       'numberingroup'             => [ '1', 'تعداد_در_گروہ', 'NUMBERINGROUP', 'NUMINGROUP' ],
+       'staticredirect'            => [ '1', '__ساکن_رجوع_مکرر__', '__STATICREDIRECT__' ],
+       'protectionlevel'           => [ '1', 'درجہ_حفاظت', 'PROTECTIONLEVEL' ],
+       'protectionexpiry'          => [ '1', 'اختتام_حفاظت', 'PROTECTIONEXPIRY' ],
+       'cascadingsources'          => [ '1', 'آبشاری_مآخذ', 'CASCADINGSOURCES' ],
+       'formatdate'                => [ '0', 'صیغہ_تاریخ', 'formatdate', 'dateformat' ],
+       'url_path'                  => [ '0', 'راہ', 'PATH' ],
+       'url_wiki'                  => [ '0', 'ویکی', 'WIKI' ],
+       'url_query'                 => [ '0', 'استفسار', 'QUERY' ],
+       'defaultsort_noerror'       => [ '0', 'نقص_نہیں', 'noerror' ],
+       'defaultsort_noreplace'     => [ '0', 'تبدیلی_نہیں', 'noreplace' ],
+       'displaytitle_noerror'      => [ '0', 'نقص_نہیں', 'noerror' ],
+       'displaytitle_noreplace'    => [ '0', 'تبدیلی_نہیں', 'noreplace' ],
+       'pagesincategory_all'       => [ '0', 'کل', 'all' ],
+       'pagesincategory_pages'     => [ '0', 'صفحات', 'pages' ],
+       'pagesincategory_subcats'   => [ '0', 'ذیلی_زمرے', 'subcats' ],
+       'pagesincategory_files'     => [ '0', 'فائلیں', 'files' ],
 ];
+
+# LinkTrail for Urdu language
+$linkTrail = "/^([ابپتٹثجچحخدڈذر​ڑ​زژسشصضطظعغفقکگل​م​نوؤہھیئےآأءۃ]+)(.*)$/sDu";
diff --git a/maintenance/postgres/archives/patch-add-3d.sql b/maintenance/postgres/archives/patch-add-3d.sql
deleted file mode 100644 (file)
index f892755..0000000
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TYPE media_type ADD VALUE '3D';
index 29ddca3..4be45b1 100644 (file)
--- a/phpcs.xml
+++ b/phpcs.xml
        </rule>
        <rule ref="MediaWiki.NamingConventions.PrefixedGlobalFunctions">
                <properties>
-                       <property name="ignoreList" type="array" value="bfNormalizeTitleStrReplace,bfNormalizeTitleStrTr,cdbShowHelp,codepointToUtf8,compare_point,cssfilter,escapeSingleString,findAuxFile,findFiles,getEscapedProfileUrl,getFileCommentFromSourceWiki,getFileUserFromSourceWiki,hexSequenceToUtf8,mccGetHelp,mccShowUsage,mimeTypeMatch,moveToExternal,NothingFunction,NothingFunctionData,resolveStub,resolveStubs,showUsage,splitFilename,utf8ToCodepoint,utf8ToHexSequence" />
+                       <!--
+                       includes/compat/normal/UtfNormalUtil.php
+                       * codepointToUtf8
+                       * escapeSingleString
+                       * hexSequenceToUtf8
+                       * utf8ToCodepoint
+                       * utf8ToHexSequence
+                       includes/GlobalFunctions.php
+                       * mimeTypeMatch
+                       maintenance/benchmarks/bench_strtr_str_replace.php
+                       * bfNormalizeTitleStrReplace
+                       * bfNormalizeTitleStrTr
+                       maintenance/cdb.php
+                       * cdbShowHelp
+                       maintenance/language/transstat.php
+                       * showUsage
+                       maintenance/mcc.php
+                       * mccGetHelp
+                       * mccShowUsage
+                       maintenance/storage/moveToExternal.php
+                       * moveToExternal
+                       maintenance/storage/resolveStubs.php
+                       * resolveStub
+                       * resolveStubs
+                       profileinfo.php
+                       * compare_point
+                       * getEscapedProfileUrl
+                       tests/phpunit/includes/HooksTest.php
+                       * NothingFunction
+                       * NothingFunctionData
+                       tests/qunit/data/styleTest.css.php
+                       * cssfilter
+                       -->
+                       <property name="ignoreList" type="array" value="bfNormalizeTitleStrReplace,bfNormalizeTitleStrTr,cdbShowHelp,codepointToUtf8,compare_point,cssfilter,escapeSingleString,getEscapedProfileUrl,hexSequenceToUtf8,mccGetHelp,mccShowUsage,mimeTypeMatch,moveToExternal,NothingFunction,NothingFunctionData,resolveStub,resolveStubs,showUsage,utf8ToCodepoint,utf8ToHexSequence" />
                </properties>
        </rule>
        <rule ref="MediaWiki.NamingConventions.ValidGlobalName">
index 6d59d5c..4df8194 100644 (file)
@@ -1922,6 +1922,7 @@ return [
                        'jquery.makeCollapsible',
                        'mediawiki.language',
                        'mediawiki.user',
+                       'mediawiki.util',
                        'mediawiki.rcfilters.filters.dm',
                        'oojs-ui.styles.icons-content',
                        'oojs-ui.styles.icons-moderation',
index dc6a366..1d578e4 100644 (file)
        #wpSummaryWidget {
                display: block;
                margin-bottom: 1em;
-               max-width: none;
        }
 
        #wpSummaryLabel {
                margin: 0;
        }
 
+       #wpSummaryWidget,
+       #wpSummaryLabel .oo-ui-fieldLayout-header {
+               max-width: none;
+       }
+
        .editCheckboxes .oo-ui-fieldLayout {
                margin-right: 1em;
        }
index 772ed92..0d65466 100644 (file)
@@ -16,7 +16,6 @@
                this.defaultParams = {};
                this.defaultFiltersEmpty = null;
                this.highlightEnabled = false;
-               this.invertedNamespaces = false;
                this.parameterMap = {};
 
                this.views = {};
         * Highlight feature has been toggled enabled or disabled
         */
 
-       /**
-        * @event invertChange
-        * @param {boolean} isInverted Namespace selected is inverted
-        *
-        * Namespace selection is inverted or straight forward
-        */
-
        /* Methods */
 
        /**
         * Propagate the change to namespace filter items.
         *
         * @param {boolean} enable Inverted property is enabled
-        * @fires invertChange
         */
        mw.rcfilters.dm.FiltersViewModel.prototype.toggleInvertedNamespaces = function ( enable ) {
-               enable = enable === undefined ? !this.invertedNamespaces : enable;
-
-               if ( this.invertedNamespaces !== enable ) {
-                       this.invertedNamespaces = enable;
-
-                       this.getFiltersByView( 'namespaces' ).forEach( function ( filterItem ) {
-                               filterItem.toggleInverted( this.invertedNamespaces );
-                       }.bind( this ) );
-
-                       this.emit( 'invertChange', this.invertedNamespaces );
-               }
+               this.toggleFilterSelected( this.getInvertModel().getName(), enable );
        };
 
        /**
-        * Check if the namespaces selection is set to be inverted
-        * @return {boolean}
+        * Get the model object that represents the 'invert' filter
+        *
+        * @return {mw.rcfilters.dm.FilterItem}
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.areNamespacesInverted = function () {
-               return !!this.invertedNamespaces;
+       mw.rcfilters.dm.FiltersViewModel.prototype.getInvertModel = function () {
+               return this.getGroup( 'invertGroup' ).getItemByParamName( 'invert' );
        };
 
        /**
index 9c56f09..4a8869a 100644 (file)
@@ -14,8 +14,6 @@
         *  with 'default' and 'inverted' as keys.
         * @cfg {boolean} [active=true] The filter is active and affecting the result
         * @cfg {boolean} [selected] The item is selected
-        * @cfg {boolean} [inverted] The item is inverted, meaning the search is excluding
-        *  this parameter.
         * @cfg {string} [namePrefix='item_'] A prefix to add to the param name to act as a unique
         *  identifier
         * @cfg {string} [cssClass] The class identifying the results that match this filter
@@ -38,7 +36,6 @@
                this.description = config.description || '';
                this.selected = !!config.selected;
 
-               this.inverted = !!config.inverted;
                this.identifiers = config.identifiers || [];
 
                // Highlight
@@ -69,8 +66,7 @@
         */
        mw.rcfilters.dm.ItemModel.prototype.getState = function () {
                return {
-                       selected: this.isSelected(),
-                       inverted: this.isInverted()
+                       selected: this.isSelected()
                };
        };
 
        /**
         * Get a prefixed label
         *
+        * @param {boolean} inverted This item should be considered inverted
         * @return {string} Prefixed label
         */
-       mw.rcfilters.dm.ItemModel.prototype.getPrefixedLabel = function () {
+       mw.rcfilters.dm.ItemModel.prototype.getPrefixedLabel = function ( inverted ) {
                if ( this.labelPrefixKey ) {
                        if ( typeof this.labelPrefixKey === 'string' ) {
                                return mw.message( this.labelPrefixKey, this.getLabel() ).parse();
@@ -97,7 +94,7 @@
                                        this.labelPrefixKey[
                                                // Only use inverted-prefix if the item is selected
                                                // Highlight-only an inverted item makes no sense
-                                               this.isInverted() && this.isSelected() ?
+                                               inverted && this.isSelected() ?
                                                        'inverted' : 'default'
                                        ],
                                        this.getLabel()
                }
        };
 
-       /**
-        * Get the inverted state of this item
-        *
-        * @return {boolean} Item is inverted
-        */
-       mw.rcfilters.dm.ItemModel.prototype.isInverted = function () {
-               return this.inverted;
-       };
-
-       /**
-        * Toggle the inverted state of the item
-        *
-        * @param {boolean} [isInverted] Item is inverted
-        * @fires update
-        */
-       mw.rcfilters.dm.ItemModel.prototype.toggleInverted = function ( isInverted ) {
-               isInverted = isInverted === undefined ? !this.inverted : isInverted;
-
-               if ( this.inverted !== isInverted ) {
-                       this.inverted = isInverted;
-                       this.emit( 'update' );
-               }
-       };
-
        /**
         * Set the highlight color
         *
index 2b17897..edb9644 100644 (file)
                        newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
                } );
 
-               // Add highlight and invert toggles to params
+               // Add highlight
                newData.params.highlight = String( Number( highlightEnabled || 0 ) );
-               newData.params.invert = String( Number( data.invert || 0 ) );
 
                return newData;
        };
                        } );
 
                        this.baseParamState = {
-                               params: $.extend( true, { invert: '0', highlight: '0' }, allParams ),
+                               params: $.extend( true, { highlight: '0' }, allParams ),
                                highlights: highlightedItems
                        };
                }
index 3e71729..6da8119 100644 (file)
                                        separator: ';',
                                        fullCoverage: true,
                                        filters: items
+                               },
+                               {
+                                       name: 'invertGroup',
+                                       type: 'boolean',
+                                       hidden: true,
+                                       filters: [ {
+                                               name: 'invert',
+                                               'default': '0'
+                                       } ]
                                } ]
                        };
                }
                                params: $.extend(
                                        true,
                                        {
-                                               invert: String( Number( this.filtersModel.areNamespacesInverted() ) ),
                                                highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
                                        },
                                        this.filtersModel.getParametersFromFilters( selectedState )
                                )
                        );
 
-                       // Update namespace inverted property
-                       this.filtersModel.toggleInvertedNamespaces( !!Number( data.params.invert ) );
-
                        // Update highlight state
                        this.filtersModel.toggleHighlight( !!Number( data.params.highlight ) );
                        this.filtersModel.getItems().forEach( function ( filterItem ) {
                                params: $.extend(
                                        true,
                                        {
-                                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ),
-                                               invert: String( Number( this.filtersModel.areNamespacesInverted() ) )
+                                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
                                        },
                                        this.filtersModel.getParametersFromFilters( selectedState )
                                ),
                        return $.extend( true, {},
                                this.filtersModel.getParametersFromFilters( savedFilters ),
                                data.highlights,
-                               { highlight: data.params.highlight, invert: data.params.invert }
+                               { highlight: data.params.highlight }
                        );
                }
                return this.filtersModel.getDefaultParams();
index c9436f4..0450639 100644 (file)
                        )
                );
 
-               this.filtersModel.toggleInvertedNamespaces( !!Number( parameters.invert ) );
-
                // Update highlight state
                this.filtersModel.getItems().forEach( function ( filterItem ) {
                        var color = parameters[ filterItem.getName() + '_color' ];
                        this.filtersModel.getParametersFromFilters(),
                        this.filtersModel.getHighlightParameters(),
                        {
-                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ),
-                               invert: String( Number( this.filtersModel.areNamespacesInverted() ) )
+                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
                        }
                );
        };
                        this.filtersModel.getParametersFromFilters( filterRepresentation ),
                        this.filtersModel.extractHighlightValues( uriQuery ),
                        {
-                               highlight: String( Number( uriQuery.highlight ) ),
-                               invert: String( Number( uriQuery.invert ) )
+                               highlight: String( Number( uriQuery.highlight ) )
                        }
                );
        };
                        {},
                        emptyParams,
                        emptyHighlights,
-                       { highlight: '0', invert: '0' }
+                       { highlight: '0' }
                );
        };
 }( mediaWiki, jQuery ) );
index 83fe189..f9b32a2 100644 (file)
                position: absolute;
                top: 50%;
                .transform( translateY( -50% ) );
+
+               // HACK: Following overrides help icon size and centers it
+               &.oo-ui-widget.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+                       box-sizing: content-box;
+                       padding: 0;
+
+                       .oo-ui-icon-help {
+                               min-width: initial;
+                               min-height: initial;
+                               width: 1.4em;
+                               height: 1.4em;
+                               margin-top: 0.2375em;
+                       }
+               }
        }
 
        &-header {
index 1a0c5ff..dceb132 100644 (file)
@@ -41,7 +41,7 @@
                        framed: false,
                        title: mw.msg( 'rcfilters-view-tags-help-icon-tooltip' ),
                        classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-helpIcon' ],
-                       href: 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:New_filters_for_edit_review/Advanced_filters#tags',
+                       href: mw.util.getUrl( 'Special:Tags' ),
                        target: '_blank'
                } );
                this.helpIcon.toggle( this.model.getCurrentView() === 'tags' );
@@ -59,7 +59,6 @@
                        classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-invertNamespacesButton' ]
                } );
                this.invertNamespacesButton.toggle( this.model.getCurrentView() === 'namespaces' );
-               this.updateInvertButton( this.model.areNamespacesInverted() );
 
                // Events
                this.backButton.connect( this, { click: 'onBackButtonClick' } );
@@ -69,8 +68,8 @@
                        .connect( this, { click: 'onInvertNamespacesButtonClick' } );
                this.model.connect( this, {
                        highlightChange: 'onModelHighlightChange',
-                       invertChange: 'onModelInvertChange',
-                       update: 'onModelUpdate'
+                       update: 'onModelUpdate',
+                       initialize: 'onModelInitialize'
                } );
 
                // Initialize
 
        /* Methods */
 
+       /**
+        * Respond to model initialization event
+        *
+        * Note: need to wait for initialization before getting the invertModel
+        * and registering its update event. Creating all the models before the UI
+        * would help with that.
+        */
+       mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelInitialize = function () {
+               this.invertModel = this.model.getInvertModel();
+               this.updateInvertButton();
+               this.invertModel.connect( this, { update: 'updateInvertButton' } );
+       };
+
        /**
         * Respond to model update event
         */
                this.highlightButton.setActive( highlightEnabled );
        };
 
-       /**
-        * Respond to model invert change event
-        *
-        * @param {boolean} isInverted Namespaces selection is inverted
-        */
-       mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelInvertChange = function ( isInverted ) {
-               this.updateInvertButton( isInverted );
-       };
-
        /**
         * Update the state of the invert button
-        *
-        * @param {boolean} isInverted Namespaces selection is inverted
         */
-       mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.updateInvertButton = function ( isInverted ) {
-               this.invertNamespacesButton.setActive( isInverted );
+       mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.updateInvertButton = function () {
+               this.invertNamespacesButton.setActive( this.invertModel.isSelected() );
                this.invertNamespacesButton.setLabel(
-                       isInverted ?
+                       this.invertModel.isSelected() ?
                                mw.msg( 'rcfilters-exclude-button-on' ) :
                                mw.msg( 'rcfilters-exclude-button-off' )
                );
index 5198c69..1292901 100644 (file)
@@ -6,17 +6,19 @@
         *
         * @constructor
         * @param {mw.rcfilters.Controller} controller RCFilters controller
+        * @param {mw.rcfilters.dm.FilterItem} invertModel
         * @param {mw.rcfilters.dm.FilterItem} model Filter item model
         * @param {Object} config Configuration object
         */
-       mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, model, config ) {
+       mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, invertModel, model, config ) {
                config = config || {};
 
                this.controller = controller;
+               this.invertModel = invertModel;
                this.model = model;
 
                // Parent
-               mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, controller, model, config );
+               mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, controller, this.invertModel, model, config );
 
                // Event
                this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
@@ -58,7 +60,7 @@
        mw.rcfilters.ui.FilterMenuOptionWidget.prototype.setCurrentMuteState = function () {
                if (
                        this.model.getGroupModel().getView() === 'namespaces' &&
-                       this.model.isInverted()
+                       this.invertModel.isSelected()
                ) {
                        // This is an inverted behavior than the other rules, specifically
                        // for inverted namespaces
index 8a36eb4..43a301f 100644 (file)
@@ -7,13 +7,14 @@
         *
         * @constructor
         * @param {mw.rcfilters.Controller} controller
+        * @param {mw.rcfilters.dm.FilterItem} invertModel
         * @param {mw.rcfilters.dm.FilterItem} model Item model
         * @param {Object} config Configuration object
         */
-       mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, model, config ) {
+       mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, invertModel, model, config ) {
                config = config || {};
 
-               mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, controller, model, config );
+               mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, controller, invertModel, model, config );
 
                this.$element
                        .addClass( 'mw-rcfilters-ui-filterTagItemWidget' );
index 757a000..ef95f2f 100644 (file)
         * @param {mw.rcfilters.dm.FilterItem} item Filter item model
         */
        mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
-               if ( item.getGroupModel().isHidden() ) {
-                       return;
-               }
-
-               if (
-                       item.isSelected() ||
-                       (
-                               this.model.isHighlightEnabled() &&
-                               item.isHighlightSupported() &&
-                               item.getHighlightColor()
-                       )
-               ) {
-                       this.addTag( item.getName(), item.getLabel() );
-               } else {
-                       this.removeTagByData( item.getName() );
+               if ( !item.getGroupModel().isHidden() ) {
+                       if (
+                               item.isSelected() ||
+                               (
+                                       this.model.isHighlightEnabled() &&
+                                       item.isHighlightSupported() &&
+                                       item.getHighlightColor()
+                               )
+                       ) {
+                               this.addTag( item.getName(), item.getLabel() );
+                       } else {
+                               this.removeTagByData( item.getName() );
+                       }
                }
 
                this.setSavedQueryVisibility();
                if ( filterItem ) {
                        return new mw.rcfilters.ui.FilterTagItemWidget(
                                this.controller,
+                               this.model.getInvertModel(),
                                filterItem,
                                {
                                        $overlay: this.$overlay
index f2e9b1d..36bc6cb 100644 (file)
@@ -6,10 +6,11 @@
         *
         * @constructor
         * @param {mw.rcfilters.Controller} controller RCFilters controller
+        * @param {mw.rcfilters.dm.ItemModel} invertModel
         * @param {mw.rcfilters.dm.ItemModel} model Item model
         * @param {Object} config Configuration object
         */
-       mw.rcfilters.ui.ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget( controller, model, config ) {
+       mw.rcfilters.ui.ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget( controller, invertModel, model, config ) {
                var layout,
                        classes = [],
                        $label = $( '<div>' )
@@ -18,6 +19,7 @@
                config = config || {};
 
                this.controller = controller;
+               this.invertModel = invertModel;
                this.model = model;
 
                // Parent
@@ -59,7 +61,7 @@
                this.excludeLabel = new OO.ui.LabelWidget( {
                        label: mw.msg( 'rcfilters-filter-excluded' )
                } );
-               this.excludeLabel.toggle( this.model.isSelected() && this.model.isInverted() );
+               this.excludeLabel.toggle( this.model.isSelected() && this.invertModel.isSelected() );
 
                layout = new OO.ui.FieldLayout( this.checkboxWidget, {
                        label: $label,
@@ -67,6 +69,7 @@
                } );
 
                // Events
+               this.invertModel.connect( this, { update: 'onModelUpdate' } );
                this.model.connect( this, { update: 'onModelUpdate' } );
                // HACK: Prevent defaults on 'click' for the label so it
                // doesn't steal the focus away from the input. This means
                this.checkboxWidget.setSelected( this.model.isSelected() );
 
                this.highlightButton.toggle( this.model.isHighlightEnabled() );
-               this.excludeLabel.toggle( this.model.isSelected() && this.model.isInverted() );
+               this.excludeLabel.toggle( this.model.isSelected() && this.invertModel.isSelected() );
        };
 
        /**
index 2aabe68..63a563c 100644 (file)
                                        currentItems.push(
                                                new mw.rcfilters.ui.FilterMenuOptionWidget(
                                                        widget.controller,
+                                                       widget.model.getInvertModel(),
                                                        filterItem,
                                                        {
                                                                $overlay: widget.$overlay
index 81889b2..cc314ac 100644 (file)
@@ -8,21 +8,22 @@
         *
         * @constructor
         * @param {mw.rcfilters.Controller} controller
+        * @param {mw.rcfilters.dm.FilterItem} invertModel
         * @param {mw.rcfilters.dm.FilterItem} model Item model
         * @param {Object} config Configuration object
         * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
         */
-       mw.rcfilters.ui.TagItemWidget = function MwRcfiltersUiTagItemWidget( controller, model, config ) {
+       mw.rcfilters.ui.TagItemWidget = function MwRcfiltersUiTagItemWidget( controller, invertModel, model, config ) {
                // Configuration initialization
                config = config || {};
 
                this.controller = controller;
+               this.invertModel = invertModel;
                this.model = model;
                this.selected = false;
 
                mw.rcfilters.ui.TagItemWidget.parent.call( this, $.extend( {
-                       data: this.model.getName(),
-                       label: $( '<div>' ).html( this.model.getPrefixedLabel() ).contents()
+                       data: this.model.getName()
                }, config ) );
 
                this.$overlay = config.$overlay || this.$element;
@@ -52,7 +53,8 @@
                this.closeButton.setTitle( mw.msg( 'rcfilters-tag-remove', this.model.getLabel() ) );
 
                // Events
-               this.model.connect( this, { update: 'onModelUpdate' } );
+               this.invertModel.connect( this, { update: 'updateUiBasedOnState' } );
+               this.model.connect( this, { update: 'updateUiBasedOnState' } );
 
                // Initialization
                this.$overlay.append( this.popup.$element );
@@ -63,8 +65,7 @@
                        .on( 'mouseenter', this.onMouseEnter.bind( this ) )
                        .on( 'mouseleave', this.onMouseLeave.bind( this ) );
 
-               this.setCurrentMuteState();
-               this.setHighlightColor();
+               this.updateUiBasedOnState();
        };
 
        /* Initialization */
        /**
         * Respond to model update event
         */
-       mw.rcfilters.ui.TagItemWidget.prototype.onModelUpdate = function () {
+       mw.rcfilters.ui.TagItemWidget.prototype.updateUiBasedOnState = function () {
                this.setCurrentMuteState();
 
                // Update label if needed
-               this.setLabel( $( '<div>' ).html( this.model.getPrefixedLabel() ).contents() );
+               this.setLabel( $( '<div>' ).html( this.model.getPrefixedLabel( this.invertModel.isSelected() ) ).contents() );
 
                this.setHighlightColor();
        };
index 8f752df..a965d3e 100644 (file)
@@ -64,6 +64,8 @@ $wgAutoloadClasses += [
        'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
 
        # tests/phpunit/includes
+       'RevisionTestModifyableContent' => "$testDir/phpunit/includes/RevisionTestModifyableContent.php",
+       'RevisionTestModifyableContentHandler' => "$testDir/phpunit/includes/RevisionTestModifyableContentHandler.php",
        'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
        'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
 
index b0f7678..9369f30 100644 (file)
@@ -618,7 +618,7 @@ class CommentStoreTest extends MediaWikiLangTestCase {
        public function testInsertTruncation() {
                $comment = str_repeat( '💣', 16400 );
                $truncated1 = str_repeat( '💣', 63 ) . '...';
-               $truncated2 = str_repeat( '💣', 16383 ) . '...';
+               $truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
 
                $store = $this->makeStore( MIGRATION_WRITE_BOTH, 'ipb_reason' );
                $fields = $store->insert( $this->db, $comment );
index 8ed85fe..a15b9b4 100644 (file)
@@ -16,7 +16,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        private $the_page;
 
-       function __construct( $name = null, array $data = [], $dataName = '' ) {
+       public function __construct( $name = null, array $data = [], $dataName = '' ) {
                parent::__construct( $name, $data, $dataName );
 
                $this->tablesUsed = array_merge( $this->tablesUsed,
@@ -39,18 +39,35 @@ class RevisionStorageTest extends MediaWikiTestCase {
        }
 
        protected function setUp() {
-               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+               global $wgContLang;
 
                parent::setUp();
 
-               $wgExtraNamespaces[12312] = 'Dummy';
-               $wgExtraNamespaces[12313] = 'Dummy_talk';
+               $this->mergeMwGlobalArrayValue(
+                       'wgExtraNamespaces',
+                       [
+                               12312 => 'Dummy',
+                               12313 => 'Dummy_talk',
+                       ]
+               );
 
-               $wgNamespaceContentModels[12312] = 'DUMMY';
-               $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
+               $this->mergeMwGlobalArrayValue(
+                       'wgNamespaceContentModels',
+                       [
+                               12312 => 'DUMMY',
+                       ]
+               );
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgContentHandlers',
+                       [
+                               'DUMMY' => 'DummyContentHandlerForTesting',
+                       ]
+               );
 
                MWNamespace::clearCaches();
-               $wgContLang->resetNamespaces(); # reset namespace cache
+               // Reset namespace cache
+               $wgContLang->resetNamespaces();
                if ( !$this->the_page ) {
                        $this->the_page = $this->createPage(
                                'RevisionStorageTest_the_page',
@@ -63,18 +80,13 @@ class RevisionStorageTest extends MediaWikiTestCase {
        }
 
        protected function tearDown() {
-               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+               global $wgContLang;
 
                parent::tearDown();
 
-               unset( $wgExtraNamespaces[12312] );
-               unset( $wgExtraNamespaces[12313] );
-
-               unset( $wgNamespaceContentModels[12312] );
-               unset( $wgContentHandlers['DUMMY'] );
-
                MWNamespace::clearCaches();
-               $wgContLang->resetNamespaces(); # reset namespace cache
+               // Reset namespace cache
+               $wgContLang->resetNamespaces();
        }
 
        protected function makeRevision( $props = null ) {
@@ -102,30 +114,33 @@ class RevisionStorageTest extends MediaWikiTestCase {
                return $rev;
        }
 
-       protected function createPage( $page, $text, $model = null ) {
-               if ( is_string( $page ) ) {
-                       if ( !preg_match( '/:/', $page ) &&
-                               ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
-                       ) {
-                               $ns = $this->getDefaultWikitextNS();
-                               $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page;
-                       }
-
-                       $page = Title::newFromText( $page );
+       /**
+        * @param string $titleString
+        * @param string $text
+        * @param string|null $model
+        *
+        * @return WikiPage
+        */
+       protected function createPage( $titleString, $text, $model = null ) {
+               if ( !preg_match( '/:/', $titleString ) &&
+                       ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
+               ) {
+                       $ns = $this->getDefaultWikitextNS();
+                       $titleString = MWNamespace::getCanonicalName( $ns ) . ':' . $titleString;
                }
 
-               if ( $page instanceof Title ) {
-                       $page = new WikiPage( $page );
-               }
+               $title = Title::newFromText( $titleString );
+               $wikipage = new WikiPage( $title );
 
-               if ( $page->exists() ) {
-                       $page->doDeleteArticle( "done" );
+               // Delete the article if it already exists
+               if ( $wikipage->exists() ) {
+                       $wikipage->doDeleteArticle( "done" );
                }
 
-               $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
-               $page->doEditContent( $content, "testing", EDIT_NEW );
+               $content = ContentHandler::makeContent( $text, $title, $model );
+               $wikipage->doEditContent( $content, __METHOD__, EDIT_NEW );
 
-               return $page;
+               return $wikipage;
        }
 
        protected function assertRevEquals( Revision $orig, Revision $rev = null ) {
@@ -158,6 +173,56 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $this->assertRevEquals( $orig, $rev );
        }
 
+       /**
+        * @covers Revision::newFromTitle
+        */
+       public function testNewFromTitle_withoutId() {
+               $page = $this->createPage(
+                       __METHOD__,
+                       'GOAT',
+                       CONTENT_MODEL_WIKITEXT
+               );
+               $latestRevId = $page->getLatest();
+
+               $rev = Revision::newFromTitle( $page->getTitle() );
+
+               $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
+               $this->assertEquals( $latestRevId, $rev->getId() );
+       }
+
+       /**
+        * @covers Revision::newFromTitle
+        */
+       public function testNewFromTitle_withId() {
+               $page = $this->createPage(
+                       __METHOD__,
+                       'GOAT',
+                       CONTENT_MODEL_WIKITEXT
+               );
+               $latestRevId = $page->getLatest();
+
+               $rev = Revision::newFromTitle( $page->getTitle(), $latestRevId );
+
+               $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
+               $this->assertEquals( $latestRevId, $rev->getId() );
+       }
+
+       /**
+        * @covers Revision::newFromTitle
+        */
+       public function testNewFromTitle_withBadId() {
+               $page = $this->createPage(
+                       __METHOD__,
+                       'GOAT',
+                       CONTENT_MODEL_WIKITEXT
+               );
+               $latestRevId = $page->getLatest();
+
+               $rev = Revision::newFromTitle( $page->getTitle(), $latestRevId + 1 );
+
+               $this->assertNull( $rev );
+       }
+
        /**
         * @covers Revision::newFromRow
         */
@@ -461,20 +526,10 @@ class RevisionStorageTest extends MediaWikiTestCase {
        }
 
        public static function provideUserWasLastToEdit() {
-               return [
-                       [ # 0
-                               3, true, # actually the last edit
-                       ],
-                       [ # 1
-                               2, true, # not the current edit, but still by this user
-                       ],
-                       [ # 2
-                               1, false, # edit by another user
-                       ],
-                       [ # 3
-                               0, false, # first edit, by this user, but another user edited in the mean time
-                       ],
-               ];
+               yield 'actually the last edit' => [ 3, true ];
+               yield 'not the current edit, but still by this user' => [ 2, true ];
+               yield 'edit by another user' => [ 1, false ];
+               yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
        }
 
        /**
@@ -502,7 +557,6 @@ class RevisionStorageTest extends MediaWikiTestCase {
                        'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
                $page->insertOn( $dbw );
 
-               # zero
                $revisions[0] = new Revision( [
                        'page' => $page->getId(),
                        // we need the title to determine the page's default content model
@@ -515,7 +569,6 @@ class RevisionStorageTest extends MediaWikiTestCase {
                ] );
                $revisions[0]->insertOn( $dbw );
 
-               # one
                $revisions[1] = new Revision( [
                        'page' => $page->getId(),
                        // still need the title, because $page->getId() is 0 (there's no entry in the page table)
@@ -528,7 +581,6 @@ class RevisionStorageTest extends MediaWikiTestCase {
                ] );
                $revisions[1]->insertOn( $dbw );
 
-               # two
                $revisions[2] = new Revision( [
                        'page' => $page->getId(),
                        'title' => $page->getTitle(),
@@ -540,7 +592,6 @@ class RevisionStorageTest extends MediaWikiTestCase {
                ] );
                $revisions[2]->insertOn( $dbw );
 
-               # three
                $revisions[3] = new Revision( [
                        'page' => $page->getId(),
                        'title' => $page->getTitle(),
@@ -552,7 +603,6 @@ class RevisionStorageTest extends MediaWikiTestCase {
                ] );
                $revisions[3]->insertOn( $dbw );
 
-               # four
                $revisions[4] = new Revision( [
                        'page' => $page->getId(),
                        'title' => $page->getTitle(),
diff --git a/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php b/tests/phpunit/includes/RevisionStorageTestContentHandlerUseDB.php
deleted file mode 100644 (file)
index 9e667f2..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- */
-class RevisionTestContentHandlerUseDB extends RevisionStorageTest {
-
-       protected function setUp() {
-               $this->setMwGlobals( 'wgContentHandlerUseDB', false );
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               $page_table = $dbw->tableName( 'page' );
-               $revision_table = $dbw->tableName( 'revision' );
-               $archive_table = $dbw->tableName( 'archive' );
-
-               if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
-                       $dbw->query( "alter table $page_table drop column page_content_model" );
-                       $dbw->query( "alter table $revision_table drop column rev_content_model" );
-                       $dbw->query( "alter table $revision_table drop column rev_content_format" );
-                       $dbw->query( "alter table $archive_table drop column ar_content_model" );
-                       $dbw->query( "alter table $archive_table drop column ar_content_format" );
-               }
-
-               parent::setUp();
-       }
-
-       /**
-        * @covers Revision::selectFields
-        */
-       public function testSelectFields() {
-               $fields = Revision::selectFields();
-
-               $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
-               $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
-               $this->assertTrue(
-                       in_array( 'rev_timestamp', $fields ),
-                       'missing rev_timestamp in list of fields'
-               );
-               $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
-
-               $this->assertFalse(
-                       in_array( 'rev_content_model', $fields ),
-                       'missing rev_content_model in list of fields'
-               );
-               $this->assertFalse(
-                       in_array( 'rev_content_format', $fields ),
-                       'missing rev_content_format in list of fields'
-               );
-       }
-
-       /**
-        * @covers Revision::getContentModel
-        */
-       public function testGetContentModel() {
-               try {
-                       $this->makeRevision( [ 'text' => 'hello hello.',
-                               'content_model' => CONTENT_MODEL_JAVASCRIPT ] );
-
-                       $this->fail( "Creating JavaScript content on a wikitext page should fail with "
-                               . "\$wgContentHandlerUseDB disabled" );
-               } catch ( MWException $ex ) {
-                       $this->assertTrue( true ); // ok
-               }
-       }
-
-       /**
-        * @covers Revision::getContentFormat
-        */
-       public function testGetContentFormat() {
-               try {
-                       // @todo change this to test failure on using a non-standard (but supported) format
-                       //       for a content model supported in the given location. As of 1.21, there are
-                       //       no alternative formats for any of the standard content models that could be
-                       //       used for this though.
-
-                       $this->makeRevision( [ 'text' => 'hello hello.',
-                               'content_model' => CONTENT_MODEL_JAVASCRIPT,
-                               'content_format' => 'text/javascript' ] );
-
-                       $this->fail( "Creating JavaScript content on a wikitext page should fail with "
-                               . "\$wgContentHandlerUseDB disabled" );
-               } catch ( MWException $ex ) {
-                       $this->assertTrue( true ); // ok
-               }
-       }
-}
index 386f219..ef4d127 100644 (file)
@@ -4,6 +4,7 @@
  * @group ContentHandler
  */
 class RevisionTest extends MediaWikiTestCase {
+
        protected function setUp() {
                global $wgContLang;
 
@@ -42,98 +43,173 @@ class RevisionTest extends MediaWikiTestCase {
                );
 
                MWNamespace::clearCaches();
-               $wgContLang->resetNamespaces(); # reset namespace cache
+               // Reset namespace cache
+               $wgContLang->resetNamespaces();
        }
 
-       function tearDown() {
+       protected function tearDown() {
                global $wgContLang;
 
                MWNamespace::clearCaches();
-               $wgContLang->resetNamespaces(); # reset namespace cache
+               // Reset namespace cache
+               $wgContLang->resetNamespaces();
 
                parent::tearDown();
        }
 
+       public function provideConstructFromArray() {
+               yield 'with text' => [
+                       [
+                               'text' => 'hello world.',
+                               'content_model' => CONTENT_MODEL_JAVASCRIPT
+                       ],
+               ];
+               yield 'with content' => [
+                       [
+                               'content' => new JavaScriptContent( 'hellow world.' )
+                       ],
+               ];
+       }
+
        /**
-        * @covers Revision::getRevisionText
+        * @dataProvider provideConstructFromArray
         */
-       public function testGetRevisionText() {
-               $row = new stdClass;
-               $row->old_flags = '';
-               $row->old_text = 'This is a bunch of revision text.';
-               $this->assertEquals(
-                       'This is a bunch of revision text.',
-                       Revision::getRevisionText( $row ) );
+       public function testConstructFromArray( $rowArray ) {
+               $rev = new Revision( $rowArray );
+               $this->assertNotNull( $rev->getContent(), 'no content object available' );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+       }
+
+       public function provideConstructFromArrayThrowsExceptions() {
+               yield 'content and text_id both not empty' => [
+                       [
+                               'content' => new WikitextContent( 'GOAT' ),
+                               'text_id' => 'someid',
+                               ],
+                       new MWException( "Text already stored in external store (id someid), " .
+                               "can't serialize content object" )
+               ];
+               yield 'with bad content object (class)' => [
+                       [ 'content' => new stdClass() ],
+                       new MWException( '`content` field must contain a Content object.' )
+               ];
+               yield 'with bad content object (string)' => [
+                       [ 'content' => 'ImAGoat' ],
+                       new MWException( '`content` field must contain a Content object.' )
+               ];
+               yield 'bad row format' => [
+                       'imastring, not a row',
+                       new MWException( 'Revision constructor passed invalid row format.' )
+               ];
        }
 
        /**
-        * @covers Revision::getRevisionText
+        * @dataProvider provideConstructFromArrayThrowsExceptions
         */
-       public function testGetRevisionTextGzip() {
-               $this->checkPHPExtension( 'zlib' );
+       public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
+               $this->setExpectedException(
+                       get_class( $expectedException ),
+                       $expectedException->getMessage(),
+                       $expectedException->getCode()
+               );
+               new Revision( $rowArray );
+       }
 
-               $row = new stdClass;
-               $row->old_flags = 'gzip';
-               $row->old_text = gzdeflate( 'This is a bunch of revision text.' );
-               $this->assertEquals(
-                       'This is a bunch of revision text.',
-                       Revision::getRevisionText( $row ) );
+       public function provideGetRevisionText() {
+               yield 'Generic test' => [
+                       'This is a goat of revision text.',
+                       [
+                               'old_flags' => '',
+                               'old_text' => 'This is a goat of revision text.',
+                       ],
+               ];
        }
 
        /**
         * @covers Revision::getRevisionText
+        * @dataProvider provideGetRevisionText
         */
-       public function testGetRevisionTextUtf8Native() {
-               $row = new stdClass;
-               $row->old_flags = 'utf-8';
-               $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
-               $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
+       public function testGetRevisionText( $expected, $rowData, $prefix = 'old_', $wiki = false ) {
                $this->assertEquals(
-                       "Wiki est l'\xc3\xa9cole superieur !",
-                       Revision::getRevisionText( $row ) );
+                       $expected,
+                       Revision::getRevisionText( (object)$rowData, $prefix, $wiki ) );
+       }
+
+       public function provideGetRevisionTextWithZlibExtension() {
+               yield 'Generic gzip test' => [
+                       'This is a small goat of revision text.',
+                       [
+                               'old_flags' => 'gzip',
+                               'old_text' => gzdeflate( 'This is a small goat of revision text.' ),
+                       ],
+               ];
        }
 
        /**
         * @covers Revision::getRevisionText
+        * @dataProvider provideGetRevisionTextWithZlibExtension
         */
-       public function testGetRevisionTextUtf8Legacy() {
-               $row = new stdClass;
-               $row->old_flags = '';
-               $row->old_text = "Wiki est l'\xe9cole superieur !";
-               $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
-               $this->assertEquals(
+       public function testGetRevisionWithZlibExtension( $expected, $rowData ) {
+               $this->checkPHPExtension( 'zlib' );
+               $this->testGetRevisionText( $expected, $rowData );
+       }
+
+       public function provideGetRevisionTextWithLegacyEncoding() {
+               yield 'Utf8Native' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       Revision::getRevisionText( $row ) );
+                       'iso-8859-1',
+                       [
+                               'old_flags' => 'utf-8',
+                               'old_text' => "Wiki est l'\xc3\xa9cole superieur !",
+                       ]
+               ];
+               yield 'Utf8Legacy' => [
+                       "Wiki est l'\xc3\xa9cole superieur !",
+                       'iso-8859-1',
+                       [
+                               'old_flags' => '',
+                               'old_text' => "Wiki est l'\xe9cole superieur !",
+                       ]
+               ];
        }
 
        /**
         * @covers Revision::getRevisionText
+        * @dataProvider provideGetRevisionTextWithLegacyEncoding
         */
-       public function testGetRevisionTextUtf8NativeGzip() {
-               $this->checkPHPExtension( 'zlib' );
+       public function testGetRevisionWithLegacyEncoding( $expected, $encoding, $rowData ) {
+               $GLOBALS['wgLegacyEncoding'] = $encoding;
+               $this->testGetRevisionText( $expected, $rowData );
+       }
 
-               $row = new stdClass;
-               $row->old_flags = 'gzip,utf-8';
-               $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" );
-               $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
-               $this->assertEquals(
+       public function provideGetRevisionTextWithGzipAndLegacyEncoding() {
+               yield 'Utf8NativeGzip' => [
+                       "Wiki est l'\xc3\xa9cole superieur !",
+                       'iso-8859-1',
+                       [
+                               'old_flags' => 'gzip,utf-8',
+                               'old_text' => gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ),
+                       ]
+               ];
+               yield 'Utf8LegacyGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       Revision::getRevisionText( $row ) );
+                       'iso-8859-1',
+                       [
+                               'old_flags' => 'gzip',
+                               'old_text' => gzdeflate( "Wiki est l'\xe9cole superieur !" ),
+                       ]
+               ];
        }
 
        /**
         * @covers Revision::getRevisionText
+        * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
         */
-       public function testGetRevisionTextUtf8LegacyGzip() {
+       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $encoding, $rowData ) {
                $this->checkPHPExtension( 'zlib' );
-
-               $row = new stdClass;
-               $row->old_flags = 'gzip';
-               $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" );
-               $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
-               $this->assertEquals(
-                       "Wiki est l'\xc3\xa9cole superieur !",
-                       Revision::getRevisionText( $row ) );
+               $GLOBALS['wgLegacyEncoding'] = $encoding;
+               $this->testGetRevisionText( $expected, $rowData );
        }
 
        /**
@@ -173,8 +249,6 @@ class RevisionTest extends MediaWikiTestCase {
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
 
-       # =========================================================================
-
        /**
         * @param string $text
         * @param string $title
@@ -183,7 +257,7 @@ class RevisionTest extends MediaWikiTestCase {
         *
         * @return Revision
         */
-       function newTestRevision( $text, $title = "Test",
+       private function newTestRevision( $text, $title = "Test",
                $model = CONTENT_MODEL_WIKITEXT, $format = null
        ) {
                if ( is_string( $title ) ) {
@@ -210,7 +284,7 @@ class RevisionTest extends MediaWikiTestCase {
                return $rev;
        }
 
-       function dataGetContentModel() {
+       public function provideGetContentModel() {
                // NOTE: we expect the help namespace to always contain wikitext
                return [
                        [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
@@ -221,7 +295,7 @@ class RevisionTest extends MediaWikiTestCase {
 
        /**
         * @group Database
-        * @dataProvider dataGetContentModel
+        * @dataProvider provideGetContentModel
         * @covers Revision::getContentModel
         */
        public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
@@ -230,7 +304,7 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedModel, $rev->getContentModel() );
        }
 
-       function dataGetContentFormat() {
+       public function provideGetContentFormat() {
                // NOTE: we expect the help namespace to always contain wikitext
                return [
                        [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
@@ -242,7 +316,7 @@ class RevisionTest extends MediaWikiTestCase {
 
        /**
         * @group Database
-        * @dataProvider dataGetContentFormat
+        * @dataProvider provideGetContentFormat
         * @covers Revision::getContentFormat
         */
        public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
@@ -251,7 +325,7 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
        }
 
-       function dataGetContentHandler() {
+       public function provideGetContentHandler() {
                // NOTE: we expect the help namespace to always contain wikitext
                return [
                        [ 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ],
@@ -262,7 +336,7 @@ class RevisionTest extends MediaWikiTestCase {
 
        /**
         * @group Database
-        * @dataProvider dataGetContentHandler
+        * @dataProvider provideGetContentHandler
         * @covers Revision::getContentHandler
         */
        public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
@@ -271,7 +345,7 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
        }
 
-       function dataGetContent() {
+       public function provideGetContent() {
                // NOTE: we expect the help namespace to always contain wikitext
                return [
                        [ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
@@ -296,7 +370,7 @@ class RevisionTest extends MediaWikiTestCase {
 
        /**
         * @group Database
-        * @dataProvider dataGetContent
+        * @dataProvider provideGetContent
         * @covers Revision::getContent
         */
        public function testGetContent( $text, $title, $model, $format,
@@ -311,7 +385,7 @@ class RevisionTest extends MediaWikiTestCase {
                );
        }
 
-       public function dataGetSize() {
+       public function provideGetSize() {
                return [
                        [ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
                        [ serialize( "hello world." ), "testing", 12 ],
@@ -321,14 +395,14 @@ class RevisionTest extends MediaWikiTestCase {
        /**
         * @covers Revision::getSize
         * @group Database
-        * @dataProvider dataGetSize
+        * @dataProvider provideGetSize
         */
        public function testGetSize( $text, $model, $expected_size ) {
                $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
                $this->assertEquals( $expected_size, $rev->getSize() );
        }
 
-       public function dataGetSha1() {
+       public function provideGetSha1() {
                return [
                        [ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
                        [
@@ -342,42 +416,13 @@ class RevisionTest extends MediaWikiTestCase {
        /**
         * @covers Revision::getSha1
         * @group Database
-        * @dataProvider dataGetSha1
+        * @dataProvider provideGetSha1
         */
        public function testGetSha1( $text, $model, $expected_hash ) {
                $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
                $this->assertEquals( $expected_hash, $rev->getSha1() );
        }
 
-       /**
-        * @covers Revision::__construct
-        */
-       public function testConstructWithText() {
-               $rev = new Revision( [
-                       'text' => 'hello world.',
-                       'content_model' => CONTENT_MODEL_JAVASCRIPT
-               ] );
-
-               $this->assertNotNull( $rev->getContent(), 'no content object available' );
-               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
-               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
-       }
-
-       /**
-        * @covers Revision::__construct
-        */
-       public function testConstructWithContent() {
-               $title = Title::newFromText( 'RevisionTest_testConstructWithContent' );
-
-               $rev = new Revision( [
-                       'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ),
-               ] );
-
-               $this->assertNotNull( $rev->getContent(), 'no content object available' );
-               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
-               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
-       }
-
        /**
         * Tests whether $rev->getContent() returns a clone when needed.
         *
@@ -400,9 +445,11 @@ class RevisionTest extends MediaWikiTestCase {
                        ]
                );
 
+               /** @var RevisionTestModifyableContent $content */
                $content = $rev->getContent( Revision::RAW );
                $content->setText( "bar" );
 
+               /** @var RevisionTestModifyableContent $content2 */
                $content2 = $rev->getContent( Revision::RAW );
                // content is mutable, expect clone
                $this->assertNotSame( $content, $content2, "expected a clone" );
@@ -410,7 +457,8 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertEquals( "foo", $content2->getText() );
 
                $content2->setText( "bla bla" );
-               $this->assertEquals( "bar", $content->getText() ); // clones should be independent
+               // clones should be independent
+               $this->assertEquals( "bar", $content->getText() );
        }
 
        /**
@@ -428,38 +476,3 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertSame( $content, $content2 );
        }
 }
-
-class RevisionTestModifyableContent extends TextContent {
-       public function __construct( $text ) {
-               parent::__construct( $text, "RevisionTestModifyableContent" );
-       }
-
-       public function copy() {
-               return new RevisionTestModifyableContent( $this->mText );
-       }
-
-       public function getText() {
-               return $this->mText;
-       }
-
-       public function setText( $text ) {
-               $this->mText = $text;
-       }
-}
-
-class RevisionTestModifyableContentHandler extends TextContentHandler {
-
-       public function __construct() {
-               parent::__construct( "RevisionTestModifyableContent", [ CONTENT_FORMAT_TEXT ] );
-       }
-
-       public function unserializeContent( $text, $format = null ) {
-               $this->checkFormat( $format );
-
-               return new RevisionTestModifyableContent( $text );
-       }
-
-       public function makeEmptyContent() {
-               return new RevisionTestModifyableContent( '' );
-       }
-}
diff --git a/tests/phpunit/includes/RevisionTestModifyableContent.php b/tests/phpunit/includes/RevisionTestModifyableContent.php
new file mode 100644 (file)
index 0000000..11e4e18
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+class RevisionTestModifyableContent extends TextContent {
+
+       public function __construct( $text ) {
+               parent::__construct( $text, "RevisionTestModifyableContent" );
+       }
+
+       public function copy() {
+               return new RevisionTestModifyableContent( $this->mText );
+       }
+
+       public function getText() {
+               return $this->mText;
+       }
+
+       public function setText( $text ) {
+               $this->mText = $text;
+       }
+
+}
diff --git a/tests/phpunit/includes/RevisionTestModifyableContentHandler.php b/tests/phpunit/includes/RevisionTestModifyableContentHandler.php
new file mode 100644 (file)
index 0000000..e262b40
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+class RevisionTestModifyableContentHandler extends TextContentHandler {
+
+       public function __construct() {
+               parent::__construct( "RevisionTestModifyableContent", [ CONTENT_FORMAT_TEXT ] );
+       }
+
+       public function unserializeContent( $text, $format = null ) {
+               $this->checkFormat( $format );
+
+               return new RevisionTestModifyableContent( $text );
+       }
+
+       public function makeEmptyContent() {
+               return new RevisionTestModifyableContent( '' );
+       }
+
+}
index 33a7f44..34434b9 100644 (file)
@@ -57,23 +57,61 @@ class CommandTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( "bar\n", $result->getStdout() );
        }
 
+       public function testStdout() {
+               $this->requirePosix();
+
+               $command = new Command();
+
+               $result = $command
+                       ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
+                       ->execute();
+
+               $this->assertNotContains( 'ThisIsStderr', $result->getStdout() );
+               $this->assertEquals( "ThisIsStderr\n", $result->getStderr() );
+       }
+
+       public function testStdoutRedirection() {
+               $this->requirePosix();
+
+               $command = new Command();
+
+               $result = $command
+                       ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
+                       ->includeStderr( true )
+                       ->execute();
+
+               $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
+               $this->assertNull( $result->getStderr() );
+       }
+
        public function testOutput() {
                global $IP;
 
                $this->requirePosix();
+               chdir( $IP );
 
                $command = new Command();
                $result = $command
-                       ->params( [ 'ls', "$IP/index.php" ] )
+                       ->params( [ 'ls', 'index.php' ] )
                        ->execute();
-               $this->assertSame( "$IP/index.php", trim( $result->getStdout() ) );
+               $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
+               $this->assertSame( null, $result->getStderr() );
 
                $command = new Command();
                $result = $command
                        ->params( [ 'ls', 'index.php', 'no-such-file' ] )
                        ->includeStderr()
                        ->execute();
+               $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
                $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStdout() );
+               $this->assertSame( null, $result->getStderr() );
+
+               $command = new Command();
+               $result = $command
+                       ->params( [ 'ls', 'index.php', 'no-such-file' ] )
+                       ->execute();
+               $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
+               $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStderr() );
        }
 
        public function testT69870() {
index edaaa39..38ade4d 100644 (file)
@@ -59,7 +59,6 @@
                                filter4: '0',
                                group3: '',
                                highlight: '0',
-                               invert: '0',
                                group1__filter1_color: null,
                                group1__filter2_color: null,
                                group2__filter3_color: null,
                        } ),
                        'Highlight parameters in Uri query set highlight state in the model'
                );
-
-               uriProcessor.updateModelBasedOnQuery( { invert: '1', urlversion: '2' } );
-               assert.deepEqual(
-                       uriProcessor.getUriParametersFromModel(),
-                       $.extend( true, {}, baseParams, {
-                               invert: '1'
-                       } ),
-                       'Invert parameter in Uri query set invert state in the model'
-               );
        } );
 
        QUnit.test( 'isNewState', function ( assert ) {
index 324a652..6a05920 100644 (file)
@@ -56,8 +56,7 @@
                                                        highlight: true,
                                                        filter1: 'c5',
                                                        group3option1: 'c1'
-                                               },
-                                               invert: true
+                                               }
                                        }
                                }
                        }
@@ -75,8 +74,7 @@
                                                        // Group type string_options
                                                        group2: 'filter4',
                                                        // Note - Group3 is sticky, so it won't show in output
-                                                       // Invert/highlight toggles
-                                                       invert: '1',
+                                                       // highlight toggle
                                                        highlight: '1'
                                                },
                                                highlights: {
                                        group2: 'filter5',
                                        filter1: '0',
                                        filter2: '0',
-                                       highlight: '1',
-                                       invert: '0'
+                                       highlight: '1'
                                },
                                highlights: {
                                        filter1_color: 'c5',