*/
const MAX_DELETE_USES = 5000;
+ /**
+ * A list of tags defined and used by MediaWiki itself.
+ */
private static $definedSoftwareTags = [
'mw-contentmodelchange',
'mw-new-redirect',
* exists, provided it is not disabled. If the message is disabled,
* we consider the tag hidden, and return false.
*
- * @param string $tag Tag
+ * @param string $tag
* @param IContextSource $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.
* or if message is disabled, returns false. Otherwise, returns the message object
* for the long description.
*
- * @param string $tag Tag
+ * @param string $tag
* @param IContextSource $context
* @return Message|bool Message object of the tag long description or false if
* there is no description.
return $msg;
}
+ /**
+ * Get truncated message for the tag's long description.
+ *
+ * @param string $tag Tag name.
+ * @param int $length Maximum length of truncated message, including ellipsis.
+ * @param IContextSource $context
+ *
+ * @return string Truncated long tag description.
+ */
+ public static function truncateTagDescription( $tag, $length, IContextSource $context ) {
+ $originalDesc = self::tagLongDescriptionMessage( $tag, $context );
+ // If there is no tag description, return empty string
+ if ( !$originalDesc ) {
+ return '';
+ }
+
+ $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
+ $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc );
+
+ return $context->getLanguage()->truncateForVisual( $escapedDesc, $length );
+ }
+
/**
* Add tags to a change given its rc_id, rev_id and/or log_id
*
&$rev_id = null, &$log_id = null, $params = null, RecentChange $rc = null,
User $user = null
) {
+ global $wgChangeTagsSchemaMigrationStage;
+
$tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
$tagsToRemove = array_filter( (array)$tagsToRemove );
// insert a row into change_tag for each new tag
if ( count( $tagsToAdd ) ) {
+ $changeTagMapping = [];
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
+ $tagDefRows = [];
+ foreach ( $tagsToAdd as $tag ) {
+ $tagDefRows[] = [
+ 'ctd_name' => $tag,
+ 'ctd_user_defined' => 0,
+ 'ctd_count' => 1
+ ];
+ }
+
+ $dbw->upsert(
+ 'change_tag_def',
+ $tagDefRows,
+ [ 'ctd_name' ],
+ [ 'ctd_count = ctd_count + 1' ],
+ __METHOD__
+ );
+
+ $res = $dbw->select(
+ 'change_tag_def',
+ [ 'ctd_name', 'ctd_id' ],
+ [ 'ctd_name' => $tagsToAdd ]
+ );
+ foreach ( $res as $row ) {
+ $changeTagMapping[$row->ctd_name] = $row->ctd_id;
+ }
+ }
+
$tagsRows = [];
foreach ( $tagsToAdd as $tag ) {
// Filter so we don't insert NULLs as zero accidentally.
'ct_rc_id' => $rc_id,
'ct_log_id' => $log_id,
'ct_rev_id' => $rev_id,
- 'ct_params' => $params
+ 'ct_params' => $params,
+ 'ct_tag_id' => $changeTagMapping[$tag] ?? null,
]
);
+
}
$dbw->insert( 'change_tag', $tagsRows, __METHOD__, [ 'IGNORE' ] );
]
);
$dbw->delete( 'change_tag', $conds, __METHOD__ );
+ if ( $dbw->affectedRows() && $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
+ $dbw->update(
+ 'change_tag_def',
+ [ 'ctd_count = ctd_count - 1' ],
+ [ 'ctd_name' => $tag ],
+ __METHOD__
+ );
+
+ $dbw->delete(
+ 'change_tag_def',
+ [ 'ctd_name' => $tag, 'ctd_count' => 0, 'ctd_user_defined' => 0 ],
+ __METHOD__
+ );
+ }
}
}
// $prevTags can be out of date on replica DBs, especially when addTags is called consecutively,
// causing loss of tags added recently in tag_summary table.
$prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
- $prevTags = $prevTags ? $prevTags : '';
+ $prevTags = $prevTags ?: '';
$prevTags = array_filter( explode( ',', $prevTags ) );
// add tags
sort( $prevTags );
sort( $newTags );
if ( $prevTags == $newTags ) {
- // No change.
return false;
}
if ( !$newTags ) {
- // no tags left, so delete the row altogether
+ // No tags left, so delete the row altogether
$dbw->delete( 'tag_summary', $tsConds, __METHOD__ );
} else {
- $dbw->replace( 'tag_summary',
- [ 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ],
- array_filter( array_merge( $tsConds, [ 'ts_tags' => implode( ',', $newTags ) ] ) ),
- __METHOD__
- );
+ // Specify the non-DEFAULT value columns in the INSERT/REPLACE clause
+ $row = array_filter( [ 'ts_tags' => implode( ',', $newTags ) ] + $tsConds );
+ // Check the unique keys for conflicts, ignoring any NULL *_id values
+ $uniqueKeys = [];
+ foreach ( [ 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ] as $uniqueColumn ) {
+ if ( isset( $row[$uniqueColumn] ) ) {
+ $uniqueKeys[] = [ $uniqueColumn ];
+ }
+ }
+
+ $dbw->replace( 'tag_summary', $uniqueKeys, $row, __METHOD__ );
}
return true;
* Is it OK to allow the user to apply all the specified tags at the same time
* as they edit/make the change?
*
+ * Extensions should not use this function, unless directly handling a user
+ * request to add a tag to a revision or log entry that the user is making.
+ *
* @param array $tags Tags that you are interested in applying
- * @param User|null $user User whose permission you wish to check, or null if
- * you don't care (e.g. maintenance scripts)
+ * @param User|null $user User whose permission you wish to check, or null to
+ * check for a generic non-blocked user with the relevant rights
* @return Status
* @since 1.25
*/
* Is it OK to allow the user to adds and remove the given tags tags to/from a
* change?
*
+ * Extensions should not use this function, unless directly handling a user
+ * request to add or remove tags from an existing revision or log entry.
+ *
* @param array $tagsToAdd Tags that you are interested in adding
* @param array $tagsToRemove Tags that you are interested in removing
- * @param User|null $user User whose permission you wish to check, or null if
- * you don't care (e.g. maintenance scripts)
+ * @param User|null $user User whose permission you wish to check, or null to
+ * check for a generic non-blocked user with the relevant rights
* @return Status
* @since 1.25
*/
* Adds and/or removes tags to/from a given change, checking whether it is
* allowed first, and adding a log entry afterwards.
*
- * Includes a call to ChangeTag::canUpdateTags(), so your code doesn't need
+ * Includes a call to ChangeTags::canUpdateTags(), so your code doesn't need
* to do that. However, it doesn't check whether the *_id parameters are a
* valid combination. That is up to you to enforce. See ApiTag::execute() for
* an example.
*
+ * Extensions should generally avoid this function. Call
+ * ChangeTags::updateTags() instead, unless directly handling a user request
+ * to add or remove tags from an existing revision or log entry.
+ *
* @param array|null $tagsToAdd If none, pass array() or null
* @param array|null $tagsToRemove If none, pass array() or null
* @param int|null $rc_id The rc_id of the change to add the tags to
* @throws MWException When unable to determine appropriate JOIN condition for tagging
*/
public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
- &$join_conds, &$options, $filter_tag = '' ) {
+ &$join_conds, &$options, $filter_tag = ''
+ ) {
global $wgUseTagFilter;
// Normalize to arrays
}
/**
- * Defines a tag in the valid_tag table, without checking that the tag name
- * is valid.
+ * Defines a tag in the valid_tag table and/or update ctd_user_defined field in change_tag_def,
+ * without checking that the tag name is valid.
* Extensions should NOT use this function; they can use the ListDefinedTags
* hook instead.
*
* @since 1.25
*/
public static function defineTag( $tag ) {
+ global $wgChangeTagsSchemaMigrationStage;
+
$dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'valid_tag',
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
+ $tagDef = [
+ 'ctd_name' => $tag,
+ 'ctd_user_defined' => 1,
+ 'ctd_count' => 0
+ ];
+ $dbw->upsert(
+ 'change_tag_def',
+ $tagDef,
+ [ 'ctd_name' ],
+ [ 'ctd_user_defined' => 1 ],
+ __METHOD__
+ );
+ }
+
+ $dbw->replace(
+ 'valid_tag',
[ 'vt_tag' ],
[ 'vt_tag' => $tag ],
- __METHOD__ );
+ __METHOD__
+ );
// clear the memcache of defined tags
self::purgeTagCacheAll();
}
/**
- * Removes a tag from the valid_tag table. The tag may remain in use by
- * extensions, and may still show up as 'defined' if an extension is setting
- * it from the ListDefinedTags hook.
+ * Removes a tag from the valid_tag table and/or update ctd_user_defined field in change_tag_def.
+ * The tag may remain in use by extensions, and may still show up as 'defined'
+ * if an extension is setting it from the ListDefinedTags hook.
*
* @param string $tag Tag to remove
* @since 1.25
*/
public static function undefineTag( $tag ) {
+ global $wgChangeTagsSchemaMigrationStage;
+
$dbw = wfGetDB( DB_MASTER );
+
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
+ $dbw->update(
+ 'change_tag_def',
+ [ 'ctd_name' => $tag ],
+ [ 'ctd_user_defined' => 0 ],
+ __METHOD__
+ );
+
+ $dbw->delete(
+ 'change_tag_def',
+ [ 'ctd_name' => $tag, 'ctd_count' => 0 ],
+ __METHOD__
+ );
+ }
+
$dbw->delete( 'valid_tag', [ 'vt_tag' => $tag ], __METHOD__ );
// clear the memcache of defined tags
/**
* Is it OK to allow the user to create this tag?
*
+ * Extensions should NOT use this function. In most cases, a tag can be
+ * defined using the ListDefinedTags hook without any checking.
+ *
* @param string $tag Tag that you are interested in creating
* @param User|null $user User whose permission you wish to check, or null if
* you don't care (e.g. maintenance scripts)
/**
* Creates a tag by adding a row to the `valid_tag` table.
+ * and/or add it to `change_tag_def` table.
+ *
+ * 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
* do that.
* @since 1.25
*/
public static function deleteTagEverywhere( $tag ) {
+ global $wgChangeTagsSchemaMigrationStage;
$dbw = wfGetDB( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
- // delete from valid_tag
+ // delete from valid_tag and/or set ctd_user_defined = 0
self::undefineTag( $tag );
// find out which revisions use this tag, so we can delete from tag_summary
// delete from change_tag
$dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
+ $dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
+ }
+
$dbw->endAtomic( __METHOD__ );
// give extensions a chance
// store the tag usage statistics
$tagUsage = self::tagUsageStatistics();
- $hitcount = isset( $tagUsage[$tag] ) ? $tagUsage[$tag] : 0;
+ $hitcount = $tagUsage[$tag] ?? 0;
// do it!
$deleteResult = self::deleteTagEverywhere( $tag );
);
}
- /**
- * @see listSoftwareActivatedTags
- * @deprecated since 1.28 call listSoftwareActivatedTags directly
- * @return array
- */
- public static function listExtensionActivatedTags() {
- wfDeprecated( __METHOD__, '1.28' );
- return self::listSoftwareActivatedTags();
- }
-
/**
* Basically lists defined tags which count even if they aren't applied to anything.
- * It returns a union of the results of listExplicitlyDefinedTags() and
- * listExtensionDefinedTags().
+ * It returns a union of the results of listExplicitlyDefinedTags()
*
* @return string[] Array of strings: tags
*/
);
}
- /**
- * Call listSoftwareDefinedTags directly
- *
- * @see listSoftwareDefinedTags
- * @deprecated since 1.28
- * @return array
- */
- public static function listExtensionDefinedTags() {
- wfDeprecated( __METHOD__, '1.28' );
- return self::listSoftwareDefinedTags();
- }
-
/**
* Invalidates the short-term cache of defined tags used by the
* list*DefinedTags functions, as well as the tag statistics cache.