113 private const MAX_DELETE_USES = 5000;
118 private const CHANGE_TAG =
'change_tag';
131 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
148 if ( $tags ===
'' || $tags ===
null ) {
157 $tags = explode(
',', $tags );
158 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
159 usort( $tags,
static function ( $a, $b ) use ( $order ) {
160 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
164 foreach ( $tags as $tag ) {
168 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
170 if ( $description ===
false ) {
175 [
'class' =>
'mw-tag-marker ' .
176 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
181 if ( !$displayTags ) {
182 return [
'', $classes ];
185 $markers = $localizer->msg(
'tag-list-wrapper' )
186 ->numParams( count( $displayTags ) )
187 ->rawParams( implode(
' ', $displayTags ) )
189 $markers =
Xml::tags(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
191 return [ $markers, $classes ];
208 $msg = $context->
msg(
"tag-$tag" );
209 if ( !$msg->exists() ) {
215 if ( $msg->isDisabled() ) {
239 return $msg ? $msg->parse() :
false;
255 $msg = $context->
msg(
"tag-$tag-description" );
256 if ( !$msg->exists() ) {
259 if ( $msg->isDisabled() ) {
282 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
285 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
286 $tags, $rc_id, $rev_id, $log_id, $params, $rc
320 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
321 &$rev_id =
null, &$log_id =
null, $params =
null,
RecentChange $rc =
null,
324 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
325 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id, $params, $rc, $user
344 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
359 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
374 $tags = array_values( $tags );
375 $count = count( $tags );
376 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
377 $lang->commaList( $tags ), $count );
378 $status->value = $tags;
402 $services = MediaWikiServices::getInstance();
403 if ( $performer !==
null ) {
404 if ( !$performer->isAllowed(
'applychangetags' ) ) {
405 return Status::newFatal(
'tags-apply-no-permission' );
408 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
409 return Status::newFatal(
410 'tags-apply-blocked',
411 $performer->getUser()->getName()
416 $user = $services->getUserFactory()->newFromAuthority( $performer );
420 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
421 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
422 $disallowedTags = array_diff( $tags, $allowedTags );
423 if ( $disallowedTags ) {
425 'tags-apply-not-allowed-multi', $disallowedTags );
428 return Status::newGood();
450 if ( $performer !==
null ) {
451 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
452 return Status::newFatal(
'tags-update-no-permission' );
455 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
456 return Status::newFatal(
457 'tags-update-blocked',
458 $performer->getUser()->getName()
463 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
467 $explicitlyDefinedTags = $changeTagStore->listExplicitlyDefinedTags();
468 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
471 'tags-update-add-not-allowed-multi', $diff );
475 if ( $tagsToRemove ) {
479 $softwareDefinedTags = $changeTagStore->listSoftwareDefinedTags();
480 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
483 'tags-update-remove-not-allowed-multi', $intersect );
487 return Status::newGood();
521 $rc_id, $rev_id, $log_id, $params,
string $reason,
Authority $performer
523 if ( !$tagsToAdd && !$tagsToRemove ) {
525 return Status::newGood( (
object)[
533 $tagsToRemove ??= [];
537 if ( !$result->isOK() ) {
538 $result->value =
null;
543 $status = PermissionStatus::newEmpty();
545 return Status::wrap( $status );
549 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
550 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagStore->updateTags( $tagsToAdd,
551 $tagsToRemove, $rc_id, $rev_id, $log_id, $params,
null, $performer->
getUser() );
552 if ( !$tagsAdded && !$tagsRemoved ) {
554 return Status::newGood( (
object)[
563 $logEntry->setPerformer( $performer->
getUser() );
564 $logEntry->setComment( $reason );
568 $revisionRecord = MediaWikiServices::getInstance()
569 ->getRevisionLookup()
570 ->getRevisionById( $rev_id );
571 if ( $revisionRecord ) {
572 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
574 } elseif ( $log_id ) {
581 if ( !$logEntry->getTarget() ) {
583 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
587 '4::revid' => $rev_id,
588 '5::logid' => $log_id,
589 '6:list:tagsAdded' => $tagsAdded,
590 '7:number:tagsAddedCount' => count( $tagsAdded ),
591 '8:list:tagsRemoved' => $tagsRemoved,
592 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
593 'initialTags' => $initialTags,
595 $logEntry->setParameters( $logParams );
596 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
598 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getPrimaryDatabase();
599 $logId = $logEntry->insert( $dbw );
601 $logEntry->publish( $logId,
'udp' );
603 return Status::newGood( (
object)[
605 'addedTags' => $tagsAdded,
606 'removedTags' => $tagsRemoved,
632 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
634 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
653 return self::CHANGE_TAG;
665 return MediaWikiServices::getInstance()->getChangeTagsStore()->makeTagSummarySubquery( $tables );
686 $config = $context->getConfig();
687 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
688 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
689 !count( $changeTagStore->listDefinedTags() ) ) {
695 foreach ( $tags as $tagInfo ) {
696 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
702 [
'for' =>
'tagfilter' ],
703 $context->msg(
'tag-filter' )->parse()
710 $data[] =
new OOUI\ComboBoxInputWidget( [
712 'name' =>
'tagfilter',
713 'value' => $selected,
714 'classes' =>
'mw-tagfilter-input',
715 'options' => $options,
718 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
719 $datalist->setTagName(
'datalist' );
720 $datalist->addOptions( $autocomplete );
727 'class' =>
'mw-tagfilter-input mw-ui-input mw-ui-input-inline',
729 'list' =>
'tagfilter-datalist',
731 ) . $datalist->getHTML();
747 MediaWikiServices::getInstance()->getChangeTagsStore()->defineTag( $tag );
760 if ( $performer !==
null ) {
761 if ( !$performer->isAllowed(
'managechangetags' ) ) {
762 return Status::newFatal(
'tags-manage-no-permission' );
764 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
765 return Status::newFatal(
766 'tags-manage-blocked',
767 $performer->getUser()->getName()
775 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
776 $definedTags = $changeTagStore->listDefinedTags();
777 if ( in_array( $tag, $definedTags ) ) {
778 return Status::newFatal(
'tags-activate-not-allowed', $tag );
782 if ( !isset( $changeTagStore->tagUsageStatistics()[$tag] ) ) {
783 return Status::newFatal(
'tags-activate-not-found', $tag );
786 return Status::newGood();
807 bool $ignoreWarnings =
false, array $logEntryTags = []
811 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
812 $result->value =
null;
815 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
817 $changeTagStore->defineTag( $tag );
819 $logId = $changeTagStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
820 null, $logEntryTags );
822 return Status::newGood( $logId );
835 if ( $performer !==
null ) {
836 if ( !$performer->isAllowed(
'managechangetags' ) ) {
837 return Status::newFatal(
'tags-manage-no-permission' );
839 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
840 return Status::newFatal(
841 'tags-manage-blocked',
842 $performer->getUser()->getName()
848 $explicitlyDefinedTags = MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
849 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
850 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
852 return Status::newGood();
873 bool $ignoreWarnings =
false, array $logEntryTags = []
877 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
878 $result->value =
null;
881 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
883 $changeTagStore->undefineTag( $tag );
885 $logId = $changeTagStore->logTagManagementAction(
'deactivate', $tag, $reason,
886 $performer->
getUser(),
null, $logEntryTags );
888 return Status::newGood( $logId );
901 return Status::newFatal(
'tags-create-no-name' );
908 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
909 || strpos( $tag,
'/' ) !==
false ) {
910 return Status::newFatal(
'tags-create-invalid-chars' );
914 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
915 if ( $title ===
null ) {
916 return Status::newFatal(
'tags-create-invalid-title-chars' );
919 return Status::newGood();
936 $services = MediaWikiServices::getInstance();
937 if ( $performer !==
null ) {
938 if ( !$performer->isAllowed(
'managechangetags' ) ) {
939 return Status::newFatal(
'tags-manage-no-permission' );
941 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
942 return Status::newFatal(
943 'tags-manage-blocked',
944 $performer->getUser()->getName()
948 $user = $services->getUserFactory()->newFromAuthority( $performer );
952 if ( !$status->isGood() ) {
957 $changeTagStore = $services->getChangeTagsStore();
959 isset( $changeTagStore->tagUsageStatistics()[$tag] ) ||
960 in_array( $tag, $changeTagStore->listDefinedTags() )
962 return Status::newFatal(
'tags-create-already-exists', $tag );
966 $canCreateResult = Status::newGood();
967 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
968 return $canCreateResult;
991 bool $ignoreWarnings =
false, array $logEntryTags = []
995 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
996 $result->value =
null;
1000 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1001 $changeTagStore->defineTag( $tag );
1002 $logId = $changeTagStore->logTagManagementAction(
'create', $tag, $reason,
1003 $performer->
getUser(),
null, $logEntryTags );
1005 return Status::newGood( $logId );
1022 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1039 $services = MediaWikiServices::getInstance();
1040 if ( $performer !==
null ) {
1041 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1042 return Status::newFatal(
'tags-delete-no-permission' );
1044 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1045 return Status::newFatal(
1046 'tags-manage-blocked',
1047 $performer->getUser()->getName()
1051 $user = $services->getUserFactory()->newFromAuthority( $performer );
1054 $changeTagStore = $services->getChangeTagsStore();
1055 $tagUsage = $changeTagStore->tagUsageStatistics();
1057 !isset( $tagUsage[$tag] ) &&
1058 !in_array( $tag, $changeTagStore->listDefinedTags() )
1060 return Status::newFatal(
'tags-delete-not-found', $tag );
1063 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1064 isset( $tagUsage[$tag] ) &&
1065 $tagUsage[$tag] > self::MAX_DELETE_USES
1067 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1070 $softwareDefined = $changeTagStore->listSoftwareDefinedTags();
1071 if ( in_array( $tag, $softwareDefined ) ) {
1074 $status = Status::newFatal(
'tags-delete-not-allowed' );
1077 $status = Status::newGood();
1080 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1102 bool $ignoreWarnings =
false, array $logEntryTags = []
1104 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1107 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1108 $result->value =
null;
1113 $hitcount = $changeTagStore->tagUsageStatistics()[$tag] ?? 0;
1116 $deleteResult = $changeTagStore->deleteTagEverywhere( $tag );
1117 if ( !$deleteResult->isOK() ) {
1118 return $deleteResult;
1122 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1123 $logId = $changeTagStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1124 $hitcount, $logEntryTags );
1126 $deleteResult->value = $logId;
1127 return $deleteResult;
1138 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1150 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1163 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1177 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1187 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1199 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1206 private const TAG_DESC_CHARACTER_LIMIT = 120;
1233 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1234 return $cache->getWithSetCallback(
1235 $cache->makeKey(
'tags-list-summary', $lang->
getCode() ),
1236 WANObjectCache::TTL_DAY,
1237 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1238 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1239 $tagHitCounts = $changeTagStore->tagUsageStatistics();
1243 foreach ( $changeTagStore->listDefinedTags() as $tagName ) {
1245 $hits = $tagHitCounts[$tagName] ?? 0;
1255 'labelMsg' => (bool)$labelMsg,
1256 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1257 'descriptionMsg' => (bool)$descriptionMsg,
1258 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1259 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1281 foreach ( $tags as &$tagInfo ) {
1282 if ( $tagInfo[
'labelMsg'] ) {
1284 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1285 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1287 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1289 if ( $tagInfo[
'descriptionMsg'] ) {
1290 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1292 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1293 self::TAG_DESC_CHARACTER_LIMIT
1296 unset( $tagInfo[
'labelMsg'] );
1297 unset( $tagInfo[
'descriptionMsg'] );
1301 usort( $tags,
static function ( $a, $b ) {
1302 return strcasecmp( $a[
'label'], $b[
'label'] );
1322 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1323 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagStore->listExplicitlyDefinedTags();
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.
A class containing constants representing the names of configuration variables.
Parent class for all special pages.
static plaintextParam( $plaintext)
Utility class for creating new RC entries.
static getMain()
Get the RequestContext object associated with the main request.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
Class for generating HTML <select> or <datalist> elements.
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
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.