110 private const MAX_DELETE_USES = 5000;
115 private const DEFINED_SOFTWARE_TAGS = [
116 'mw-contentmodelchange',
118 'mw-removed-redirect',
119 'mw-changed-redirect-target',
126 'mw-server-side-upload',
132 private const CHANGE_TAG =
'change_tag';
137 private const CHANGE_TAG_DEF =
'change_tag_def';
161 $coreTags = MediaWikiServices::getInstance()->getMainConfig()->get(
162 MainConfigNames::SoftwareTags );
165 if ( !is_array( $coreTags ) ) {
166 wfWarn(
'wgSoftwareTags should be associative array of enabled tags.
167 Please refer to documentation for the list of tags you can enable' );
168 return $softwareTags;
171 $availableSoftwareTags = !$all ?
172 array_keys( array_filter( $coreTags ) ) :
173 array_keys( $coreTags );
175 $softwareTags = array_intersect(
176 $availableSoftwareTags,
177 self::DEFINED_SOFTWARE_TAGS
180 return $softwareTags;
197 if ( $tags ===
'' || $tags ===
null ) {
206 $tags = explode(
',', $tags );
207 $order = array_flip( self::listDefinedTags() );
208 usort( $tags,
static function ( $a, $b ) use ( $order ) {
209 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
213 foreach ( $tags as $tag ) {
219 if ( $description ===
false ) {
224 [
'class' =>
'mw-tag-marker ' .
230 if ( !$displayTags ) {
231 return [
'', $classes ];
234 $markers = $localizer->msg(
'tag-list-wrapper' )
235 ->numParams( count( $displayTags ) )
236 ->rawParams( implode(
' ', $displayTags ) )
238 $markers =
Xml::tags(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
240 return [ $markers, $classes ];
257 $msg = $context->
msg(
"tag-$tag" );
258 if ( !$msg->exists() ) {
264 if ( $msg->isDisabled() ) {
288 return $msg ? $msg->parse() :
false;
304 $msg = $context->
msg(
"tag-$tag-description" );
305 if ( !$msg->exists() ) {
308 if ( $msg->isDisabled() ) {
330 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
333 $result =
self::updateTags( $tags,
null, $rc_id, $rev_id, $log_id, $params, $rc );
334 return (
bool)$result[0];
366 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
367 &$rev_id =
null, &$log_id =
null, $params =
null,
RecentChange $rc =
null,
370 $tagsToAdd = array_filter(
372 static function ( $value ) {
373 return ( $value ??
'' ) !==
'';
376 $tagsToRemove = array_filter(
377 (array)$tagsToRemove,
378 static function ( $value ) {
379 return ( $value ??
'' ) !==
'';
383 if ( !$rc_id && !$rev_id && !$log_id ) {
384 throw new BadMethodCallException(
'At least one of: RCID, revision ID, and log ID MUST be ' .
385 'specified when adding or removing a tag from a change!' );
396 $rc_id = $dbw->newSelectQueryBuilder()
399 ->join(
'recentchanges',
null, [
400 'rc_timestamp = log_timestamp',
403 ->where( [
'log_id' => $log_id ] )
404 ->caller( __METHOD__ )
406 } elseif ( $rev_id ) {
407 $rc_id = $dbw->newSelectQueryBuilder()
410 ->join(
'recentchanges',
null, [
411 'rc_this_oldid = rev_id'
413 ->where( [
'rev_id' => $rev_id ] )
414 ->caller( __METHOD__ )
417 } elseif ( !$log_id && !$rev_id ) {
419 $log_id = $dbw->newSelectQueryBuilder()
420 ->select(
'rc_logid' )
421 ->from(
'recentchanges' )
422 ->where( [
'rc_id' => $rc_id ] )
423 ->caller( __METHOD__ )
425 $rev_id = $dbw->newSelectQueryBuilder()
426 ->select(
'rc_this_oldid' )
427 ->from(
'recentchanges' )
428 ->where( [
'rc_id' => $rc_id ] )
429 ->caller( __METHOD__ )
433 if ( $log_id && !$rev_id ) {
434 $rev_id = $dbw->newSelectQueryBuilder()
435 ->select(
'ls_value' )
436 ->from(
'log_search' )
437 ->where( [
'ls_field' =>
'associated_rev_id',
'ls_log_id' => $log_id ] )
438 ->caller( __METHOD__ )
440 } elseif ( !$log_id && $rev_id ) {
441 $log_id = $dbw->newSelectQueryBuilder()
442 ->select(
'ls_log_id' )
443 ->from(
'log_search' )
444 ->where( [
'ls_field' =>
'associated_rev_id',
'ls_value' => (
string)$rev_id ] )
445 ->caller( __METHOD__ )
452 $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
453 $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
456 $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
457 $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
461 if ( $prevTags == $newTags ) {
462 return [ [], [], $prevTags ];
466 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
467 if ( count( $tagsToAdd ) ) {
468 $changeTagMapping = [];
469 foreach ( $tagsToAdd as $tag ) {
470 $changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
474 $dbw->onTransactionPreCommitOrIdle(
static function () use ( $dbw, $tagsToAdd, $fname ) {
476 self::CHANGE_TAG_DEF,
477 [
'ctd_count = ctd_count + 1' ],
478 [
'ctd_name' => $tagsToAdd ],
484 foreach ( $tagsToAdd as $tag ) {
489 $tagsRows[] = array_filter(
491 'ct_rc_id' => $rc_id,
492 'ct_log_id' => $log_id,
493 'ct_rev_id' => $rev_id,
494 'ct_params' => $params,
495 'ct_tag_id' => $changeTagMapping[$tag] ??
null,
501 $dbw->insert( self::CHANGE_TAG, $tagsRows, __METHOD__, [
'IGNORE' ] );
505 if ( count( $tagsToRemove ) ) {
507 foreach ( $tagsToRemove as $tag ) {
508 $conds = array_filter(
510 'ct_rc_id' => $rc_id,
511 'ct_log_id' => $log_id,
512 'ct_rev_id' => $rev_id,
513 'ct_tag_id' => $changeTagDefStore->getId( $tag ),
516 $dbw->delete( self::CHANGE_TAG, $conds, __METHOD__ );
517 if ( $dbw->affectedRows() ) {
519 $dbw->onTransactionPreCommitOrIdle(
static function () use ( $dbw, $tag, $fname ) {
521 self::CHANGE_TAG_DEF,
522 [
'ctd_count = ctd_count - 1' ],
523 [
'ctd_name' => $tag ],
528 self::CHANGE_TAG_DEF,
529 [
'ctd_name' => $tag,
'ctd_count' => 0,
'ctd_user_defined' => 0 ],
537 $userObj = $user ? MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $user ) :
null;
538 Hooks::runner()->onChangeTagsAfterUpdateTags( $tagsToAdd, $tagsToRemove, $prevTags,
539 $rc_id, $rev_id, $log_id, $params, $rc, $userObj );
541 return [ $tagsToAdd, $tagsToRemove, $prevTags ];
556 IDatabase $db, $rc_id =
null, $rev_id =
null, $log_id =
null
558 if ( !$rc_id && !$rev_id && !$log_id ) {
559 throw new BadMethodCallException(
'At least one of: RCID, revision ID, and log ID MUST be ' .
560 'specified when loading tags from a change!' );
563 $conds = array_filter(
565 'ct_rc_id' => $rc_id,
566 'ct_rev_id' => $rev_id,
567 'ct_log_id' => $log_id,
571 ->select( [
'ct_tag_id',
'ct_params' ] )
572 ->from( self::CHANGE_TAG )
574 ->caller( __METHOD__ )
578 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
579 foreach ( $result as $row ) {
580 $tagName = $changeTagDefStore->getName( (
int)$row->ct_tag_id );
581 $tags[$tagName] = $row->ct_params;
597 public static function getTags(
IDatabase $db, $rc_id =
null, $rev_id =
null, $log_id =
null ) {
598 return array_keys( self::getTagsWithData( $db, $rc_id, $rev_id, $log_id ) );
613 $tags = array_values( $tags );
614 $count = count( $tags );
616 $lang->commaList( $tags ), $count );
617 $status->value = $tags;
641 if ( $performer !==
null ) {
642 if ( !$performer->isAllowed(
'applychangetags' ) ) {
646 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
648 'tags-apply-blocked',
649 $performer->getUser()->getName()
654 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
659 Hooks::runner()->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
660 $disallowedTags = array_diff( $tags, $allowedTags );
661 if ( $disallowedTags ) {
663 'tags-apply-not-allowed-multi', $disallowedTags );
690 array $tags, $rc_id, $rev_id, $log_id, $params,
Authority $performer
694 if ( !$result->isOK() ) {
695 $result->value =
null;
724 if ( $performer !==
null ) {
725 if ( !$performer->isAllowed(
'changetags' ) ) {
729 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
731 'tags-update-blocked',
732 $performer->getUser()->getName()
741 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
744 'tags-update-add-not-allowed-multi', $diff );
748 if ( $tagsToRemove ) {
753 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
756 'tags-update-remove-not-allowed-multi', $intersect );
794 $rc_id, $rev_id, $log_id, $params,
string $reason,
Authority $performer
796 if ( !$tagsToAdd && !$tagsToRemove ) {
806 $tagsToRemove ??= [];
810 if ( !$result->isOK() ) {
811 $result->value =
null;
816 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
817 if ( $user->pingLimiter(
'changetag' ) ) {
823 $tagsToRemove, $rc_id, $rev_id, $log_id, $params,
null, $user );
824 if ( !$tagsAdded && !$tagsRemoved ) {
835 $logEntry->setPerformer( $performer->
getUser() );
836 $logEntry->setComment( $reason );
840 $revisionRecord = MediaWikiServices::getInstance()
841 ->getRevisionLookup()
842 ->getRevisionById( $rev_id );
843 if ( $revisionRecord ) {
844 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
846 } elseif ( $log_id ) {
853 if ( !$logEntry->getTarget() ) {
859 '4::revid' => $rev_id,
860 '5::logid' => $log_id,
861 '6:list:tagsAdded' => $tagsAdded,
862 '7:number:tagsAddedCount' => count( $tagsAdded ),
863 '8:list:tagsRemoved' => $tagsRemoved,
864 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
865 'initialTags' => $initialTags,
867 $logEntry->setParameters( $logParams );
868 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
871 $logId = $logEntry->insert( $dbw );
873 $logEntry->publish( $logId,
'udp' );
877 'addedTags' => $tagsAdded,
878 'removedTags' => $tagsRemoved,
903 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
905 $useTagFilter = MediaWikiServices::getInstance()->getMainConfig()->get(
906 MainConfigNames::UseTagFilter );
909 $tables = (array)$tables;
910 $fields = (array)$fields;
911 $conds = (array)$conds;
912 $options = (array)$options;
920 if ( in_array(
'recentchanges', $tables ) ) {
921 $join_cond = self::DISPLAY_TABLE_ALIAS .
'.ct_rc_id=rc_id';
922 } elseif ( in_array(
'logging', $tables ) ) {
923 $join_cond = self::DISPLAY_TABLE_ALIAS .
'.ct_log_id=log_id';
924 } elseif ( in_array(
'revision', $tables ) ) {
925 $join_cond = self::DISPLAY_TABLE_ALIAS .
'.ct_rev_id=rev_id';
926 } elseif ( in_array(
'archive', $tables ) ) {
927 $join_cond = self::DISPLAY_TABLE_ALIAS .
'.ct_rev_id=ar_rev_id';
929 throw new InvalidArgumentException(
'Unable to determine appropriate JOIN condition for tagging.' );
932 if ( !$useTagFilter ) {
936 if ( !is_array( $filter_tag ) ) {
938 $filter_tag = (string)$filter_tag;
941 if ( $filter_tag !== [] && $filter_tag !==
'' ) {
946 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
947 foreach ( (array)$filter_tag as $filterTagName ) {
949 $filterTagIds[] = $changeTagDefStore->getId( $filterTagName );
955 if ( $filterTagIds !== [] ) {
959 [ $join_cond, self::DISPLAY_TABLE_ALIAS .
'.ct_tag_id' => $filterTagIds ]
961 $conds[] = self::DISPLAY_TABLE_ALIAS .
".ct_tag_id IS NULL";
966 if ( $filterTagIds !== [] ) {
967 $conds[self::DISPLAY_TABLE_ALIAS .
'.ct_tag_id'] = $filterTagIds;
974 is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
975 !in_array(
'DISTINCT', $options )
977 $options[] =
'DISTINCT';
990 $tagTable = self::CHANGE_TAG;
991 if ( self::$avoidReopeningTablesForTesting && defined(
'MW_PHPUNIT_TEST' ) ) {
994 if ( $db->getType() ===
'mysql' ) {
1003 $tagTable =
'change_tag_for_display_query';
1004 if ( !$db->tableExists( $tagTable ) ) {
1006 'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $db->tableName( $tagTable )
1007 .
' LIKE ' . $db->tableName( self::CHANGE_TAG ),
1011 'INSERT IGNORE INTO ' . $db->tableName( $tagTable )
1012 .
' SELECT * FROM ' . $db->tableName( self::CHANGE_TAG ),
1030 $tables = (array)$tables;
1033 if ( in_array(
'recentchanges', $tables ) ) {
1034 $join_cond =
'ct_rc_id=rc_id';
1035 } elseif ( in_array(
'logging', $tables ) ) {
1036 $join_cond =
'ct_log_id=log_id';
1037 } elseif ( in_array(
'revision', $tables ) ) {
1038 $join_cond =
'ct_rev_id=rev_id';
1039 } elseif ( in_array(
'archive', $tables ) ) {
1040 $join_cond =
'ct_rev_id=ar_rev_id';
1042 throw new InvalidArgumentException(
'Unable to determine appropriate JOIN condition for tagging.' );
1045 $tagTables = [ self::CHANGE_TAG, self::CHANGE_TAG_DEF ];
1046 $join_cond_ts_tags = [ self::CHANGE_TAG_DEF => [
'JOIN',
'ct_tag_id=ctd_id' ] ];
1047 $field =
'ctd_name';
1050 ',', $tagTables, $field, $join_cond, $join_cond_ts_tags
1072 $config = $context->getConfig();
1073 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
1074 !count( self::listDefinedTags() ) ) {
1080 foreach ( $tags as $tagInfo ) {
1081 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
1087 [
'for' =>
'tagfilter' ],
1088 $context->msg(
'tag-filter' )->parse()
1095 $data[] =
new OOUI\ComboBoxInputWidget( [
1096 'id' =>
'tagfilter',
1097 'name' =>
'tagfilter',
1098 'value' => $selected,
1099 'classes' =>
'mw-tagfilter-input',
1100 'options' => $options,
1103 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
1104 $datalist->setTagName(
'datalist' );
1105 $datalist->addOptions( $autocomplete );
1112 'class' =>
'mw-tagfilter-input mw-ui-input mw-ui-input-inline',
1113 'id' =>
'tagfilter',
1114 'list' =>
'tagfilter-datalist',
1116 ) . $datalist->getHTML();
1134 'ctd_user_defined' => 1,
1138 self::CHANGE_TAG_DEF,
1141 [
'ctd_user_defined' => 1 ],
1161 self::CHANGE_TAG_DEF,
1162 [
'ctd_user_defined' => 0 ],
1163 [
'ctd_name' => $tag ],
1168 self::CHANGE_TAG_DEF,
1169 [
'ctd_name' => $tag,
'ctd_count' => 0 ],
1192 UserIdentity $user, $tagCount =
null, array $logEntryTags = []
1197 $logEntry->setPerformer( $user );
1201 $logEntry->setComment( $reason );
1203 $params = [
'4::tag' => $tag ];
1204 if ( $tagCount !==
null ) {
1205 $params[
'5:number:count'] = $tagCount;
1207 $logEntry->setParameters( $params );
1208 $logEntry->setRelations( [
'Tag' => $tag ] );
1209 $logEntry->addTags( $logEntryTags );
1211 $logId = $logEntry->insert( $dbw );
1212 $logEntry->publish( $logId );
1226 if ( $performer !==
null ) {
1227 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1230 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1232 'tags-manage-blocked',
1233 $performer->getUser()->getName()
1242 if ( in_array( $tag, $definedTags ) ) {
1248 if ( !isset( $tagUsage[$tag] ) ) {
1273 bool $ignoreWarnings =
false, array $logEntryTags = []
1277 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1278 $result->value =
null;
1287 null, $logEntryTags );
1302 if ( $performer !==
null ) {
1303 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1306 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1308 'tags-manage-blocked',
1309 $performer->getUser()->getName()
1316 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
1340 bool $ignoreWarnings =
false, array $logEntryTags = []
1344 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1345 $result->value =
null;
1354 $performer->
getUser(),
null, $logEntryTags );
1368 if ( $tag ===
'' ) {
1376 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
1377 || strpos( $tag,
'/' ) !==
false ) {
1404 if ( $performer !==
null ) {
1405 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1408 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1410 'tags-manage-blocked',
1411 $performer->getUser()->getName()
1415 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
1419 if ( !$status->isGood() ) {
1425 if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
1431 Hooks::runner()->onChangeTagCanCreate( $tag, $user, $canCreateResult );
1432 return $canCreateResult;
1455 bool $ignoreWarnings =
false, array $logEntryTags = []
1459 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1460 $result->value =
null;
1469 $performer->
getUser(),
null, $logEntryTags );
1488 $dbw->startAtomic( __METHOD__ );
1491 $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
1497 $dbw->delete( self::CHANGE_TAG, [
'ct_tag_id' => $tagId ], __METHOD__ );
1498 $dbw->delete( self::CHANGE_TAG_DEF, [
'ctd_name' => $tag ], __METHOD__ );
1499 $dbw->endAtomic( __METHOD__ );
1505 if ( !$status->isOK() ) {
1506 wfDebug(
'ChangeTagAfterDelete error condition downgraded to warning' );
1507 $status->setOK(
true );
1531 if ( $performer !==
null ) {
1532 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1535 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1537 'tags-manage-blocked',
1538 $performer->getUser()->getName()
1542 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
1545 if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
1549 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1550 isset( $tagUsage[$tag] ) &&
1551 $tagUsage[$tag] > self::MAX_DELETE_USES
1553 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1557 if ( in_array( $tag, $softwareDefined ) ) {
1566 Hooks::runner()->onChangeTagCanDelete( $tag, $user, $status );
1588 bool $ignoreWarnings =
false, array $logEntryTags = []
1592 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1593 $result->value =
null;
1599 $hitcount = $tagUsage[$tag] ?? 0;
1603 if ( !$deleteResult->isOK() ) {
1604 return $deleteResult;
1609 $hitcount, $logEntryTags );
1611 $deleteResult->value = $logId;
1612 return $deleteResult;
1624 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1625 if ( !$hookContainer->isRegistered(
'ChangeTagsListActive' ) ) {
1628 $hookRunner =
new HookRunner( $hookContainer );
1629 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1630 return $cache->getWithSetCallback(
1631 $cache->makeKey(
'active-tags' ),
1632 WANObjectCache::TTL_MINUTE * 5,
1633 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1637 $hookRunner->onChangeTagsListActive( $tags );
1641 'checkKeys' => [ $cache->makeKey(
'active-tags' ) ],
1642 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1643 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1658 return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
1670 $fname = __METHOD__;
1672 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1673 return $cache->getWithSetCallback(
1674 $cache->makeKey(
'valid-tags-db' ),
1675 WANObjectCache::TTL_MINUTE * 5,
1676 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1679 $setOpts += Database::getCacheSetOptions(
$dbr );
1680 $tags =
$dbr->newSelectQueryBuilder()
1681 ->select(
'ctd_name' )
1682 ->from( self::CHANGE_TAG_DEF )
1683 ->where( [
'ctd_user_defined' => 1 ] )
1685 ->fetchFieldValues();
1687 return array_unique( $tags );
1690 'checkKeys' => [ $cache->makeKey(
'valid-tags-db' ) ],
1691 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1692 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1709 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1710 if ( !$hookContainer->isRegistered(
'ListDefinedTags' ) ) {
1713 $hookRunner =
new HookRunner( $hookContainer );
1714 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1715 return $cache->getWithSetCallback(
1716 $cache->makeKey(
'valid-tags-hook' ),
1717 WANObjectCache::TTL_MINUTE * 5,
1718 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1721 $hookRunner->onListDefinedTags( $tags );
1722 return array_unique( $tags );
1725 'checkKeys' => [ $cache->makeKey(
'valid-tags-hook' ) ],
1726 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1727 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1738 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1740 $cache->touchCheckKey( $cache->makeKey(
'active-tags' ) );
1741 $cache->touchCheckKey( $cache->makeKey(
'valid-tags-db' ) );
1742 $cache->touchCheckKey( $cache->makeKey(
'valid-tags-hook' ) );
1743 $cache->touchCheckKey( $cache->makeKey(
'tags-usage-statistics' ) );
1745 MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
1755 $fname = __METHOD__;
1757 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1758 return $cache->getWithSetCallback(
1759 $cache->makeKey(
'tags-usage-statistics' ),
1760 WANObjectCache::TTL_MINUTE * 5,
1761 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1763 $res =
$dbr->newSelectQueryBuilder()
1764 ->select( [
'ctd_name',
'ctd_count' ] )
1765 ->from( self::CHANGE_TAG_DEF )
1766 ->orderBy(
'ctd_count', SelectQueryBuilder::SORT_DESC )
1771 foreach (
$res as $row ) {
1772 $out[$row->ctd_name] = $row->ctd_count;
1778 'checkKeys' => [ $cache->makeKey(
'tags-usage-statistics' ) ],
1779 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1780 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1789 private const TAG_DESC_CHARACTER_LIMIT = 120;
1816 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1817 return $cache->getWithSetCallback(
1818 $cache->makeKey(
'tags-list-summary',
$lang->getCode() ),
1819 WANObjectCache::TTL_DAY,
1820 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1825 foreach ( self::listDefinedTags() as $tagName ) {
1827 $hits = $tagHitCounts[$tagName] ?? 0;
1837 'labelMsg' => (bool)$labelMsg,
1838 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1839 'descriptionMsg' => (bool)$descriptionMsg,
1840 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1863 foreach ( $tags as &$tagInfo ) {
1864 if ( $tagInfo[
'labelMsg'] ) {
1866 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1869 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1871 if ( $tagInfo[
'descriptionMsg'] ) {
1872 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1873 $tagInfo[
'description'] =
$lang->truncateForVisual(
1875 self::TAG_DESC_CHARACTER_LIMIT
1878 unset( $tagInfo[
'labelMsg'] );
1879 unset( $tagInfo[
'descriptionMsg'] );
1883 usort( $tags,
static function ( $a, $b ) {
1884 return strcasecmp( $a[
'label'], $b[
'label'] );
1904 return $performer->
isAllowed(
'changetags' ) && (bool)self::listExplicitlyDefinedTags();
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Base class for language-specific code.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
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.
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
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.
if(!isset( $args[0])) $lang