Merge "Update OOjs UI to v0.2.4"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 3 Dec 2014 00:12:23 +0000 (00:12 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 3 Dec 2014 00:12:23 +0000 (00:12 +0000)
28 files changed:
RELEASE-NOTES-1.25
docs/hooks.txt
includes/PrefixSearch.php
includes/api/ApiMain.php
includes/api/ApiQueryPrefixSearch.php
includes/api/i18n/en.json
includes/api/i18n/mk.json
includes/api/i18n/qqq.json
includes/api/i18n/zh-hans.json
includes/installer/i18n/nap.json
includes/resourceloader/ResourceLoader.php
includes/specialpage/SpecialPage.php
includes/specials/SpecialEditWatchlist.php
includes/specials/SpecialJavaScriptTest.php
includes/specials/SpecialListusers.php
includes/specials/SpecialLog.php
includes/specials/SpecialPagesWithProp.php
includes/specials/SpecialRedirect.php
includes/specials/SpecialWatchlist.php
languages/i18n/ar.json
languages/i18n/bn.json
languages/i18n/hr.json
languages/i18n/nap.json
languages/i18n/ur.json
resources/Resources.php
resources/src/jquery/jquery.getAttrs.js
resources/src/mediawiki/mediawiki.searchSuggest.js
tests/phpunit/includes/PrefixSearchTest.php

index e848472..92e0a2c 100644 (file)
@@ -59,6 +59,35 @@ production.
 * Added a hook, "ApiOpenSearchSuggest", to allow extensions to provide extracts
   and images for ApiOpenSearch output. The semantics are identical to the
   "OpenSearchXml" hook provided by the OpenSearchXml extension.
+* PrefixSearchBackend hook now has an $offset parameter. Combined with $limit,
+  this allows for pagination of prefix results. Extensions using this hook
+  should implement supporting behavior. Not doing so can result in undefined
+  behavior from API clients trying to continue through prefix results.
+
+==== External libraries ====
+* MediaWiki now requires certain external libraries to be installed. In the past
+  these were bundled inside the git repository of MediaWiki core, but now they
+  need to be installed separately. For users using the tarball, this will be taken
+  care of and no action will be required. Users using git will either need to use
+  composer to fetch dependencies or use the mediawiki/vendor repository which includes
+  all dependencies for MediaWiki core and ones used in Wikimedia deployment. Detailed
+  instructions can be found at <https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries>.
+* The following libraries are now required:
+** psr/log 1.0.0
+*** This library provides the interfaces set by the PSR-3 standard (<http://www.php-fig.org/psr/psr-3/>)
+    which are used by MediaWiki interally by the MWLogger class.
+*** See the structured logging RfC (<https://www.mediawiki.org/wiki/Requests_for_comment/Structured_logging>)
+    for more background information.
+** cssjanus/cssjanus 1.1.1
+*** This library was formerly bundled with MediaWiki core and has now been removed. It automatically
+    flips CSS for RTL support.
+** leafo/lessphp 0.5.0
+*** This library was formerly bundled with MediaWiki core and has now been removed. It compiles LESS
+    files into CSS.
+** cdb/cdb 1.0.0
+*** This library was formerly a part of MediaWiki core, and has now been split out into a separate library.
+    It provides CDB functions which are used in the Interwiki and Localization caches. More information
+    about the library can be found at <https://www.mediawiki.org/wiki/CDB>.
 
 === Bug fixes in 1.25 ===
 * (T73003) No additional code will be generated to try to load CSS-embedded
@@ -118,6 +147,7 @@ production.
   in JSON format.
 * (T76051) list=tags will now continue correctly.
 * (T76052) list=tags can now indicate whether a tag is defined.
+* (T75522) list=prefixsearch now supports continuation
 
 === Action API internal changes in 1.25 ===
 * ApiHelp has been rewritten to support i18n and paginated HTML output.
index 0146b86..a4e02c6 100644 (file)
@@ -2166,6 +2166,7 @@ $ns : array of int namespace keys to search in
 $search : search term (not guaranteed to be conveniently normalized)
 $limit : maximum number of results to return
 &$results : out param: array of page names (strings)
+$offset : number of results to offset from the beginning
 
 'PrefixSearchExtractNamespace': Called if core was not able to extract a
 namespace from the search string so that extensions can attempt it.
index 955313b..a4e64ee 100644 (file)
@@ -34,11 +34,12 @@ abstract class PrefixSearch {
         * @param string $search
         * @param int $limit
         * @param array $namespaces Used if query is not explicitly prefixed
+        * @param int $offset How many results to offset from the beginning
         * @return array Array of strings
         */
-       public static function titleSearch( $search, $limit, $namespaces = array() ) {
+       public static function titleSearch( $search, $limit, $namespaces = array(), $offset = 0 ) {
                $prefixSearch = new StringPrefixSearch;
-               return $prefixSearch->search( $search, $limit, $namespaces );
+               return $prefixSearch->search( $search, $limit, $namespaces, $offset );
        }
 
        /**
@@ -47,9 +48,10 @@ abstract class PrefixSearch {
         * @param string $search
         * @param int $limit
         * @param array $namespaces Used if query is not explicitly prefixed
+        * @param int $offset How many results to offset from the beginning
         * @return array Array of strings or Title objects
         */
-       public function search( $search, $limit, $namespaces = array() ) {
+       public function search( $search, $limit, $namespaces = array(), $offset = 0 ) {
                $search = trim( $search );
                if ( $search == '' ) {
                        return array(); // Return empty result
@@ -65,7 +67,7 @@ abstract class PrefixSearch {
                                $ns = $namespaces; // no explicit prefix, use default namespaces
                                wfRunHooks( 'PrefixSearchExtractNamespace', array( &$ns, &$search ) );
                        }
-                       return $this->searchBackend( $ns, $search, $limit );
+                       return $this->searchBackend( $ns, $search, $limit, $offset );
                }
 
                // Is this a namespace prefix?
@@ -80,7 +82,7 @@ abstract class PrefixSearch {
                        wfRunHooks( 'PrefixSearchExtractNamespace', array( &$namespaces, &$search ) );
                }
 
-               return $this->searchBackend( $namespaces, $search, $limit );
+               return $this->searchBackend( $namespaces, $search, $limit, $offset );
        }
 
        /**
@@ -88,12 +90,13 @@ abstract class PrefixSearch {
         * @param string $search
         * @param int $limit
         * @param array $namespaces
+        * @param int $offset How many results to offset from the beginning
         *
         * @return array
         */
-       public function searchWithVariants( $search, $limit, array $namespaces ) {
+       public function searchWithVariants( $search, $limit, array $namespaces, $offset = 0 ) {
                wfProfileIn( __METHOD__ );
-               $searches = $this->search( $search, $limit, $namespaces );
+               $searches = $this->search( $search, $limit, $namespaces, $offset );
 
                // if the content language has variants, try to retrieve fallback results
                $fallbackLimit = $limit - count( $searches );
@@ -141,20 +144,21 @@ abstract class PrefixSearch {
         * @param array $namespaces
         * @param string $search
         * @param int $limit
+        * @param int $offset How many results to offset from the beginning
         * @return array Array of strings
         */
-       protected function searchBackend( $namespaces, $search, $limit ) {
+       protected function searchBackend( $namespaces, $search, $limit, $offset ) {
                if ( count( $namespaces ) == 1 ) {
                        $ns = $namespaces[0];
                        if ( $ns == NS_MEDIA ) {
                                $namespaces = array( NS_FILE );
                        } elseif ( $ns == NS_SPECIAL ) {
-                               return $this->titles( $this->specialSearch( $search, $limit ) );
+                               return $this->titles( $this->specialSearch( $search, $limit, $offset ) );
                        }
                }
                $srchres = array();
-               if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) {
-                       return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit ) );
+               if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres, $offset ) ) ) {
+                       return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit, $offset ) );
                }
                return $this->strings( $this->handleResultFromHook( $srchres, $namespaces, $search, $limit ) );
        }
@@ -189,8 +193,8 @@ abstract class PrefixSearch {
                                // returned match to the front.  This might look odd but the alternative
                                // is to put the redirect in front and drop the match.  The name of the
                                // found match is often more descriptive/better formed than the name of
-                               // the redirec AND by definition they share a prefix.  Hopefully this
-                               // choice is less confusing and more helpful.  But it might now be.  But
+                               // the redirect AND by definition they share a prefix.  Hopefully this
+                               // choice is less confusing and more helpful.  But it might not be.  But
                                // it is the choice we're going with for now.
                                return $this->pullFront( $key, $srchres );
                        }
@@ -266,9 +270,10 @@ abstract class PrefixSearch {
         *
         * @param string $search Term
         * @param int $limit Max number of items to return
+        * @param int $offset Number of items to offset
         * @return array
         */
-       protected function specialSearch( $search, $limit ) {
+       protected function specialSearch( $search, $limit, $offset ) {
                global $wgContLang;
 
                $searchParts = explode( '/', $search, 2 );
@@ -284,7 +289,7 @@ abstract class PrefixSearch {
                        }
                        $special = SpecialPageFactory::getPage( $specialTitle->getText() );
                        if ( $special ) {
-                               $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit );
+                               $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset );
                                return array_map( function ( $sub ) use ( $specialTitle ) {
                                        return $specialTitle->getSubpage( $sub );
                                }, $subpages );
@@ -316,12 +321,17 @@ abstract class PrefixSearch {
                ksort( $keys );
 
                $srchres = array();
+               $skipped = 0;
                foreach ( $keys as $pageKey => $page ) {
                        if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
                                // bug 27671: Don't use SpecialPage::getTitleFor() here because it
                                // localizes its input leading to searches for e.g. Special:All
                                // returning Spezial:MediaWiki-Systemnachrichten and returning
                                // Spezial:Alle_Seiten twice when $wgLanguageCode == 'de'
+                               if ( $offset > 0 && $skipped < $offset ) {
+                                       $skipped++;
+                                       continue;
+                               }
                                $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page );
                        }
 
@@ -342,9 +352,10 @@ abstract class PrefixSearch {
         * @param array $namespaces Namespaces to search in
         * @param string $search Term
         * @param int $limit Max number of items to return
+        * @param int $offset Number of items to skip
         * @return array Array of Title objects
         */
-       protected function defaultSearchBackend( $namespaces, $search, $limit ) {
+       protected function defaultSearchBackend( $namespaces, $search, $limit, $offset ) {
                $ns = array_shift( $namespaces ); // support only one namespace
                if ( in_array( NS_MAIN, $namespaces ) ) {
                        $ns = NS_MAIN; // if searching on many always default to main
@@ -360,7 +371,11 @@ abstract class PrefixSearch {
                                'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() )
                        ),
                        __METHOD__,
-                       array( 'LIMIT' => $limit, 'ORDER BY' => 'page_title' )
+                       array(
+                               'LIMIT' => $limit,
+                               'ORDER BY' => 'page_title',
+                               'OFFSET' => $offset
+                       )
                );
                $srchres = array();
                foreach ( $res as $row ) {
index 004bfae..7600066 100644 (file)
@@ -526,6 +526,7 @@ class ApiMain extends ApiBase {
                if ( $matchOrigin ) {
                        $response->header( "Access-Control-Allow-Origin: $originParam" );
                        $response->header( 'Access-Control-Allow-Credentials: true' );
+                       $response->header( 'Access-Control-Allow-Headers: Api-User-Agent' );
                        $this->getOutput()->addVaryHeader( 'Origin' );
                }
 
index 95f8483..069e30b 100644 (file)
@@ -43,20 +43,21 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                $search = $params['search'];
                $limit = $params['limit'];
                $namespaces = $params['namespace'];
+               $offset = $params['offset'];
 
                $searcher = new TitlePrefixSearch;
-               $titles = $searcher->searchWithVariants( $search, $limit, $namespaces );
+               $titles = $searcher->searchWithVariants( $search, $limit + 1, $namespaces, $offset );
                if ( $resultPageSet ) {
                        $resultPageSet->populateFromTitles( $titles );
-                       /** @todo If this module gets an 'offset' parameter, use it here */
-                       $offset = 1;
                        foreach ( $titles as $index => $title ) {
-                               $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset ) );
+                               $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset + 1 ) );
                        }
                } else {
                        $result = $this->getResult();
+                       $count = 0;
                        foreach ( $titles as $title ) {
-                               if ( !$limit-- ) {
+                               if ( ++$count > $limit ) {
+                                       $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] );
                                        break;
                                }
                                $vals = array(
@@ -70,6 +71,7 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                                }
                                $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
                                if ( !$fit ) {
+                                       $this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
                                        break;
                                }
                        }
@@ -102,6 +104,10 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                                        ApiBase::PARAM_MAX => 100,
                                        ApiBase::PARAM_MAX2 => 200,
                                ),
+                               'offset' => array(
+                                       ApiBase::PARAM_DFLT => 0,
+                                       ApiBase::PARAM_TYPE => 'integer',
+                               ),
                        );
        }
 
index a15b9cf..f7e3a57 100644 (file)
        "apihelp-query+prefixsearch-param-search": "Search string.",
        "apihelp-query+prefixsearch-param-namespace": "Namespaces to search.",
        "apihelp-query+prefixsearch-param-limit": "Maximum number of results to return.",
+       "apihelp-query+prefixsearch-param-offset": "Number of results to skip.",
        "apihelp-query+prefixsearch-example-simple": "Search for page titles beginning with \"meaning\"",
 
        "apihelp-query+protectedtitles-description": "List all titles protected from creation.",
index a323e90..73d4b64 100644 (file)
        "apihelp-expandtemplates-description": "Ги проширува сите шаблони во викитекст.",
        "apihelp-expandtemplates-param-title": "Наслов на страница.",
        "apihelp-expandtemplates-param-text": "Викитекст за претворање.",
+       "apihelp-expandtemplates-param-revid": "Назнака на преработката, за <nowiki>{{REVISIONID}}</nowiki> и слични променливи.",
        "apihelp-expandtemplates-param-prop": "Кои информации треба да ги добиете:\n;wikitext:The expanded wikitext.\n;categories: Категориите присутно во вносот кои не се претставени во викитекстуалниот извод.\n;volatile: Дали изводот е месно врзан и не треба да се преупотребува на други места во страницата.\n;ttl: Максималното време по кое треба да се поништи меѓускладираниот резултат.\n;parsetree: XML-дрвото на расчленување за изводот.\nИмајте на ум дека ако не изберете никаква вредност, резултатот ќе го содржи викитекстот, но изводот ќе биде во застарен формат.",
        "apihelp-expandtemplates-param-includecomments": "Дали во изводот да се вклучени HTML-коментари.",
        "apihelp-expandtemplates-param-generatexml": "Создај XML-дрво на расчленување (заменето со $1prop=parsetree).",
        "apihelp-opensearch-param-limit": "Максималниот број на резултати за прикажување.",
        "apihelp-opensearch-param-namespace": "Именски простори за пребарување.",
        "apihelp-opensearch-param-suggest": "Не прави ништо ако [https://www.mediawiki.org/wiki/Manual:$wgEnableOpenSearchSuggest $wgEnableOpenSearchSuggest] е неточно.",
+       "apihelp-opensearch-param-redirects": "Како да се работи со пренасочувања:\n;return: Дај го самото пренасочување.\n;resolve: Дај ја целната страница. Може да даде помалку од $1limit резултати.\nОд историски причини, по основно е „return“ за $1format=json и „resolve“ за други формати.",
        "apihelp-opensearch-param-format": "Формат на изводот.",
        "apihelp-opensearch-example-te": "Најди страници што почнуваат со „Те“",
        "apihelp-options-description": "Смени ги нагодувањата на тековниот корисник.\n\nМожат да се зададат само можностите заведени во јадрото или во едно од воспоставените додатоци, или пак можности со клуч кој ја има претставката „userjs-“ (предвиден за употреба од кориснички скрипти).",
        "apihelp-paraminfo-param-helpformat": "Формат на помошните низи.",
        "apihelp-paraminfo-param-querymodules": "Список на називи на модули за барања (вредност на параметарот prop=, meta= или list=). Користете го „$1modules=query+foo“ наместо „$1querymodules=foo“.",
        "apihelp-paraminfo-param-mainmodule": "Добави информации и за главниот (врховен) модул. Користете го „$1modules=main“ наместо тоа.",
+       "apihelp-paraminfo-param-pagesetmodule": "Дај ги сите информации и за модулот на збирот страници (укажувајќи titles= и сродни).",
+       "apihelp-paraminfo-param-formatmodules": "Список на називи на форматни модули (вредностза параметарот format=). Наместо тоа, користете го „$1modules“.",
        "apihelp-parse-param-summary": "Опис за расчленување.",
        "apihelp-parse-param-preview": "Расчлени во прегледен режим.",
        "apihelp-parse-param-sectionpreview": "Расчлени во прегледен режим на поднасловот (го овозможува и прегледниот режим).",
index 31d8871..0889dc9 100644 (file)
        "apihelp-query+prefixsearch-param-search": "{{doc-apihelp-param|query+prefixsearch|search}}",
        "apihelp-query+prefixsearch-param-namespace": "{{doc-apihelp-param|query+prefixsearch|namespace}}",
        "apihelp-query+prefixsearch-param-limit": "{{doc-apihelp-param|query+prefixsearch|limit}}",
+       "apihelp-query+prefixsearch-param-offset": "{{doc-apihelp-param|query+prefixsearch|offset}}",
        "apihelp-query+prefixsearch-example-simple": "{{doc-apihelp-example|query+prefixsearch}}",
        "apihelp-query+protectedtitles-description": "{{doc-apihelp-description|query+protectedtitles}}",
        "apihelp-query+protectedtitles-param-namespace": "{{doc-apihelp-param|query+protectedtitles|namespace}}",
        "apihelp-upload-example-filekey": "{{doc-apihelp-example|upload}}",
        "apihelp-userrights-description": "{{doc-apihelp-description|userrights}}",
        "apihelp-userrights-param-user": "{{doc-apihelp-param|userrights|user}}\n{{Identical|Username}}",
-       "apihelp-userrights-param-userid": "{{doc-apihelp-param|userrights|userid}}",
+       "apihelp-userrights-param-userid": "{{doc-apihelp-param|userrights|userid}}\n{{Identical|User ID}}",
        "apihelp-userrights-param-add": "{{doc-apihelp-param|userrights|add}}",
        "apihelp-userrights-param-remove": "{{doc-apihelp-param|userrights|remove}}",
        "apihelp-userrights-param-reason": "{{doc-apihelp-param|userrights|reason}}",
index 32cc01e..0f954cd 100644 (file)
@@ -90,6 +90,7 @@
        "apihelp-expandtemplates-description": "展开维基文本中的所有模板。",
        "apihelp-expandtemplates-param-title": "页面标题。",
        "apihelp-expandtemplates-param-text": "要转换的wiki文本。",
+       "apihelp-expandtemplates-param-revid": "修订版本ID,用于<nowiki>{{REVISIONID}}</nowiki>和类似变体。",
        "apihelp-expandtemplates-example-simple": "展开wiki文本“<nowiki>{{Project:Sandbox}}</nowiki>”",
        "apihelp-feedcontributions-description": "返回用户贡献纲要。",
        "apihelp-feedcontributions-param-feedformat": "纲要的格式。",
index 17e0d50..c459010 100644 (file)
@@ -62,6 +62,7 @@
        "config-safe-mode": "<strong>Warning:</strong> PHP's [http://www.php.net/features.safe-mode safe mode] è attivato.\nPutesse fà cocche probblema, specialmente si state ausanno 'a funziona 'e carrecà file e 'o supporto d' ' e funziune <code>math</code>.",
        "config-xml-bad": "'O modulo XML 'e PHP è mancante.\nA MediaWiki servessero 'e funziune prisente dint'a stu modulo e nun faticarrà c' 'a configurazione 'e mò.\nSi se sta eseguenno Mandrake, installare 'o pacco php-xml.",
        "config-pcre-old": "<strong>Errore fatale:</strong> s'addimanna PCRE  $1 o succiessivo.\n'O file vuosto binario PHP è acucchiato c' 'o PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Cchiù nfurmaziune].",
+       "config-pcre-no-utf8": "<strong>Fatale:</strong> 'E module PCRE d' 'o PHP pare ca se so' compilate senza PCRE_UTF8 supporto.\nA MediaWiki serve nu supporto UTF-8 pe' putè funziunà apposto.",
        "config-memory-raised": "'O valore 'e PHP <code>memory_limit</code> è $1, aumentato a $2.",
        "config-memory-bad": "<strong>Attenziò:</strong> 'o valore 'e PHP <code>memory_limit</code> è $1.\nProbabbilmente troppo basso.\n'A installazione se putesse scassà!",
        "config-ctype": "'''Errore''': 'o PHP s'adda ghienchere c' 'o supporto pe' l'[http://www.php.net/manual/it/ctype.installation.php estensione Ctype].",
index eecb936..2c54f69 100644 (file)
@@ -1490,6 +1490,9 @@ class ResourceLoader {
                // When called from the installer, it is possible that a required PHP extension
                // is missing (at least for now; see bug 47564). If this is the case, throw an
                // exception (caught by the installer) to prevent a fatal error later on.
+               if ( !class_exists( 'lessc' ) ) {
+                       throw new MWException( 'MediaWiki requires the lessphp compiler' );
+               }
                if ( !function_exists( 'ctype_digit' ) ) {
                        throw new MWException( 'lessc requires the Ctype extension' );
                }
index c0a94af..072f87f 100644 (file)
@@ -303,10 +303,28 @@ class SpecialPage {
         *   - `prefixSearchSubpages( "" )` should return `array( foo", "bar", "baz" )`
         *
         * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
         * @return string[] Matching subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               $subpages = $this->getSubpagesForPrefixSearch();
+               if ( !$subpages ) {
+                       return array();
+               }
+
+               return self::prefixSearchArray( $search, $limit, $subpages, $offset );
+       }
+
+       /**
+        * Return an array of subpages that this special page will accept for prefix
+        * searches. If this method requires a query you might instead want to implement
+        * prefixSearchSubpages() directly so you can support $limit and $offset. This
+        * method is better for static-ish lists of things.
+        *
+        * @return string[] subpages to search from
+        */
+       protected function getSubpagesForPrefixSearch() {
                return array();
        }
 
@@ -318,11 +336,13 @@ class SpecialPage {
         * @param string $search
         * @param int $limit
         * @param array $subpages
+        * @param int $offset
         * @return string[]
         */
-       protected static function prefixSearchArray( $search, $limit, array $subpages ) {
+       protected static function prefixSearchArray( $search, $limit, array $subpages, $offset ) {
                $escaped = preg_quote( $search, '/' );
-               return array_slice( preg_grep( "/^$escaped/i", $subpages ), 0, $limit );
+               return array_slice( preg_grep( "/^$escaped/i",
+                       array_slice( $subpages, $offset ) ), 0, $limit );
        }
 
        /**
index 9007603..cb97420 100644 (file)
@@ -134,22 +134,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @see also SpecialWatchlist::getSubpagesForPrefixSearch
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               return self::prefixSearchArray(
-                       $search,
-                       $limit,
-                       // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
-                       // here and there - no 'edit' here, because that the default for this page
-                       array(
-                               'clear',
-                               'raw',
-                       )
+       public function getSubpagesForPrefixSearch() {
+               // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
+               // here and there - no 'edit' here, because that the default for this page
+               return array(
+                       'clear',
+                       'raw',
                );
        }
 
index 0efebb3..a61a673 100644 (file)
@@ -173,18 +173,12 @@ HTML;
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               return self::prefixSearchArray(
-                       $search,
-                       $limit,
-                       array_keys( self::$frameworks )
-               );
+       public function getSubpagesForPrefixSearch() {
+               return array_keys( self::$frameworks );
        }
 
        protected function getGroupName() {
index dad9074..fa20994 100644 (file)
@@ -397,15 +397,12 @@ class SpecialListUsers extends IncludableSpecialPage {
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               $subpages = User::getAllGroups();
-               return self::prefixSearchArray( $search, $limit, $subpages );
+       public function getSubpagesForPrefixSearch() {
+               return User::getAllGroups();
        }
 
        protected function getGroupName() {
index 99704a9..04c2fe9 100644 (file)
@@ -119,17 +119,15 @@ class SpecialLog extends SpecialPage {
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
+       public function getSubpagesForPrefixSearch() {
                $subpages = $this->getConfig()->get( 'LogTypes' );
                $subpages[] = 'all';
                sort( $subpages );
-               return self::prefixSearchArray( $search, $limit, $subpages );
+               return $subpages;
        }
 
        private function parseParams( FormOptions $opts, $par ) {
index f5b19cc..670a397 100644 (file)
@@ -83,11 +83,13 @@ class SpecialPagesWithProp extends QueryPage {
         *
         * @param string $search Prefix to search for
         * @param int $limit Maximum number of results to return
+        * @param int $offset Number of pages to skip
         * @return string[] Matching subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               $subpages = array_keys( $this->getExistingPropNames() );
-               return self::prefixSearchArray( $search, $limit, $subpages );
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               $subpages = array_keys( $this->queryExistingProps( $limit, $offset ) );
+               // We've already limited and offsetted, set to N and 0 respectively.
+               return self::prefixSearchArray( $search, count( $subpages ), $subpages, 0 );
        }
 
        /**
@@ -154,23 +156,38 @@ class SpecialPagesWithProp extends QueryPage {
 
        public function getExistingPropNames() {
                if ( $this->existingPropNames === null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
-                       $res = $dbr->select(
-                               'page_props',
-                               'pp_propname',
-                               '',
-                               __METHOD__,
-                               array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
-                       );
-                       $propnames = array();
-                       foreach ( $res as $row ) {
-                               $propnames[$row->pp_propname] = $row->pp_propname;
-                       }
-                       $this->existingPropNames = $propnames;
+                       $this->existingPropNames = $this->queryExistingProps();
                }
                return $this->existingPropNames;
        }
 
+       protected function queryExistingProps( $limit = null, $offset = 0 ) {
+               $opts = array(
+                       'DISTINCT', 'ORDER BY' => 'pp_propname'
+               );
+               if ( $limit ) {
+                       $opts['LIMIT'] = $limit;
+               }
+               if ( $offset ) {
+                       $opts['OFFSET'] = $offset;
+               }
+
+               $res = wfGetDB( DB_SLAVE )->select(
+                       'page_props',
+                       'pp_propname',
+                       '',
+                       __METHOD__,
+                       $opts
+               );
+
+               $propnames = array();
+               foreach ( $res as $row ) {
+                       $propnames[$row->pp_propname] = $row->pp_propname;
+               }
+
+               return $propnames;
+       }
+
        protected function getGroupName() {
                return 'pages';
        }
index a2683e5..72d21eb 100644 (file)
@@ -263,22 +263,16 @@ class SpecialRedirect extends FormSpecialPage {
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               return self::prefixSearchArray(
-                       $search,
-                       $limit,
-                       array(
-                               "file",
-                               "page",
-                               "revision",
-                               "user",
-                       )
+       protected function getSubpagesForPrefixSearch() {
+               return array(
+                       "file",
+                       "page",
+                       "revision",
+                       "user",
                );
        }
 
index 421840f..a06955d 100644 (file)
@@ -79,22 +79,16 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        }
 
        /**
-        * Return an array of subpages beginning with $search that this special page will accept.
+        * Return an array of subpages that this special page will accept.
         *
-        * @param string $search Prefix to search for
-        * @param int $limit Maximum number of results to return
-        * @return string[] Matching subpages
+        * @see also SpecialEditWatchlist::getSubpagesForPrefixSearch
+        * @return string[] subpages
         */
-       public function prefixSearchSubpages( $search, $limit = 10 ) {
-               // See also SpecialEditWatchlist::prefixSearchSubpages
-               return self::prefixSearchArray(
-                       $search,
-                       $limit,
-                       array(
-                               'clear',
-                               'edit',
-                               'raw',
-                       )
+       public function getSubpagesForPrefixSearch() {
+               return array(
+                       'clear',
+                       'edit',
+                       'raw',
                );
        }
 
index 50a7561..31c8f1a 100644 (file)
        "alllogstext": "عرض شامل لكل السجلات المتوفرة في {{SITENAME}}.\nباستطاعتك جعل القائمة أكثر تحديداً، وذلك باختيار نوع السجل واسم المستخدم (حساس لحالة الحروف)، أو الصفحة المتأثرة (أيضاً حساس لحالة الحروف).",
        "logempty": "لا توجد مدخلات مطابقة في السجل.",
        "log-title-wildcard": "ابحث عن عناوين تبدأ بهذا النص",
-       "showhideselectedlogentries": "إطÙ\87ار/إخÙ\81اء Ø³Ø¬Ù\84ات Ø§Ù\84دخÙ\88ل المختارة",
+       "showhideselectedlogentries": "غÙ\8aر Ø±Ø¤Ù\8aØ© Ù\85دخÙ\84ات Ø§Ù\84سجل المختارة",
        "allpages": "كل الصفحات",
        "nextpage": "الصفحة التالية ($1)",
        "prevpage": "الصفحة السابقة ($1)",
index 720082f..a6192ee 100644 (file)
        "viewsourceold": "উৎস দেখাও",
        "editlink": "সম্পাদনা",
        "viewsourcelink": "উৎস দেখুন",
-       "editsectionhint": "পরিচ্ছেদ সম্পাদনা: $1",
+       "editsectionhint": "à¦\85নà§\81চ্ছেদ সম্পাদনা: $1",
        "toc": "পরিচ্ছেদসমূহ",
        "showtoc": "দেখাও",
        "hidetoc": "আড়ালে রাখো",
index bf2b96a..4b9c4fc 100644 (file)
        "changeemail-oldemail": "Trenutačna adresa e-pošte:",
        "changeemail-newemail": "Nova adresa e-pošte:",
        "changeemail-none": "(ništa)",
-       "changeemail-password": "Zaporka za {{SITENAME}}:",
+       "changeemail-password": "Zaporka za projekt {{SITENAME}}:",
        "changeemail-submit": "Promijeni E-mail",
        "changeemail-throttled": "Nedavno ste se previše puta pokušali prijaviti.\nMolimo Vas pričekajte $1 prije nego što pokušate ponovno.",
        "bold_sample": "Podebljani tekst",
        "table_pager_first": "Prva stranica",
        "table_pager_last": "Zadnja stranica",
        "table_pager_limit": "Prikaži $1 slika po stranici",
-       "table_pager_limit_label": "Stavke po stranici:",
+       "table_pager_limit_label": "Broj stavki po stranici:",
        "table_pager_limit_submit": "Idi",
        "table_pager_empty": "Nema rezultata",
        "autosumm-blank": "uklonjen cjelokupni sadržaj stranice",
index d6a919f..d18c67e 100644 (file)
        "log-name-pagelang": "Càgna 'o riggistro 'e llengue",
        "log-description-pagelang": "Chest'è nu riggistro 'e cagnamiente 'e lengua d' 'e paggene.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|ave cagnato}} 'a lengua d' 'a paggena $3 'a $4 a $5.",
-       "default-skin-not-found": "Oops! 'A skin predefinta ' 'o wii vuosto, definita 'n <code dir=\"ltr\">$wgDefaultSkin</code> comme <code>$1</code>, nun se tròva.\n\n'A installazione pare ca tenesse 'e skin ccà abbascio. Vedite [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manuale: configurazione skin] pe' n'avè cchiù nfurmaziune ncopp' 'a manera 'e ll'abbià o scegliere chilla predefinita.\n\n$2\n\n; Si avite installato MediaWiki mò mò:\n: Probabbilmente l'avite installato 'a git, o direttamente 'a 'o codece sorgente ausanno cocch'atu metodo. Chesto era permesso. Verite 'e installà cocche skin 'a [https://www.mediawiki.org/wiki/Category:All_skins directory ncoppa mediawiki.org], tramite:\n:* Scarrecanno 'o [https://www.mediawiki.org/wiki/Download programma 'e installazione tarball], ca venesse fornito ch' 'e diverze skin ed estenziune. Putite fare copia-azzecca d' 'a directory <code dir=\"ltr\">skins/</code>.\n:* Clonanno uno 'e chiste repository <code>mediawiki/skins/*</code> pe' bbìa d' 'o git dint' 'a directory <code>skins/</code> d' 'a installazione MediaWiki vosta.\n: Facenno accussì nun se mmescasse 'o repository git vuosto si site sviluppatore MediaWiki.\n\n; Si avite MediaWiki agghiurnato MediaWiki mò mò:\n: MediaWiki 1.24 e verziune appriesso nun abbìa automatecamente 'e skin installate (vedite [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manuale: rilevamento automateco skin]). Putite copiare 'e linee ccà abbascio dint' 'o <code>LocalSettings.php</code> pe' putè appiccià tutt' 'e skin installate mò mò:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si avite cagnato mò mò <code>LocalSettings.php</code>:\n: Cuntrullate 'e nomme d' 'e skin n'ata vota pe' ve sparagnà cocch'errore 'e battitura.",
-       "default-skin-not-found-no-skins": "Oops! 'A skin predefinita p' 'o wiki vuosto, definita 'n <code dir=\"ltr\">$wgDefaultSkin</code> comme <code>$1</code>, nun se tròva.\n\nNun avite installato nisciuno skin.\n\n; Si avite installato MediaWiki mò mò:\n: Probabbilmente l'avite installato 'a git, o direttamente 'a 'o codece sorgente ausanno cocch'atu metodo. Chesto era permesso. Verite 'e installà cocche skin 'a [https://www.mediawiki.org/wiki/Category:All_skins directory ncoppa mediawiki.org], tramite:\n:* Scarrecanno 'o [https://www.mediawiki.org/wiki/Download programma 'e installazione tarball], ca venesse fornito ch' 'e diverze skin ed estenziune. Putite fare copia-azzecca d' 'a directory <code dir=\"ltr\">skins/</code>.\n:* Clonanno uno 'e chiste repository <code>mediawiki/skins/*</code> pe' bbìa d' 'o git dint' 'a directory <code>skins/</code> d' 'a installazione MediaWiki vosta.\n: Facenno accussì nun se mmescasse 'o repository git vuosto si site sviluppatore MediaWiki. Vedite [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manuale: rilevamento automateco skin]) pe n'avè nfurmaziune ncopp' 'a maniera d'appiccià e scegliere chella predefinita.",
+       "default-skin-not-found": "Oops! 'A skin predefinta ' 'o wiki vuosto, definita 'n <code dir=\"ltr\">$wgDefaultSkin</code> comme <code>$1</code>, nun se tròva.\n\n'A installazione pare ca tenesse 'e skin ccà abbascio. Vedite [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manuale: configurazione skin] pe' n'avè cchiù nfurmaziune ncopp' 'a manera 'e ll'abbià o scegliere chilla predefinita.\n\n$2\n\n; Si avite installato MediaWiki mò mò:\n: Probabbilmente l'avite installato 'a git, o direttamente 'a 'o codece sorgente ausanno cocch'atu metodo. Chesto era permesso. Verite 'e installà cocche skin 'a [https://www.mediawiki.org/wiki/Category:All_skins directory ncoppa mediawiki.org], tramite:\n:* Scarrecanno 'o [https://www.mediawiki.org/wiki/Download programma 'e installazione tarball], ca venesse fornito ch' 'e diverze skin ed estenziune. Putite fare copia-azzecca d' 'a directory <code dir=\"ltr\">skins/</code>.\n:* Scarrecanne 'e tarballs individuale 'e skin 'a [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* Clonanno uno 'e chiste repository <code>mediawiki/skins/*</code> pe' bbìa d' 'o git dint' 'a directory <code>skins/</code> d' 'a installazione MediaWiki vosta.\n: Facenno accussì nun se mmescasse 'o repository git vuosto si site sviluppatore MediaWiki.\n\n; Si avite MediaWiki agghiurnato MediaWiki mò mò:\n: MediaWiki 1.24 e verziune appriesso nun abbìa automatecamente 'e skin installate (vedite [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manuale: rilevamento automateco skin]). Putite copiare 'e linee ccà abbascio dint' 'o <code>LocalSettings.php</code> pe' putè appiccià tutt' 'e skin installate mò mò:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si avite cagnato mò mò <code>LocalSettings.php</code>:\n: Cuntrullate 'e nomme d' 'e skin n'ata vota pe' ve sparagnà cocch'errore 'e battitura.",
+       "default-skin-not-found-no-skins": "Oops! 'A skin predefinita p' 'o wiki vuosto, definita 'n <code dir=\"ltr\">$wgDefaultSkin</code> comme <code>$1</code>, nun se tròva.\n\nNun avite installato nisciuno skin.\n\n; Si avite installato MediaWiki mò mò:\n: Probabbilmente l'avite installato 'a git, o direttamente 'a 'o codece sorgente ausanno cocch'atu metodo. Chesto era permesso. Verite 'e installà cocche skin 'a [https://www.mediawiki.org/wiki/Category:All_skins directory ncoppa mediawiki.org], tramite:\n:* Scarrecanno 'o [https://www.mediawiki.org/wiki/Download programma 'e installazione tarball], ca venesse fornito ch' 'e diverze skin ed estenziune. Putite fare copia-azzecca d' 'a directory <code dir=\"ltr\">skins/</code>.\n:* Scarrecanne 'e tarballs individuale 'e skin 'a [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* Clonanno uno 'e chiste repository <code>mediawiki/skins/*</code> pe' bbìa d' 'o git dint' 'a directory <code>skins/</code> d' 'a installazione MediaWiki vosta.\n: Facenno accussì nun se mmescasse 'o repository git vuosto si site sviluppatore MediaWiki. Vedite [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manuale: rilevamento automateco skin]) pe n'avè nfurmaziune ncopp' 'a maniera d'appiccià e scegliere chella predefinita.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (funzione appicciata)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 ('''funzione stutata''')",
        "mediastatistics": "Statistiche d' 'e media",
index 4bc058c..73d8eef 100644 (file)
        "savefile": "فائل محفوظ کریں",
        "sourcefilename": "اسم ملف (فائل) کا منبع:",
        "destfilename": "تعین شدہ اسم ملف:",
-       "watchthisupload": "یہ صفحہ زیر نظر کیجیۓ",
+       "watchthisupload": "یہ صفحہ زیر نظر کریں",
        "license": "اجازہ:",
        "license-header": "اجازہ کاری",
        "listfiles": "فہرست فائل",
        "listredirects": "فہرست متبادل ربط",
        "unusedtemplates": "غیر استعمال شدہ سانچے",
        "randompage": "بےترتیب صفحہ",
+       "randomincategory-category": "زمرہ:",
        "statistics": "اعداد و شمار",
+       "statistics-header-pages": "احصائے صفحات",
+       "statistics-header-edits": "احصائے تدوین",
        "statistics-header-users": "ارکان کے اعداد و شمار",
+       "statistics-header-hooks": "احصائے دیگر",
+       "statistics-articles": "مندرج صفحات",
+       "statistics-pages": "صفحات",
+       "statistics-pages-desc": "(ویکی اقتباسات کے کل صفحات، بشمولِ تبادلۂ خیال، رجوع مکررات وغیرہ۔)",
+       "statistics-files": "زبراثقال شدہ ملفات",
+       "statistics-edits": "ویکی اقتباسات کے آغاز سے کل صفحاتی ترمیم",
+       "statistics-edits-average": "فی صفحہ اوسط ترامیم",
+       "statistics-users": "مندرج [[خاص:فہرست صارفین، صارف فہرست|صارفین]]",
+       "statistics-users-active": "متحرک صارفین",
        "doubleredirects": "دوہرے متبادل ربط",
        "brokenredirects": "نامکمل متبادل ربط",
+       "brokenredirects-edit": "ترمیم کریں",
+       "brokenredirects-delete": "حذف",
        "nbytes": "$1 {{PLURAL:$1|لکمہ|لکمہ جات}}",
        "ncategories": "{{PLURAL:$1|زمرہ|زمرہ جات}} $1",
        "nmembers": "{{PLURAL:$1|رکن|اراکین}}",
index f2c3227..9755d25 100644 (file)
@@ -951,6 +951,7 @@ return array(
                        'jquery.client',
                        'jquery.placeholder',
                        'jquery.suggestions',
+                       'jquery.getAttrs',
                        'mediawiki.api',
                ),
        ),
index 5d6a1d4..a2e2be5 100644 (file)
@@ -2,6 +2,16 @@
  * @class jQuery.plugin.getAttrs
  */
 
+function serializeControls( controls ) {
+       var i, data = {}, len = controls.length;
+
+       for ( i = 0; i < len; i++ ) {
+               data[ controls[i].name ] = controls[i].value;
+       }
+
+       return data;
+}
+
 /**
  * Get the attributes of an element directy as a plain object.
  *
  * @return {Object}
  */
 jQuery.fn.getAttrs = function () {
-       var i,
-               map = this[0].attributes,
-               attrs = {},
-               len = map.length;
-
-       for ( i = 0; i < len; i++ ) {
-               attrs[ map[i].name ] = map[i].value;
-       }
+       return serializeControls( this[0].attributes );
+};
 
-       return attrs;
+/**
+ * Get form data as a plain object mapping form control names to their values.
+ *
+ * @return {Object}
+ */
+jQuery.fn.serializeObject = function () {
+       return serializeControls( this.serializeArray() );
 };
 
 /**
index a214cb3..d372e8f 100644 (file)
                        baseHref = $form.attr( 'action' );
                        baseHref += baseHref.indexOf( '?' ) > -1 ? '&' : '?';
 
-                       linkParams = {};
-                       $.each( $form.serializeArray(), function ( idx, obj ) {
-                               linkParams[ obj.name ] = obj.value;
-                       } );
+                       linkParams = $form.serializeObject();
 
                        return {
                                textParam: context.data.$textbox.attr( 'name' ),
index 411d406..d0f6208 100644 (file)
@@ -70,6 +70,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                        'Example/Baz',
                                        'Example Bar',
                                ),
+                               // Third result when testing offset
+                               'offsetresult' => array(
+                                       'Example Foo',
+                               ),
                        ) ),
                        array( array(
                                'Talk namespace prefix',
@@ -94,6 +98,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                        'Special:AllMessages',
                                        'Special:AllMyFiles',
                                ),
+                               // Third result when testing offset
+                               'offsetresult' => array(
+                                       'Special:AllMyUploads',
+                               ),
                        ) ),
                        array( array(
                                'Special namespace with prefix',
@@ -103,6 +111,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                        'Special:UncategorizedCategories',
                                        'Special:UncategorizedFiles',
                                ),
+                               // Third result when testing offset
+                               'offsetresult' => array(
+                                       'Special:UncategorizedImages',
+                               ),
                        ) ),
                        array( array(
                                'Special page name',
@@ -145,6 +157,30 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                );
        }
 
+       /**
+        * @dataProvider provideSearch
+        * @covers PrefixSearch::search
+        * @covers PrefixSearch::searchBackend
+        */
+       public function testSearchWithOffset( Array $case ) {
+               $this->searchProvision( null );
+               $searcher = new StringPrefixSearch;
+               $results = $searcher->search( $case['query'], 3, array(), 1 );
+
+               // We don't expect the first result when offsetting
+               array_shift( $case['results'] );
+               // And sometimes we expect a different last result
+               $expected = isset( $case['offsetresult'] ) ?
+                       array_merge( $case['results'], $case['offsetresult'] ):
+                       $case['results'];
+
+               $this->assertEquals(
+                       $expected,
+                       $results,
+                       $case[0]
+               );
+       }
+
        public static function provideSearchBackend() {
                return array(
                        array( array(