137 private const MAX_DELETE_USES = 5000;
142 private const CHANGE_TAG =
'change_tag';
179 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
196 if ( $tags ===
'' || $tags ===
null ) {
200 $localizer = RequestContext::getMain();
205 $tags = explode(
',', $tags );
206 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
207 usort( $tags,
static function ( $a, $b ) use ( $order ) {
208 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
212 foreach ( $tags as $tag ) {
216 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
218 if ( $description ===
false ) {
221 $displayTags[] = Html::rawElement(
223 [
'class' =>
'mw-tag-marker ' .
224 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
229 if ( !$displayTags ) {
230 return [
'', $classes ];
233 $markers = $localizer->msg(
'tag-list-wrapper' )
234 ->numParams( count( $displayTags ) )
235 ->rawParams( implode(
' ', $displayTags ) )
237 $markers = Html::rawElement(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
239 return [ $markers, $classes ];
256 $msg = $context->
msg(
"tag-$tag" );
257 if ( !$msg->exists() ) {
261 return $context->
msg(
new RawMessage(
'$1', [ Message::plaintextParam( $tag ) ] ) );
263 if ( $msg->isDisabled() ) {
285 $msg = $context->
msg(
"tag-$tag-helppage" )->inContentLanguage();
286 if ( !$msg->isDisabled() ) {
306 if ( $msg && $link ) {
307 $label = $msg->parse();
309 if ( !str_contains( $label,
'<a ' ) ) {
310 return Html::rawElement(
'a', [
'href' => $link ], $label );
313 return $msg ? $msg->parse() :
false;
329 $msg = $context->
msg(
"tag-$tag-description" );
330 return $msg->isDisabled() ? false : $msg;
347 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
351 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
352 $tags, $rc_id, $rev_id, $log_id,
$params, $rc
386 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
391 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
392 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id,
$params, $rc, $user
412 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
428 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
442 $lang = RequestContext::getMain()->getLanguage();
443 $tags = array_values( $tags );
444 $count = count( $tags );
445 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
446 $lang->commaList( $tags ), $count );
447 $status->value = $tags;
471 $services = MediaWikiServices::getInstance();
472 if ( $performer !==
null ) {
473 if ( !$performer->isAllowed(
'applychangetags' ) ) {
474 return Status::newFatal(
'tags-apply-no-permission' );
477 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
478 return Status::newFatal(
479 'tags-apply-blocked',
480 $performer->getUser()->getName()
485 $user = $services->getUserFactory()->newFromAuthority( $performer );
489 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
490 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
491 $disallowedTags = array_diff( $tags, $allowedTags );
492 if ( $disallowedTags ) {
494 'tags-apply-not-allowed-multi', $disallowedTags );
497 return Status::newGood();
519 if ( $performer !==
null ) {
520 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
521 return Status::newFatal(
'tags-update-no-permission' );
524 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
525 return Status::newFatal(
526 'tags-update-blocked',
527 $performer->getUser()->getName()
532 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
536 $explicitlyDefinedTags = $changeTagsStore->listExplicitlyDefinedTags();
537 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
540 'tags-update-add-not-allowed-multi', $diff );
544 if ( $tagsToRemove ) {
548 $softwareDefinedTags = $changeTagsStore->listSoftwareDefinedTags();
549 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
552 'tags-update-remove-not-allowed-multi', $intersect );
556 return Status::newGood();
592 if ( !$tagsToAdd && !$tagsToRemove ) {
594 return Status::newGood( (
object)[
602 $tagsToRemove ??= [];
606 if ( !$result->isOK() ) {
607 $result->value =
null;
612 $status = PermissionStatus::newEmpty();
614 return Status::wrap( $status );
618 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
619 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagsStore->updateTags( $tagsToAdd,
620 $tagsToRemove, $rc_id, $rev_id, $log_id,
$params,
null, $performer->
getUser() );
621 if ( !$tagsAdded && !$tagsRemoved ) {
623 return Status::newGood( (
object)[
632 $logEntry->setPerformer( $performer->
getUser() );
633 $logEntry->setComment( $reason );
637 $revisionRecord = MediaWikiServices::getInstance()
638 ->getRevisionLookup()
639 ->getRevisionById( $rev_id );
640 if ( $revisionRecord ) {
641 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
643 } elseif ( $log_id ) {
650 if ( !$logEntry->getTarget() ) {
652 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
656 '4::revid' => $rev_id,
657 '5::logid' => $log_id,
658 '6:list:tagsAdded' => $tagsAdded,
659 '7:number:tagsAddedCount' => count( $tagsAdded ),
660 '8:list:tagsRemoved' => $tagsRemoved,
661 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
662 'initialTags' => $initialTags,
664 $logEntry->setParameters( $logParams );
665 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
667 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
668 $logId = $logEntry->insert( $dbw );
670 $logEntry->publish( $logId,
'udp' );
672 return Status::newGood( (
object)[
674 'addedTags' => $tagsAdded,
675 'removedTags' => $tagsRemoved,
701 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
704 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
726 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 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
768 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
769 !count( $changeTagsStore->listDefinedTags() ) ) {
775 $context->getLanguage(),
781 foreach ( $tags as $tagInfo ) {
782 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
788 [
'for' =>
'tagfilter' ],
789 $context->msg(
'tag-filter' )->parse()
794 $options = Html::listDropdownOptionsOoui( $autocomplete );
796 $data[] =
new OOUI\ComboBoxInputWidget( [
798 'name' =>
'tagfilter',
799 'value' => $selected,
800 'classes' =>
'mw-tagfilter-input',
801 'options' => $options,
804 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
805 $datalist->setTagName(
'datalist' );
806 $datalist->addOptions( $autocomplete );
808 $data[] = Html::input(
813 'class' => [
'mw-tagfilter-input',
'mw-ui-input',
'mw-ui-input-inline' ],
816 'list' =>
'tagfilter-datalist',
818 ) . $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 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
864 $definedTags = $changeTagsStore->listDefinedTags();
865 if ( in_array( $tag, $definedTags ) ) {
866 return Status::newFatal(
'tags-activate-not-allowed', $tag );
870 if ( !isset( $changeTagsStore->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 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
905 $changeTagsStore->defineTag( $tag );
907 $logId = $changeTagsStore->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 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
971 $changeTagsStore->undefineTag( $tag );
973 $logId = $changeTagsStore->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 $changeTagsStore = $services->getChangeTagsStore();
1047 isset( $changeTagsStore->tagUsageStatistics()[$tag] ) ||
1048 in_array( $tag, $changeTagsStore->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 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1089 $changeTagsStore->defineTag( $tag );
1090 $logId = $changeTagsStore->logTagManagementAction(
'create', $tag, $reason,
1091 $performer->
getUser(),
null, $logEntryTags );
1093 return Status::newGood( $logId );
1111 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1128 $services = MediaWikiServices::getInstance();
1129 if ( $performer !==
null ) {
1130 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1131 return Status::newFatal(
'tags-delete-no-permission' );
1133 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1134 return Status::newFatal(
1135 'tags-manage-blocked',
1136 $performer->getUser()->getName()
1140 $user = $services->getUserFactory()->newFromAuthority( $performer );
1143 $changeTagsStore = $services->getChangeTagsStore();
1144 $tagUsage = $changeTagsStore->tagUsageStatistics();
1146 !isset( $tagUsage[$tag] ) &&
1147 !in_array( $tag, $changeTagsStore->listDefinedTags() )
1149 return Status::newFatal(
'tags-delete-not-found', $tag );
1152 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1153 isset( $tagUsage[$tag] ) &&
1154 $tagUsage[$tag] > self::MAX_DELETE_USES
1156 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1159 $softwareDefined = $changeTagsStore->listSoftwareDefinedTags();
1160 if ( in_array( $tag, $softwareDefined ) ) {
1163 $status = Status::newFatal(
'tags-delete-not-allowed' );
1166 $status = Status::newGood();
1169 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1191 bool $ignoreWarnings =
false, array $logEntryTags = []
1193 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1196 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1197 $result->value =
null;
1202 $hitcount = $changeTagsStore->tagUsageStatistics()[$tag] ?? 0;
1205 $deleteResult = $changeTagsStore->deleteTagEverywhere( $tag );
1206 if ( !$deleteResult->isOK() ) {
1207 return $deleteResult;
1211 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1212 $logId = $changeTagsStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1213 $hitcount, $logEntryTags );
1215 $deleteResult->value = $logId;
1216 return $deleteResult;
1228 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1241 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1255 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1270 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1281 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1294 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1301 private const TAG_DESC_CHARACTER_LIMIT = 120;
1334 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
1335 bool $useAllTags = self::USE_ALL_TAGS
1337 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1339 if ( $useAllTags ) {
1340 $tagKeys = $changeTagsStore->listDefinedTags();
1341 $cacheKey =
'tags-list-summary';
1343 $tagKeys = $changeTagsStore->getCoreDefinedTags();
1344 $cacheKey =
'core-software-tags-summary';
1348 $tagHitCounts =
null;
1349 if ( $activeOnly ) {
1350 $tagHitCounts = $changeTagsStore->tagUsageStatistics();
1353 $cacheKey .=
'-all';
1356 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1357 return $cache->getWithSetCallback(
1358 $cache->makeKey( $cacheKey, $lang->
getCode() ),
1359 WANObjectCache::TTL_DAY,
1360 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer, $tagKeys, $tagHitCounts ) {
1362 foreach ( $tagKeys as $tagName ) {
1364 if ( $tagHitCounts !==
null ) {
1366 $hits = $tagHitCounts[$tagName] ?? 0;
1378 'labelMsg' => (bool)$labelMsg,
1379 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1380 'descriptionMsg' => (bool)$descriptionMsg,
1381 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1382 'helpLink' => $helpLink,
1383 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1407 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
bool $useAllTags = self::USE_ALL_TAGS
1411 foreach ( $tags as &$tagInfo ) {
1412 if ( $tagInfo[
'labelMsg'] ) {
1414 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1415 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1417 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1419 if ( $tagInfo[
'descriptionMsg'] ) {
1420 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1422 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1423 self::TAG_DESC_CHARACTER_LIMIT
1426 unset( $tagInfo[
'labelMsg'] );
1427 unset( $tagInfo[
'descriptionMsg'] );
1431 usort( $tags,
static function ( $a, $b ) {
1432 return strcasecmp( $a[
'label'], $b[
'label'] );
1452 $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1453 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagsStore->listExplicitlyDefinedTags();
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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.