117 private const MAX_DELETE_USES = 5000;
122 private const CHANGE_TAG =
'change_tag';
135 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
152 if ( $tags ===
'' || $tags ===
null ) {
156 $localizer = RequestContext::getMain();
161 $tags = explode(
',', $tags );
162 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
163 usort( $tags,
static function ( $a, $b ) use ( $order ) {
164 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
168 foreach ( $tags as $tag ) {
172 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
174 if ( $description ===
false ) {
177 $displayTags[] = Html::rawElement(
179 [
'class' =>
'mw-tag-marker ' .
180 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
185 if ( !$displayTags ) {
186 return [
'', $classes ];
189 $markers = $localizer->msg(
'tag-list-wrapper' )
190 ->numParams( count( $displayTags ) )
191 ->rawParams( implode(
' ', $displayTags ) )
193 $markers = Html::rawElement(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
195 return [ $markers, $classes ];
212 $msg = $context->
msg(
"tag-$tag" );
213 if ( !$msg->exists() ) {
217 return $context->
msg(
new RawMessage(
'$1', [ Message::plaintextParam( $tag ) ] ) );
219 if ( $msg->isDisabled() ) {
243 return $msg ? $msg->parse() :
false;
259 $msg = $context->
msg(
"tag-$tag-description" );
260 if ( !$msg->exists() ) {
263 if ( $msg->isDisabled() ) {
286 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
289 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
290 $tags, $rc_id, $rev_id, $log_id,
$params, $rc
324 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
328 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
329 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id,
$params, $rc, $user
348 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
363 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
377 $lang = RequestContext::getMain()->getLanguage();
378 $tags = array_values( $tags );
379 $count = count( $tags );
380 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
381 $lang->commaList( $tags ), $count );
382 $status->value = $tags;
406 $services = MediaWikiServices::getInstance();
407 if ( $performer !==
null ) {
408 if ( !$performer->isAllowed(
'applychangetags' ) ) {
409 return Status::newFatal(
'tags-apply-no-permission' );
412 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
413 return Status::newFatal(
414 'tags-apply-blocked',
415 $performer->getUser()->getName()
420 $user = $services->getUserFactory()->newFromAuthority( $performer );
424 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
425 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
426 $disallowedTags = array_diff( $tags, $allowedTags );
427 if ( $disallowedTags ) {
429 'tags-apply-not-allowed-multi', $disallowedTags );
432 return Status::newGood();
454 if ( $performer !==
null ) {
455 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
456 return Status::newFatal(
'tags-update-no-permission' );
459 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
460 return Status::newFatal(
461 'tags-update-blocked',
462 $performer->getUser()->getName()
467 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
471 $explicitlyDefinedTags = $changeTagStore->listExplicitlyDefinedTags();
472 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
475 'tags-update-add-not-allowed-multi', $diff );
479 if ( $tagsToRemove ) {
483 $softwareDefinedTags = $changeTagStore->listSoftwareDefinedTags();
484 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
487 'tags-update-remove-not-allowed-multi', $intersect );
491 return Status::newGood();
527 if ( !$tagsToAdd && !$tagsToRemove ) {
529 return Status::newGood( (
object)[
537 $tagsToRemove ??= [];
541 if ( !$result->isOK() ) {
542 $result->value =
null;
547 $status = PermissionStatus::newEmpty();
549 return Status::wrap( $status );
553 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
554 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagStore->updateTags( $tagsToAdd,
555 $tagsToRemove, $rc_id, $rev_id, $log_id,
$params,
null, $performer->
getUser() );
556 if ( !$tagsAdded && !$tagsRemoved ) {
558 return Status::newGood( (
object)[
567 $logEntry->setPerformer( $performer->
getUser() );
568 $logEntry->setComment( $reason );
572 $revisionRecord = MediaWikiServices::getInstance()
573 ->getRevisionLookup()
574 ->getRevisionById( $rev_id );
575 if ( $revisionRecord ) {
576 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
578 } elseif ( $log_id ) {
585 if ( !$logEntry->getTarget() ) {
587 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
591 '4::revid' => $rev_id,
592 '5::logid' => $log_id,
593 '6:list:tagsAdded' => $tagsAdded,
594 '7:number:tagsAddedCount' => count( $tagsAdded ),
595 '8:list:tagsRemoved' => $tagsRemoved,
596 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
597 'initialTags' => $initialTags,
599 $logEntry->setParameters( $logParams );
600 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
602 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
603 $logId = $logEntry->insert( $dbw );
605 $logEntry->publish( $logId,
'udp' );
607 return Status::newGood( (
object)[
609 'addedTags' => $tagsAdded,
610 'removedTags' => $tagsRemoved,
636 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
638 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
659 return self::CHANGE_TAG;
671 return MediaWikiServices::getInstance()->getChangeTagsStore()->makeTagSummarySubquery( $tables );
689 $context = RequestContext::getMain();
692 $config = $context->getConfig();
693 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
694 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
695 !count( $changeTagStore->listDefinedTags() ) ) {
701 foreach ( $tags as $tagInfo ) {
702 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
708 [
'for' =>
'tagfilter' ],
709 $context->msg(
'tag-filter' )->parse()
714 $options = Html::listDropdownOptionsOoui( $autocomplete );
716 $data[] =
new OOUI\ComboBoxInputWidget( [
718 'name' =>
'tagfilter',
719 'value' => $selected,
720 'classes' =>
'mw-tagfilter-input',
721 'options' => $options,
724 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
725 $datalist->setTagName(
'datalist' );
726 $datalist->addOptions( $autocomplete );
728 $data[] = Html::input(
733 'class' => [
'mw-tagfilter-input',
'mw-ui-input',
'mw-ui-input-inline' ],
736 'list' =>
'tagfilter-datalist',
738 ) . $datalist->getHTML();
754 MediaWikiServices::getInstance()->getChangeTagsStore()->defineTag( $tag );
767 if ( $performer !==
null ) {
768 if ( !$performer->isAllowed(
'managechangetags' ) ) {
769 return Status::newFatal(
'tags-manage-no-permission' );
771 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
772 return Status::newFatal(
773 'tags-manage-blocked',
774 $performer->getUser()->getName()
782 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
783 $definedTags = $changeTagStore->listDefinedTags();
784 if ( in_array( $tag, $definedTags ) ) {
785 return Status::newFatal(
'tags-activate-not-allowed', $tag );
789 if ( !isset( $changeTagStore->tagUsageStatistics()[$tag] ) ) {
790 return Status::newFatal(
'tags-activate-not-found', $tag );
793 return Status::newGood();
814 bool $ignoreWarnings =
false, array $logEntryTags = []
818 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
819 $result->value =
null;
822 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
824 $changeTagStore->defineTag( $tag );
826 $logId = $changeTagStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
827 null, $logEntryTags );
829 return Status::newGood( $logId );
842 if ( $performer !==
null ) {
843 if ( !$performer->isAllowed(
'managechangetags' ) ) {
844 return Status::newFatal(
'tags-manage-no-permission' );
846 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
847 return Status::newFatal(
848 'tags-manage-blocked',
849 $performer->getUser()->getName()
855 $explicitlyDefinedTags = MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
856 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
857 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
859 return Status::newGood();
880 bool $ignoreWarnings =
false, array $logEntryTags = []
884 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
885 $result->value =
null;
888 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
890 $changeTagStore->undefineTag( $tag );
892 $logId = $changeTagStore->logTagManagementAction(
'deactivate', $tag, $reason,
893 $performer->
getUser(),
null, $logEntryTags );
895 return Status::newGood( $logId );
908 return Status::newFatal(
'tags-create-no-name' );
915 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
916 || strpos( $tag,
'/' ) !==
false ) {
917 return Status::newFatal(
'tags-create-invalid-chars' );
921 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
922 if ( $title ===
null ) {
923 return Status::newFatal(
'tags-create-invalid-title-chars' );
926 return Status::newGood();
943 $services = MediaWikiServices::getInstance();
944 if ( $performer !==
null ) {
945 if ( !$performer->isAllowed(
'managechangetags' ) ) {
946 return Status::newFatal(
'tags-manage-no-permission' );
948 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
949 return Status::newFatal(
950 'tags-manage-blocked',
951 $performer->getUser()->getName()
955 $user = $services->getUserFactory()->newFromAuthority( $performer );
959 if ( !$status->isGood() ) {
964 $changeTagStore = $services->getChangeTagsStore();
966 isset( $changeTagStore->tagUsageStatistics()[$tag] ) ||
967 in_array( $tag, $changeTagStore->listDefinedTags() )
969 return Status::newFatal(
'tags-create-already-exists', $tag );
973 $canCreateResult = Status::newGood();
974 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
975 return $canCreateResult;
998 bool $ignoreWarnings =
false, array $logEntryTags = []
1002 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1003 $result->value =
null;
1007 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1008 $changeTagStore->defineTag( $tag );
1009 $logId = $changeTagStore->logTagManagementAction(
'create', $tag, $reason,
1010 $performer->
getUser(),
null, $logEntryTags );
1012 return Status::newGood( $logId );
1029 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1046 $services = MediaWikiServices::getInstance();
1047 if ( $performer !==
null ) {
1048 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1049 return Status::newFatal(
'tags-delete-no-permission' );
1051 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1052 return Status::newFatal(
1053 'tags-manage-blocked',
1054 $performer->getUser()->getName()
1058 $user = $services->getUserFactory()->newFromAuthority( $performer );
1061 $changeTagStore = $services->getChangeTagsStore();
1062 $tagUsage = $changeTagStore->tagUsageStatistics();
1064 !isset( $tagUsage[$tag] ) &&
1065 !in_array( $tag, $changeTagStore->listDefinedTags() )
1067 return Status::newFatal(
'tags-delete-not-found', $tag );
1070 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1071 isset( $tagUsage[$tag] ) &&
1072 $tagUsage[$tag] > self::MAX_DELETE_USES
1074 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1077 $softwareDefined = $changeTagStore->listSoftwareDefinedTags();
1078 if ( in_array( $tag, $softwareDefined ) ) {
1081 $status = Status::newFatal(
'tags-delete-not-allowed' );
1084 $status = Status::newGood();
1087 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1109 bool $ignoreWarnings =
false, array $logEntryTags = []
1111 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1114 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1115 $result->value =
null;
1120 $hitcount = $changeTagStore->tagUsageStatistics()[$tag] ?? 0;
1123 $deleteResult = $changeTagStore->deleteTagEverywhere( $tag );
1124 if ( !$deleteResult->isOK() ) {
1125 return $deleteResult;
1129 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1130 $logId = $changeTagStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1131 $hitcount, $logEntryTags );
1133 $deleteResult->value = $logId;
1134 return $deleteResult;
1145 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1157 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1170 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1184 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1194 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1206 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1213 private const TAG_DESC_CHARACTER_LIMIT = 120;
1240 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1241 return $cache->getWithSetCallback(
1242 $cache->makeKey(
'tags-list-summary', $lang->
getCode() ),
1243 WANObjectCache::TTL_DAY,
1244 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1245 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1246 $tagHitCounts = $changeTagStore->tagUsageStatistics();
1250 foreach ( $changeTagStore->listDefinedTags() as $tagName ) {
1252 $hits = $tagHitCounts[$tagName] ?? 0;
1262 'labelMsg' => (bool)$labelMsg,
1263 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1264 'descriptionMsg' => (bool)$descriptionMsg,
1265 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1266 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1288 foreach ( $tags as &$tagInfo ) {
1289 if ( $tagInfo[
'labelMsg'] ) {
1291 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1292 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1294 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1296 if ( $tagInfo[
'descriptionMsg'] ) {
1297 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1299 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1300 self::TAG_DESC_CHARACTER_LIMIT
1303 unset( $tagInfo[
'labelMsg'] );
1304 unset( $tagInfo[
'descriptionMsg'] );
1308 usort( $tags,
static function ( $a, $b ) {
1309 return strcasecmp( $a[
'label'], $b[
'label'] );
1329 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1330 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagStore->listExplicitlyDefinedTags();
array $params
The job parameters.
Base class for language-specific code.
truncateForVisual( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e....
getCode()
Get the internal language code for this language object.
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 new RC entries.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
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.