See StartProfiler.sample for details.
* $wgMangleFlashPolicy was added to make MediaWiki's mangling of anything that
might be a flash policy directive configurable.
+* ApiOpenSearch now supports XML output. The OpenSearchXml extension should no
+ longer be used. If extracts and page images are desired, the TextExtracts and
+ PageImages extensions are required.
+* $wgOpenSearchTemplate is deprecated in favor of $wgOpenSearchTemplates.
=== New features in 1.25 ===
* (bug 62861) Updated plural rules to CLDR 26. Includes incompatible changes
* The debug logging internals have been overhauled, and are now using the
PSR-3 interfaces.
* Update CSSJanus to v1.1.1.
+* 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.
=== Bug fixes in 1.25 ===
* (bug 71003) No additional code will be generated to try to load CSS-embedded
* Query page data for generator=search and generator=prefixsearch will now
include an "index" field, which may be used by the client for sorting the
search results.
+* ApiOpenSearch now supports XML output.
+* ApiOpenSearch will now output descriptions and URLs as array indexes 2 and 3
+ in JSON format.
+* (bug T76051) list=tags will now continue correctly.
+* (bug T76052) list=tags can now indicate whether a tag is defined.
=== Action API internal changes in 1.25 ===
* ApiHelp has been rewritten to support i18n and paginated HTML output.
'RebuildLocalisationCache' => __DIR__ . '/maintenance/rebuildLocalisationCache.php',
'RebuildMessages' => __DIR__ . '/maintenance/rebuildmessages.php',
'RebuildRecentchanges' => __DIR__ . '/maintenance/rebuildrecentchanges.php',
+ 'RebuildSitesCache' => __DIR__ . '/maintenance/rebuildSitesCache.php',
'RebuildTextIndex' => __DIR__ . '/maintenance/rebuildtextindex.php',
'RecentChange' => __DIR__ . '/includes/changes/RecentChange.php',
'RecompressTracked' => __DIR__ . '/maintenance/storage/recompressTracked.php',
'SiteArray' => __DIR__ . '/includes/site/SiteList.php',
'SiteConfiguration' => __DIR__ . '/includes/SiteConfiguration.php',
'SiteList' => __DIR__ . '/includes/site/SiteList.php',
+ 'SiteListFileCache' => __DIR__ . '/includes/site/SiteListFileCache.php',
+ 'SiteListFileCacheBuilder' => __DIR__ . '/includes/site/SiteListFileCacheBuilder.php',
'SiteObject' => __DIR__ . '/includes/site/Site.php',
'SiteSQLStore' => __DIR__ . '/includes/site/SiteSQLStore.php',
'SiteStats' => __DIR__ . '/includes/SiteStats.php',
&$help: Array of HTML strings to be joined for the output.
$options: Array Options passed to ApiHelp::getHelp
+'ApiOpenSearchSuggest': Called when constructing the OpenSearch results. Hooks
+can alter or append to the array.
+&$results: array of associative arrays. Keys are:
+ - title: Title object.
+ - redirect from: Title or null.
+ - extract: Description for this result.
+ - extract trimmed: If truthy, the extract will not be trimmed to
+ $wgOpenSearchDescriptionLength.
+ - image: Thumbnail for this result. Value is an array with subkeys 'source'
+ (url), 'width', 'height', 'alt', 'align'.
+ - url: Url for the given title.
+
'APIQueryAfterExecute': After calling the execute() method of an
action=query submodule. Use this to extend core API modules.
&$module: Module object
/** @} */ # end of Interwiki caching settings.
+/**
+ * @name SiteStore caching settings.
+ * @{
+ */
+
+/**
+ * Specify the file location for the SiteStore json cache file.
+ */
+$wgSitesCacheFile = false;
+
+/** @} */ # end of SiteStore caching settings.
+
/**
* If local interwikis are set up which allow redirects,
* set this regexp to restrict URLs which will be displayed
* PHP wrapper to avoid firing up mediawiki for every keystroke
*
* Placeholders: {searchTerms}
+ *
+ * @deprecated since 1.25 Use $wgOpenSearchTemplates['application/x-suggestions+json'] instead
*/
$wgOpenSearchTemplate = false;
+/**
+ * Templates for OpenSearch suggestions, defaults to API action=opensearch
+ *
+ * Sites with heavy load would typically have these point to a custom
+ * PHP wrapper to avoid firing up mediawiki for every keystroke
+ *
+ * Placeholders: {searchTerms}
+ */
+$wgOpenSearchTemplates = array(
+ 'application/x-suggestions+json' => false,
+ 'application/x-suggestions+xml' => false,
+);
+
/**
* Enable OpenSearch suggestions requested by MediaWiki. Set this to
* false if you've disabled scripts that use api?action=opensearch and
*/
$wgOpenSearchDefaultLimit = 10;
+/**
+ * Minimum length of extract in <Description>. Actual extracts will last until the end of sentence.
+ */
+$wgOpenSearchDescriptionLength = 100;
+
/**
* Expiry time for search suggestion responses
*/
/**@{
* Allowed values for Parser::$mOutputType
* Parameter to Parser::startExternalParse().
+ * Use of Parser consts is preferred:
+ * - Parser::OT_HTML
+ * - Parser::OT_WIKI
+ * - Parser::OT_PREPROCESS
+ * - Parser::OT_MSG
+ * - Parser::OT_PLAIN
*/
define( 'OT_HTML', 1 );
define( 'OT_WIKI', 2 );
/**@{
* Flags for Parser::setFunctionHook
+ * Use of Parser consts is preferred:
+ * - Parser::SFH_NO_HASH
+ * - Parser::SFH_OBJECT_ARGS
*/
define( 'SFH_NO_HASH', 1 );
define( 'SFH_OBJECT_ARGS', 2 );
}
$title = self::getGroupPage( $group );
if ( $title ) {
- $page = $title->getPrefixedText();
+ $page = $title->getFullText();
return "[[$page|$text]]";
} else {
return $text;
* @param array $params All supplied parameters for the module
* @return bool
*/
- public final function validateToken( $token, array $params ) {
+ final public function validateToken( $token, array $params ) {
$tokenType = $this->needsToken();
$salts = ApiQueryTokens::getTokenTypeSalts();
if ( !isset( $salts[$tokenType] ) ) {
* Created on Oct 13, 2006
*
* Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2008 Brion Vibber <brion@wikimedia.org>
+ * Copyright © 2014 Brad Jorsch <bjorsch@wikimedia.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
class ApiOpenSearch extends ApiBase {
+ private $format = null;
+ private $fm = null;
+
/**
- * Override built-in handling of format parameter.
- * Only JSON is supported.
+ * Get the output format
*
- * @return ApiFormatBase
+ * @return string
*/
- public function getCustomPrinter() {
- $params = $this->extractRequestParams();
- $format = $params['format'];
- $allowed = array( 'json', 'jsonfm' );
- if ( in_array( $format, $allowed ) ) {
- return $this->getMain()->createPrinterByName( $format );
+ protected function getFormat() {
+ if ( $this->format === null ) {
+ $params = $this->extractRequestParams();
+ $format = $params['format'];
+
+ $allowedParams = $this->getAllowedParams();
+ if ( !in_array( $format, $allowedParams['format'][ApiBase::PARAM_TYPE] ) ) {
+ $format = $allowedParams['format'][ApiBase::PARAM_DFLT];
+ }
+
+ if ( substr( $format, -2 ) === 'fm' ) {
+ $this->format = substr( $format, 0, -2 );
+ $this->fm = 'fm';
+ } else {
+ $this->format = $format;
+ $this->fm = '';
+ }
}
+ return $this->format;
+ }
+
+ public function getCustomPrinter() {
+ switch( $this->getFormat() ) {
+ case 'json':
+ return $this->getMain()->createPrinterByName( 'json' . $this->fm );
- return $this->getMain()->createPrinterByName( $allowed[0] );
+ case 'xml':
+ $printer = $this->getMain()->createPrinterByName( 'xml' . $this->fm );
+ $printer->setRootElement( 'SearchSuggestion' );
+ return $printer;
+
+ default:
+ ApiBase::dieDebug( __METHOD__, "Unsupported format '{$this->getFormat()}'" );
+ }
}
public function execute() {
$namespaces = $params['namespace'];
$suggest = $params['suggest'];
- // Some script that was loaded regardless of wgEnableOpenSearchSuggest, likely cached.
- if ( $suggest && !$this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
- $searches = array();
+ if ( $params['redirects'] === null ) {
+ // Backwards compatibility, don't resolve for JSON.
+ $resolveRedir = $this->getFormat() !== 'json';
} else {
+ $resolveRedir = $params['redirects'] === 'resolve';
+ }
+
+ $results = array();
+
+ if ( !$suggest || $this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
// Open search results may be stored for a very long time
$this->getMain()->setCacheMaxAge( $this->getConfig()->get( 'SearchSuggestCacheExpiry' ) );
$this->getMain()->setCacheMode( 'public' );
+ $this->search( $search, $limit, $namespaces, $resolveRedir, $results );
+
+ // Allow hooks to populate extracts and images
+ wfRunHooks( 'ApiOpenSearchSuggest', array( &$results ) );
- $searcher = new StringPrefixSearch;
- $searches = $searcher->searchWithVariants( $search, $limit, $namespaces );
+ // Trim extracts, if necessary
+ $length = $this->getConfig()->get( 'OpenSearchDescriptionLength' );
+ foreach ( $results as &$r ) {
+ if ( is_string( $r['extract'] ) && !$r['extract trimmed'] ) {
+ $r['extract'] = self::trimExtract( $r['extract'], $length );
+ }
+ }
}
- // Set top level elements
+
+ // Populate result object
+ $this->populateResult( $search, $results );
+ }
+
+ /**
+ * Perform the search
+ *
+ * @param string $search Text to search
+ * @param int $limit Maximum items to return
+ * @param array $namespaces Namespaces to search
+ * @param bool $resolveRedir Whether to resolve redirects
+ * @param array &$results Put results here
+ */
+ protected function search( $search, $limit, $namespaces, $resolveRedir, &$results ) {
+ // Find matching titles as Title objects
+ $searcher = new TitlePrefixSearch;
+ $titles = $searcher->searchWithVariants( $search, $limit, $namespaces );
+
+ if ( $resolveRedir ) {
+ // Query for redirects
+ $db = $this->getDb();
+ $lb = new LinkBatch( $titles );
+ $res = $db->select(
+ array( 'page', 'redirect' ),
+ array( 'page_namespace', 'page_title', 'rd_namespace', 'rd_title' ),
+ array(
+ 'rd_from = page_id',
+ 'rd_interwiki IS NULL OR rd_interwiki = ' . $db->addQuotes( '' ),
+ $lb->constructSet( 'page', $db ),
+ ),
+ __METHOD__
+ );
+ $redirects = array();
+ foreach ( $res as $row ) {
+ $redirects[$row->page_namespace][$row->page_title] =
+ array( $row->rd_namespace, $row->rd_title );
+ }
+
+ // Bypass any redirects
+ $seen = array();
+ foreach ( $titles as $title ) {
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+ $from = null;
+ if ( isset( $redirects[$ns][$dbkey] ) ) {
+ list( $ns, $dbkey ) = $redirects[$ns][$dbkey];
+ $from = $title;
+ $title = Title::makeTitle( $ns, $dbkey );
+ }
+ if ( !isset( $seen[$ns][$dbkey] ) ) {
+ $seen[$ns][$dbkey] = true;
+ $results[$title->getArticleId()] = array(
+ 'title' => $title,
+ 'redirect from' => $from,
+ 'extract' => false,
+ 'extract trimmed' => false,
+ 'image' => false,
+ 'url' => wfExpandUrl( $title->getFullUrl(), PROTO_CURRENT ),
+ );
+ }
+ }
+ } else {
+ foreach ( $titles as $title ) {
+ $results[$title->getArticleId()] = array(
+ 'title' => $title,
+ 'redirect from' => null,
+ 'extract' => false,
+ 'extract trimmed' => false,
+ 'image' => false,
+ 'url' => wfExpandUrl( $title->getFullUrl(), PROTO_CURRENT ),
+ );
+ }
+ }
+ }
+
+ /**
+ * @param string $search
+ * @param array &$results
+ */
+ protected function populateResult( $search, &$results ) {
$result = $this->getResult();
- $result->addValue( null, 0, $search );
- $result->addValue( null, 1, $searches );
+
+ switch ( $this->getFormat() ) {
+ case 'json':
+ // http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.1
+ $result->addValue( null, 0, strval( $search ) );
+ $terms = array();
+ $descriptions = array();
+ $urls = array();
+ foreach ( $results as $r ) {
+ $terms[] = $r['title']->getPrefixedText();
+ $descriptions[] = strval( $r['extract'] );
+ $urls[] = $r['url'];
+ }
+ $result->addValue( null, 1, $terms );
+ $result->addValue( null, 2, $descriptions );
+ $result->addValue( null, 3, $urls );
+ break;
+
+ case 'xml':
+ // http://msdn.microsoft.com/en-us/library/cc891508%28v=vs.85%29.aspx
+ $imageKeys = array(
+ 'source' => true,
+ 'alt' => true,
+ 'width' => true,
+ 'height' => true,
+ 'align' => true,
+ );
+ $items = array();
+ foreach ( $results as $r ) {
+ $item = array();
+ $result->setContent( $item, $r['title']->getPrefixedText(), 'Text' );
+ $result->setContent( $item, $r['url'], 'Url' );
+ if ( is_string( $r['extract'] ) && $r['extract'] !== '' ) {
+ $result->setContent( $item, $r['extract'], 'Description' );
+ }
+ if ( is_array( $r['image'] ) && isset( $r['image']['source'] ) ) {
+ $item['Image'] = array_intersect_key( $r['image'], $imageKeys );
+ }
+ $items[] = $item;
+ }
+ $result->setIndexedTagName( $items, 'Item' );
+ $result->addValue( null, 'version', '2.0' );
+ $result->addValue( null, 'xmlns', 'http://opensearch.org/searchsuggest2' );
+ $query = array();
+ $result->setContent( $query, strval( $search ) );
+ $result->addValue( null, 'Query', $query );
+ $result->addValue( null, 'Section', $items );
+ break;
+
+ default:
+ ApiBase::dieDebug( __METHOD__, "Unsupported format '{$this->getFormat()}'" );
+ }
}
public function getAllowedParams() {
ApiBase::PARAM_ISMULTI => true
),
'suggest' => false,
+ 'redirects' => array(
+ ApiBase::PARAM_TYPE => array( 'return', 'resolve' ),
+ ),
'format' => array(
ApiBase::PARAM_DFLT => 'json',
- ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' ),
+ ApiBase::PARAM_TYPE => array( 'json', 'jsonfm', 'xml', 'xmlfm' ),
)
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Opensearch';
}
+
+ /**
+ * Trim an extract to a sensible length.
+ *
+ * Adapted from Extension:OpenSearchXml, which adapted it from
+ * Extension:ActiveAbstract.
+ *
+ * @param string $text
+ * @param int $len Target length; actual result will continue to the end of a sentence.
+ * @return string
+ */
+ public static function trimExtract( $text, $length ) {
+ static $regex = null;
+
+ if ( $regex === null ) {
+ $endchars = array(
+ '([^\d])\.\s', '\!\s', '\?\s', // regular ASCII
+ '。', // full-width ideographic full-stop
+ '.', '!', '?', // double-width roman forms
+ '。', // half-width ideographic full stop
+ );
+ $endgroup = implode( '|', $endchars );
+ $end = "(?:$endgroup)";
+ $sentence = ".{{$length},}?$end+";
+ $regex = "/^($sentence)/u";
+ }
+
+ $matches = array();
+ if ( preg_match( $regex, $text, $matches ) ) {
+ return trim( $matches[1] );
+ } else {
+ // Just return the first line
+ $lines = explode( "\n", $text );
+ return trim( $lines[0] );
+ }
+ }
+
+ /**
+ * Fetch the template for a type.
+ *
+ * @param string $type MIME type
+ * @return string
+ */
+ public static function getOpenSearchTemplate( $type ) {
+ global $wgOpenSearchTemplate, $wgCanonicalServer;
+
+ if ( $wgOpenSearchTemplate && $type === 'application/x-suggestions+json' ) {
+ return $wgOpenSearchTemplate;
+ }
+
+ $ns = implode( '|', SearchEngine::defaultNamespaces() );
+ if ( !$ns ) {
+ $ns = "0";
+ }
+
+ switch ( $type ) {
+ case 'application/x-suggestions+json':
+ return $wgCanonicalServer . wfScript( 'api' )
+ . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
+
+ case 'application/x-suggestions+xml':
+ return $wgCanonicalServer . wfScript( 'api' )
+ . '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
+
+ default:
+ throw new MWException( __METHOD__ . ": Unknown type '$type'" );
+ }
+ }
}
*/
class ApiQueryTags extends ApiQueryBase {
- /**
- * @var ApiResult
- */
- private $result;
-
- private $limit;
- private $fld_displayname = false, $fld_description = false,
- $fld_hitcount = false;
-
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'tg' );
}
$prop = array_flip( $params['prop'] );
- $this->fld_displayname = isset( $prop['displayname'] );
- $this->fld_description = isset( $prop['description'] );
- $this->fld_hitcount = isset( $prop['hitcount'] );
-
- $this->limit = $params['limit'];
- $this->result = $this->getResult();
+ $fld_displayname = isset( $prop['displayname'] );
+ $fld_description = isset( $prop['description'] );
+ $fld_hitcount = isset( $prop['hitcount'] );
+ $fld_defined = isset( $prop['defined'] );
+
+ $limit = $params['limit'];
+ $result = $this->getResult();
+
+ $definedTags = array_fill_keys( ChangeTags::listDefinedTags(), 0 );
+
+ # Fetch defined tags that aren't past the continuation
+ if ( $params['continue'] !== null ) {
+ $cont = $params['continue'];
+ $tags = array_filter( array_keys( $definedTags ), function ( $v ) use ( $cont ) {
+ return $v >= $cont;
+ } );
+ $tags = array_fill_keys( $tags, 0 );
+ } else {
+ $tags = $definedTags;
+ }
+ # Merge in all used tags
$this->addTables( 'change_tag' );
$this->addFields( 'ct_tag' );
-
- $this->addFieldsIf( array( 'hitcount' => 'COUNT(*)' ), $this->fld_hitcount );
-
- $this->addOption( 'LIMIT', $this->limit + 1 );
+ $this->addFields( array( 'hitcount' => $fld_hitcount ? 'COUNT(*)' : '0' ) );
+ $this->addOption( 'LIMIT', $limit + 1 );
$this->addOption( 'GROUP BY', 'ct_tag' );
$this->addWhereRange( 'ct_tag', 'newer', $params['continue'], null );
-
$res = $this->select( __METHOD__ );
-
- $ok = true;
-
foreach ( $res as $row ) {
- if ( !$ok ) {
- break;
- }
- $ok = $this->doTag( $row->ct_tag, $this->fld_hitcount ? $row->hitcount : 0 );
+ $tags[$row->ct_tag] = (int)$row->hitcount;
}
- // include tags with no hits yet
- foreach ( ChangeTags::listDefinedTags() as $tag ) {
- if ( !$ok ) {
+ # Now make sure the array is sorted for proper continuation
+ ksort( $tags );
+
+ $count = 0;
+ foreach ( $tags as $tagName => $hitcount ) {
+ if ( ++$count > $limit ) {
+ $this->setContinueEnumParameter( 'continue', $tagName );
break;
}
- $ok = $this->doTag( $tag, 0 );
- }
-
- $this->result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'tag' );
- }
- private function doTag( $tagName, $hitcount ) {
- static $count = 0;
- static $doneTags = array();
+ $tag = array();
+ $tag['name'] = $tagName;
- if ( in_array( $tagName, $doneTags ) ) {
- return true;
- }
-
- if ( ++$count > $this->limit ) {
- $this->setContinueEnumParameter( 'continue', $tagName );
-
- return false;
- }
-
- $tag = array();
- $tag['name'] = $tagName;
-
- if ( $this->fld_displayname ) {
- $tag['displayname'] = ChangeTags::tagDescription( $tagName );
- }
-
- if ( $this->fld_description ) {
- $msg = wfMessage( "tag-$tagName-description" );
- $tag['description'] = $msg->exists() ? $msg->text() : '';
- }
+ if ( $fld_displayname ) {
+ $tag['displayname'] = ChangeTags::tagDescription( $tagName );
+ }
- if ( $this->fld_hitcount ) {
- $tag['hitcount'] = $hitcount;
- }
+ if ( $fld_description ) {
+ $msg = $this->msg( "tag-$tagName-description" );
+ $tag['description'] = $msg->exists() ? $msg->text() : '';
+ }
- $doneTags[] = $tagName;
+ if ( $fld_hitcount ) {
+ $tag['hitcount'] = $hitcount;
+ }
- $fit = $this->result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
- if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $tagName );
+ if ( $fld_defined && isset( $definedTags[$tagName] ) ) {
+ $tag['defined'] = '';
+ }
- return false;
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $tagName );
+ break;
+ }
}
- return true;
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'tag' );
}
public function getCacheMode( $params ) {
'name',
'displayname',
'description',
- 'hitcount'
+ 'hitcount',
+ 'defined',
),
ApiBase::PARAM_ISMULTI => true
)
protected function getExamplesMessages() {
return array(
- 'action=query&list=tags&tgprop=displayname|description|hitcount'
+ 'action=query&list=tags&tgprop=displayname|description|hitcount|defined'
=> 'apihelp-query+tags-example-simple',
);
}
"apihelp-main-param-uselang": "Мова для выкарыстаньня ў перакладах паведамленьняў. Сьпіс кодаў можа быць атрыманы з [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]] з siprop=languages, або трэба вызначыць «user», каб ужываць наладкі мовы цяперашняга карыстальніка, або вызначыць «content», каб ужываць мову зьместу гэтай вікі.",
"apihelp-block-description": "Блякаваньне ўдзельніка.",
"apihelp-block-param-user": "Імя ўдзельніка, IP-адрас або IP-дыяпазон, якія вы хочаце заблякаваць.",
- "apihelp-block-param-expiry": "Час заканчэньня. Можа быць адносным (напрыклад, «5 months» або «2 weeks») ці аблсалютным (напрыклад, «2014-09-18T12:34:56Z»). Калі выстаўлены на «infinite», «indefinite» ці «never», блякаваньне будзе бестэрміновым."
+ "apihelp-block-param-expiry": "Час заканчэньня. Можа быць адносным (напрыклад, «5 months» або «2 weeks») ці аблсалютным (напрыклад, «2014-09-18T12:34:56Z»). Калі выстаўлены на «infinite», «indefinite» ці «never», блякаваньне будзе бестэрміновым.",
+ "apihelp-block-param-reason": "Прычына блякаваньня."
}
"apihelp-opensearch-param-limit": "Maximum number of results to return.",
"apihelp-opensearch-param-namespace": "Namespaces to search.",
"apihelp-opensearch-param-suggest": "Do nothing if [https://www.mediawiki.org/wiki/Manual:$wgEnableOpenSearchSuggest $wgEnableOpenSearchSuggest] is false.",
+ "apihelp-opensearch-param-redirects": "How to handle redirects:\n;return:Return the redirect itself.\n;resolve:Return the target page. May return fewer than $1limit results.\nFor historical reasons, the default is \"return\" for $1format=json and \"resolve\" for other formats.",
"apihelp-opensearch-param-format": "The format of the output.",
"apihelp-opensearch-example-te": "Find pages beginning with \"Te\"",
"apihelp-query+tags-description": "List change tags.",
"apihelp-query+tags-param-limit": "The maximum number of tags to list.",
- "apihelp-query+tags-param-prop": "Which properties to get:\n;name:Adds name of tag.\n;displayname:Adds system message for the tag.\n;description:Adds description of the tag.\n;hitcount:Adds the amount of revisions that have this tag.",
+ "apihelp-query+tags-param-prop": "Which properties to get:\n;name:Adds name of tag.\n;displayname:Adds system message for the tag.\n;description:Adds description of the tag.\n;hitcount:Adds the amount of revisions that have this tag.\n;defined:Indicate whether the tag is defined.",
"apihelp-query+tags-example-simple": "List available tags",
"apihelp-query+templates-description": "Returns all pages transcluded on the given pages.",
"apihelp-query+alldeletedrevisions-param-to": "Запри го исписот на овој наслов.",
"apihelp-query+alldeletedrevisions-example-user": "Список на последните 50 избришани придонеси на Корисник:Пример",
"apihelp-query+alldeletedrevisions-example-ns-main": "Список на последните 50 избришани преработки во главниот именски простор",
+ "apihelp-query+allimages-example-B": "Прикажи список на податотеки што почнуваат со буквата „Б“",
+ "apihelp-query+allimages-example-recent": "Прикажи список на неодамна подигнати податотеки сличен на [[Special:NewFiles]]",
+ "apihelp-query+allimages-example-generator": "Прикажи информации за околу 4 податотеки што почнуваат со буквата „Т“",
+ "apihelp-query+alllinks-description": "Наброј ги сите врски што водат кон даден именски простор.",
+ "apihelp-query+alllinks-param-from": "Наслов на врската од која ќе почне набројувањето.",
+ "apihelp-query+alllinks-param-to": "Наслов на врската на која ќе запре набројувањето.",
+ "apihelp-query+alllinks-param-prefix": "Пребарај ги сите сврзани наслови што почнуваат со оваа вредност.",
+ "apihelp-query+alllinks-param-unique": "Прикажувај само различни поврзани наслови. Не може да се користи со $1prop=ids.\nКога се користи како создавач, дава целни страници наместо изворни.",
"apihelp-query+alllinks-param-namespace": "Именскиот простор што се набројува.",
"apihelp-query+alllinks-param-limit": "Колку вкупно ставки да се дадат.",
"apihelp-query+alllinks-param-dir": "Насока на исписот.",
"apihelp-opensearch-param-limit": "{{doc-apihelp-param|opensearch|limit}}",
"apihelp-opensearch-param-namespace": "{{doc-apihelp-param|opensearch|namespace}}",
"apihelp-opensearch-param-suggest": "{{doc-apihelp-param|opensearch|suggest}}",
+ "apihelp-opensearch-param-redirects": "{{doc-apihelp-param|opensearch|redirects}}",
"apihelp-opensearch-param-format": "{{doc-apihelp-param|opensearch|format}}",
"apihelp-opensearch-example-te": "{{doc-apihelp-example|opensearch}}",
"apihelp-options-description": "{{doc-apihelp-description|options}}",
"authors": [
"Jopparn",
"Lokal Profil",
- "WikiPhoenix"
+ "WikiPhoenix",
+ "Victorsa"
]
},
"apihelp-main-param-action": "Vilken åtgärd som ska utföras.",
"apihelp-createaccount-description": "Skapa ett nytt användarkonto.",
"apihelp-createaccount-param-name": "Användarnamn.",
"apihelp-createaccount-param-password": "Lösenord (ignoreras om $1mailpassword angetts).",
+ "apihelp-createaccount-param-domain": "Domän för extern autentisering (frivillig).",
"apihelp-createaccount-param-email": "Användarens e-postadress (valfritt).",
"apihelp-createaccount-param-realname": "Användarens riktiga namn (valfritt).",
"apihelp-createaccount-example-pass": "Skapa användaren \"testuser\" med lösenordet \"test123\"",
"apihelp-expandtemplates-param-text": "Wikitext att konvertera.",
"apihelp-feedcontributions-param-year": "Från år (och tidigare).",
"apihelp-feedcontributions-param-month": "Från månad (och tidigare).",
- "apihelp-feedcontributions-example-simple": "Returnerar bidrag för [[User:Example]]",
+ "apihelp-feedcontributions-example-simple": "Returnera bidrag för [[User:Example]]",
"apihelp-feedrecentchanges-param-days": "Dagar att begränsa resultaten till.",
"apihelp-feedrecentchanges-param-limit": "Maximalt antal resultat att returnera.",
"apihelp-feedrecentchanges-param-hideminor": "Dölj mindre ändringar.",
"apihelp-feedrecentchanges-param-target": "Visa endast ändringarna av sidor som den här sidan länkar till.",
"apihelp-feedrecentchanges-example-simple": "Visa senaste ändringar",
"apihelp-feedrecentchanges-example-30days": "Visa senaste ändringar för 30 dygn",
+ "apihelp-filerevert-description": "Återställ en fil till en äldre version.",
"apihelp-filerevert-param-comment": "Ladda upp kommentar.",
"apihelp-filerevert-example-revert": "Återställ Wiki.png till versionen från 2011-03-05T15:27:40Z",
+ "apihelp-help-example-main": "Hjälp för huvudmodul",
"apihelp-help-example-recursive": "All hjälp på en sida",
"apihelp-help-example-help": "Hjälp för själva hjälpmodulen",
"apihelp-imagerotate-description": "Rotera en eller flera bilder.",
- "apihelp-imagerotate-param-rotation": "Grader att rotera medurs.",
+ "apihelp-imagerotate-param-rotation": "Grader att rotera bild medurs.",
"apihelp-imagerotate-example-simple": "Rotera [[:File:Example.png]] med 90 grader",
"apihelp-imagerotate-example-generator": "Rotera alla bilder i [[:Category:Flip]] med 180 grader",
+ "apihelp-import-param-summary": "Importera sammanfattning.",
"apihelp-import-param-xml": "Uppladdad XML-fil.",
+ "apihelp-import-param-interwikisource": "För interwiki-importer: wiki som du vill importera från.",
+ "apihelp-import-param-interwikipage": "För interwiki-importer: sidan som du vill importera.",
+ "apihelp-import-param-fullhistory": "För interwiki-importer: importera hela historiken, inte bara den aktuella versionen.",
+ "apihelp-import-param-templates": "För interwiki-importer: importera även alla mallar som ingår.",
+ "apihelp-import-param-namespace": "För interwiki-importer: importera till denna namnrymd.",
+ "apihelp-import-param-rootpage": "Importera som undersida till denna sida.",
+ "apihelp-import-example-import": "Importera [[meta:Help:Parserfunktioner]] till namnrymd 100 med full historik.",
"apihelp-login-param-name": "Användarnamn.",
"apihelp-login-param-password": "Lösenord.",
"apihelp-login-param-domain": "Domän (valfritt).",
"apihelp-move-param-unwatch": "Ta bort sidan och omdirigeringen från din bevakningslista.",
"apihelp-move-param-ignorewarnings": "Ignorera alla varningar.",
"apihelp-opensearch-param-search": "Söksträng.",
+ "apihelp-opensearch-param-limit": "Maximalt antal resultat att returnera.",
"apihelp-opensearch-param-namespace": "Namnrymder att genomsöka.",
"apihelp-opensearch-param-suggest": "Gör ingenting om [https://www.mediawiki.org/wiki/Manual:$wgEnableOpenSearchSuggest $wgEnableOpenSearchSuggest] är falskt.",
+ "apihelp-opensearch-param-format": "Formatet för utdata.",
+ "apihelp-opensearch-example-te": "Hitta sidor som börjar med \"Te\"",
"apihelp-options-example-reset": "Återställ alla inställningar",
"apihelp-paraminfo-param-helpformat": "Format för hjälpsträngar.",
"apihelp-patrol-example-revid": "Patrullera en sidversion",
"apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Kan inte användas med $3user.",
"apihelp-query+allfileusages-example-unique": "Lista unika filtitlar",
"apihelp-query+allimages-param-sort": "Egenskap att sortera efter.",
- "apihelp-query+allmessages-param-lang": "Returnerar meddelanden på detta språk.",
- "apihelp-query+allmessages-example-ipb": "Visa meddelande som börjar med \"ipb-\"",
+ "apihelp-query+allmessages-param-lang": "Returnera meddelanden på detta språk.",
+ "apihelp-query+allmessages-example-ipb": "Visa meddelanden som börjar med \"ipb-\"",
"apihelp-query+allmessages-example-de": "Visa meddelandena \"august\" och \"mainpage\" på tyska",
"apihelp-query+allpages-param-filterredir": "Vilka sidor att lista.",
"apihelp-query+stashimageinfo-description": "Returnerar filinformation för temporära filer.",
"apihelp-query+iwlinks-param-title": "用于搜索的跨wiki链接。必须与$1prefix一起使用。",
"apihelp-query+iwlinks-example-simple": "从[[首页]]获取跨wiki链接",
"apihelp-query+langbacklinks-param-lang": "用于语言链接的语言。",
+ "apihelp-query+langbacklinks-param-title": "要搜索的语言链接。必须与$1lang一起使用。",
"apihelp-query+langbacklinks-example-simple": "获取链接至[[:fr:Test]]的页面",
"apihelp-query+langbacklinks-example-generator": "获取链接至[[:fr:Test]]的页面的信息",
"apihelp-query+langlinks-param-limit": "返回多少语言链接。",
"apihelp-userrights-param-remove": "将用户从这些组中移除。",
"apihelp-userrights-param-reason": "更改原因。",
"apihelp-userrights-example-user": "将用户FooBot添加至“机器人”用户组,并从“管理员”和“行政员”组移除",
+ "apihelp-userrights-example-userid": "将ID为123的用户加入至“机器人”组,并将其从“管理员”和“行政员”组移除",
"apihelp-watch-example-watch": "监视页面“首页”",
"apihelp-watch-example-unwatch": "取消监视页面“首页”",
"apihelp-dbg-description": "输出数据为PHP的var_export()格式。",
"@metadata": {
"authors": [
"Cwlin0416",
- "Liuxinyu970226"
+ "Liuxinyu970226",
+ "LNDDYL"
]
},
"apihelp-main-param-action": "要執行的動作。",
"apihelp-edit-param-sectiontitle": "新章節的標題。",
"apihelp-edit-param-text": "頁面內容。",
"apihelp-edit-param-summary": "編輯摘要。 當未設定 $1section=new 與 $1sectiontitle 時也會當做章節標題。",
+ "apihelp-edit-param-minor": "小編輯。",
+ "apihelp-edit-param-notminor": "非小編輯。",
"apihelp-edit-param-createonly": "若頁面已存在,則不編輯頁面。",
"apihelp-edit-param-nocreate": "若頁面不存在,則產生錯誤。",
"apihelp-edit-param-watch": "加入頁面至您的監視清單。",
# Syntax for arguments (see Parser::setFunctionHook):
# "name for lookup in localized magic words array",
# function callback,
- # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
+ # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
# instead of {{#int:...}})
$noHashFunctions = array(
'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
'revisiontimestamp', 'revisionuser', 'cascadingsources',
);
foreach ( $noHashFunctions as $func ) {
- $parser->setFunctionHook( $func, array( __CLASS__, $func ), SFH_NO_HASH );
+ $parser->setFunctionHook( $func, array( __CLASS__, $func ), Parser::SFH_NO_HASH );
}
- $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), Parser::SFH_NO_HASH );
+ $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), Parser::SFH_NO_HASH );
$parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) );
$parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) );
- $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS );
+ $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), Parser::SFH_OBJECT_ARGS );
$parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
if ( $wgAllowDisplayTitle ) {
- $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), Parser::SFH_NO_HASH );
}
if ( $wgAllowSlowParserFunctions ) {
$parser->setFunctionHook(
'pagesinnamespace',
array( __CLASS__, 'pagesinnamespace' ),
- SFH_NO_HASH
+ Parser::SFH_NO_HASH
);
}
}
* @return string Corrected HTML output
*/
public static function tidy( $text ) {
- global $wgTidyInternal;
-
$wrapper = new MWTidyWrapper;
$wrappedtext = $wrapper->getWrapped( $text );
$retVal = null;
- if ( $wgTidyInternal ) {
- $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal );
- } else {
- $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal );
- }
+ $correctedtext = self::clean( $wrappedtext, false, $retVal );
if ( $retVal < 0 ) {
wfDebug( "Possible tidy configuration error!\n" );
* @return bool Whether the HTML is valid
*/
public static function checkErrors( $text, &$errorStr = null ) {
+ $retval = 0;
+ $errorStr = self::clean( $text, true, $retval );
+ return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
+ }
+
+ /**
+ * Perform a clean/repair operation
+ * @param string $text HTML to check
+ * @param bool $stderr Whether to read result from STDERR rather than STDOUT
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
+ */
+ private static function clean( $text, $stderr = false, &$retval = null ) {
global $wgTidyInternal;
- $retval = 0;
if ( $wgTidyInternal ) {
- $errorStr = self::execInternalTidy( $text, true, $retval );
+ if ( wfIsHHVM() ) {
+ if ( $stderr ) {
+ throw new MWException( __METHOD__.": error text return from HHVM tidy is not supported" );
+ }
+ return self::hhvmClean( $text, $retval );
+ } else {
+ return self::phpClean( $text, $stderr, $retval );
+ }
} else {
- $errorStr = self::execExternalTidy( $text, true, $retval );
+ return self::externalClean( $text, $stderr, $retval );
}
-
- return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
}
/**
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execExternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function externalClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
wfProfileIn( __METHOD__ );
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function phpClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgDebugTidy;
wfProfileIn( __METHOD__ );
wfProfileOut( __METHOD__ );
return $cleansource;
}
+
+ /**
+ * Use the tidy extension for HHVM from
+ * https://github.com/wikimedia/mediawiki-php-tidy
+ *
+ * This currently does not support the object-oriented interface, but
+ * tidy_repair_string() can be used for the most common tasks.
+ *
+ * @param string $text HTML to check
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
+ */
+ private static function hhvmClean( $text, &$retval ) {
+ global $wgTidyConf;
+ wfProfileIn( __METHOD__ );
+ $cleansource = tidy_repair_string( $text, $wgTidyConf, 'utf8' );
+ if ( $cleansource === false ) {
+ $cleansource = null;
+ $retval = -1;
+ } else {
+ $retval = 0;
+ }
+ wfProfileOut( __METHOD__ );
+ return $cleansource;
+ }
}
const HALF_PARSED_VERSION = 2;
# Flags for Parser::setFunctionHook
- # Also available as global constants from Defines.php
const SFH_NO_HASH = 1;
const SFH_OBJECT_ARGS = 2;
}
$allArgs = array( &$this );
- if ( $flags & SFH_OBJECT_ARGS ) {
+ if ( $flags & self::SFH_OBJECT_ARGS ) {
# Convert arguments to PPNodes and collect for appending to $allArgs
$funcArgs = array();
foreach ( $args as $k => $v ) {
* The callback function should have the form:
* function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... }
*
- * Or with SFH_OBJECT_ARGS:
+ * Or with Parser::SFH_OBJECT_ARGS:
* function myParserFunction( $parser, $frame, $args ) { ... }
*
* The callback may either return the text result of the function, or an array with the text
* @param string $id The magic word ID
* @param callable $callback The callback function (and object) to use
* @param int $flags A combination of the following flags:
- * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
+ * Parser::SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
*
- * SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. This
- * allows for conditional expansion of the parse tree, allowing you to eliminate dead
+ * Parser::SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text.
+ * This allows for conditional expansion of the parse tree, allowing you to eliminate dead
* branches and thus speed up parsing. It is also possible to analyse the parse tree of
* the arguments, and to control the way they are expanded.
*
$syn = $wgContLang->lc( $syn );
}
# Add leading hash
- if ( !( $flags & SFH_NO_HASH ) ) {
+ if ( !( $flags & self::SFH_NO_HASH ) ) {
$syn = '#' . $syn;
}
# Remove trailing colon
/**
* Get OpenSearch suggestion template
*
+ * @deprecated since 1.25
* @return string
*/
public static function getOpenSearchTemplate() {
- global $wgOpenSearchTemplate, $wgCanonicalServer;
-
- if ( $wgOpenSearchTemplate ) {
- return $wgOpenSearchTemplate;
- } else {
- $ns = implode( '|', SearchEngine::defaultNamespaces() );
- if ( !$ns ) {
- $ns = "0";
- }
-
- return $wgCanonicalServer . wfScript( 'api' )
- . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
- }
+ wfDeprecated( __METHOD__, '1.25' );
+ return ApiOpenSearch::getOpenSearchTemplate( 'application/x-suggestions+json' );
}
/**
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.25
+ *
+ * @file
+ *
+ * @license GNU GPL v2+
+ */
+class SiteListFileCache {
+
+ /**
+ * @var SiteList
+ */
+ private $sites = null;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param string $cacheFile
+ */
+ public function __construct( $cacheFile ) {
+ $this->cacheFile = $cacheFile;
+ }
+
+ /**
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ if ( $this->sites === null ) {
+ $this->sites = $this->loadSitesFromCache();
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * @since 1.25
+ */
+ public function getSite( $globalId ) {
+ $sites = $this->getSites();
+
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @return SiteList
+ */
+ private function loadSitesFromCache() {
+ $data = $this->loadJsonFile();
+
+ $sites = new SiteList();
+
+ // @todo lazy initialize the site objects in the site list (e.g. only when needed to access)
+ foreach( $data['sites'] as $siteArray ) {
+ $sites[] = $this->newSiteFromArray( $siteArray );
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @throws MWException
+ * @return array
+ */
+ private function loadJsonFile() {
+ if ( !is_readable( $this->cacheFile ) ) {
+ throw new MWException( 'SiteList cache file not found.' );
+ }
+
+ $contents = file_get_contents( $this->cacheFile );
+ $data = json_decode( $contents, true );
+
+ if ( !is_array( $data ) || !array_key_exists( 'sites', $data ) ) {
+ throw new MWException( 'SiteStore json cache data is invalid.' );
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return Site
+ */
+ private function newSiteFromArray( array $data ) {
+ $siteType = array_key_exists( 'type', $data ) ? $data['type'] : Site::TYPE_UNKNOWN;
+ $site = Site::newForType( $siteType );
+
+ $site->setGlobalId( $data['globalid'] );
+ $site->setInternalId( $data['internalid'] );
+ $site->setForward( $data['forward'] );
+ $site->setGroup( $data['group'] );
+ $site->setLanguageCode( $data['language'] );
+ $site->setSource( $data['source'] );
+ $site->setExtraData( $data['data'] );
+ $site->setExtraConfig( $data['config'] );
+
+ foreach( $data['identifiers'] as $identifier ) {
+ $site->addLocalId( $identifier['type'], $identifier['key'] );
+ }
+
+ return $site;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.25
+ *
+ * @file
+ *
+ * @license GNU GPL v2+
+ */
+class SiteListFileCacheBuilder {
+
+ /**
+ * @var SiteStore
+ */
+ private $siteStore;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param SiteStore $siteStore
+ * @param string $cacheFile
+ */
+ public function __construct( SiteStore $siteStore, $cacheFile ) {
+ $this->siteStore = $siteStore;
+ $this->cacheFile = $cacheFile;
+ }
+
+ public function build() {
+ $this->sites = $this->siteStore->getSites( 'recache' );
+ $this->cacheSites( $this->sites->getArrayCopy() );
+ }
+
+ /**
+ * @param Site[] $sites
+ *
+ * @throws MWException if in manualRecache mode
+ * @return bool
+ */
+ private function cacheSites( array $sites ) {
+ $sitesArray = array();
+
+ foreach ( $sites as $site ) {
+ $globalId = $site->getGlobalId();
+ $sitesArray[$globalId] = $this->getSiteAsArray( $site );
+ }
+
+ $json = json_encode( array(
+ 'sites' => $sitesArray
+ ) );
+
+ $result = file_put_contents( $this->cacheFile, $json );
+
+ return $result !== false;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array
+ */
+ private function getSiteAsArray( Site $site ) {
+ $siteEntry = unserialize( $site->serialize() );
+ $siteIdentifiers = $this->buildLocalIdentifiers( $site );
+ $identifiersArray = array();
+
+ foreach( $siteIdentifiers as $identifier ) {
+ $identifiersArray[] = $identifier;
+ }
+
+ $siteEntry['identifiers'] = $identifiersArray;
+
+ return $siteEntry;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array Site local identifiers
+ */
+ private function buildLocalIdentifiers( Site $site ) {
+ $localIds = array();
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array(
+ 'type' => $idType,
+ 'key' => $id
+ );
+ }
+ }
+
+ return $localIds;
+ }
+
+}
*/
private $cacheTimeout = 3600;
+ /**
+ * @var BagOStuff
+ */
+ private $cache;
+
/**
* @since 1.21
*
* @param ORMTable|null $sitesTable
+ * @param BagOStuff|null $cache
*
* @return SiteStore
*/
- public static function newInstance( ORMTable $sitesTable = null ) {
- return new static( $sitesTable );
+ public static function newInstance( ORMTable $sitesTable = null, BagOStuff $cache = null ) {
+ if ( $cache === null ) {
+ $cache = wfGetMainCache();
+ }
+
+ return new static( $cache, $sitesTable );
}
/**
*
* @since 1.21
*
+ * @param BagOStuff $cache
* @param ORMTable|null $sitesTable
*/
- protected function __construct( ORMTable $sitesTable = null ) {
+ protected function __construct( BagOStuff $cache, ORMTable $sitesTable = null ) {
if ( $sitesTable === null ) {
$sitesTable = $this->newSitesTable();
}
+ $this->cache = $cache;
$this->sitesTable = $sitesTable;
}
if ( $source === 'cache' ) {
if ( $this->sites === null ) {
- $cache = wfGetMainCache();
- $sites = $cache->get( $this->getCacheKey() );
+ $sites = $this->cache->get( $this->getCacheKey() );
if ( is_object( $sites ) ) {
$this->sites = $sites;
}
}
- $cache = wfGetMainCache();
- $cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
+ $this->cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
wfProfileOut( __METHOD__ );
}
public function reset() {
wfProfileIn( __METHOD__ );
// purge cache
- $cache = wfGetMainCache();
- $cache->delete( $this->getCacheKey() );
+ $this->cache->delete( $this->getCacheKey() );
$this->sites = null;
wfProfileOut( __METHOD__ );
* @return array
*/
private function findInstalledSkins() {
- $styleDirectory = $this->config->get( 'StyleDirectory' ); // @todo we should inject this directly?
+ $styleDirectory = $this->config->get( 'StyleDirectory' );
// Get all subdirectories which might contains skins
$possibleSkins = scandir( $styleDirectory );
$possibleSkins = array_filter( $possibleSkins, function ( $maybeDir ) use ( $styleDirectory ) {
if ( $user->isAllowed( 'deletedhistory' ) ) {
$n = $title->isDeleted();
if ( $n ) {
- $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
+ $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
// If the user can't undelete but can view deleted
// history show them a "View .. deleted" tab instead.
$msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
->setContext( $this->getContext() )->numParams( $n )->text(),
- 'href' => $undelTitle->getLocalURL( array( 'target' => $title->getPrefixedDBkey() ) )
+ 'href' => $undelTitle->getLocalURL()
);
}
}
'autofocus' => true,
'required' => true,
'validation-callback' => array( __CLASS__, 'validateTargetField' ),
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Expiry' => array(
'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
* @return string
*/
protected function preText() {
- $this->getOutput()->addModules( 'mediawiki.special.block' );
+ $this->getOutput()->addModules( array( 'mediawiki.special.block', 'mediawiki.userSuggest' ) );
$text = $this->msg( 'blockiptext' )->parse();
$lang = $this->getLanguage();
$out->setPageTitle( $this->msg( 'ipblocklist' ) );
$out->addModuleStyles( 'mediawiki.special' );
+ $out->addModules( 'mediawiki.userSuggest' );
$request = $this->getRequest();
$par = $request->getVal( 'ip', $par );
'tabindex' => '1',
'size' => '45',
'default' => $this->target,
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Options' => array(
'type' => 'multiselect',
return Status::newFatal( $msg );
} elseif ( !$this->category ) {
- return; // no data sent
+ return false; // no data sent
}
$title = $this->getRandomTitle();
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'unblockip' ) );
- $out->addModules( 'mediawiki.special' );
+ $out->addModules( array( 'mediawiki.special', 'mediawiki.userSuggest' ) );
$form = new HTMLForm( $this->getFields(), $this->getContext() );
$form->setWrapperLegendMsg( 'unblockip' );
'autofocus' => true,
'size' => '45',
'required' => true,
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Name' => array(
'type' => 'info',
strcmp( $hexIP, $end ) <= 0 );
}
+ /**
+ * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
+ *
+ * @since 1.25
+ *
+ * @param string $ip the IP to check
+ * @param array $ranges the IP ranges, each element a range
+ *
+ * @return bool true if the specified adress belongs to the specified range; otherwise, false.
+ */
+ public static function isInRanges( $ip, $ranges ) {
+ foreach ( $ranges as $range ) {
+ if ( self::isInRange( $ip, $range ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Convert some unusual representations of IPv4 addresses to their
* canonical dotted quad representation.
"Gazimagomedov",
"StanProg",
"Bjankuloski06",
- "Vodnokon4e"
+ "Vodnokon4e",
+ "ShadeOfGrey"
]
},
"tog-underline": "Подчертаване на препратките:",
"specialpages-group-wiki": "Данни и инструменти",
"specialpages-group-redirects": "Пренасочващи специални страници",
"specialpages-group-spam": "Инструменти против спам",
+ "specialpages-group-developer": "Инструменти за разработчици",
"blankpage": "Празна страница",
"intentionallyblankpage": "Тази страница умишлено е оставена празна",
"external_image_whitelist": " #Оставете този ред така, както го виждате. <pre>\n#Поставете долу фрагменти от регулярни изрази (само частта между //).\n#Тези фрагменти ще се съпоставят с интернет адресите на външните (hotlinked) картинки.\n#Картинките, чиито адреси отговарят на вписаните регулярни изрази, ще се визуализират, за останалите ще се появява само връзка.\n#Редовете, започващи с # се възприемат като коментари.\n#Командите са чувствителни на малки и главни букви.\n\n#Слагайте всички фрагменти от регулярни изрази НАД този ред. Оставете този ред така, както го виждате. </pre>",
"logentry-move-move": "$1 {{GENDER:$2|премести}} страница „$3“ като „$4“",
"logentry-move-move-noredirect": "$1 {{GENDER:$2|премести}} страницата „$3“ като „$4“ без пренасочване",
"logentry-move-move_redir": "$1 {{GENDER:$2|премести}} страницата $3 като $4 (върху пренасочване)",
- "logentry-move-move_redir-noredirect": "$1 {GENDER:$2|премести}} върху пренасочване $3 като $4 без пренасочване",
+ "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|премести}} върху пренасочване $3 като $4 без пренасочване",
"logentry-patrol-patrol": "$1 {{GENDER:$2|отбеляза}} като патрулирана версия $4 на страницата „$3“",
"logentry-patrol-patrol-auto": "$1 автоматично {{GENDER:$2|отбеляза}} като патрулирана версия $4 на страницата $3",
"logentry-newusers-newusers": "Потребителската сметка $1 беше {{GENDER:$2|създадена}}",
"mainpage-description": "Pàgina principal",
"policy-url": "Project:Polítiques",
"portal": "Portal de la comunitat",
- "portal-url": "Project:Portal de la comunitat",
+ "portal-url": "Project:Portal",
"privacy": "Política de privadesa",
"privacypage": "Project:Política de privadesa",
"badaccess": "Error de permisos",
"filerenameerror": "An n'é mìa pusébil cambiêr al nòm ed \"$1\" in \"$2\".",
"filedeleteerror": "An n'é mìa pusébil scanşlêr al file \"$1\".",
"directorycreateerror": "An n'é mìa pusébil fêr la directory \"$1\".",
+ "directoryreadonlyerror": "La cartèla \"$1\" la 's pōl sōl lēşer.",
+ "directorynotreadableerror": "La cartèla \"$1\" la 's pōl mìa lēşer.",
"filenotfound": "An n'é mìa pusébil catêr al file \"$1\".",
"unexpected": "Valōr mìa pervést \"$1\"=\"$2\".",
"formerror": "Erōr: an n'é ma pusébil spidîr al môdul.",
"right-editprotected": "Mudéfica 'l pàgini prutèti cun \"{{int:protect-level-sysop}}\"",
"right-editsemiprotected": "Mudéfica 'l pàgini prutèti cun \"{{int:protect-level-autoconfirmed}}\"",
"right-editinterface": "Mudéfica al colegamèint tra sistēma e utèint",
+ "right-editusercssjs": "Mudéfica i file CSS e JS 'd êter utèint",
+ "right-editusercss": "Mudéfica i file CSS 'd êter utèint",
+ "right-edituserjs": "Mudéfica i file JS 'd êter utèint",
+ "right-editmyusercss": "Mudéfica i file CSS dal só utèint",
+ "right-editmyuserjs": "Mudéfica i file JavaScript dal só utèint",
+ "right-viewmywatchlist": "Guêrda la lésta di tō tgnû 'd ôc specêl",
+ "right-editmywatchlist": "Mudéfica i tō tgnu 'd ôc. Da nutêr che soquânti asiòun a prân incòra zuntêr dal pàgini ânca sèinsa avèiren al dirét.",
+ "right-viewmyprivateinfo": "Guêrda al tō infurmasiòun personêli (per eşèimpi: indirés ed pôsta eletrônica, nòm vèira)",
+ "right-editmyprivateinfo": "Câmbia 'l tō infurmasiòun personêli (per eşèimpi: indirés ed pôsta eletrônica, nòm vèira)",
+ "right-editmyoptions": "Câmbia al tō preferèinsi",
+ "right-rollback": "Scanşèla a la şvêlta al mudéfichi ed l'ûltèint ch'l'à mudifichê 'na pàgina pariculêra",
"newuserlogpage": "Utèint nōv",
"action-read": "lēzer cla pàgina ché",
"action-edit": "Mudifichêr cla pàgina ché",
"content-model-text": "ჩვეულებრივი ტექსტი",
"content-model-javascript": "ჯავასკრიპტი",
"content-model-css": "CSS",
+ "duplicate-args-category": "გვერდები, რომლებიც იყენებენ დუბლიკატ არგუმენტებს თარგების გამოძახებისას",
+ "duplicate-args-category-desc": "გვერდები, რომლებიც იყენებენ დუბლიკატ არგუმენტებს თარგების გამოძახებისას, როგორებიც არის <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ან <code><nowiki>{{foo|bar|1=bar}}</nowiki></code>.",
"expensive-parserfunction-warning": "ყურადღება. მოცემული გვერდი შეიცავს ძალიან ბევრ მძიმე ფუნქციას.\n\nგამოძახებათა რაოდენობა შეზღუდულია $2 დონეზე.ამ შემთხვევაში უნდა გაკეთდეს $1 გამოძახება.",
"expensive-parserfunction-category": "გვერდი ძალიან ბევრი მძიმე ფუნქციის მოთხოვნით",
"post-expand-template-inclusion-warning": "ყურადღება. ჩართული თარგების ზომა ძალიან დიდია. ზოგი თარგი შეიძლება დარჩეს გათიშული.",
"license": "Лицензияландыруы:",
"license-header": "Лицензияландыруы",
"nolicense": "Ештеңе таңдалмаған",
+ "licenses-edit": "Лицензия талғауларын өңдеу",
"license-nopreview": "(Қарап шығу жетімді емес)",
"upload_source_url": "(жарамды, баршаға қатынаулы URL)",
"upload_source_file": "(компьютеріңіздегі файл)",
"logentry-newusers-autocreate": "$1 қатысушы аккаунтын автоматты түрде {{GENDER:$2|тіркеді}}",
"logentry-rights-rights": "$1 $3 үшін топ мүшелігін $4 дегеннен $5 дегенге {{GENDER:$2|өзгерті}}",
"logentry-rights-rights-legacy": "$1 $3 үшін топ мүшелігін {{GENDER:$2|өзгерті}}",
+ "logentry-upload-upload": "$1 $3 файлын {{GENDER:$2|жүктеді}}",
"rightsnone": "(ешқандай)",
"revdelete-summary": "өңдеменің қысқаша мазмұндамасы",
"feedback-subject": "Тақырып:",
"download": "parsisiųsti",
"unwatchedpages": "Nestebimi puslapiai",
"listredirects": "Peradresavimų sąrašas",
+ "listduplicatedfiles": "Pasikartojančių rinkmenų sąrašas",
"unusedtemplates": "Nenaudojami šablonai",
"unusedtemplatestext": "Šis puslapis rodo sąrašą puslapių, esančių {{ns:template}} vardų srityje, kurie nėra įterpti į jokį kitą puslapį. Nepamirškite patikrinti kitų nuorodų prieš juos ištrinant.",
"unusedtemplateswlh": "kitos nuorodos",
"movepagetalktext": "Susietas aptarimo puslapis bus automatiškai perkeltas kartu su juo, '''išskyrus:''':\n*Puslapis nauju pavadinimu jau turi netuščią aptarimo puslapį, arba\n*Paliksite žemiau esančia varnelę nepažymėtą.\n\nŠiais atvejais jūs savo nuožiūra turite perkelti arba apjungti aptarimo puslapį.",
"movearticle": "Pervardinti puslapį:",
"moveuserpage-warning": "'''Dėmesio:''' Jūs ruošiatės perkelti naudotojo puslapį. Atkreipkite dėmesį, kad bus perkeltas tik puslapis, naudotojas ''nebus'' pervadintas.",
+ "movecategorypage-warning": "<strong>Dėmesio:</strong> Jūs ketinate pervadinti kategorijos puslapį. Atminkite, kad tik pats puslapis bus pervadintas, tačiau kategorijai priskirti puslapiai <em>nebus</em> perkelti naujon kategorijon.",
"movenologintext": "Norėdami pervadinti puslapį, turite būti užsiregistravęs naudotojas ir būti [[Special:UserLogin|prisijungęs]].",
"movenotallowed": "Jūs neturite teisių pervadinti puslapių.",
"movenotallowedfile": "Jūs neturite teisės perkelti failus.",
"fileduplicatesearch-result-n": "Šis failas „$1“ turi $2 {{PLURAL:$2|identišką dublikatą|identiškus dublikatus|identiškų dublikatų}}.",
"fileduplicatesearch-noresults": "Nėra failo pavadinimu \"$1\".",
"specialpages": "Specialieji puslapiai",
- "specialpages-note": "* Įprastą specialius puslapius.\n* <span class=\"mw-specialpagerestricted\">Tik specialius puslapius.</span>",
+ "specialpages-note": "* Įprasti specialieji puslapiai.\n* <span class=\"mw-specialpagerestricted\">Apriboto pasiekiamumo specialieji puslapiai.</span>",
"specialpages-group-maintenance": "Sistemos palaikymo pranešimai",
"specialpages-group-other": "Kiti specialieji puslapiai",
"specialpages-group-login": "Prisijungti / sukurti paskyrą",
"specialpages-group-wiki": "Wiki duomenys ir priemonės",
"specialpages-group-redirects": "Specialieji nukreipimo puslapiai",
"specialpages-group-spam": "Šlamšto valdymo priemonės",
+ "specialpages-group-developer": "Kūrėjo įrankai",
"blankpage": "Tuščias puslapis",
"intentionallyblankpage": "Šis puslapis specialiai paliktas tuščias",
"external_image_whitelist": " #Palikite šią eilutę, tokią kokia yra <pre>\n#Įrašykite standartinių išraiškų fragmentus (tik dalį tarp //)\n#Juos bus bandoma sutapdinti su išorinių paveikslėlių adresais\n#Tie, kurie sutaps, bus rodomi kaip paveikslėliai, o kiti bus rodomi tik kaip nuorodos\n#Raidžių dydis nėra svarbus\n#Eilutės, prasidedančios # yra komentarai\n\n#Įterpkite visus standartinių išraiškų fragmentus prieš šią eilutę. Palikite šią eilutę, tokią kokia yra </pre>",
"duration-decades": "$1 {{PLURAL:$1|dešimtmetis|dešimtmečiai|dešimtmečių}}",
"duration-centuries": "$1 {{PLURAL:$1|amžius|amžiai|amžių}}",
"duration-millennia": "$1 {{PLURAL:$1|tūkstantmetis|tūkstantmečiai|tūkstantmečių}}",
+ "limitreport-title": "Analizatoriaus duomenys:",
"expand_templates_output": "Rezultatas",
"expand_templates_ok": "Gerai",
"expand_templates_remove_comments": "Pašalinti komentarus",
"specialpages-group-wiki": "വിവരങ്ങളും ഉപകരണങ്ങളും",
"specialpages-group-redirects": "തിരിച്ചുവിടൽ സംബന്ധിച്ച പ്രത്യേക താളുകൾ",
"specialpages-group-spam": "പാഴെഴുത്ത് ഉപകരണങ്ങൾ",
+ "specialpages-group-developer": "വികസന ഉപകരണങ്ങൾ",
"blankpage": "ശൂന്യതാൾ",
"intentionallyblankpage": "ഈ താൾ മനഃപൂർവ്വം ശൂന്യമായി ഇട്ടിരിക്കുന്നതാണ്.",
"external_image_whitelist": "#ഈ വരി ഇതേ പോലെ സൂക്ഷിക്കുക <pre>\n#റെഗുലർ എക്സ്പ്രെഷൻ ഘടകങ്ങൾ (ഭാഗം // എന്നതിന്റെയുള്ളിൽ ആയിരിക്കുന്ന വിധത്തിൽ) താഴെ ചേർക്കുക\n#ഇത് പുറത്തുനിന്നുള്ള (ഹോട്ട്ലിങ്ക്ഡ്) ചിത്രങ്ങളുടെ യൂ.ആർ.എല്ലുമായി ഒത്തുനോക്കുന്നതാണ്.\n#പൊരുത്തപ്പെട്ടു പോകുന്ന യൂ.ആർ.എല്ലുകൾ മാത്രം പ്രദർശിപ്പിക്കും, അല്ലാത്തവ ചിത്രത്തിലേയ്ക്കുള്ള കണ്ണിയായി കാണും\n#കുറിപ്പുകളായി കണക്കാക്കാൻ വരികളുടെയാദ്യം # ചേർക്കുക\n#ഇത് കേസ് സെൻസിറ്റീവ് ആണ്\n\n#എല്ലാ റെജെക്സ് ഘടകങ്ങളും ഈ വരിക്ക് മേലേയായി ചേർക്കുക. ഈ വരി ഇതേ പോലെ നിലനിർത്തുക </pre>",
"revdelete-uname-unhid": "ഉപയോക്തൃനാമം മറച്ചത് ഒഴിവാക്കിയിരിക്കുന്നു",
"revdelete-restricted": "കാര്യനിർവാഹകർക്ക് പ്രവർത്തന അതിരുകൾ ഏർപ്പെടുത്തിയിരിക്കുന്നു",
"revdelete-unrestricted": "കാര്യനിർവാഹകർക്ക് ഏർപ്പെടുത്തിയ പ്രവർത്തന അതിരുകൾ നീക്കം ചെയ്തിരിക്കുന്നു",
+ "logentry-merge-merge": "$3 എന്ന താൾ $4 എന്നതിലേക്ക് ($5 നാൾപ്പതിപ്പ് വരെ), $1 {{GENDER:$2|ലയിപ്പിച്ചു}}",
"logentry-move-move": "$1 എന്ന ഉപയോക്താവ് $3 എന്ന താൾ $4 എന്നാക്കി {{GENDER:$2|മാറ്റിയിരിക്കുന്നു}}",
"logentry-move-move-noredirect": "$3 എന്ന താൾ $4 എന്ന തലക്കെട്ടിലേയ്ക്ക് തിരിച്ചുവിടലില്ലാതെ $1 {{GENDER:$2|മാറ്റി}}",
"logentry-move-move_redir": "$3 എന്ന താൾ $4 എന്ന താളിനു മുകളിലേയ്ക്ക്, $1 {{GENDER:$2|മാറ്റിയിരിക്കുന്നു}}",
"specialpages-group-wiki": "Data e strumiente",
"specialpages-group-redirects": "Redirezionamiente d' 'e paggene speciale",
"specialpages-group-spam": "Strumiente p' 'o spam",
+ "specialpages-group-developer": "Strumiente p' 'e sviluppature",
"blankpage": "Paggene abbacante",
"intentionallyblankpage": "Sta paggena s'è lassata abbacante apposta",
"external_image_whitelist": " #Lassate sta linea accussì accussì comme sta<pre>\n#Mettete piezze 'espressione regolare (chilla parta nfra 'e //) sotto\n#Chille s'azzeccano ch' 'e ndirizze URL 'e l'immaggine 'e fore (collegamiente cavere)\n#Chille cu nu cunfronto positivo sarranno mmustate comme immaggene, o pure comme a nu link a l'immaggine ca mmustano\n#Linee c'accumenciano pe' # songo trattate comme commente\n#Chist'è insenzitivo p' 'e maiuscole e minuscole\n\n#Mettete tutt' 'e piezze regex ncopp' 'a stalinea. Lassate sta linea eguale eguale comme 'a verite</pre>",
"error": "Грешка",
"databaseerror": "Грешка у бази података",
"databaseerror-text": "Дошло је до грешке у упиту базе података. Можда је у питању програмска грешка.",
+ "databaseerror-textcl": "Дошло је до грешке у упиту базе података.",
"databaseerror-query": "Упит: $1",
"databaseerror-function": "Функција: $1",
"databaseerror-error": "Грешка: $1",
"specialpages-group-wiki": "Подаци и алати",
"specialpages-group-redirects": "Преусмеравање посебних страница",
"specialpages-group-spam": "Алатке против непожељних порука",
+ "specialpages-group-developer": "Програмерски алати",
"blankpage": "Празна страница",
"intentionallyblankpage": "Ова страница је намерно остављена празном.",
"external_image_whitelist": " #Оставите овај ред онаквим какав јесте<pre>\n#Испод додајте одломке регуларних израза (само део који се налази између //)\n#Они ће бити упоређени с адресама спољашњих слика\n#Оне које се поклапају биће приказане као слике, а преостале као везе до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте</pre>",
"Ktchankt",
"Kc kennylau",
"Mywood",
- "Impersonator 1"
+ "Impersonator 1",
+ "Cedric tsan cantonais"
]
},
"tog-underline": "連結加底線:",
"tog-prefershttps": "簽到後繼續用加密連線",
"underline-always": "全部",
"underline-never": "永不",
- "underline-default": "瀏覽器預設",
+ "underline-default": "瀏覽器或瀏覽器膚色預設",
"editfont-style": "編輯區字型樣式:",
"editfont-default": "瀏覽器預設",
"editfont-monospace": "固定間距字型",
"listingcontinuesabbrev": "續",
"index-category": "做咗索引嘅版",
"noindex-category": "未做索引嘅版",
+ "broken-file-category": "有失效文件鏈接嘅版",
"about": "關於",
"article": "內容頁",
"newwindow": "(響新視窗度打開)",
"cancel": "取消",
"moredotdotdot": "更多...",
+ "morenotlisted": "爾張清單重未完成。",
"mypage": "版",
"mytalk": "傾偈",
"anontalk": "同呢個 IP 傾偈",
"otherlanguages": "第啲語言",
"redirectedfrom": "(由$1跳轉過來)",
"redirectpagesub": "跳轉頁",
+ "redirectto": "跳轉去:",
"lastmodifiedat": "呢一頁嘅最後修改係響$1 $2。",
"viewcount": "呢一頁已經有$1人次睇過。",
"protectedpage": "受保護頁",
"hidetoc": "收埋",
"collapsible-collapse": "摺埋",
"collapsible-expand": "展開",
+ "confirmable-confirm": "�確唔確定?",
+ "confirmable-yes": "確定。",
+ "confirmable-no": "唔確定。",
"thisisdeleted": "睇下定係還原$1?",
"viewdeleted": "去睇$1?",
"restorelink": "$1次已刪除嘅編輯",
"filerenameerror": "檔案 \"$1\" 唔改得做 \"$2\"。",
"filedeleteerror": "檔案 \"$1\" 唔刪得。",
"directorycreateerror": "目錄 \"$1\" 開唔到。",
+ "directoryreadonlyerror": "\"$1\"係唯讀文件,無得編輯。",
+ "directorynotreadableerror": "讀唔到\"$1\"。",
"filenotfound": "檔案 \"$1\" 搵唔到。",
"unexpected": "意外數值。 \"$1\"=\"$2\"。",
"formerror": "錯誤:表格交唔到",
"viewsourcetext": "你可以睇吓或者複製呢一頁嘅原始碼:",
"viewyourtext": "你可以睇同複製呢版入面<strong>由你改</strong>嘅原碼:",
"protectedinterface": "呢一頁提供軟件嘅介面文字,呢一頁已經保護咗以預防濫用。\n要加或者改所有維基站嘅翻譯,請去 [//translatewiki.net/ translatewiki.net]嘅 MediaWiki 本地化項目。",
- "editinginterface": "'''警告:'''你而家編輯緊嘅呢一個用嚟提供介面文字嘅頁面。響呢一頁嘅更改會影響到其他用戶使用中嘅介面外觀。要加或者改所有維基站嘅翻譯,請去 [//translatewiki.net/ translatewiki.net]嘅 MediaWiki 本地化項目。",
+ "editinginterface": "'''警告''':閣下而家編輯緊嘅係為爾隻軟件提供介面文字嘅版。\n改爾一版會自動改埋爾個維基嘅其他用戶用緊嘅介面嘅文字。",
+ "translateinterface": "要加或者改所有維基項目嘅翻譯,請去MediaWiki嘅本地化項目:[//translatewiki.net/ translatewiki.net]。",
"cascadeprotected": "呢一版已經保護咗唔能夠編輯,因為佢係響以下嘅{{PLURAL:$1|一|幾}}頁度包含咗,當中啟用咗\"連串\"保護選項來保護嗰一版: $2",
"namespaceprotected": "你無權編輯響'''$1'''空間名裏面嘅呢一版。",
"customcssprotected": "你無權改呢版CSS,因為佢包含其他用戶嘅個人設定。",
"invalidtitle-knownnamespace": "名域 \"$2\" 同版名 \"$3\" 無效嘅標題",
"invalidtitle-unknownnamespace": "未知名域號碼 \"$1\" 同版名 \"$2\" 無效嘅標題",
"exception-nologin": "未簽到",
- "exception-nologin-text": "請[[Special:Userlogin|簽到]]之後先至睇或者改呢版。",
+ "exception-nologin-text": "請[[Special:Userlogin|簽到]]之後先至睇或者改爾版。",
"exception-nologin-text-manual": "請$1之後先至睇或者改呢版。",
"virus-badscanner": "壞設定: 未知嘅病毒掃瞄器: ''$1''",
"virus-scanfailed": "掃瞄失敗 (代碼 $1)",
"createaccount-text": "有人響{{SITENAME}}度用咗你個電郵開咗個名叫 \"$2\" 嘅新戶口 ($4),密碼係 \"$3\" 。你應該而家登入,改埋個密碼。\n\n如果個戶口係開錯咗嘅話,你可以唔埋呢篇信。",
"login-throttled": "你已經試咗太多次簽到動作。\n請等$1再試過。",
"login-abort-generic": "你簽到失敗",
+ "login-migrated-generic": "由於閣下嘅用戶已經搬走徂,因此閣下嘅爾個用戶名已經唔存在。",
"loginlanguagelabel": "語言:$1",
"suspicious-userlogout": "你去登出嘅要求已經拒絕咗,因為佢可能由壞咗嘅瀏覽器或者快取代理傳送。",
"createacct-another-realname-tip": "真名可以唔填。\n如果你畀埋佢,有需要嘅時候會用佢來標示你嘅工夫。",
"passwordreset-capture-help": "如果揀呢度,電郵連臨時密碼金向你顯示,同時會送畀用戶。",
"passwordreset-email": "電郵地址:",
"passwordreset-emailtitle": "{{SITENAME}}嘅戶口資料",
+ "passwordreset-emailtext-ip": "有人(可能係閣下自己,來自IP地址$1)請求更改閣下喺{{SITENAME}}($4)嘅密碼。同爾個電子郵件有關聯嘅用戶包括:\n\n$2\n\n{{PLURAL:$3|爾個|爾啲}}臨時密碼會喺{{$5}}日之後失效。\n\n如果係閣下自己請求改密碼嘅,請馬上登錄{{SITENAME}}並且更改密碼。如果閣下諗返起自己個密碼,或者根本無申請過改密碼嘅話,請忽略爾條訊息,繼續用返舊密碼。",
+ "passwordreset-emailtext-user": "{{SITENAME}}用戶$1請求更改閣下喺{{SITENAME}}道嘅密碼$4。同爾個電子郵件有關聯嘅用戶包括:\n\n$2\n\n{{PLURAL:$3|爾個|爾啲}}臨時密碼會喺{{$5}}日之後失效。\n\n如果係閣下自己請求改密碼嘅,請馬上登錄{{SITENAME}}並且更改密碼。如果閣下諗返起自己個密碼,或者根本無申請過改密碼嘅話,請忽略爾條訊息,繼續用返舊密碼。",
"passwordreset-emailelement": "用戶名:$1\n臨時密碼:$2",
"passwordreset-emailsent": "密碼重設電郵經已送出。",
"passwordreset-emailsent-capture": "密碼重設電郵經已送出,下面有顯示。",
"preview": "預覽",
"showpreview": "顯示預覽",
"showdiff": "顯示差異",
- "anoneditwarning": "'''警告:'''你重未登入。你嘅 IP 位址會喺呢個頁面嘅修訂歷史中記錄落嚟。",
+ "blankarticle": "<strong>警告</strong>:閣下開緊爾版係空白嘅,撳多次「{{int:savearticle}}」就會開一個乜都無嘅版(可能被視為破壞)。",
+ "anoneditwarning": "'''警告:'''閣下重未登入。閣下嘅 IP 地址會喺爾一版嘅修訂歷史裡邊記錄落嚟。",
"anonpreviewwarning": "''你重未登入,你嘅 IP 位址會喺呢個頁面嘅修訂歷史中記錄落嚟。''",
"missingsummary": "'''提醒:''' 你未提供編輯摘要。如果你再撳多一下「{{int:savearticle}}」嘅話,咁你儲存嘅編輯就會無摘要。",
"missingcommenttext": "請輸入一個註解。",
"anontalkpagetext": "----''呢度係匿名用戶嘅討論頁,佢可能係重未開戶口,或者佢重唔識開戶口。我哋會用數字表示嘅IP地址嚟代表佢。一個IP地址係可以由幾個用戶夾來用。如果你係匿名用戶,同覺得呢啲留言係同你冇關係嘅話,唔該去[[Special:UserLogin/signup|開一個新戶口]]或[[Special:UserLogin|登入]],避免喺以後嘅留言會同埋其它用戶混淆。''",
"noarticletext": "喺呢一頁而家並冇任何嘅文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌],\n或者[{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯呢一版]</span>。",
"noarticletext-nopermission": "呢一頁而家冇任何文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],或者<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌]</span>。",
+ "missing-revision": "The revision #$1 of the page named \"{{FULLPAGENAME}}\" does not exist.\n\nThis is usually caused by following an outdated history link to a page that has been deleted.\nDetails can be found in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].\n\n《{{FULLPAGENAME}}》嘅編輯#$1唔存在。\n\n恁通常係因為一條過徂時嘅鏈接帶徂閣下去一個已經刪除徂嘅版。\n詳情請查閱[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪文紀錄]。",
"userpage-userdoesnotexist": "用戶戶口\"<nowiki>$1</nowiki>\"重未開。請響䦒/編輯呢版之前先檢查一下。",
"userpage-userdoesnotexist-view": "用戶戶口\"$1\"重未開。",
"blocked-notice-logextract": "呢位用戶而家被封鎖緊。\n下面有最近嘅封鎖紀錄以供參考:",
"postedit-confirmation-saved": "呢版經已儲存咗。",
"edit-already-exists": "唔可以開一新版。\n佢已經存在。",
"defaultmessagetext": "預設訊息文字",
- "editwarning-warning": "離開呢一版會令到你嘅修改唔見咗。\n你可以響你嘅喜好設定嘅\"{{int:prefs-editing}}\"小節度停用呢個警告。",
+ "content-not-allowed-here": "「$1」唔可以輸入[[$2]]。",
+ "editwarning-warning": "離開爾一版會令到閣下嘅修改唔見咗。\n閣下可以喺喜好設定嘅\"{{int:prefs-editing}}\"小節度停用爾個警告。",
+ "editpage-notsupportedcontentformat-title": "唔支持爾種內容格式。",
+ "content-model-wikitext": "維基文字",
+ "content-model-text": "純文字",
+ "content-model-javascript": "JavaScript程式語言",
+ "content-model-css": "層疊樣式表",
+ "duplicate-args-category": "爾版用徂幾個重複加類嘅模。",
"expensive-parserfunction-warning": "警告: 呢一版有太多耗費嘅語法功能呼叫。\n\n佢應該少過$2次呼叫,佢而家係$1次呼叫。",
"expensive-parserfunction-category": "響版度有太多嘅耗費嘅語法功能呼叫",
"post-expand-template-inclusion-warning": "警告: 包含模大細太大。\n有啲模將唔會包含。",
"specialpages-group-wiki": "Wiki資料同工具",
"specialpages-group-redirects": "跳轉特別頁",
"specialpages-group-spam": "反垃圾工具",
+ "specialpages-group-developer": "開發者工具",
"blankpage": "空白頁",
"intentionallyblankpage": "呢一版係留空咗嘅,用來作測速等用嘅。",
"external_image_whitelist": " #留番呢行一樣嘅字<pre>\n#響下面(//嘅中間部份)入正規表達式\n#呢啲將會同外面(已超連結嘅)圖像配合\n#嗰啲晒對到出來嘅會顯示做圖像,唔係嘅話就只係會顯示連結\n#有 # 開頭嘅行會當做註解\n#無分大細楷\n\n#響呢行上面入晒全部嘅regex。留番呢行一樣嘅字</pre>",
"tog-prefershttps": "登入時始終使用安全連線",
"underline-always": "永遠使用",
"underline-never": "永不使用",
- "underline-default": "外觀或瀏覽器預設值",
+ "underline-default": "依外觀或瀏覽器預設值",
"editfont-style": "編輯區字型樣式:",
"editfont-default": "瀏覽器預設值",
"editfont-monospace": "等距字型",
"newarticle": "(新)",
"newarticletext": "您正連結至一頁不存在頁面。\n要建立該頁面,請在下方的編輯框中輸入內容 (詳情請參考 [$1 説明頁面])。\n如果您是不小心來到此頁面,請點選瀏覽器的 <strong>返回</strong> 按鈕。",
"anontalkpagetext": "----\n<em>此討論頁面是給尚未建立帳號的匿名使用者使用</em>\n因此我們必須使用 IP 位址來辨識身份,但相同的 IP 位址可能由許多不同的使用者所共用。\n如果您是匿名使用者並且覺得評論的內容與您無關,請 [[Special:UserLogin/signup|建立新帳號]] 或 [[Special:UserLogin|登入]] 避免與其他匿名使用者混淆。",
- "noarticletext": "此頁面目前沒有內容,\n您可以在其它頁面中 [[Special:Search/{{PAGENAME}}|搜尋此頁面標題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜尋相關日誌],\n或 [{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯此頁]</span>。",
+ "noarticletext": "此頁面目前沒有內容,您可以在其它頁面中[[Special:Search/{{PAGENAME}}|搜尋此頁面標題]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜尋相關日誌]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯此頁]</span>。",
"noarticletext-nopermission": "此頁面目前沒有內容,\n您可以在其它頁面中 [[Special:Search/{{PAGENAME}}|搜尋此頁面標題]],或 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜尋相關日誌]</span>,但您沒有權限建立此頁面。",
"missing-revision": "頁面名稱 \"{{FULLPAGENAME}}\" 的 #$1 修訂版本不存在。\n\n通常是因連結到過期的歷史頁面,該頁面已被刪除。\n詳情請參考 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌]。",
"userpage-userdoesnotexist": "使用者帳號 \"$1\" 尚未註冊。\n若您要建立/編輯此頁面,請先檢查是否正確。",
"searchrelated": "相關",
"searchall": "全部",
"showingresults": "以下顯示從第 <strong>$2</strong> 筆開始,共 {{PLURAL:$1|<strong>1</strong> 筆結果|<strong>$1</strong> 筆結果}}:",
- "showingresultsinrange": "以下顯示從第 <strong>$2</strong> 筆至第 <strong>$3</strong> 筆中的 {{PLURAL:$1|<strong>1</strong> 筆結果|<strong>$1</strong> 筆結果}}:",
+ "showingresultsinrange": "以下顯示從第 <strong>$2</strong> 筆至第 <strong>$3</strong> 筆中的 {{PLURAL:$1|<strong>$1</strong> 筆結果}}:",
"search-showingresults": "{{PLURAL:$4|第 <strong>$1</strong> 筆結果,共 <strong>$3</strong> 筆|第 <strong>$1 - $2</strong> 筆結果,共 <strong>$3</strong> 筆}}",
"search-nonefound": "無符合查詢條件的結果。",
"powersearch-legend": "進階搜尋",
--- /dev/null
+-- SQL to insert update keys into the initial tables after a
+-- fresh installation of MediaWiki's database.
+-- This is read and executed by the install script; you should
+-- not have to run it by itself unless doing a manual install.
+-- Insert keys here if either the unnecessary would cause heavy
+-- processing or could potentially cause trouble by lowering field
+-- sizes, adding constraints, etc.
+-- When adjusting field sizes, it is recommended removing old
+-- patches but to play safe, update keys should also inserted here.
+
+-- The /*_*/ comments in this and other files are
+-- replaced with the defined table prefix by the installer
+-- and updater scripts. If you are installing or running
+-- updates manually, you will need to manually insert the
+-- table prefix if any when running these scripts.
+--
+
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'filearchive-fa_major_mime-patch-fa_major_mime-chemical.sql', null );
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'image-img_major_mime-patch-img_major_mime-chemical.sql', null );
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'oldimage-oi_major_mime-patch-oi_major_mime-chemical.sql', null );
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'user_groups-ug_group-patch-ug_group-length-increase-255.sql', null );
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'user_former_groups-ufg_group-patch-ufg_group-length-increase-255.sql', null );
+INSERT INTO /*_*/updatelog (ul_key, ul_value)
+ VALUES( 'user_properties-up_property-patch-up_property.sql', null );
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script to dump the SiteStore as a static json file.
+ *
+ * @ingroup Maintenance
+ */
+class RebuildSitesCache extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+
+ $this->mDescription = "Dumps site store as json";
+ $this->addOption( 'file', 'File to output the json to', false, true );
+ }
+
+ public function execute() {
+ $siteListFileCacheBuilder = new SiteListFileCacheBuilder(
+ SiteSQLStore::newInstance(),
+ $this->getCacheFile()
+ );
+
+ $siteListFileCacheBuilder->build();
+ }
+
+ /**
+ * @return string
+ */
+ private function getCacheFile() {
+ if ( $this->hasOption( 'file' ) ) {
+ $jsonFile = $this->getOption( 'file' );
+ } else {
+ $jsonFile = $this->getConfig()->get( 'SitesCacheFile' );
+
+ if ( $jsonFile === false ) {
+ $this->error( 'Error: No sites cache file is set in configuration.', 1 );
+ }
+ }
+
+ return $jsonFile;
+ }
+
+}
+
+$maintClass = "RebuildSitesCache";
+require_once RUN_MAINTENANCE_IF_MAIN;
'method' => 'get',
'template' => $searchPage->getCanonicalURL( 'search={searchTerms}' ) );
-if ( $wgEnableAPI ) {
- // JSON interface for search suggestions.
- // Supported in Firefox 2 and later.
- $urls[] = array(
- 'type' => 'application/x-suggestions+json',
- 'method' => 'get',
- 'template' => SearchEngine::getOpenSearchTemplate() );
+foreach ( $wgOpenSearchTemplates as $type => $template ) {
+ if ( !$template && $wgEnableAPI ) {
+ $template = ApiOpenSearch::getOpenSearchTemplate( $type );
+ }
+
+ if ( $template ) {
+ $urls[] = array(
+ 'type' => $type,
+ 'method' => 'get',
+ 'template' => $template,
+ );
+ }
}
// Allow hooks to override the suggestion URL settings in a more
border: 1px solid @colorGray12;
&:hover,
- &:active {
+ &:active,
+ &:visited {
// make sure that is isn't inheriting from a general rule
color: @colorButtonText;
}
//
// Markup:
// <div class="mw-ui-checkbox">
-// <input type="checkbox" id="kss-example-5"><label for="kss-example-5">Standard checkbox</label>
+// <input type="checkbox" id="kss-example-3">
+// <label for="kss-example-3">Standard checkbox</label>
// </div>
// <div class="mw-ui-checkbox">
-// <input type="checkbox" id="kss-example-5-checked" checked><label for="kss-example-5-checked">Standard checked checkbox</label>
+// <input type="checkbox" id="kss-example-3-checked" checked>
+// <label for="kss-example-3-checked">Standard checked checkbox</label>
// </div>
// <div class="mw-ui-checkbox">
-// <input type="checkbox" id="kss-example-5-disabled" disabled><label for="kss-example-5-disabled">Disabled checkbox</label>
+// <input type="checkbox" id="kss-example-3-disabled" disabled>
+// <label for="kss-example-3-disabled">Disabled checkbox</label>
// </div>
// <div class="mw-ui-checkbox">
-// <input type="checkbox" id="kss-example-5-disabled-checked" disabled checked><label for="kss-example-5-disabled-checked">Disabled checked checkbox</label>
+// <input type="checkbox" id="kss-example-3-disabled-checked" disabled checked>
+// <label for="kss-example-3-disabled-checked">Disabled checked checkbox</label>
// </div>
//
-// Styleguide 5.
+// Styleguide 3.
.mw-ui-checkbox {
display: inline-block;
vertical-align: middle;
height: @checkboxSize;
// This is needed for Firefox mobile (See bug 71750 to workaround default Firefox stylesheet)
max-width: none;
- margin-right: .4em;
+ margin-right: 0.4em;
// the pseudo before element of the label after the checkbox now looks like a checkbox
& + label::before {
content: '';
cursor: pointer;
+ .box-sizing(border-box);
position: absolute;
left: 0;
border-radius: @borderRadius;
height: @checkboxSize;
background-color: #fff;
border: 1px solid @colorGray7;
- .box-sizing(border-box);
}
// when the input is checked, style the label pseudo before element that followed as a checked checkbox
border-width: 2px;
}
+ &:focus:hover + label::before,
&:hover + label::before {
border-bottom-width: 3px;
}
// Forms
//
-// Styleguide 3.
+// Styleguide 5.
// VForm
//
// </div>
// </form>
//
-// Styleguide 3.1.
+// Styleguide 5.1.
.mw-ui-vform {
.box-sizing(border-box);
// </div>
// </form>
//
- // Styleguide 3.2.
+ // Styleguide 5.2.
.error,
.errorbox,
.warningbox,
// However, icon-only elements do not yet degrade to text-only elements in these
// browsers.
//
-// Styleguide 4.
+// Styleguide 6.
.mw-ui-icon {
position: relative;
// <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok">OK</div>
// <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok mw-ui-button mw-ui-progressive">OK</div>
//
- // Styleguide 4.1.1.
+ // Styleguide 6.1.1.
&.mw-ui-icon-element {
@width: @iconSize + ( 2 * @gutterWidth );
// <div class="mw-ui-icon mw-ui-icon-before mw-ui-icon-ok">OK</div>
// <div class="mw-ui-icon mw-ui-icon-before mw-ui-icon-ok mw-ui-progressive mw-ui-button">OK</div>
//
- // Styleguide 4.1.2
+ // Styleguide 6.1.2
&.mw-ui-icon-before {
&:before {
position: relative;
// Markup:
// <div class="mw-ui-icon mw-ui-icon-after mw-ui-icon-ok mw-ui-progressive mw-ui-button">OK</div>
//
- // Styleguide 4.1.3
+ // Styleguide 6.1.3
&.mw-ui-icon-after {
&:after {
position: relative;
font-style: italic;
font-weight: normal;
}
-// Inputs
+// Text inputs
//
// Apply the mw-ui-input class to input and textarea fields.
//
//
// Markup:
// <div class="mw-ui-radio">
-// <input type="radio" id="kss-example-7" name="kss-example-7">
-// <label for="kss-example-7">Standard radio</label>
+// <input type="radio" id="kss-example-4" name="kss-example-4">
+// <label for="kss-example-4">Standard radio</label>
// </div>
// <div class="mw-ui-radio">
-// <input type="radio" id="kss-example-7-checked" name="kss-example-7" checked>
-// <label for="kss-example-7-checked">Standard checked radio</label>
+// <input type="radio" id="kss-example-4-checked" name="kss-example-4" checked>
+// <label for="kss-example-4-checked">Standard checked radio</label>
// </div>
// <div class="mw-ui-radio">
-// <input type="radio" id="kss-example-7-disabled" name="kss-example-7-disabled" disabled>
-// <label for="kss-example-7-disabled">Disabled radio</label>
+// <input type="radio" id="kss-example-4-disabled" name="kss-example-4-disabled" disabled>
+// <label for="kss-example-4-disabled">Disabled radio</label>
// </div>
// <div class="mw-ui-radio">
-// <input type="radio" id="kss-example-7-disabled-checked" name="kss-example-7-disabled" disabled checked>
-// <label for="kss-example-7-disabled-checked">Disabled checked radio</label>
+// <input type="radio" id="kss-example-4-disabled-checked" name="kss-example-4-disabled" disabled checked>
+// <label for="kss-example-4-disabled-checked">Disabled checked radio</label>
// </div>
//
-// Styleguide 7.
+// Styleguide 4.
.mw-ui-radio {
display: inline-block;
vertical-align: middle;
line-height: @radioSize;
* {
+ // reset font sizes (see bug 72727)
font: inherit;
vertical-align: middle;
}
// the pseudo before element of the label after the radio now looks like a radio
& + label::before {
- cursor: pointer;
content: '';
+ cursor: pointer;
.box-sizing(border-box);
position: absolute;
left: 0;
background-origin: border-box;
}
+ &:active + label::before {
+ background-color: @colorGray13;
+ border-color: @colorGray13;
+ }
+
&:focus + label::before {
border-width: 2px;
}
border-bottom-width: 3px;
}
- &:active + label::before {
- background-color: @colorGray13;
- border-color: @colorGray13;
- }
-
- // disabled checked boxes have a gray background
+ // disabled radios have a gray background
&:disabled + label::before {
cursor: default;
- border-color: @colorGray14;
background-color: @colorGray14;
+ border-color: @colorGray14;
}
- // disabled and checked boxes have a white circle
+ // disabled and checked radios have a white circle
&:disabled:checked + label::before {
.background-image-svg('images/radio_disabled.svg', 'images/radio_disabled.png');
}
'wgLang' => Language::factory( 'en' ),
'wgAllowUserJs' => false,
'wgDefaultLanguageVariant' => false,
+ 'wgMetaNamespace' => 'Project',
) );
}
*/
class GenderCacheTest extends MediaWikiLangTestCase {
- protected function setUp() {
- global $wgDefaultUserOptions;
- parent::setUp();
+ function addDBData() {
//ensure the correct default gender
- $wgDefaultUserOptions['gender'] = 'unknown';
- }
+ $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', array( 'gender' => 'unknown' ) );
- function addDBData() {
$user = User::newFromName( 'UTMale' );
if ( $user->getID() == 0 ) {
$user->addToDatabase();
global $wgParserConf, $wgContLang;
$parser = new Parser( $wgParserConf );
- $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), SFH_OBJECT_ARGS );
+ $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), Parser::SFH_OBJECT_ARGS );
$parser->parse(
"Foo<$tag>Bar</$tag>Baz",
Title::newFromText( 'Test' ),
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @covers SiteListFileCacheBuilder
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class SiteListFileCacheBuilderTest extends PHPUnit_Framework_TestCase {
+
+ public function testBuild() {
+ $cacheFile = $this->getCacheFile();
+
+ $cacheBuilder = $this->newSiteListFileCacheBuilder( $this->getSites(), $cacheFile );
+ $cacheBuilder->build();
+
+ $contents = file_get_contents( $cacheFile );
+ $this->assertEquals( json_encode( $this->getExpectedData() ), $contents );
+ }
+
+ private function getExpectedData() {
+ return array(
+ 'sites' => array(
+ 'foobar' => array(
+ 'globalid' => 'foobar',
+ 'type' => 'unknown',
+ 'group' => 'none',
+ 'source' => 'local',
+ 'language' => null,
+ 'localids' => array(),
+ 'config' => array(),
+ 'data' => array(),
+ 'forward' => false,
+ 'internalid' => null,
+ 'identifiers' => array()
+ ),
+ 'enwiktionary' => array(
+ 'globalid' => 'enwiktionary',
+ 'type' => 'mediawiki',
+ 'group' => 'wiktionary',
+ 'source' => 'local',
+ 'language' => 'en',
+ 'localids' => array(
+ 'equivalent' => array( 'enwiktionary' )
+ ),
+ 'config' => array(),
+ 'data' => array(
+ 'paths' => array(
+ 'page_path' => 'https://en.wiktionary.org/wiki/$1',
+ 'file_path' => 'https://en.wiktionary.org/w/$1'
+ )
+ ),
+ 'forward' => false,
+ 'internalid' => null,
+ 'identifiers' => array(
+ array(
+ 'type' => 'equivalent',
+ 'key' => 'enwiktionary'
+ )
+ )
+ )
+ )
+ );
+ }
+
+ private function newSiteListFileCacheBuilder( SiteList $sites, $cacheFile ) {
+ return new SiteListFileCacheBuilder(
+ $this->getSiteSQLStore( $sites ),
+ $cacheFile
+ );
+ }
+
+ private function getSiteSQLStore( SiteList $sites ) {
+ $siteSQLStore = $this->getMockBuilder( 'SiteSQLStore' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $siteSQLStore->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( $sites ) );
+
+ return $siteSQLStore;
+ }
+
+ private function getSites() {
+ $sites = array();
+
+ $site = new Site();
+ $site->setGlobalId( 'foobar' );
+ $sites[] = $site;
+
+ $site = new MediaWikiSite();
+ $site->setGlobalId( 'enwiktionary' );
+ $site->setGroup( 'wiktionary' );
+ $site->setLanguageCode( 'en' );
+ $site->addNavigationId( 'enwiktionary' );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
+ $sites[] = $site;
+
+ return new SiteList( $sites );
+ }
+
+ private function getCacheFile() {
+ return sys_get_temp_dir() . '/sites-' . time() . '.json';
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @covers SiteListFileCache
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class SiteListFileCacheTest extends PHPUnit_Framework_TestCase {
+
+ public function testGetSites() {
+ $cacheFile = $this->getCacheFile();
+
+ $sites = $this->getSites();
+ $cacheBuilder = $this->newSiteListFileCacheBuilder( $sites, $cacheFile );
+ $cacheBuilder->build();
+
+ $cache = new SiteListFileCache( $cacheFile );
+ $this->assertEquals( $sites, $cache->getSites() );
+ }
+
+ public function testGetSite() {
+ $cacheFile = $this->getCacheFile();
+
+ $sites = $this->getSites();
+ $cacheBuilder = $this->newSiteListFileCacheBuilder( $sites, $cacheFile );
+ $cacheBuilder->build();
+
+ $cache = new SiteListFileCache( $cacheFile );
+
+ $this->assertEquals( $sites->getSite( 'enwiktionary' ), $cache->getSite( 'enwiktionary' ) );
+ }
+
+ private function newSiteListFileCacheBuilder( SiteList $sites, $cacheFile ) {
+ return new SiteListFileCacheBuilder(
+ $this->getSiteSQLStore( $sites ),
+ $cacheFile
+ );
+ }
+
+ private function getSiteSQLStore( SiteList $sites ) {
+ $siteSQLStore = $this->getMockBuilder( 'SiteSQLStore' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $siteSQLStore->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( $sites ) );
+
+ return $siteSQLStore;
+ }
+
+ private function getSites() {
+ $sites = array();
+
+ $site = new Site();
+ $site->setGlobalId( 'foobar' );
+ $sites[] = $site;
+
+ $site = new MediaWikiSite();
+ $site->setGlobalId( 'enwiktionary' );
+ $site->setGroup( 'wiktionary' );
+ $site->setLanguageCode( 'en' );
+ $site->addNavigationId( 'enwiktionary' );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
+ $sites[] = $site;
+
+ return new SiteList( $sites );
+ }
+
+ private function getCacheFile() {
+ return sys_get_temp_dir() . '/sites-' . time() . '.json';
+ }
+
+}
'wgLang' => Language::factory( 'en' ),
'wgAllowUserJs' => false,
'wgDefaultLanguageVariant' => false,
+ 'wgMetaNamespace' => 'Project',
'wgLocalInterwikis' => array( 'localtestiw' ),
'wgCapitalLinks' => true,
protected function makeCodec( $lang ) {
$gender = $this->getGenderCache();
$lang = Language::factory( $lang );
+ // language object can came from cache, which does not respect test settings
+ $lang->resetNamespaces();
return new MediaWikiTitleCodec( $lang, $gender );
}