133 private const MAX_DELETE_USES = 5000;
138 private const CHANGE_TAG =
'change_tag';
151 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
168 if ( $tags ===
'' || $tags ===
null ) {
172 $localizer = RequestContext::getMain();
177 $tags = explode(
',', $tags );
178 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
179 usort( $tags,
static function ( $a, $b ) use ( $order ) {
180 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
184 foreach ( $tags as $tag ) {
188 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
190 if ( $description ===
false ) {
193 $displayTags[] = Html::rawElement(
195 [
'class' =>
'mw-tag-marker ' .
196 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
201 if ( !$displayTags ) {
202 return [
'', $classes ];
205 $markers = $localizer->msg(
'tag-list-wrapper' )
206 ->numParams( count( $displayTags ) )
207 ->rawParams( implode(
' ', $displayTags ) )
209 $markers = Html::rawElement(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
211 return [ $markers, $classes ];
228 $msg = $context->
msg(
"tag-$tag" );
229 if ( !$msg->exists() ) {
233 return $context->
msg(
new RawMessage(
'$1', [ Message::plaintextParam( $tag ) ] ) );
235 if ( $msg->isDisabled() ) {
257 $msg = $context->
msg(
"tag-$tag-helppage" )->inContentLanguage();
258 if ( $msg->exists() && !$msg->isDisabled() ) {
281 if ( $msg && $link ) {
282 $label = $msg->parse();
284 if ( !str_contains( $label,
'<a ' ) ) {
285 return Html::rawElement(
'a', [
'href' => $link ], $label );
288 return $msg ? $msg->parse() :
false;
304 $msg = $context->
msg(
"tag-$tag-description" );
305 if ( !$msg->exists() ) {
308 if ( $msg->isDisabled() ) {
331 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
334 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
335 $tags, $rc_id, $rev_id, $log_id,
$params, $rc
369 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
373 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
374 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id,
$params, $rc, $user
393 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
408 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
422 $lang = RequestContext::getMain()->getLanguage();
423 $tags = array_values( $tags );
424 $count = count( $tags );
425 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
426 $lang->commaList( $tags ), $count );
427 $status->value = $tags;
451 $services = MediaWikiServices::getInstance();
452 if ( $performer !==
null ) {
453 if ( !$performer->isAllowed(
'applychangetags' ) ) {
454 return Status::newFatal(
'tags-apply-no-permission' );
457 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
458 return Status::newFatal(
459 'tags-apply-blocked',
460 $performer->getUser()->getName()
465 $user = $services->getUserFactory()->newFromAuthority( $performer );
469 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
470 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
471 $disallowedTags = array_diff( $tags, $allowedTags );
472 if ( $disallowedTags ) {
474 'tags-apply-not-allowed-multi', $disallowedTags );
477 return Status::newGood();
499 if ( $performer !==
null ) {
500 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
501 return Status::newFatal(
'tags-update-no-permission' );
504 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
505 return Status::newFatal(
506 'tags-update-blocked',
507 $performer->getUser()->getName()
512 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
516 $explicitlyDefinedTags = $changeTagStore->listExplicitlyDefinedTags();
517 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
520 'tags-update-add-not-allowed-multi', $diff );
524 if ( $tagsToRemove ) {
528 $softwareDefinedTags = $changeTagStore->listSoftwareDefinedTags();
529 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
532 'tags-update-remove-not-allowed-multi', $intersect );
536 return Status::newGood();
572 if ( !$tagsToAdd && !$tagsToRemove ) {
574 return Status::newGood( (
object)[
582 $tagsToRemove ??= [];
586 if ( !$result->isOK() ) {
587 $result->value =
null;
592 $status = PermissionStatus::newEmpty();
594 return Status::wrap( $status );
598 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
599 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagStore->updateTags( $tagsToAdd,
600 $tagsToRemove, $rc_id, $rev_id, $log_id,
$params,
null, $performer->
getUser() );
601 if ( !$tagsAdded && !$tagsRemoved ) {
603 return Status::newGood( (
object)[
612 $logEntry->setPerformer( $performer->
getUser() );
613 $logEntry->setComment( $reason );
617 $revisionRecord = MediaWikiServices::getInstance()
618 ->getRevisionLookup()
619 ->getRevisionById( $rev_id );
620 if ( $revisionRecord ) {
621 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
623 } elseif ( $log_id ) {
630 if ( !$logEntry->getTarget() ) {
632 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
636 '4::revid' => $rev_id,
637 '5::logid' => $log_id,
638 '6:list:tagsAdded' => $tagsAdded,
639 '7:number:tagsAddedCount' => count( $tagsAdded ),
640 '8:list:tagsRemoved' => $tagsRemoved,
641 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
642 'initialTags' => $initialTags,
644 $logEntry->setParameters( $logParams );
645 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
647 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
648 $logId = $logEntry->insert( $dbw );
650 $logEntry->publish( $logId,
'udp' );
652 return Status::newGood( (
object)[
654 'addedTags' => $tagsAdded,
655 'removedTags' => $tagsRemoved,
681 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
683 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
704 return self::CHANGE_TAG;
716 return MediaWikiServices::getInstance()->getChangeTagsStore()->makeTagSummarySubquery( $tables );
734 $context = RequestContext::getMain();
737 $config = $context->getConfig();
738 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
739 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
740 !count( $changeTagStore->listDefinedTags() ) ) {
746 foreach ( $tags as $tagInfo ) {
747 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
753 [
'for' =>
'tagfilter' ],
754 $context->msg(
'tag-filter' )->parse()
759 $options = Html::listDropdownOptionsOoui( $autocomplete );
761 $data[] =
new OOUI\ComboBoxInputWidget( [
763 'name' =>
'tagfilter',
764 'value' => $selected,
765 'classes' =>
'mw-tagfilter-input',
766 'options' => $options,
769 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
770 $datalist->setTagName(
'datalist' );
771 $datalist->addOptions( $autocomplete );
773 $data[] = Html::input(
778 'class' => [
'mw-tagfilter-input',
'mw-ui-input',
'mw-ui-input-inline' ],
781 'list' =>
'tagfilter-datalist',
783 ) . $datalist->getHTML();
799 MediaWikiServices::getInstance()->getChangeTagsStore()->defineTag( $tag );
812 if ( $performer !==
null ) {
813 if ( !$performer->isAllowed(
'managechangetags' ) ) {
814 return Status::newFatal(
'tags-manage-no-permission' );
816 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
817 return Status::newFatal(
818 'tags-manage-blocked',
819 $performer->getUser()->getName()
827 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
828 $definedTags = $changeTagStore->listDefinedTags();
829 if ( in_array( $tag, $definedTags ) ) {
830 return Status::newFatal(
'tags-activate-not-allowed', $tag );
834 if ( !isset( $changeTagStore->tagUsageStatistics()[$tag] ) ) {
835 return Status::newFatal(
'tags-activate-not-found', $tag );
838 return Status::newGood();
859 bool $ignoreWarnings =
false, array $logEntryTags = []
863 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
864 $result->value =
null;
867 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
869 $changeTagStore->defineTag( $tag );
871 $logId = $changeTagStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
872 null, $logEntryTags );
874 return Status::newGood( $logId );
887 if ( $performer !==
null ) {
888 if ( !$performer->isAllowed(
'managechangetags' ) ) {
889 return Status::newFatal(
'tags-manage-no-permission' );
891 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
892 return Status::newFatal(
893 'tags-manage-blocked',
894 $performer->getUser()->getName()
900 $explicitlyDefinedTags = MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
901 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
902 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
904 return Status::newGood();
925 bool $ignoreWarnings =
false, array $logEntryTags = []
929 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
930 $result->value =
null;
933 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
935 $changeTagStore->undefineTag( $tag );
937 $logId = $changeTagStore->logTagManagementAction(
'deactivate', $tag, $reason,
938 $performer->
getUser(),
null, $logEntryTags );
940 return Status::newGood( $logId );
953 return Status::newFatal(
'tags-create-no-name' );
960 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
961 || strpos( $tag,
'/' ) !==
false ) {
962 return Status::newFatal(
'tags-create-invalid-chars' );
966 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
967 if ( $title ===
null ) {
968 return Status::newFatal(
'tags-create-invalid-title-chars' );
971 return Status::newGood();
988 $services = MediaWikiServices::getInstance();
989 if ( $performer !==
null ) {
990 if ( !$performer->isAllowed(
'managechangetags' ) ) {
991 return Status::newFatal(
'tags-manage-no-permission' );
993 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
994 return Status::newFatal(
995 'tags-manage-blocked',
996 $performer->getUser()->getName()
1000 $user = $services->getUserFactory()->newFromAuthority( $performer );
1004 if ( !$status->isGood() ) {
1009 $changeTagStore = $services->getChangeTagsStore();
1011 isset( $changeTagStore->tagUsageStatistics()[$tag] ) ||
1012 in_array( $tag, $changeTagStore->listDefinedTags() )
1014 return Status::newFatal(
'tags-create-already-exists', $tag );
1018 $canCreateResult = Status::newGood();
1019 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
1020 return $canCreateResult;
1043 bool $ignoreWarnings =
false, array $logEntryTags = []
1047 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1048 $result->value =
null;
1052 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1053 $changeTagStore->defineTag( $tag );
1054 $logId = $changeTagStore->logTagManagementAction(
'create', $tag, $reason,
1055 $performer->
getUser(),
null, $logEntryTags );
1057 return Status::newGood( $logId );
1074 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1091 $services = MediaWikiServices::getInstance();
1092 if ( $performer !==
null ) {
1093 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1094 return Status::newFatal(
'tags-delete-no-permission' );
1096 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1097 return Status::newFatal(
1098 'tags-manage-blocked',
1099 $performer->getUser()->getName()
1103 $user = $services->getUserFactory()->newFromAuthority( $performer );
1106 $changeTagStore = $services->getChangeTagsStore();
1107 $tagUsage = $changeTagStore->tagUsageStatistics();
1109 !isset( $tagUsage[$tag] ) &&
1110 !in_array( $tag, $changeTagStore->listDefinedTags() )
1112 return Status::newFatal(
'tags-delete-not-found', $tag );
1115 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1116 isset( $tagUsage[$tag] ) &&
1117 $tagUsage[$tag] > self::MAX_DELETE_USES
1119 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1122 $softwareDefined = $changeTagStore->listSoftwareDefinedTags();
1123 if ( in_array( $tag, $softwareDefined ) ) {
1126 $status = Status::newFatal(
'tags-delete-not-allowed' );
1129 $status = Status::newGood();
1132 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1154 bool $ignoreWarnings =
false, array $logEntryTags = []
1156 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1159 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1160 $result->value =
null;
1165 $hitcount = $changeTagStore->tagUsageStatistics()[$tag] ?? 0;
1168 $deleteResult = $changeTagStore->deleteTagEverywhere( $tag );
1169 if ( !$deleteResult->isOK() ) {
1170 return $deleteResult;
1174 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1175 $logId = $changeTagStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1176 $hitcount, $logEntryTags );
1178 $deleteResult->value = $logId;
1179 return $deleteResult;
1190 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1202 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1215 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1229 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1239 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1251 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1258 private const TAG_DESC_CHARACTER_LIMIT = 120;
1286 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1287 return $cache->getWithSetCallback(
1288 $cache->makeKey(
'tags-list-summary', $lang->
getCode() ),
1289 WANObjectCache::TTL_DAY,
1290 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1291 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1292 $tagHitCounts = $changeTagStore->tagUsageStatistics();
1296 foreach ( $changeTagStore->listDefinedTags() as $tagName ) {
1298 $hits = $tagHitCounts[$tagName] ?? 0;
1309 'labelMsg' => (bool)$labelMsg,
1310 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1311 'descriptionMsg' => (bool)$descriptionMsg,
1312 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1313 'helpLink' => $helpLink,
1314 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1336 foreach ( $tags as &$tagInfo ) {
1337 if ( $tagInfo[
'labelMsg'] ) {
1339 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1340 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1342 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1344 if ( $tagInfo[
'descriptionMsg'] ) {
1345 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1347 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1348 self::TAG_DESC_CHARACTER_LIMIT
1351 unset( $tagInfo[
'labelMsg'] );
1352 unset( $tagInfo[
'descriptionMsg'] );
1356 usort( $tags,
static function ( $a, $b ) {
1357 return strcasecmp( $a[
'label'], $b[
'label'] );
1377 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1378 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagStore->listExplicitlyDefinedTags();
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.