* @ingroup Change tagging
*/
-use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableAccessException;
use Wikimedia\Rdbms\Database;
class ChangeTags {
* @return array Array with two items: (html, classes)
* - html: String: HTML for displaying the tags (empty string when param $tags is empty)
* - classes: Array of strings: CSS classes used in the generated html, one class for each tag
+ * @return-taint onlysafefor_htmlnoent
*/
public static function formatSummaryRow( $tags, $page, IContextSource $context = null ) {
if ( !$tags ) {
}
$taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
- $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc );
- return $context->getLanguage()->truncateForVisual( $escapedDesc, $length );
+ return $context->getLanguage()->truncateForVisual( $taglessDesc, $length );
}
/**
* @param int|null $rc_id The rc_id of the change to add the tags to
* @param int|null $rev_id The rev_id of the change to add the tags to
* @param int|null $log_id The log_id of the change to add the tags to
- * @param string $params Params to put in the ct_params field of table 'change_tag'
+ * @param string|null $params Params to put in the ct_params field of table 'change_tag'
* @param RecentChange|null $rc Recent change, in case the tagging accompanies the action
* (this should normally be the case)
*
* Pass a variable whose value is null if the rev_id is not relevant or unknown.
* @param int|null &$log_id The log_id of the change to add the tags to.
* Pass a variable whose value is null if the log_id is not relevant or unknown.
- * @param string $params Params to put in the ct_params field of table
+ * @param string|null $params Params to put in the ct_params field of table
* 'change_tag' when adding tags
* @param RecentChange|null $rc Recent change being tagged, in case the tagging accompanies
* the action
}
// insert a row into change_tag for each new tag
+ $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
if ( count( $tagsToAdd ) ) {
$changeTagMapping = [];
if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
- $changeTagDefStore = new NameTableStore(
- MediaWikiServices::getInstance()->getDBLoadBalancer(),
- MediaWikiServices::getInstance()->getMainWANObjectCache(),
- LoggerFactory::getInstance( 'NameTableSqlStore' ),
- 'change_tag_def',
- 'ctd_id',
- 'ctd_name',
- null,
- false,
- function ( $insertFields ) {
- $insertFields['ctd_user_defined'] = 0;
- $insertFields['ctd_count'] = 0;
- return $insertFields;
- }
- );
-
foreach ( $tagsToAdd as $tag ) {
$changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
}
$tagsRows = [];
foreach ( $tagsToAdd as $tag ) {
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagName = null;
+ } else {
+ $tagName = $tag;
+ }
// Filter so we don't insert NULLs as zero accidentally.
// Keep in mind that $rc_id === null means "I don't care/know about the
// rc_id, just delete $tag on this revision/log entry". It doesn't
// mean "only delete tags on this revision/log WHERE rc_id IS NULL".
$tagsRows[] = array_filter(
[
- 'ct_tag' => $tag,
+ 'ct_tag' => $tagName,
'ct_rc_id' => $rc_id,
'ct_log_id' => $log_id,
'ct_rev_id' => $rev_id,
// delete from change_tag
if ( count( $tagsToRemove ) ) {
foreach ( $tagsToRemove as $tag ) {
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagName = null;
+ $tagId = $changeTagDefStore->getId( $tag );
+ } else {
+ $tagName = $tag;
+ $tagId = null;
+ }
$conds = array_filter(
[
- 'ct_tag' => $tag,
+ 'ct_tag' => $tagName,
'ct_rc_id' => $rc_id,
'ct_log_id' => $log_id,
- 'ct_rev_id' => $rev_id
+ 'ct_rev_id' => $rev_id,
+ 'ct_tag_id' => $tagId,
]
);
$dbw->delete( 'change_tag', $conds, __METHOD__ );
public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = ''
) {
- global $wgUseTagFilter;
+ global $wgUseTagFilter, $wgChangeTagsSchemaMigrationStage;
// Normalize to arrays
$tables = (array)$tables;
throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
}
+ $tagTables[] = 'change_tag';
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagTables[] = 'change_tag_def';
+ $join_cond_ts_tags = [ $join_cond, 'ct_tag_id=ctd_id' ];
+ $field = 'ctd_name';
+ } else {
+ $field = 'ct_tag';
+ $join_cond_ts_tags = $join_cond;
+ }
+
$fields['ts_tags'] = wfGetDB( DB_REPLICA )->buildGroupConcatField(
- ',', 'change_tag', 'ct_tag', $join_cond
+ ',', $tagTables, $field, $join_cond_ts_tags
);
if ( $wgUseTagFilter && $filter_tag ) {
$tables[] = 'change_tag';
$join_conds['change_tag'] = [ 'INNER JOIN', $join_cond ];
- $conds['ct_tag'] = $filter_tag;
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $filterTagIds = [];
+ $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
+ foreach ( (array)$filter_tag as $filterTagName ) {
+ try {
+ $filterTagIds[] = $changeTagDefStore->getId( $filterTagName );
+ } catch ( NameTableAccessException $exception ) {
+ // Return nothing.
+ $conds[] = '0';
+ break;
+ };
+ }
+ $conds['ct_tag_id'] = $filterTagIds;
+ } else {
+ $conds['ct_tag'] = $filter_tag;
+ }
+
if (
is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
!in_array( 'DISTINCT', $options )
);
}
- $dbw->replace(
- 'valid_tag',
- [ 'vt_tag' ],
- [ 'vt_tag' => $tag ],
- __METHOD__
- );
-
+ if ( $wgChangeTagsSchemaMigrationStage < MIGRATION_NEW ) {
+ $dbw->replace(
+ 'valid_tag',
+ [ 'vt_tag' ],
+ [ 'vt_tag' => $tag ],
+ __METHOD__
+ );
+ }
// clear the memcache of defined tags
self::purgeTagCacheAll();
}
if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
$dbw->update(
'change_tag_def',
- [ 'ctd_name' => $tag ],
[ 'ctd_user_defined' => 0 ],
+ [ 'ctd_name' => $tag ],
__METHOD__
);
);
}
- $dbw->delete( 'valid_tag', [ 'vt_tag' => $tag ], __METHOD__ );
+ if ( $wgChangeTagsSchemaMigrationStage < MIGRATION_NEW ) {
+ $dbw->delete( 'valid_tag', [ 'vt_tag' => $tag ], __METHOD__ );
+ }
// clear the memcache of defined tags
self::purgeTagCacheAll();
* @param string $tag
* @param string $reason
* @param User $user Who to attribute the action to
- * @param int $tagCount For deletion only, how many usages the tag had before
+ * @param int|null $tagCount For deletion only, how many usages the tag had before
* it was deleted.
* @param array $logEntryTags Change tags to apply to the entry
* that will be created in the tag management log
* Extensions should NOT use this function; they can use the ListDefinedTags
* hook instead.
*
- * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to
+ * Includes a call to ChangeTag::canCreateTag(), so your code doesn't need to
* do that.
*
* @param string $tag
// delete from valid_tag and/or set ctd_user_defined = 0
self::undefineTag( $tag );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
+ $conditions = [ 'ct_tag_id' => $tagId ];
+ } else {
+ $conditions = [ 'ct_tag' => $tag ];
+ }
+
// find out which revisions use this tag, so we can delete from tag_summary
$result = $dbw->select( 'change_tag',
- [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ],
- [ 'ct_tag' => $tag ],
+ [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id' ],
+ $conditions,
__METHOD__ );
foreach ( $result as $row ) {
// remove the tag from the relevant row of tag_summary
}
// delete from change_tag
- $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
+ $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ );
+ } else {
+ $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
+ }
if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
$dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
/**
* Lists tags explicitly defined in the `valid_tag` table of the database.
* Tags in table 'change_tag' which are not in table 'valid_tag' are not
- * included.
+ * included. In case of new backend loads the data from `change_tag_def` table.
*
* Tries memcached first.
*
$cache->makeKey( 'valid-tags-db' ),
WANObjectCache::TTL_MINUTE * 5,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
+ global $wgChangeTagsSchemaMigrationStage;
$dbr = wfGetDB( DB_REPLICA );
$setOpts += Database::getCacheSetOptions( $dbr );
- $tags = $dbr->selectFieldValues( 'valid_tag', 'vt_tag', [], $fname );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tags = self::listExplicitlyDefinedTagsNewBackend();
+ } else {
+ $tags = $dbr->selectFieldValues( 'valid_tag', 'vt_tag', [], $fname );
+ }
return array_filter( array_unique( $tags ) );
},
);
}
+ /**
+ * Lists tags explicitly user defined tags. When ctd_user_defined is true.
+ *
+ * @return string[] Array of strings: tags
+ * @since 1.25
+ */
+ private static function listExplicitlyDefinedTagsNewBackend() {
+ $dbr = wfGetDB( DB_REPLICA );
+ return $dbr->selectFieldValues(
+ 'change_tag_def',
+ 'ctd_name',
+ [ 'ctd_user_defined' => 1 ],
+ __METHOD__
+ );
+ }
+
/**
* Lists tags defined by core or extensions using the ListDefinedTags hook.
* Extensions need only define those tags they deem to be in active use.
* @return array Array of string => int
*/
public static function tagUsageStatistics() {
+ global $wgChangeTagsSchemaMigrationStage, $wgTagStatisticsNewTable;
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ||
+ ( $wgTagStatisticsNewTable && $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD )
+ ) {
+ return self::newTagUsageStatistics();
+ }
+
$fname = __METHOD__;
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
return $cache->getWithSetCallback(
);
}
+ /**
+ * Same self::tagUsageStatistics() but uses change_tag_def.
+ *
+ * @return array Array of string => int
+ */
+ private static function newTagUsageStatistics() {
+ $dbr = wfGetDB( DB_REPLICA );
+ $res = $dbr->select(
+ 'change_tag_def',
+ [ 'ctd_name', 'ctd_count' ],
+ [],
+ __METHOD__,
+ [ 'ORDER BY' => 'ctd_count DESC' ]
+ );
+
+ $out = [];
+ foreach ( $res as $row ) {
+ $out[$row->ctd_name] = $row->ctd_count;
+ }
+
+ return $out;
+ }
+
/**
* Indicate whether change tag editing UI is relevant
*