133 private const MAX_DELETE_USES = 5000;
138 private const CHANGE_TAG =
'change_tag';
174 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
191 if ( $tags ===
'' || $tags ===
null ) {
195 $localizer = RequestContext::getMain();
200 $tags = explode(
',', $tags );
201 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
202 usort( $tags,
static function ( $a, $b ) use ( $order ) {
203 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
207 foreach ( $tags as $tag ) {
211 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
213 if ( $description ===
false ) {
216 $displayTags[] = Html::rawElement(
218 [
'class' =>
'mw-tag-marker ' .
219 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
224 if ( !$displayTags ) {
225 return [
'', $classes ];
228 $markers = $localizer->msg(
'tag-list-wrapper' )
229 ->numParams( count( $displayTags ) )
230 ->rawParams( implode(
' ', $displayTags ) )
232 $markers = Html::rawElement(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
234 return [ $markers, $classes ];
251 $msg = $context->
msg(
"tag-$tag" );
252 if ( !$msg->exists() ) {
256 return $context->
msg(
new RawMessage(
'$1', [ Message::plaintextParam( $tag ) ] ) );
258 if ( $msg->isDisabled() ) {
280 $msg = $context->
msg(
"tag-$tag-helppage" )->inContentLanguage();
281 if ( $msg->exists() && !$msg->isDisabled() ) {
304 if ( $msg && $link ) {
305 $label = $msg->parse();
307 if ( !str_contains( $label,
'<a ' ) ) {
308 return Html::rawElement(
'a', [
'href' => $link ], $label );
311 return $msg ? $msg->parse() :
false;
327 $msg = $context->
msg(
"tag-$tag-description" );
328 if ( !$msg->exists() ) {
331 if ( $msg->isDisabled() ) {
354 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
357 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
358 $tags, $rc_id, $rev_id, $log_id,
$params, $rc
392 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
396 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
397 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id,
$params, $rc, $user
416 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
431 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
445 $lang = RequestContext::getMain()->getLanguage();
446 $tags = array_values( $tags );
447 $count = count( $tags );
448 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
449 $lang->commaList( $tags ), $count );
450 $status->value = $tags;
474 $services = MediaWikiServices::getInstance();
475 if ( $performer !==
null ) {
476 if ( !$performer->isAllowed(
'applychangetags' ) ) {
477 return Status::newFatal(
'tags-apply-no-permission' );
480 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
481 return Status::newFatal(
482 'tags-apply-blocked',
483 $performer->getUser()->getName()
488 $user = $services->getUserFactory()->newFromAuthority( $performer );
492 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
493 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
494 $disallowedTags = array_diff( $tags, $allowedTags );
495 if ( $disallowedTags ) {
497 'tags-apply-not-allowed-multi', $disallowedTags );
500 return Status::newGood();
522 if ( $performer !==
null ) {
523 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
524 return Status::newFatal(
'tags-update-no-permission' );
527 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
528 return Status::newFatal(
529 'tags-update-blocked',
530 $performer->getUser()->getName()
535 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
539 $explicitlyDefinedTags = $changeTagStore->listExplicitlyDefinedTags();
540 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
543 'tags-update-add-not-allowed-multi', $diff );
547 if ( $tagsToRemove ) {
551 $softwareDefinedTags = $changeTagStore->listSoftwareDefinedTags();
552 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
555 'tags-update-remove-not-allowed-multi', $intersect );
559 return Status::newGood();
595 if ( !$tagsToAdd && !$tagsToRemove ) {
597 return Status::newGood( (
object)[
605 $tagsToRemove ??= [];
609 if ( !$result->isOK() ) {
610 $result->value =
null;
615 $status = PermissionStatus::newEmpty();
617 return Status::wrap( $status );
621 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
622 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagStore->updateTags( $tagsToAdd,
623 $tagsToRemove, $rc_id, $rev_id, $log_id,
$params,
null, $performer->
getUser() );
624 if ( !$tagsAdded && !$tagsRemoved ) {
626 return Status::newGood( (
object)[
635 $logEntry->setPerformer( $performer->
getUser() );
636 $logEntry->setComment( $reason );
640 $revisionRecord = MediaWikiServices::getInstance()
641 ->getRevisionLookup()
642 ->getRevisionById( $rev_id );
643 if ( $revisionRecord ) {
644 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
646 } elseif ( $log_id ) {
653 if ( !$logEntry->getTarget() ) {
655 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
659 '4::revid' => $rev_id,
660 '5::logid' => $log_id,
661 '6:list:tagsAdded' => $tagsAdded,
662 '7:number:tagsAddedCount' => count( $tagsAdded ),
663 '8:list:tagsRemoved' => $tagsRemoved,
664 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
665 'initialTags' => $initialTags,
667 $logEntry->setParameters( $logParams );
668 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
670 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
671 $logId = $logEntry->insert( $dbw );
673 $logEntry->publish( $logId,
'udp' );
675 return Status::newGood( (
object)[
677 'addedTags' => $tagsAdded,
678 'removedTags' => $tagsRemoved,
704 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
706 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
727 return self::CHANGE_TAG;
739 return MediaWikiServices::getInstance()->getChangeTagsStore()->makeTagSummarySubquery( $tables );
759 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
760 bool $useAllTags = self::USE_ALL_TAGS
763 $context = RequestContext::getMain();
766 $config = $context->getConfig();
767 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
768 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
769 !count( $changeTagStore->listDefinedTags() ) ) {
775 $context->getLanguage(),
782 foreach ( $tags as $tagInfo ) {
783 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
789 [
'for' =>
'tagfilter' ],
790 $context->msg(
'tag-filter' )->parse()
795 $options = Html::listDropdownOptionsOoui( $autocomplete );
797 $data[] =
new OOUI\ComboBoxInputWidget( [
799 'name' =>
'tagfilter',
800 'value' => $selected,
801 'classes' =>
'mw-tagfilter-input',
802 'options' => $options,
805 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
806 $datalist->setTagName(
'datalist' );
807 $datalist->addOptions( $autocomplete );
809 $data[] = Html::input(
814 'class' => [
'mw-tagfilter-input',
'mw-ui-input',
'mw-ui-input-inline' ],
817 'list' =>
'tagfilter-datalist',
819 ) . $datalist->getHTML();
835 MediaWikiServices::getInstance()->getChangeTagsStore()->defineTag( $tag );
848 if ( $performer !==
null ) {
849 if ( !$performer->isAllowed(
'managechangetags' ) ) {
850 return Status::newFatal(
'tags-manage-no-permission' );
852 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
853 return Status::newFatal(
854 'tags-manage-blocked',
855 $performer->getUser()->getName()
863 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
864 $definedTags = $changeTagStore->listDefinedTags();
865 if ( in_array( $tag, $definedTags ) ) {
866 return Status::newFatal(
'tags-activate-not-allowed', $tag );
870 if ( !isset( $changeTagStore->tagUsageStatistics()[$tag] ) ) {
871 return Status::newFatal(
'tags-activate-not-found', $tag );
874 return Status::newGood();
895 bool $ignoreWarnings =
false, array $logEntryTags = []
899 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
900 $result->value =
null;
903 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
905 $changeTagStore->defineTag( $tag );
907 $logId = $changeTagStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
908 null, $logEntryTags );
910 return Status::newGood( $logId );
923 if ( $performer !==
null ) {
924 if ( !$performer->isAllowed(
'managechangetags' ) ) {
925 return Status::newFatal(
'tags-manage-no-permission' );
927 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
928 return Status::newFatal(
929 'tags-manage-blocked',
930 $performer->getUser()->getName()
936 $explicitlyDefinedTags = MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
937 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
938 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
940 return Status::newGood();
961 bool $ignoreWarnings =
false, array $logEntryTags = []
965 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
966 $result->value =
null;
969 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
971 $changeTagStore->undefineTag( $tag );
973 $logId = $changeTagStore->logTagManagementAction(
'deactivate', $tag, $reason,
974 $performer->
getUser(),
null, $logEntryTags );
976 return Status::newGood( $logId );
989 return Status::newFatal(
'tags-create-no-name' );
996 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
997 || strpos( $tag,
'/' ) !==
false ) {
998 return Status::newFatal(
'tags-create-invalid-chars' );
1002 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
1003 if ( $title ===
null ) {
1004 return Status::newFatal(
'tags-create-invalid-title-chars' );
1007 return Status::newGood();
1024 $services = MediaWikiServices::getInstance();
1025 if ( $performer !==
null ) {
1026 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1027 return Status::newFatal(
'tags-manage-no-permission' );
1029 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1030 return Status::newFatal(
1031 'tags-manage-blocked',
1032 $performer->getUser()->getName()
1036 $user = $services->getUserFactory()->newFromAuthority( $performer );
1040 if ( !$status->isGood() ) {
1045 $changeTagStore = $services->getChangeTagsStore();
1047 isset( $changeTagStore->tagUsageStatistics()[$tag] ) ||
1048 in_array( $tag, $changeTagStore->listDefinedTags() )
1050 return Status::newFatal(
'tags-create-already-exists', $tag );
1054 $canCreateResult = Status::newGood();
1055 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
1056 return $canCreateResult;
1079 bool $ignoreWarnings =
false, array $logEntryTags = []
1083 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1084 $result->value =
null;
1088 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1089 $changeTagStore->defineTag( $tag );
1090 $logId = $changeTagStore->logTagManagementAction(
'create', $tag, $reason,
1091 $performer->
getUser(),
null, $logEntryTags );
1093 return Status::newGood( $logId );
1110 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1127 $services = MediaWikiServices::getInstance();
1128 if ( $performer !==
null ) {
1129 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1130 return Status::newFatal(
'tags-delete-no-permission' );
1132 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1133 return Status::newFatal(
1134 'tags-manage-blocked',
1135 $performer->getUser()->getName()
1139 $user = $services->getUserFactory()->newFromAuthority( $performer );
1142 $changeTagStore = $services->getChangeTagsStore();
1143 $tagUsage = $changeTagStore->tagUsageStatistics();
1145 !isset( $tagUsage[$tag] ) &&
1146 !in_array( $tag, $changeTagStore->listDefinedTags() )
1148 return Status::newFatal(
'tags-delete-not-found', $tag );
1151 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1152 isset( $tagUsage[$tag] ) &&
1153 $tagUsage[$tag] > self::MAX_DELETE_USES
1155 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1158 $softwareDefined = $changeTagStore->listSoftwareDefinedTags();
1159 if ( in_array( $tag, $softwareDefined ) ) {
1162 $status = Status::newFatal(
'tags-delete-not-allowed' );
1165 $status = Status::newGood();
1168 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1190 bool $ignoreWarnings =
false, array $logEntryTags = []
1192 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1195 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1196 $result->value =
null;
1201 $hitcount = $changeTagStore->tagUsageStatistics()[$tag] ?? 0;
1204 $deleteResult = $changeTagStore->deleteTagEverywhere( $tag );
1205 if ( !$deleteResult->isOK() ) {
1206 return $deleteResult;
1210 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1211 $logId = $changeTagStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1212 $hitcount, $logEntryTags );
1214 $deleteResult->value = $logId;
1215 return $deleteResult;
1226 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1238 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1251 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1265 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1275 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1287 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1294 private const TAG_DESC_CHARACTER_LIMIT = 120;
1327 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
1328 bool $useAllTags = self::USE_ALL_TAGS
1330 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1332 if ( $useAllTags ) {
1333 $tagKeys = $changeTagStore->listDefinedTags();
1334 $cacheKey =
'tags-list-summary';
1336 $tagKeys = $changeTagStore->getSoftwareTags(
true );
1337 $cacheKey =
'core-software-tags-summary';
1341 $tagHitCounts =
null;
1342 if ( $activeOnly ) {
1343 $tagHitCounts = $changeTagStore->tagUsageStatistics();
1346 $cacheKey .=
'-all';
1349 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1350 return $cache->getWithSetCallback(
1351 $cache->makeKey( $cacheKey, $lang->
getCode() ),
1352 WANObjectCache::TTL_DAY,
1353 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer, $tagKeys, $tagHitCounts ) {
1355 foreach ( $tagKeys as $tagName ) {
1357 if ( $tagHitCounts !==
null ) {
1359 $hits = $tagHitCounts[$tagName] ?? 0;
1371 'labelMsg' => (bool)$labelMsg,
1372 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1373 'descriptionMsg' => (bool)$descriptionMsg,
1374 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1375 'helpLink' => $helpLink,
1376 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1401 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
bool $useAllTags = self::USE_ALL_TAGS,
1406 foreach ( $tags as &$tagInfo ) {
1407 if ( $tagInfo[
'labelMsg'] ) {
1411 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1412 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1415 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1418 if ( $labelsOnly ) {
1419 unset( $tagInfo[
'description'] );
1420 } elseif ( $tagInfo[
'descriptionMsg'] ) {
1422 if (
wfEscapeWikiText( $tagInfo[
'description'] ) !== $tagInfo[
'description'] ) {
1423 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1424 $tagInfo[
'description'] = Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() );
1427 self::TAG_DESC_CHARACTER_LIMIT );
1429 unset( $tagInfo[
'labelMsg'] );
1430 unset( $tagInfo[
'descriptionMsg'] );
1434 usort( $tags,
static function ( $a, $b ) {
1435 return strcasecmp( $a[
'label'], $b[
'label'] );
1455 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1456 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagStore->listExplicitlyDefinedTags();
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
array $params
The job parameters.
Class for creating new log entries and inserting them into the database.
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
Parent class for all special pages.
Utility class for creating and reading rows in the recentchanges table.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.