}
/**
- * Get a short description for a tag.
+ * Get the message object for the tag's short description.
*
* Checks if message key "mediawiki:tag-$tag" exists. If it does not,
- * returns the HTML-escaped tag name. Uses the message if the message
- * exists, provided it is not disabled. If the message is disabled,
- * we consider the tag hidden, and return false.
+ * returns the tag name in a RawMessage. If the message exists, it is
+ * used, provided it is not disabled. If the message is disabled, we
+ * consider the tag hidden, and return false.
*
+ * @since 1.34
* @param string $tag
* @param MessageLocalizer $context
- * @return string|bool Tag description or false if tag is to be hidden.
- * @since 1.25 Returns false if tag is to be hidden.
+ * @return Message|bool Tag description, or false if tag is to be hidden.
*/
- public static function tagDescription( $tag, MessageLocalizer $context ) {
+ public static function tagShortDescriptionMessage( $tag, MessageLocalizer $context ) {
$msg = $context->msg( "tag-$tag" );
if ( !$msg->exists() ) {
- // No such message, so return the HTML-escaped tag name.
- return htmlspecialchars( $tag );
+ // No such message
+ return new RawMessage( '$1', [ Message::plaintextParam( $tag ) ] );
}
if ( $msg->isDisabled() ) {
// The message exists but is disabled, hide the tag.
}
// Message exists and isn't disabled, use it.
- return $msg->parse();
+ return $msg;
+ }
+
+ /**
+ * Get a short description for a tag.
+ *
+ * Checks if message key "mediawiki:tag-$tag" exists. If it does not,
+ * returns the HTML-escaped tag name. Uses the message if the message
+ * exists, provided it is not disabled. If the message is disabled,
+ * we consider the tag hidden, and return false.
+ *
+ * @param string $tag
+ * @param MessageLocalizer $context
+ * @return string|bool Tag description or false if tag is to be hidden.
+ * @since 1.25 Returns false if tag is to be hidden.
+ */
+ public static function tagDescription( $tag, MessageLocalizer $context ) {
+ $msg = self::tagShortDescriptionMessage( $tag, $context );
+ return $msg ? $msg->parse() : false;
}
/**
$cache->touchCheckKey( $cache->makeKey( 'active-tags' ) );
$cache->touchCheckKey( $cache->makeKey( 'valid-tags-db' ) );
$cache->touchCheckKey( $cache->makeKey( 'valid-tags-hook' ) );
+ $cache->touchCheckKey( $cache->makeKey( 'tags-usage-statistics' ) );
MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
}
* @return array Array of string => int
*/
public static function tagUsageStatistics() {
- $dbr = wfGetDB( DB_REPLICA );
- $res = $dbr->select(
- 'change_tag_def',
- [ 'ctd_name', 'ctd_count' ],
- [],
- __METHOD__,
- [ 'ORDER BY' => 'ctd_count DESC' ]
- );
+ $fname = __METHOD__;
- $out = [];
- foreach ( $res as $row ) {
- $out[$row->ctd_name] = $row->ctd_count;
- }
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ return $cache->getWithSetCallback(
+ $cache->makeKey( 'tags-usage-statistics' ),
+ WANObjectCache::TTL_MINUTE * 5,
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select(
+ 'change_tag_def',
+ [ 'ctd_name', 'ctd_count' ],
+ [],
+ $fname,
+ [ 'ORDER BY' => 'ctd_count DESC' ]
+ );
- return $out;
+ $out = [];
+ foreach ( $res as $row ) {
+ $out[$row->ctd_name] = $row->ctd_count;
+ }
+
+ return $out;
+ },
+ [
+ 'checkKeys' => [ $cache->makeKey( 'tags-usage-statistics' ) ],
+ 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+ 'pcTTL' => WANObjectCache::TTL_PROC_LONG
+ ]
+ );
}
/**
}
}
+ /**
+ * Get essential data about getRcFiltersConfigVars() for change detection.
+ *
+ * @internal For use by Resources.php only.
+ * @see ResourceLoaderModule::getDefinitionSummary() and ResourceLoaderModule::getVersionHash()
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public static function getRcFiltersConfigSummary( ResourceLoaderContext $context ) {
+ return [
+ // Reduce version computation by avoiding Message parsing
+ 'RCFiltersChangeTags' => self::getChangeTagListSummary( $context ),
+ 'StructuredChangeFiltersEditWatchlistUrl' =>
+ SpecialPage::getTitleFor( 'EditWatchlist' )->getLocalURL()
+ ];
+ }
+
/**
* Get config vars to export with the mediawiki.rcfilters.filters.ui module.
*
+ * @internal For use by Resources.php only.
* @param ResourceLoaderContext $context
* @return array
*/
}
/**
- * Fetch the change tags list for the front end
+ * Get (cheap to compute) information about change tags.
+ *
+ * Returns an array of associative arrays with information about each tag:
+ * - name: Tag name (string)
+ * - labelMsg: Short description message (Message object)
+ * - descriptionMsg: Long description message (Message object)
+ * - cssClass: CSS class to use for RC entries with this tag
+ * - hits: Number of RC entries that have this tag
*
* @param ResourceLoaderContext $context
- * @return array Tag data
+ * @return array[] Information about each tag
*/
- protected static function getChangeTagList( ResourceLoaderContext $context ) {
- $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
- return $cache->getWithSetCallback(
- $cache->makeKey( 'changeslistspecialpage-changetags', $context->getLanguage() ),
- $cache::TTL_MINUTE * 10,
- function () use ( $context ) {
- $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
- $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
-
- $tagStats = ChangeTags::tagUsageStatistics();
- $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
-
- // Sort by hits (disabled for now)
- //arsort( $tagHitCounts );
-
- // HACK work around ChangeTags::truncateTagDescription() requiring a RequestContext
- $fakeContext = RequestContext::newExtraneousContext( Title::newFromText( 'Dwimmerlaik' ) );
- $fakeContext->setLanguage( Language::factory( $context->getLanguage() ) );
-
- // Build the list and data
- $result = [];
- foreach ( $tagHitCounts as $tagName => $hits ) {
- if (
- (
- // Only get active tags
- isset( $explicitlyDefinedTags[ $tagName ] ) ||
- isset( $softwareActivatedTags[ $tagName ] )
- ) &&
- // Only get tags with more than 0 hits
- $hits > 0
- ) {
- $result[] = [
- 'name' => $tagName,
- 'label' => Sanitizer::stripAllTags(
- ChangeTags::tagDescription( $tagName, $context )
- ),
- 'description' =>
- ChangeTags::truncateTagDescription(
- $tagName,
- self::TAG_DESC_CHARACTER_LIMIT,
- $fakeContext
- ),
- 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
- 'hits' => $hits,
- ];
- }
+ protected static function getChangeTagInfo( ResourceLoaderContext $context ) {
+ $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+ $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
+
+ $tagStats = ChangeTags::tagUsageStatistics();
+ $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
+
+ $result = [];
+ foreach ( $tagHitCounts as $tagName => $hits ) {
+ if (
+ (
+ // Only get active tags
+ isset( $explicitlyDefinedTags[ $tagName ] ) ||
+ isset( $softwareActivatedTags[ $tagName ] )
+ ) &&
+ // Only get tags with more than 0 hits
+ $hits > 0
+ ) {
+ $labelMsg = ChangeTags::tagShortDescriptionMessage( $tagName, $context );
+ if ( $labelMsg === false ) {
+ // Tag is hidden, skip it
+ continue;
}
+ $result[] = [
+ 'name' => $tagName,
+ // 'label' and 'description' filled in by getChangeTagList()
+ 'labelMsg' => $labelMsg,
+ 'descriptionMsg' => ChangeTags::tagLongDescriptionMessage( $tagName, $context ),
+ 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
+ 'hits' => $hits,
+ ];
+ }
+ }
+ return $result;
+ }
- // Instead of sorting by hit count (disabled, see above), sort by display name
- usort( $result, function ( $a, $b ) {
- return strcasecmp( $a['label'], $b['label'] );
- } );
+ /**
+ * Get information about change tags for use in getRcFiltersConfigSummary().
+ *
+ * This expands labelMsg and descriptionMsg to the raw values of each message, which captures
+ * changes in the messages but avoids the expensive step of parsing them.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array[] Result of getChangeTagInfo(), with messages expanded to raw contents
+ */
+ protected static function getChangeTagListSummary( ResourceLoaderContext $context ) {
+ $tags = self::getChangeTagInfo( $context );
+ foreach ( $tags as &$tagInfo ) {
+ $tagInfo['labelMsg'] = $tagInfo['labelMsg']->plain();
+ if ( $tagInfo['descriptionMsg'] ) {
+ $tagInfo['descriptionMsg'] = $tagInfo['descriptionMsg']->plain();
+ }
+ }
+ return $tags;
+ }
- return $result;
- },
- [
- 'lockTSE' => 30
- ]
- );
+ /**
+ * Get information about change tags to export to JS via getRcFiltersConfigVars().
+ *
+ * This removes labelMsg and descriptionMsg, and adds label and description, which are parsed,
+ * stripped and (in the case of description) truncated versions of these messages. Message
+ * parsing is expensive, so to detect whether the tag list has changed, use
+ * getChangeTagListSummary() instead.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array[] Result of getChangeTagInfo(), with messages parsed, stripped and truncated
+ */
+ protected static function getChangeTagList( ResourceLoaderContext $context ) {
+ $tags = self::getChangeTagInfo( $context );
+ $language = Language::factory( $context->getLanguage() );
+ foreach ( $tags as &$tagInfo ) {
+ $tagInfo['label'] = Sanitizer::stripAllTags( $tagInfo['labelMsg']->parse() );
+ $tagInfo['description'] = $tagInfo['descriptionMsg'] ?
+ $language->truncateForVisual(
+ Sanitizer::stripAllTags( $tagInfo['descriptionMsg']->parse() ),
+ self::TAG_DESC_CHARACTER_LIMIT
+ ) :
+ '';
+ unset( $tagInfo['labelMsg'] );
+ unset( $tagInfo['descriptionMsg'] );
+ }
+
+ // Instead of sorting by hit count (disabled for now), sort by display name
+ usort( $tags, function ( $a, $b ) {
+ return strcasecmp( $a['label'], $b['label'] );
+ } );
+ return $tags;
}
/**