115 private const MAX_DELETE_USES = 5000;
120 private const CHANGE_TAG =
'change_tag';
133 return MediaWikiServices::getInstance()->getChangeTagsStore()->getSoftwareTags( $all );
150 if ( $tags ===
'' || $tags ===
null ) {
154 $localizer = RequestContext::getMain();
159 $tags = explode(
',', $tags );
160 $order = array_flip( MediaWikiServices::getInstance()->getChangeTagsStore()->
listDefinedTags() );
161 usort( $tags,
static function ( $a, $b ) use ( $order ) {
162 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
166 foreach ( $tags as $tag ) {
170 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
172 if ( $description ===
false ) {
175 $displayTags[] = Html::rawElement(
177 [
'class' =>
'mw-tag-marker ' .
178 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
183 if ( !$displayTags ) {
184 return [
'', $classes ];
187 $markers = $localizer->msg(
'tag-list-wrapper' )
188 ->numParams( count( $displayTags ) )
189 ->rawParams( implode(
' ', $displayTags ) )
191 $markers = Html::rawElement(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
193 return [ $markers, $classes ];
210 $msg = $context->
msg(
"tag-$tag" );
211 if ( !$msg->exists() ) {
215 return $context->
msg(
new RawMessage(
'$1', [ Message::plaintextParam( $tag ) ] ) );
217 if ( $msg->isDisabled() ) {
241 return $msg ? $msg->parse() :
false;
257 $msg = $context->
msg(
"tag-$tag-description" );
258 if ( !$msg->exists() ) {
261 if ( $msg->isDisabled() ) {
284 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
287 return MediaWikiServices::getInstance()->getChangeTagsStore()->addTags(
288 $tags, $rc_id, $rev_id, $log_id,
$params, $rc
322 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
326 return MediaWikiServices::getInstance()->getChangeTagsStore()->updateTags(
327 $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id,
$params, $rc, $user
346 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTagsWithData( $db, $rc_id, $rev_id, $log_id );
361 return MediaWikiServices::getInstance()->getChangeTagsStore()->getTags( $db, $rc_id, $rev_id, $log_id );
375 $lang = RequestContext::getMain()->getLanguage();
376 $tags = array_values( $tags );
377 $count = count( $tags );
378 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
379 $lang->commaList( $tags ), $count );
380 $status->value = $tags;
404 $services = MediaWikiServices::getInstance();
405 if ( $performer !==
null ) {
406 if ( !$performer->isAllowed(
'applychangetags' ) ) {
407 return Status::newFatal(
'tags-apply-no-permission' );
410 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
411 return Status::newFatal(
412 'tags-apply-blocked',
413 $performer->getUser()->getName()
418 $user = $services->getUserFactory()->newFromAuthority( $performer );
422 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
423 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
424 $disallowedTags = array_diff( $tags, $allowedTags );
425 if ( $disallowedTags ) {
427 'tags-apply-not-allowed-multi', $disallowedTags );
430 return Status::newGood();
452 if ( $performer !==
null ) {
453 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
454 return Status::newFatal(
'tags-update-no-permission' );
457 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
458 return Status::newFatal(
459 'tags-update-blocked',
460 $performer->getUser()->getName()
465 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
469 $explicitlyDefinedTags = $changeTagStore->listExplicitlyDefinedTags();
470 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
473 'tags-update-add-not-allowed-multi', $diff );
477 if ( $tagsToRemove ) {
481 $softwareDefinedTags = $changeTagStore->listSoftwareDefinedTags();
482 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
485 'tags-update-remove-not-allowed-multi', $intersect );
489 return Status::newGood();
525 if ( !$tagsToAdd && !$tagsToRemove ) {
527 return Status::newGood( (
object)[
535 $tagsToRemove ??= [];
539 if ( !$result->isOK() ) {
540 $result->value =
null;
545 $status = PermissionStatus::newEmpty();
547 return Status::wrap( $status );
551 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
552 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagStore->updateTags( $tagsToAdd,
553 $tagsToRemove, $rc_id, $rev_id, $log_id,
$params,
null, $performer->
getUser() );
554 if ( !$tagsAdded && !$tagsRemoved ) {
556 return Status::newGood( (
object)[
565 $logEntry->setPerformer( $performer->
getUser() );
566 $logEntry->setComment( $reason );
570 $revisionRecord = MediaWikiServices::getInstance()
571 ->getRevisionLookup()
572 ->getRevisionById( $rev_id );
573 if ( $revisionRecord ) {
574 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
576 } elseif ( $log_id ) {
583 if ( !$logEntry->getTarget() ) {
585 $logEntry->setTarget( SpecialPage::getTitleFor(
'Tags' ) );
589 '4::revid' => $rev_id,
590 '5::logid' => $log_id,
591 '6:list:tagsAdded' => $tagsAdded,
592 '7:number:tagsAddedCount' => count( $tagsAdded ),
593 '8:list:tagsRemoved' => $tagsRemoved,
594 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
595 'initialTags' => $initialTags,
597 $logEntry->setParameters( $logParams );
598 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
600 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
601 $logId = $logEntry->insert( $dbw );
603 $logEntry->publish( $logId,
'udp' );
605 return Status::newGood( (
object)[
607 'addedTags' => $tagsAdded,
608 'removedTags' => $tagsRemoved,
634 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
636 MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQuery(
657 return self::CHANGE_TAG;
669 return MediaWikiServices::getInstance()->getChangeTagsStore()->makeTagSummarySubquery( $tables );
687 $context = RequestContext::getMain();
690 $config = $context->getConfig();
691 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
692 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
693 !count( $changeTagStore->listDefinedTags() ) ) {
699 foreach ( $tags as $tagInfo ) {
700 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
706 [
'for' =>
'tagfilter' ],
707 $context->msg(
'tag-filter' )->parse()
712 $options = Html::listDropdownOptionsOoui( $autocomplete );
714 $data[] =
new OOUI\ComboBoxInputWidget( [
716 'name' =>
'tagfilter',
717 'value' => $selected,
718 'classes' =>
'mw-tagfilter-input',
719 'options' => $options,
722 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
723 $datalist->setTagName(
'datalist' );
724 $datalist->addOptions( $autocomplete );
726 $data[] = Html::input(
731 'class' => [
'mw-tagfilter-input',
'mw-ui-input',
'mw-ui-input-inline' ],
734 'list' =>
'tagfilter-datalist',
736 ) . $datalist->getHTML();
752 MediaWikiServices::getInstance()->getChangeTagsStore()->defineTag( $tag );
765 if ( $performer !==
null ) {
766 if ( !$performer->isAllowed(
'managechangetags' ) ) {
767 return Status::newFatal(
'tags-manage-no-permission' );
769 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
770 return Status::newFatal(
771 'tags-manage-blocked',
772 $performer->getUser()->getName()
780 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
781 $definedTags = $changeTagStore->listDefinedTags();
782 if ( in_array( $tag, $definedTags ) ) {
783 return Status::newFatal(
'tags-activate-not-allowed', $tag );
787 if ( !isset( $changeTagStore->tagUsageStatistics()[$tag] ) ) {
788 return Status::newFatal(
'tags-activate-not-found', $tag );
791 return Status::newGood();
812 bool $ignoreWarnings =
false, array $logEntryTags = []
816 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
817 $result->value =
null;
820 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
822 $changeTagStore->defineTag( $tag );
824 $logId = $changeTagStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
825 null, $logEntryTags );
827 return Status::newGood( $logId );
840 if ( $performer !==
null ) {
841 if ( !$performer->isAllowed(
'managechangetags' ) ) {
842 return Status::newFatal(
'tags-manage-no-permission' );
844 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
845 return Status::newFatal(
846 'tags-manage-blocked',
847 $performer->getUser()->getName()
853 $explicitlyDefinedTags = MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
854 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
855 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
857 return Status::newGood();
878 bool $ignoreWarnings =
false, array $logEntryTags = []
882 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
883 $result->value =
null;
886 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
888 $changeTagStore->undefineTag( $tag );
890 $logId = $changeTagStore->logTagManagementAction(
'deactivate', $tag, $reason,
891 $performer->
getUser(),
null, $logEntryTags );
893 return Status::newGood( $logId );
906 return Status::newFatal(
'tags-create-no-name' );
913 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
914 || strpos( $tag,
'/' ) !==
false ) {
915 return Status::newFatal(
'tags-create-invalid-chars' );
919 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
920 if ( $title ===
null ) {
921 return Status::newFatal(
'tags-create-invalid-title-chars' );
924 return Status::newGood();
941 $services = MediaWikiServices::getInstance();
942 if ( $performer !==
null ) {
943 if ( !$performer->isAllowed(
'managechangetags' ) ) {
944 return Status::newFatal(
'tags-manage-no-permission' );
946 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
947 return Status::newFatal(
948 'tags-manage-blocked',
949 $performer->getUser()->getName()
953 $user = $services->getUserFactory()->newFromAuthority( $performer );
957 if ( !$status->isGood() ) {
962 $changeTagStore = $services->getChangeTagsStore();
964 isset( $changeTagStore->tagUsageStatistics()[$tag] ) ||
965 in_array( $tag, $changeTagStore->listDefinedTags() )
967 return Status::newFatal(
'tags-create-already-exists', $tag );
971 $canCreateResult = Status::newGood();
972 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
973 return $canCreateResult;
996 bool $ignoreWarnings =
false, array $logEntryTags = []
1000 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1001 $result->value =
null;
1005 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1006 $changeTagStore->defineTag( $tag );
1007 $logId = $changeTagStore->logTagManagementAction(
'create', $tag, $reason,
1008 $performer->
getUser(),
null, $logEntryTags );
1010 return Status::newGood( $logId );
1027 return MediaWikiServices::getInstance()->getChangeTagsStore()->deleteTagEverywhere( $tag );
1044 $services = MediaWikiServices::getInstance();
1045 if ( $performer !==
null ) {
1046 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1047 return Status::newFatal(
'tags-delete-no-permission' );
1049 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1050 return Status::newFatal(
1051 'tags-manage-blocked',
1052 $performer->getUser()->getName()
1056 $user = $services->getUserFactory()->newFromAuthority( $performer );
1059 $changeTagStore = $services->getChangeTagsStore();
1060 $tagUsage = $changeTagStore->tagUsageStatistics();
1062 !isset( $tagUsage[$tag] ) &&
1063 !in_array( $tag, $changeTagStore->listDefinedTags() )
1065 return Status::newFatal(
'tags-delete-not-found', $tag );
1068 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1069 isset( $tagUsage[$tag] ) &&
1070 $tagUsage[$tag] > self::MAX_DELETE_USES
1072 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1075 $softwareDefined = $changeTagStore->listSoftwareDefinedTags();
1076 if ( in_array( $tag, $softwareDefined ) ) {
1079 $status = Status::newFatal(
'tags-delete-not-allowed' );
1082 $status = Status::newGood();
1085 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
1107 bool $ignoreWarnings =
false, array $logEntryTags = []
1109 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1112 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1113 $result->value =
null;
1118 $hitcount = $changeTagStore->tagUsageStatistics()[$tag] ?? 0;
1121 $deleteResult = $changeTagStore->deleteTagEverywhere( $tag );
1122 if ( !$deleteResult->isOK() ) {
1123 return $deleteResult;
1127 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1128 $logId = $changeTagStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
1129 $hitcount, $logEntryTags );
1131 $deleteResult->value = $logId;
1132 return $deleteResult;
1143 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareActivatedTags();
1155 return MediaWikiServices::getInstance()->getChangeTagsStore()->listDefinedTags();
1168 return MediaWikiServices::getInstance()->getChangeTagsStore()->listExplicitlyDefinedTags();
1182 return MediaWikiServices::getInstance()->getChangeTagsStore()->listSoftwareDefinedTags();
1192 MediaWikiServices::getInstance()->getChangeTagsStore()->purgeTagCacheAll();
1204 return MediaWikiServices::getInstance()->getChangeTagsStore()->tagUsageStatistics();
1211 private const TAG_DESC_CHARACTER_LIMIT = 120;
1238 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1239 return $cache->getWithSetCallback(
1240 $cache->makeKey(
'tags-list-summary', $lang->
getCode() ),
1241 WANObjectCache::TTL_DAY,
1242 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1243 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1244 $tagHitCounts = $changeTagStore->tagUsageStatistics();
1248 foreach ( $changeTagStore->listDefinedTags() as $tagName ) {
1250 $hits = $tagHitCounts[$tagName] ?? 0;
1260 'labelMsg' => (bool)$labelMsg,
1261 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1262 'descriptionMsg' => (bool)$descriptionMsg,
1263 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1264 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1286 foreach ( $tags as &$tagInfo ) {
1287 if ( $tagInfo[
'labelMsg'] ) {
1289 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1290 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1292 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1294 if ( $tagInfo[
'descriptionMsg'] ) {
1295 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1297 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1298 self::TAG_DESC_CHARACTER_LIMIT
1301 unset( $tagInfo[
'labelMsg'] );
1302 unset( $tagInfo[
'descriptionMsg'] );
1306 usort( $tags,
static function ( $a, $b ) {
1307 return strcasecmp( $a[
'label'], $b[
'label'] );
1327 $changeTagStore = MediaWikiServices::getInstance()->getChangeTagsStore();
1328 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.
Class for generating HTML <select> or <datalist> elements.
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.