* Constructing MovePage directly is deprecated. Use MovePageFactory.
* TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
* wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
+* Building a new SearchResult is hard-deprecated, always call
+ SearchResult::newFromTitle(). This class is being refactored into an abstract
+ class. If you extend this class please be sure to override all its methods
+ or extend RevisionSearchResult.
=== Other changes in 1.34 ===
* …
'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
+ 'MediaWiki\\FileBackend\\LockManager\\LockManagerGroupFactory' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroupFactory.php',
'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php',
'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php',
'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php',
+ 'RevisionSearchResult' => __DIR__ . '/includes/search/RevisionSearchResult.php',
+ 'RevisionSearchResultTrait' => __DIR__ . '/includes/search/RevisionSearchResultTrait.php',
'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
// Don't share DB, storage, or memcached connections
MediaWikiServices::resetChildProcessServices();
FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingletons();
JobQueueGroup::destroySingletons();
ObjectCache::clear();
RedisConnectionPool::destroySingletons();
use MediaWiki\Block\BlockManager;
use MediaWiki\Block\BlockRestrictionStore;
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Permissions\PermissionManager;
return $this->getService( 'LocalServerObjectCache' );
}
+ /**
+ * @since 1.34
+ * @return LockManagerGroupFactory
+ */
+ public function getLockManagerGroupFactory() : LockManagerGroupFactory {
+ return $this->getService( 'LockManagerGroupFactory' );
+ }
+
/**
* @since 1.32
* @return MagicWordFactory
use MediaWiki\Config\ConfigRepository;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Interwiki\ClassicInterwikiLookup;
use MediaWiki\Interwiki\InterwikiLookup;
return \ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
},
+ 'LockManagerGroupFactory' => function ( MediaWikiServices $services ) : LockManagerGroupFactory {
+ return new LockManagerGroupFactory(
+ WikiMap::getCurrentWikiDbDomain()->getId(),
+ $services->getMainConfig()->get( 'LockManagers' ),
+ $services->getDBLoadBalancerFactory()
+ );
+ },
+
'MagicWordFactory' => function ( MediaWikiServices $services ) : MagicWordFactory {
return new MagicWordFactory( $services->getContentLanguage() );
},
"apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
"apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
"apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+ "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
"apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
"apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
"apierror-unknownformat": "Okänt format \"$1\".",
"apiwarn-compare-no-next": "Sidversion $2 är den senaste sidversionen av $1, det finns ingen sidversion för <kbd>torelative=next</kbd> att jämföra med.",
"apiwarn-compare-no-prev": "Sidversionen $2 är den tidigaste sidversion för $1, det finns ingen sidversion för <kbd>torelative=prev</kbd> att jämföra med.",
+ "apiwarn-deprecation-post-without-content-type": "En POST-begäran gjordes utan en <code>Content-Type</code> i sidhuvudet. Det fungerar inte ordentligt.",
"api-feed-error-title": "Fel ($1)"
}
"科劳",
"SolidBlock",
"神樂坂秀吉",
- "94rain"
+ "94rain",
+ "予弦"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
$block = DatabaseBlock::newFromID( $blockCookieId );
if (
$block instanceof DatabaseBlock &&
- $this->shouldApplyCookieBlock( $block, $user->isAnon() )
+ $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
) {
return $block;
}
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Rdbms\LBFactory;
/**
* Class to handle file lock manager registration
* @since 1.19
*/
class LockManagerGroup {
- /** @var LockManagerGroup[] (domain => LockManagerGroup) */
- protected static $instances = [];
+ /** @var string domain (usually wiki ID) */
+ protected $domain;
- protected $domain; // string; domain (usually wiki ID)
+ /** @var LBFactory */
+ protected $lbFactory;
/** @var array Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
protected $managers = [];
/**
+ * Do not call this directly. Use LockManagerGroupFactory.
+ *
* @param string $domain Domain (usually wiki ID)
+ * @param array[] $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
*/
- protected function __construct( $domain ) {
+ public function __construct( $domain, array $lockManagerConfigs, LBFactory $lbFactory ) {
$this->domain = $domain;
- }
-
- /**
- * @param bool|string $domain Domain (usually wiki ID). Default: false.
- * @return LockManagerGroup
- */
- public static function singleton( $domain = false ) {
- if ( $domain === false ) {
- $domain = WikiMap::getCurrentWikiDbDomain()->getId();
- }
+ $this->lbFactory = $lbFactory;
- if ( !isset( self::$instances[$domain] ) ) {
- self::$instances[$domain] = new self( $domain );
- self::$instances[$domain]->initFromGlobals();
- }
-
- return self::$instances[$domain];
- }
-
- /**
- * Destroy the singleton instances
- */
- public static function destroySingletons() {
- self::$instances = [];
- }
-
- /**
- * Register lock managers from the global variables
- */
- protected function initFromGlobals() {
- global $wgLockManagers;
-
- $this->register( $wgLockManagers );
- }
-
- /**
- * Register an array of file lock manager configurations
- *
- * @param array $configs
- * @throws Exception
- */
- protected function register( array $configs ) {
- foreach ( $configs as $config ) {
+ foreach ( $lockManagerConfigs as $config ) {
$config['domain'] = $this->domain;
if ( !isset( $config['name'] ) ) {
throw new Exception( "Cannot register a lock manager with no name." );
}
}
+ /**
+ * @deprecated since 1.34, use LockManagerGroupFactory
+ *
+ * @param bool|string $domain Domain (usually wiki ID). Default: false.
+ * @return LockManagerGroup
+ */
+ public static function singleton( $domain = false ) {
+ return MediaWikiServices::getInstance()->getLockManagerGroupFactory()
+ ->getLockManagerGroup( $domain );
+ }
+
+ /**
+ * Destroy the singleton instances
+ *
+ * @deprecated since 1.34, use resetServiceForTesting() on LockManagerGroupFactory
+ */
+ public static function destroySingletons() {
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
+ }
+
/**
* Get the lock manager object with a given name
*
$class = $this->managers[$name]['class'];
$config = $this->managers[$name]['config'];
if ( $class === DBLockManager::class ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lb = $lbFactory->getMainLB( $config['domain'] );
+ $lb = $this->lbFactory->getMainLB( $config['domain'] );
$config['dbServers']['localDBMaster'] = $lb->getLazyConnectionRef(
DB_MASTER,
[],
}
$config['logger'] = LoggerFactory::getInstance( 'LockManager' );
+ // XXX Looks like phan is right, we are trying to instantiate an abstract class and it
+ // throws. Did this ever work? Presumably we need to detect the right subclass? Or
+ // should we just get rid of this? It looks like it never worked since it was first
+ // introduced by 0cf832a3394 in 2016, so if no one's complained until now, clearly it
+ // can't be very useful?
// @phan-suppress-next-line PhanTypeInstantiateAbstract
$this->managers[$name]['instance'] = new $class( $config );
}
* Get the default lock manager configured for the site.
* Returns NullLockManager if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
*/
public function getDefault() {
* or at least some other effective configured lock manager.
* Throws an exception if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
* @throws Exception
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\FileBackend\LockManager;
+
+use LockManagerGroup;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * Service to construct LockManagerGroups.
+ */
+class LockManagerGroupFactory {
+ /** @var string */
+ private $defaultDomain;
+
+ /** @var array */
+ private $lockManagerConfigs;
+
+ /** @var LBFactory */
+ private $lbFactory;
+
+ /** @var LockManagerGroup[] (domain => LockManagerGroup) */
+ private $instances = [];
+
+ /**
+ * Do not call directly, use MediaWikiServices.
+ *
+ * @param string $defaultDomain
+ * @param array $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
+ */
+ public function __construct( $defaultDomain, array $lockManagerConfigs, LBFactory $lbFactory ) {
+ $this->defaultDomain = $defaultDomain;
+ $this->lockManagerConfigs = $lockManagerConfigs;
+ $this->lbFactory = $lbFactory;
+ }
+
+ /**
+ * @param string|bool $domain Domain (usually wiki ID). false for the default (normally the
+ * current wiki's domain).
+ * @return LockManagerGroup
+ */
+ public function getLockManagerGroup( $domain = false ) : LockManagerGroup {
+ if ( $domain === false ) {
+ $domain = $this->defaultDomain;
+ }
+
+ if ( !isset( $this->instances[$domain] ) ) {
+ $this->instances[$domain] =
+ new LockManagerGroup( $domain, $this->lockManagerConfigs, $this->lbFactory );
+ }
+
+ return $this->instances[$domain];
+ }
+}
if ( self::isEnabled() ) {
throw new Exception( 'Profiling is already enabled.' );
}
+
+ $args = [ $flags ];
+ if ( $options ) {
+ $args[] = $options;
+ }
+
self::$enabled = true;
self::callAny(
[
'tideways_enable',
'tideways_xhprof_enable'
],
- [ $flags, $options ]
+ $args
);
}
/** @var array List of shared database already attached to this connection */
private $alreadyAttached = [];
- /** @var bool Whether full text is enabled */
- private static $fulltextEnabled = null;
-
/** @var string[] See https://www.sqlite.org/lang_transaction.html */
private static $VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
}
/**
- * Attaches external database to our connection, see https://sqlite.org/lang_attach.html
- * for details.
+ * Attaches external database to the connection handle
+ *
+ * @see https://sqlite.org/lang_attach.html
*
* @param string $name Database name to be used in queries like
* SELECT foo FROM dbname.table
* @param string $pageStatus
* @throws MWException
*/
- public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
- $oldid, $watchers, $pageStatus = 'changed' ) {
+ public function actuallyNotifyOnPageChange(
+ $editor,
+ $title,
+ $timestamp,
+ $summary,
+ $minorEdit,
+ $oldid,
+ $watchers,
+ $pageStatus = 'changed'
+ ) {
# we use $wgPasswordSender as sender's address
global $wgUsersNotifiedOnAllChanges;
global $wgEnotifWatchlist, $wgBlockDisablesLogin;
global $wgEnotifMinorEdits, $wgEnotifUserTalk;
+ $messageCache = MediaWikiServices::getInstance()->getMessageCache();
+
# The following code is only run, if several conditions are met:
# 1. EmailNotification for pages (other than user_talk pages) must be enabled
# 2. minor edits (changes) are only regarded if the global flag indicates so
&& $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
) {
$targetUser = User::newFromName( $title->getText() );
- $this->compose( $targetUser, self::USER_TALK );
+ $this->compose( $targetUser, self::USER_TALK, $messageCache );
$userTalkId = $targetUser->getId();
}
&& !( $wgBlockDisablesLogin && $watchingUser->getBlock() )
&& Hooks::run( 'SendWatchlistEmailNotification', [ $watchingUser, $title, $this ] )
) {
- $this->compose( $watchingUser, self::WATCHLIST );
+ $this->compose( $watchingUser, self::WATCHLIST, $messageCache );
}
}
}
continue;
}
$user = User::newFromName( $name );
- $this->compose( $user, self::ALL_CHANGES );
+ $this->compose( $user, self::ALL_CHANGES, $messageCache );
}
$this->sendMails();
/**
* Generate the generic "this page has been changed" e-mail text.
+ * @param MessageCache $messageCache
*/
- private function composeCommonMailtext() {
+ private function composeCommonMailtext( MessageCache $messageCache ) {
global $wgPasswordSender, $wgNoReplyAddress;
global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
global $wgEnotifImpersonal, $wgEnotifUseRealName;
$body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
$body = strtr( $body, $keys );
- $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
+ $body = $messageCache->transform( $body, false, null, $this->title );
$this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
# Reveal the page editor's address as REPLY-TO address only if
* Call sendMails() to send any mails that were queued.
* @param User $user
* @param string $source
+ * @param MessageCache $messageCache
*/
- private function compose( $user, $source ) {
+ private function compose( $user, $source, MessageCache $messageCache ) {
global $wgEnotifImpersonal;
if ( !$this->composed_common ) {
- $this->composeCommonMailtext();
+ $this->composeCommonMailtext( $messageCache );
}
if ( $wgEnotifImpersonal ) {
if ( !$wgRestrictDisplayTitle ||
( $title instanceof Title
&& !$title->hasFragment()
- && $title->equals( $parser->mTitle ) )
+ && $title->equals( $parser->getTitle() ) )
) {
$old = $parser->mOutput->getProperty( 'displaytitle' );
if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
* @return string
*/
public static function protectionlevel( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$restrictions = $titleObject->getRestrictions( strtolower( $type ) );
# Title::getRestrictions returns an array, its possible it may have
* @return string
*/
public static function protectionexpiry( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$expiry = $titleObject->getRestrictionExpiry( strtolower( $type ) );
// getRestrictionExpiry() returns false on invalid type; trying to
* @since 1.23
*/
public static function cascadingsources( $parser, $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areCascadeProtectionSourcesLoaded()
|| $parser->incrementExpensiveFunctionCount()
) {
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
--- /dev/null
+<?php
+
+/**
+ * SearchResult class based on the Revision information.
+ * This class is suited for search engines that do not store a specialized version of the searched
+ * content.
+ */
+class RevisionSearchResult extends SearchResult {
+ use RevisionSearchResultTrait;
+
+ /**
+ * @param Title|null $title
+ */
+ public function __construct( $title ) {
+ $this->mTitle = $title;
+ $this->initFromTitle( $title );
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Transitional trait used to share the methods between SearchResult and RevisionSearchResult.
+ * All the content of this trait can be moved to RevisionSearchResult once SearchResult is finally
+ * refactored into an abstract class.
+ * NOTE: This trait MUST NOT be used by something else than SearchResult and RevisionSearchResult.
+ * It will be removed without deprecation period once SearchResult
+ */
+trait RevisionSearchResultTrait {
+ /**
+ * @var Revision
+ */
+ protected $mRevision = null;
+
+ /**
+ * @var File
+ */
+ protected $mImage = null;
+
+ /**
+ * @var Title
+ */
+ protected $mTitle;
+
+ /**
+ * @var string
+ */
+ protected $mText;
+
+ /**
+ * Initialize from a Title and if possible initializes a corresponding
+ * Revision and File.
+ *
+ * @param Title $title
+ */
+ protected function initFromTitle( $title ) {
+ $this->mTitle = $title;
+ $services = MediaWikiServices::getInstance();
+ if ( !is_null( $this->mTitle ) ) {
+ $id = false;
+ Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
+ $this->mRevision = Revision::newFromTitle(
+ $this->mTitle, $id, Revision::READ_NORMAL );
+ if ( $this->mTitle->getNamespace() === NS_FILE ) {
+ $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
+ }
+ }
+ }
+
+ /**
+ * Check if this is result points to an invalid title
+ *
+ * @return bool
+ */
+ public function isBrokenTitle() {
+ return is_null( $this->mTitle );
+ }
+
+ /**
+ * Check if target page is missing, happens when index is out of date
+ *
+ * @return bool
+ */
+ public function isMissingRevision() {
+ return !$this->mRevision && !$this->mImage;
+ }
+
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Get the file for this page, if one exists
+ * @return File|null
+ */
+ public function getFile() {
+ return $this->mImage;
+ }
+
+ /**
+ * Lazy initialization of article text from DB
+ */
+ protected function initText() {
+ if ( !isset( $this->mText ) ) {
+ if ( $this->mRevision != null ) {
+ $content = $this->mRevision->getContent();
+ $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
+ } else { // TODO: can we fetch raw wikitext for commons images?
+ $this->mText = '';
+ }
+ }
+ }
+
+ /**
+ * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
+ * @return string Highlighted text snippet, null (and not '') if not supported
+ */
+ public function getTextSnippet( $terms = [] ) {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted title, '' if not supported
+ */
+ public function getTitleSnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
+ */
+ public function getRedirectSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object for the redirect to this page, null if none or not supported
+ */
+ public function getRedirectTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant section name, null if none or not supported
+ */
+ public function getSectionSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object (pagename+fragment) for the section,
+ * null if none or not supported
+ */
+ public function getSectionTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant category name or '' if none or not supported
+ */
+ public function getCategorySnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Timestamp
+ */
+ public function getTimestamp() {
+ if ( $this->mRevision ) {
+ return $this->mRevision->getTimestamp();
+ } elseif ( $this->mImage ) {
+ return $this->mImage->getTimestamp();
+ }
+ return '';
+ }
+
+ /**
+ * @return int Number of words
+ */
+ public function getWordCount() {
+ $this->initText();
+ return str_word_count( $this->mText );
+ }
+
+ /**
+ * @return int Size in bytes
+ */
+ public function getByteSize() {
+ $this->initText();
+ return strlen( $this->mText );
+ }
+
+ /**
+ * @return string Interwiki prefix of the title (return iw even if title is broken)
+ */
+ public function getInterwikiPrefix() {
+ return '';
+ }
+
+ /**
+ * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
+ */
+ public function getInterwikiNamespaceText() {
+ return '';
+ }
+
+ /**
+ * Did this match file contents (eg: PDF/DJVU)?
+ * @return bool
+ */
+ public function isFileMatch() {
+ return false;
+ }
+}
* @ingroup Search
*/
-use MediaWiki\MediaWikiServices;
-
/**
- * @todo FIXME: This class is horribly factored. It would probably be better to
- * have a useful base class to which you pass some standard information, then
- * let the fancy self-highlighters extend that.
+ * NOTE: this class is being refactored into an abstract base class.
+ * If you extend this class directly, please implement all the methods declared
+ * in RevisionSearchResultTrait or extend RevisionSearchResult.
+ *
+ * Once the hard-deprecation period is over (1.36?):
+ * - all methods declared in RevisionSearchResultTrait should be declared
+ * as abstract in this class
+ * - RevisionSearchResultTrait body should be moved to RevisionSearchResult and then removed without
+ * deprecation
+ * - caveat: all classes extending this one may potentially break if they did not properly implement
+ * all the methods.
* @ingroup Search
*/
class SearchResult {
use SearchResultTrait;
+ use RevisionSearchResultTrait;
- /**
- * @var Revision
- */
- protected $mRevision = null;
-
- /**
- * @var File
- */
- protected $mImage = null;
-
- /**
- * @var Title
- */
- protected $mTitle;
-
- /**
- * @var string
- */
- protected $mText;
+ public function __construct() {
+ if ( self::class === static::class ) {
+ wfDeprecated( __METHOD__, '1.34' );
+ }
+ }
/**
* Return a new SearchResult and initializes it with a title.
* @return SearchResult
*/
public static function newFromTitle( $title, ISearchResultSet $parentSet = null ) {
- $result = new static();
- $result->initFromTitle( $title );
+ $result = new RevisionSearchResult( $title );
if ( $parentSet ) {
$parentSet->augmentResult( $result );
}
return $result;
}
-
- /**
- * Initialize from a Title and if possible initializes a corresponding
- * Revision and File.
- *
- * @param Title $title
- */
- protected function initFromTitle( $title ) {
- $this->mTitle = $title;
- $services = MediaWikiServices::getInstance();
- if ( !is_null( $this->mTitle ) ) {
- $id = false;
- Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
- $this->mRevision = Revision::newFromTitle(
- $this->mTitle, $id, Revision::READ_NORMAL );
- if ( $this->mTitle->getNamespace() === NS_FILE ) {
- $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
- }
- }
- }
-
- /**
- * Check if this is result points to an invalid title
- *
- * @return bool
- */
- public function isBrokenTitle() {
- return is_null( $this->mTitle );
- }
-
- /**
- * Check if target page is missing, happens when index is out of date
- *
- * @return bool
- */
- public function isMissingRevision() {
- return !$this->mRevision && !$this->mImage;
- }
-
- /**
- * @return Title
- */
- public function getTitle() {
- return $this->mTitle;
- }
-
- /**
- * Get the file for this page, if one exists
- * @return File|null
- */
- public function getFile() {
- return $this->mImage;
- }
-
- /**
- * Lazy initialization of article text from DB
- */
- protected function initText() {
- if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null ) {
- $content = $this->mRevision->getContent();
- $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
- } else { // TODO: can we fetch raw wikitext for commons images?
- $this->mText = '';
- }
- }
- }
-
- /**
- * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
- * @return string Highlighted text snippet, null (and not '') if not supported
- */
- public function getTextSnippet( $terms = [] ) {
- return '';
- }
-
- /**
- * @return string Highlighted title, '' if not supported
- */
- public function getTitleSnippet() {
- return '';
- }
-
- /**
- * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
- */
- public function getRedirectSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object for the redirect to this page, null if none or not supported
- */
- public function getRedirectTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant section name, null if none or not supported
- */
- public function getSectionSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object (pagename+fragment) for the section,
- * null if none or not supported
- */
- public function getSectionTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant category name or '' if none or not supported
- */
- public function getCategorySnippet() {
- return '';
- }
-
- /**
- * @return string Timestamp
- */
- public function getTimestamp() {
- if ( $this->mRevision ) {
- return $this->mRevision->getTimestamp();
- } elseif ( $this->mImage ) {
- return $this->mImage->getTimestamp();
- }
- return '';
- }
-
- /**
- * @return int Number of words
- */
- public function getWordCount() {
- $this->initText();
- return str_word_count( $this->mText );
- }
-
- /**
- * @return int Size in bytes
- */
- public function getByteSize() {
- $this->initText();
- return strlen( $this->mText );
- }
-
- /**
- * @return string Interwiki prefix of the title (return iw even if title is broken)
- */
- public function getInterwikiPrefix() {
- return '';
- }
-
- /**
- * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
- */
- public function getInterwikiNamespaceText() {
- return '';
- }
-
- /**
- * Did this match file contents (eg: PDF/DJVU)?
- * @return bool
- */
- public function isFileMatch() {
- return false;
- }
-
}
* @ingroup Search
*/
-class SqlSearchResult extends SearchResult {
+class SqlSearchResult extends RevisionSearchResult {
/** @var string[] */
private $terms;
* @param string[] $terms list of parsed terms
*/
public function __construct( Title $title, array $terms ) {
- $this->initFromTitle( $title );
+ parent::__construct( $title );
$this->terms = $terms;
}
"rcfilters-filter-showlinkedto-label": "Vis ændringer på sider der linker til",
"rcfilters-filter-showlinkedto-option-label": "<strong>Sider som linker til</strong> den valgte side",
"rcfilters-target-page-placeholder": "Indtast et sidenavn (eller en kategori)",
+ "rcfilters-allcontents-label": "Alt indhold",
"rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
"rclistfromreset": "Nulstil datovalg",
"rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
"Fitoschido",
"KATRINE1993",
"Vlad5250",
- "Sarri.greek"
+ "Sarri.greek",
+ "Kostajh"
]
},
"tog-underline": "Υπογράμμιση συνδέσμων:",
"history": "Ιστορικό σελίδας",
"history_short": "Ιστορικό",
"history_small": "ιστορικό",
- "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή μου",
+ "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή σας",
"printableversion": "Έκδοση εκτύπωσης",
"permalink": "Σταθερός σύνδεσμος",
"print": "Εκτύπωση",
"PhiLiP",
"Qiyue2001",
"Xiaomingyan",
- "神樂坂秀吉"
+ "神樂坂秀吉",
+ "予弦"
]
},
"exif-imagewidth": "宽度",
"tag-filter": "Filtrer les [[Special:Tags|balises]] :",
"tag-filter-submit": "Filtrer",
"tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Balise|Balises}}]] : $2",
- "tag-mw-contentmodelchange": "modification du modèle de contenu",
+ "tag-mw-contentmodelchange": "Modification du modèle de contenu",
"tag-mw-contentmodelchange-description": "Modifications qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel changent le modèle de contenu] d'une page",
"tag-mw-new-redirect": "Nouvelle redirection",
"tag-mw-new-redirect-description": "Modifications qui créent une nouvelle redirection ou transforment une page en redirection",
"tag-mw-changed-redirect-target-description": "Modifications qui changent la cible d’une redirection",
"tag-mw-blank": "Blanchiment",
"tag-mw-blank-description": "Modifications qui suppriment le contenu des pages",
- "tag-mw-replace": "Remplacé",
+ "tag-mw-replace": "Contenu remplacé",
"tag-mw-replace-description": "Modifications qui enlèvent plus de 90% du contenu des pages",
"tag-mw-rollback": "Révocation",
"tag-mw-rollback-description": "Modifications qui annulent des modifications existantes en utilisant le lien de révocation (''rollback'')",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pengguna lain mengirim surel kepada saya",
- "email-allow-new-users-label": "Izinkan email dari pengguna baru",
+ "email-allow-new-users-label": "Izinkan surel dari pengguna baru",
"email-blacklist-label": "Cegah para pengguna ini mengirim saya surel:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruang nama",
"last": "prec",
"page_first": "prima",
"page_last": "ultima",
- "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+ "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
"history-fieldset-title": "Filtra versioni",
"history-show-deleted": "Solo versioni cancellate",
"histfirst": "prima",
"prefs-personal": "Profil pangguno",
"prefs-rc": "Parubahan baru",
"prefs-watchlist": "Daftar pantau",
+ "prefs-editwatchlist": "Suntiang daftar pantauan",
+ "prefs-editwatchlist-label": "Suntiang entri daftar pantauan Sanak:",
"prefs-watchlist-days": "Jumlah hari dalam daftar pantau:",
"prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|hari}}",
"prefs-watchlist-edits": "Jumlah suntiangan nan ditunjuakan pado daftar pantau:",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pangguno lain mangirim surel",
+ "email-allow-new-users-label": "Izinkan surel dari pangguno baru",
+ "email-blacklist-label": "Panggono ko indak dapek kirim surel ka Ambo:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruangnamo",
"default": "baku",
"prefs-files": "Berkas",
- "prefs-custom-css": "CSS paribadi",
- "prefs-custom-js": "JS paribadi",
+ "prefs-custom-css": "CSS surang",
+ "prefs-custom-js": "JS surang",
"prefs-common-config": "CSS/JS untuak kasado kulik:",
"prefs-reset-intro": "Angku dapek manggunokan laman ko untuak mangambalikan pangaturan ka setelan baku situs ko.\nPangambalian pangaturan indak dapek dibatalan.",
"prefs-emailconfirm-label": "Surel konfirmasi:",
"prefs-advancedwatchlist": "Piliahan lanjuik",
"prefs-displayrc": "Piliahan tampilan",
"prefs-displaywatchlist": "Piliahan tampilan",
+ "prefs-changesrc": "Parubahan ditampilkan",
"prefs-diffs": "Pabedoan",
"userrights": "Manajemen hak pangguno",
"userrights-lookup-user": "Mangatua kalompok pangguno",
"KlaasZ4usV",
"Elroy",
"PiefPafPier",
- "Ecthelion3"
+ "Ecthelion3",
+ "RadioAzureus"
]
},
"tog-underline": "Verwijzingen onderstrepen:",
"rcfilters-filter-showlinkedto-label": "Toon wijzigingen op pagina's gekoppeld naar",
"rcfilters-filter-showlinkedto-option-label": "<strong>Pagina's gekoppeld naar</strong> de geselecteerde pagina",
"rcfilters-target-page-placeholder": "Voer een paginanaam (of categorie) in",
- "rcfilters-allcontents-label": "Alle inhoud",
- "rcfilters-alldiscussions-label": "Al het overleg",
+ "rcfilters-allcontents-label": "De volledige inhoud",
+ "rcfilters-alldiscussions-label": "Alle discussies",
"rcnotefrom": "Wijzigingen sinds <strong>$3 om $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wijziging|wijzigingen}}).",
"rclistfromreset": "Datum selectie opnieuw instellen",
"rclistfrom": "Wijzigingen bekijken vanaf $3 $2",
"immobile-target-namespace-iw": "Een interwikikoppeling is geen geldige bestemming voor het hernoemen van een pagina.",
"immobile-source-page": "Deze pagina kan niet hernoemd worden.",
"immobile-target-page": "Het is niet mogelijk te hernoemen naar die paginanaam.",
- "movepage-invalid-target-title": "De opgevraagde naam is ongeldig.",
+ "movepage-invalid-target-title": "De gevraagde naam is ongeldig.",
"bad-target-model": "De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.",
"imagenocrossnamespace": "Een mediabestand kan niet naar een andere naamruimte verplaatst worden",
"nonfile-cannot-move-to-file": "Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte",
"showdiff": "Yong-a wallak",
"anoneditwarning": "<strong>Warning:</strong> You are not logged in. Noonook IP-karl-up will be publicly djinang il noonook wallak. Noonook-il <strong>[$1 log in]</strong> or <strong>[$2 create an gudak]</strong>, noonook wallak will be attributed to noonook niall-kwel-le, along with other benefits.",
"blockedtitle": "Niall be nap-nap",
- "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"email this user\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
+ "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
"loginreqlink": "yaarlkoorl",
"newarticletext": "Noonook ngwaliny beda bibol uart-yogow yeye.\nWallak bibol qadgin mar waangkin ngardal (djinang [$1 mar yira bibol] ngatta katitjiny)\nWarra bainya noonook nidja, click noonook bowser's <strong>woort koorl</strong>button",
- "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify him/her.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
+ "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify balang.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
"noarticletext": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} create this page]</span>.",
"noarticletext-nopermission": "Nidja yeye uart text il nidja bibol.\nNoonook [[Special:Search/{{PAGENAME}}|genuniny-ung nidja bibol katta wir-iny]] bura wam bibol, ka <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} genuniny boonadairn]</span>, noonook uart kaya ijow walbirniny nidja bibol.",
"userpage-userdoesnotexist-view": "Niall gaduk $1 be uart yeye-quadga",
"recentchangeslinked-feed": "Noyyang wallak",
"recentchangeslinked-toolbox": "Noyyang wallak",
"recentchangeslinked-title": "Wallak noyyanging $1",
- "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol (or il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
+ "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol ({{ns:category}} il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
"recentchangeslinked-page": "Bibol kwel-le:",
"recentchangeslinked-to": "Yong-a wallak bibol beda nitja bibol",
"upload": "Yirra file",
"rcfilters-filter-showlinkedto-label": "Pokaż zmiany na stronach linkujących do",
"rcfilters-filter-showlinkedto-option-label": "<strong>Strony linkujące do</strong> zaznaczonej strony",
"rcfilters-target-page-placeholder": "Wprowadź nazwę strony (lub kategorii)",
- "rcfilters-alldiscussions-label": "Wszystkie dyskusje",
+ "rcfilters-allcontents-label": "Wszystkie (treść)",
+ "rcfilters-alldiscussions-label": "Wszystkie (dyskusje)",
"rcnotefrom": "Poniżej {{PLURAL:$5|pokazano zmianę|pokazano zmiany}} {{PLURAL:$5|wykonaną|wykonane}} po <strong>$3, $4</strong> (nie więcej niż '''$1''' pozycji).",
"rclistfromreset": "Zresetuj wybór daty",
"rclistfrom": "Pokaż nowe zmiany od $3 $2",
"ipb-disableusertalk": "Edytowanie przez tego użytkownika swojej strony dyskusji",
"ipb-change-block": "Zmień ustawienia blokady",
"ipb-confirm": "Potwierdzam blokadę",
- "ipb-sitewide": "Całkowita",
- "ipb-partial": "Częściowa",
+ "ipb-sitewide": "Całkowicie",
+ "ipb-partial": "Częściowo",
"ipb-sitewide-help": "Wszystkie strony na wiki i wszystkie akcje inne edycyjne.",
"ipb-partial-help": "Konkretne strony lub przestrzenie nazw.",
"ipb-pages-label": "Strony",
"revdelete-hide-image": "فائيل جو مواد لڪايو",
"revdelete-hide-name": "هدف ۽ نيمپيما لڪايو",
"revdelete-hide-comment": "سنوار جو تتُ",
- "revdelete-hide-user": "اÙ\8aÚ\8aÙ\8aٽر جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آءÙ\90Ù¾Ù\90ي پتو",
+ "revdelete-hide-user": "سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99 جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آئÙ\90Ù¾ي پتو",
"revdelete-hide-restricted": "منتظمن توڙي ٻين کان مليل اعداد دٻايو",
"revdelete-radio-same": "(نہ بدلايو)",
"revdelete-radio-set": "لڪل",
"rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
"rcfilters-filtergroup-user-experience-level": "واپرائيندڙن جي رجسٽريشن ۽ تجربو",
"rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
- "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 اÙ\8aÚ\8aÙ\8aٽر.",
+ "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99.",
"rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
"rcfilters-filter-user-experience-level-unregistered-description": "سنواريندڙ جيڪي داخل ٿيل ناھن.",
"rcfilters-filter-user-experience-level-newcomer-label": "نوان ايندڙ",
"imagelinks": "فائيل جو استعمال",
"linkstoimage": "ھن فائيل کي {{PLURAL:$1|ھيٺيون صفحو استعمال ڪري ٿو|$1 ھيٺيان صفحا استعمال ڪن ٿا}}:",
"nolinkstoimage": "ڪي بہ صفحا ناھن جيڪي ھن فائيل کي استعمال ڪندا ھجن.",
+ "linkstoimage-redirect": "$1 (فائيل چورڻو) $2",
"sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
"sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
"filepage-nofile": "ھن نالي سان ڪوبہ فائيل وجود نٿو رکي.",
"protectlogpage": "تحفظ لاگ",
"protectedarticle": "محفوظ ٿيل \"[[$1]]\"",
"modifiedarticleprotection": "\"[[$1]]\" جي تحفظ جي سطح تبديل ڪئي",
+ "unprotectedarticle": "\"[[$1]]\" تان تحفظ ھٽايو ويو",
"movedarticleprotection": "\"[[$2]]\" جو حفاظت درجو \"[[$1]]\" جي طرف منتقل ڪيو",
+ "unprotectedarticle-comment": "\"[[$1]]\" تان {{GENDER:$2|تحفظ ھٽايو}}",
"prot_1movedto2": "[[$1]] کي چوري [[$2]] تي رکيو ويو",
"protect-legend": "تحفظڻ جي پڪ ڪريو",
"protectcomment": "سبب:",
"htmlform-cloner-delete": "هٽايو",
"htmlform-title-not-exists": "$1 وجود نٿو رکي.",
"logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
+ "logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
"logentry-delete-revision": "$1 $3: $4 صفحي تي {{PLURAL:$5|ھڪ مسودي|$5 مسودن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
"revdelete-content-hid": "مواد لڪيل",
"revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
+ "revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
"logentry-block-block": "$1، {{GENDER:$4|$3}} تي $5 وقت جي خاتمي تائين {{GENDER:$2|بندش هئي آهي}} $6",
"logentry-move-move": "$1 {{GENDER:$2|چوريو}} صفحو $3 ڏانهن $4",
"logentry-move-move-noredirect": "$1 $3 صفحي کي $4 ڏانھن {{GENDER:$2|چوريو}} سواءِ ڪو ريڊائريڪٽ ڇڏيندي",
"logentry-patrol-patrol-auto": "$1 پاڻمرادو صفحي $3 جي $4 مسودي تي گشت ڪيل طور {{GENDER:$2|نشان لڳايو}}",
"logentry-newusers-create": "واپرائيندڙ کاتو $1 {{GENDER:$2|سرجيو ويو}}",
"logentry-newusers-autocreate": "واپرائيندڙ کاتو $1 پاڻمرادو {{GENDER:$2|کوليو ويو}}",
+ "logentry-protect-unprotect": "$1 $3 تان تحفظ {{GENDER:$2|ھٽايو}}",
"logentry-protect-protect": "$1 {{GENDER:$2|محفوظ ڪيو}} $3 $4",
"logentry-upload-upload": "$1 {{GENDER:$2|چاڙهيو}} $3",
"logentry-upload-overwrite": "$1 $3 جو ھڪ نئون ورزن {{GENDER:$2|چاڙھيو}}",
"mw-widgets-usersmultiselect-placeholder": "وڌيڪ شامل ڪيو...",
"date-range-from": "تاريخ کان:",
"date-range-to": "تاريخ تائين:",
+ "randomrootpage": "بلاترتيب پاڙ صفحو",
"log-action-filter-all": "سڀ"
}
"Suchichi02",
"神樂坂秀吉",
"WQL",
- "Looong"
+ "Looong",
+ "予弦"
]
},
"tog-underline": "链接下划线:",
"requires": {
"lodash": "^4.17.11"
}
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
}
}
},
}
}
},
- "humanize-duration": {
- "version": "3.15.3",
- "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz",
- "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==",
- "dev": true
- },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
}
},
"lodash": {
- "version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.get": {
}
},
"mixin-deep": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
- "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
"dev": true
},
"set-value": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
- "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
}
},
"union-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
- "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
- "set-value": "^0.4.3"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
- "set-value": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
- "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.1",
- "to-object-path": "^0.3.0"
- }
- }
+ "set-value": "^2.0.1"
}
},
"uniq": {
"sauce-connect-launcher": "~1.2.3"
}
},
- "wdio-spec-reporter": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz",
- "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==",
- "dev": true,
- "requires": {
- "babel-runtime": "~6.26.0",
- "chalk": "^2.3.0",
- "humanize-duration": "~3.15.0"
- }
- },
"wdio-sync": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz",
"postcss-less": "2.0.0",
"qunit": "2.9.1",
"stylelint-config-wikimedia": "0.6.0",
+ "wdio-dot-reporter": "0.0.10",
"wdio-junit-reporter": "0.4.4",
"wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
"wdio-mocha-framework": "0.6.4",
"wdio-sauce-service": "0.4.14",
- "wdio-spec-reporter": "0.1.5",
"webdriverio": "4.14.4"
}
}
)
);
- if ( this.cache ) {
- this.cache.set( pageData );
- }
-
// Offer the exact text as a suggestion if the page exists
if ( this.addQueryInput && pageExists && !pageExistsExact ) {
titles.unshift( this.getQueryValue() );
+ // Ensure correct page metadata gets used
+ pageData[ this.getQueryValue() ] = pageData[ titleObj.getPrefixedText() ];
+ }
+
+ if ( this.cache ) {
+ this.cache.set( pageData );
}
for ( i = 0, len = titles.length; i < len; i++ ) {
'class' => NullLockManager::class,
] ];
$reset = function () {
- LockManagerGroup::destroySingletons();
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
};
$setup[] = $reset;
$teardown[] = $reset;
*/
private function mockResultClosure( $title, $setters = [] ) {
return function () use ( $title, $setters ){
- $result = MockSearchResult::newFromTitle( Title::newFromText( $title ) );
+ $result = new MockSearchResult( Title::newFromText( $title ) );
foreach ( $setters as $method => $param ) {
$result->$method( $param );
<?php
-class MockSearchResult extends SearchResult {
+class MockSearchResult extends RevisionSearchResult {
private $isMissingRevision = false;
private $isBrokenTitle = false;
--- /dev/null
+<?php
+
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers MediaWiki\FileBackend\LockManager\LockManagerGroupFactory
+ * @todo Should we somehow test that the LockManagerGroup objects are as we expect? How do we do
+ * that without getting into testing LockManagerGroup itself?
+ */
+class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase {
+ public function testGetLockManagerGroup() {
+ $mockLbFactory = $this->createMock( LBFactory::class );
+ $mockLbFactory->expects( $this->never() )->method( $this->anything() );
+
+ $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory );
+ $lbmUnspecified = $factory->getLockManagerGroup();
+ $lbmFalse = $factory->getLockManagerGroup( false );
+ $lbmDefault = $factory->getLockManagerGroup( 'defaultDomain' );
+ $lbmOther = $factory->getLockManagerGroup( 'otherDomain' );
+
+ $this->assertSame( $lbmUnspecified, $lbmFalse );
+ $this->assertSame( $lbmFalse, $lbmDefault );
+ $this->assertSame( $lbmDefault, $lbmUnspecified );
+ $this->assertNotEquals( $lbmUnspecified, $lbmOther );
+ $this->assertNotEquals( $lbmFalse, $lbmOther );
+ $this->assertNotEquals( $lbmDefault, $lbmOther );
+
+ $this->assertSame( $lbmUnspecified, $factory->getLockManagerGroup() );
+ $this->assertSame( $lbmFalse, $factory->getLockManagerGroup( false ) );
+ $this->assertSame( $lbmDefault, $factory->getLockManagerGroup( 'defaultDomain' ) );
+ $this->assertSame( $lbmOther, $factory->getLockManagerGroup( 'otherDomain' ) );
+ }
+}
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Since this is a unit test, we don't test the singleton() or destroySingletons() methods. We also
+ * can't test get() with a valid argument, because that winds up calling static methods of
+ * ObjectCache and LoggerFactory that aren't yet compatible with proper unit tests. Those will be
+ * tested in the integration test for now.
+ *
+ * @covers LockManagerGroup
+ */
+class LockManagerGroupTest extends MediaWikiUnitTestCase {
+ private function getMockLBFactory() {
+ $mock = $this->createMock( LBFactory::class );
+ $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
+ return $mock;
+ }
+
+ public function testConstructorNoConfigs() {
+ new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $this->assertTrue( true, 'No exception thrown' );
+ }
+
+ public function testConstructorConfigWithNoName() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register a lock manager with no name.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'class' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testConstructorConfigWithNoClass() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register lock manager `c` with no class.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'name' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testGetUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->get( 'c' );
+ }
+
+ public function testConfigUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->config( 'c' );
+ }
+
+ public function testConfig() {
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b', 'foo' => 'c' ] ],
+ $this->getMockLBFactory() );
+ $this->assertSame(
+ [ 'class' => 'b', 'name' => 'a', 'foo' => 'c', 'domain' => 'domain' ],
+ $lmg->config( 'a' )
+ );
+ }
+
+ public function testGetDefaultNull() {
+ $lmg = new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $expected = new NullLockManager( [] );
+ $actual = $lmg->getDefault();
+ // Have to get rid of the $sessions for equality check to work
+ TestingAccessWrapper::newFromObject( $actual )->session = null;
+ TestingAccessWrapper::newFromObject( $expected )->session = null;
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testGetAnyException() {
+ // XXX Isn't the name 'getAny' misleading if we don't get whatever's available?
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `fsLockManager`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->getAny();
+ }
+}
// Test reporter for stdout.
// See also: http://webdriver.io/guide/testrunner/reporters.html
- reporters: [ 'spec', 'junit' ],
+ reporters: [ 'dot', 'junit' ],
reporterOptions: {
junit: {
outputDir: logPath