109 private const MAX_DELETE_USES = 5000;
114 private const DEFINED_SOFTWARE_TAGS = [
115 'mw-contentmodelchange',
117 'mw-removed-redirect',
118 'mw-changed-redirect-target',
125 'mw-server-side-upload',
131 private const CHANGE_TAG =
'change_tag';
136 private const CHANGE_TAG_DEF =
'change_tag_def';
158 $coreTags = MediaWikiServices::getInstance()->getMainConfig()->get(
159 MainConfigNames::SoftwareTags );
162 if ( !is_array( $coreTags ) ) {
163 wfWarn(
'wgSoftwareTags should be associative array of enabled tags.
164 Please refer to documentation for the list of tags you can enable' );
165 return $softwareTags;
168 $availableSoftwareTags = !$all ?
169 array_keys( array_filter( $coreTags ) ) :
170 array_keys( $coreTags );
172 $softwareTags = array_intersect(
173 $availableSoftwareTags,
174 self::DEFINED_SOFTWARE_TAGS
177 return $softwareTags;
194 if ( $tags ===
'' || $tags ===
null ) {
198 $localizer = RequestContext::getMain();
203 $tags = explode(
',', $tags );
204 $order = array_flip( self::listDefinedTags() );
205 usort( $tags,
static function ( $a, $b ) use ( $order ) {
206 return ( $order[ $a ] ?? INF ) <=> ( $order[ $b ] ?? INF );
210 foreach ( $tags as $tag ) {
214 $classes[] = Sanitizer::escapeClass(
"mw-tag-$tag" );
216 if ( $description ===
false ) {
219 $displayTags[] = Xml::tags(
221 [
'class' =>
'mw-tag-marker ' .
222 Sanitizer::escapeClass(
"mw-tag-marker-$tag" ) ],
227 if ( !$displayTags ) {
228 return [
'', $classes ];
231 $markers = $localizer->msg(
'tag-list-wrapper' )
232 ->numParams( count( $displayTags ) )
233 ->rawParams( implode(
' ', $displayTags ) )
235 $markers = Xml::tags(
'span', [
'class' =>
'mw-tag-markers' ], $markers );
237 return [ $markers, $classes ];
254 $msg = $context->
msg(
"tag-$tag" );
255 if ( !$msg->exists() ) {
261 if ( $msg->isDisabled() ) {
285 return $msg ? $msg->parse() :
false;
301 $msg = $context->
msg(
"tag-$tag-description" );
302 if ( !$msg->exists() ) {
305 if ( $msg->isDisabled() ) {
328 public static function addTags( $tags, $rc_id =
null, $rev_id =
null,
331 $result =
self::updateTags( $tags,
null, $rc_id, $rev_id, $log_id, $params, $rc );
332 return (
bool)$result[0];
365 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id =
null,
366 &$rev_id =
null, &$log_id =
null, $params =
null,
RecentChange $rc =
null,
369 $tagsToAdd = array_filter(
371 static function ( $value ) {
372 return ( $value ??
'' ) !==
'';
375 $tagsToRemove = array_filter(
376 (array)$tagsToRemove,
377 static function ( $value ) {
378 return ( $value ??
'' ) !==
'';
382 if ( !$rc_id && !$rev_id && !$log_id ) {
383 throw new MWException(
'At least one of: RCID, revision ID, and log ID MUST be ' .
384 'specified when adding or removing a tag from a change!' );
395 $rc_id = $dbw->newSelectQueryBuilder()
398 ->join(
'recentchanges',
null, [
399 'rc_timestamp = log_timestamp',
402 ->where( [
'log_id' => $log_id ] )
403 ->caller( __METHOD__ )
405 } elseif ( $rev_id ) {
406 $rc_id = $dbw->newSelectQueryBuilder()
409 ->join(
'recentchanges',
null, [
410 'rc_this_oldid = rev_id'
412 ->where( [
'rev_id' => $rev_id ] )
413 ->caller( __METHOD__ )
416 } elseif ( !$log_id && !$rev_id ) {
418 $log_id = $dbw->newSelectQueryBuilder()
419 ->select(
'rc_logid' )
420 ->from(
'recentchanges' )
421 ->where( [
'rc_id' => $rc_id ] )
422 ->caller( __METHOD__ )
424 $rev_id = $dbw->newSelectQueryBuilder()
425 ->select(
'rc_this_oldid' )
426 ->from(
'recentchanges' )
427 ->where( [
'rc_id' => $rc_id ] )
428 ->caller( __METHOD__ )
432 if ( $log_id && !$rev_id ) {
433 $rev_id = $dbw->newSelectQueryBuilder()
434 ->select(
'ls_value' )
435 ->from(
'log_search' )
436 ->where( [
'ls_field' =>
'associated_rev_id',
'ls_log_id' => $log_id ] )
437 ->caller( __METHOD__ )
439 } elseif ( !$log_id && $rev_id ) {
440 $log_id = $dbw->newSelectQueryBuilder()
441 ->select(
'ls_log_id' )
442 ->from(
'log_search' )
443 ->where( [
'ls_field' =>
'associated_rev_id',
'ls_value' => (
string)$rev_id ] )
444 ->caller( __METHOD__ )
451 $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
452 $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
455 $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
456 $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
460 if ( $prevTags == $newTags ) {
461 return [ [], [], $prevTags ];
465 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
466 if ( count( $tagsToAdd ) ) {
467 $changeTagMapping = [];
468 foreach ( $tagsToAdd as $tag ) {
469 $changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
473 $dbw->onTransactionPreCommitOrIdle(
static function () use ( $dbw, $tagsToAdd, $fname ) {
475 self::CHANGE_TAG_DEF,
476 [
'ctd_count = ctd_count + 1' ],
477 [
'ctd_name' => $tagsToAdd ],
483 foreach ( $tagsToAdd as $tag ) {
488 $tagsRows[] = array_filter(
490 'ct_rc_id' => $rc_id,
491 'ct_log_id' => $log_id,
492 'ct_rev_id' => $rev_id,
493 'ct_params' => $params,
494 'ct_tag_id' => $changeTagMapping[$tag] ??
null,
500 $dbw->insert( self::CHANGE_TAG, $tagsRows, __METHOD__, [
'IGNORE' ] );
504 if ( count( $tagsToRemove ) ) {
506 foreach ( $tagsToRemove as $tag ) {
507 $conds = array_filter(
509 'ct_rc_id' => $rc_id,
510 'ct_log_id' => $log_id,
511 'ct_rev_id' => $rev_id,
512 'ct_tag_id' => $changeTagDefStore->getId( $tag ),
515 $dbw->delete( self::CHANGE_TAG, $conds, __METHOD__ );
516 if ( $dbw->affectedRows() ) {
518 $dbw->onTransactionPreCommitOrIdle(
static function () use ( $dbw, $tag, $fname ) {
520 self::CHANGE_TAG_DEF,
521 [
'ctd_count = ctd_count - 1' ],
522 [
'ctd_name' => $tag ],
527 self::CHANGE_TAG_DEF,
528 [
'ctd_name' => $tag,
'ctd_count' => 0,
'ctd_user_defined' => 0 ],
536 $userObj = $user ? MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $user ) :
null;
537 Hooks::runner()->onChangeTagsAfterUpdateTags( $tagsToAdd, $tagsToRemove, $prevTags,
538 $rc_id, $rev_id, $log_id, $params, $rc, $userObj );
540 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 MWException(
'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 ) );
612 $lang = RequestContext::getMain()->getLanguage();
613 $tags = array_values( $tags );
614 $count = count( $tags );
615 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
616 $lang->commaList( $tags ), $count );
617 $status->value = $tags;
641 if ( $performer !==
null ) {
642 if ( !$performer->isAllowed(
'applychangetags' ) ) {
643 return Status::newFatal(
'tags-apply-no-permission' );
646 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
647 return Status::newFatal(
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 );
666 return Status::newGood();
690 array $tags, $rc_id, $rev_id, $log_id, $params,
Authority $performer
694 if ( !$result->isOK() ) {
695 $result->value =
null;
702 return Status::newGood(
true );
724 if ( $performer !==
null ) {
725 if ( !$performer->isAllowed(
'changetags' ) ) {
726 return Status::newFatal(
'tags-update-no-permission' );
729 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
730 return Status::newFatal(
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 );
760 return Status::newGood();
794 $rc_id, $rev_id, $log_id, $params,
string $reason,
Authority $performer
796 if ( $tagsToAdd ===
null ) {
799 if ( $tagsToRemove ===
null ) {
802 if ( !$tagsToAdd && !$tagsToRemove ) {
804 return Status::newGood( (
object)[
813 if ( !$result->isOK() ) {
814 $result->value =
null;
819 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
820 if ( $user->pingLimiter(
'changetag' ) ) {
821 return Status::newFatal(
'actionthrottledtext' );
825 list( $tagsAdded, $tagsRemoved, $initialTags ) =
self::updateTags( $tagsToAdd,
826 $tagsToRemove, $rc_id, $rev_id, $log_id, $params,
null, $user );
827 if ( !$tagsAdded && !$tagsRemoved ) {
829 return Status::newGood( (
object)[
838 $logEntry->setPerformer( $performer->
getUser() );
839 $logEntry->setComment( $reason );
843 $revisionRecord = MediaWikiServices::getInstance()
844 ->getRevisionLookup()
845 ->getRevisionById( $rev_id );
846 if ( $revisionRecord ) {
847 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
849 } elseif ( $log_id ) {
856 if ( !$logEntry->getTarget() ) {
862 '4::revid' => $rev_id,
863 '5::logid' => $log_id,
864 '6:list:tagsAdded' => $tagsAdded,
865 '7:number:tagsAddedCount' => count( $tagsAdded ),
866 '8:list:tagsRemoved' => $tagsRemoved,
867 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
868 'initialTags' => $initialTags,
870 $logEntry->setParameters( $logParams );
871 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
874 $logId = $logEntry->insert( $dbw );
876 $logEntry->publish( $logId,
'udp' );
878 return Status::newGood( (
object)[
880 'addedTags' => $tagsAdded,
881 'removedTags' => $tagsRemoved,
907 &$join_conds, &$options, $filter_tag =
'',
bool $exclude =
false
909 $useTagFilter = MediaWikiServices::getInstance()->getMainConfig()->get(
910 MainConfigNames::UseTagFilter );
913 $tables = (array)$tables;
914 $fields = (array)$fields;
915 $conds = (array)$conds;
916 $options = (array)$options;
921 if ( in_array(
'recentchanges', $tables ) ) {
922 $join_cond =
'ct_rc_id=rc_id';
923 } elseif ( in_array(
'logging', $tables ) ) {
924 $join_cond =
'ct_log_id=log_id';
925 } elseif ( in_array(
'revision', $tables ) ) {
926 $join_cond =
'ct_rev_id=rev_id';
927 } elseif ( in_array(
'archive', $tables ) ) {
928 $join_cond =
'ct_rev_id=ar_rev_id';
930 throw new MWException(
'Unable to determine appropriate JOIN condition for tagging.' );
933 if ( !$useTagFilter ) {
937 if ( !is_array( $filter_tag ) ) {
939 $filter_tag = (string)$filter_tag;
942 if ( $filter_tag !== [] && $filter_tag !==
'' ) {
947 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
948 foreach ( (array)$filter_tag as $filterTagName ) {
950 $filterTagIds[] = $changeTagDefStore->getId( $filterTagName );
956 if ( $filterTagIds !== [] ) {
957 $tables[] = $tagTable;
958 $join_conds[$tagTable] = [
960 [ $join_cond,
'ct_tag_id' => $filterTagIds ]
962 $conds[] =
"ct_tag_id IS NULL";
965 $tables[] = $tagTable;
966 $join_conds[$tagTable] = [
'JOIN', $join_cond ];
967 if ( $filterTagIds !== [] ) {
968 $conds[
'ct_tag_id'] = $filterTagIds;
975 is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
976 !in_array(
'DISTINCT', $options )
978 $options[] =
'DISTINCT';
991 $tagTable = self::CHANGE_TAG;
992 if ( self::$avoidReopeningTablesForTesting && defined(
'MW_PHPUNIT_TEST' ) ) {
995 if ( $db->getType() ===
'mysql' ) {
1004 $tagTable =
'change_tag_for_display_query';
1005 if ( !$db->tableExists( $tagTable ) ) {
1007 'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $db->tableName( $tagTable )
1008 .
' LIKE ' . $db->tableName( self::CHANGE_TAG ),
1012 'INSERT IGNORE INTO ' . $db->tableName( $tagTable )
1013 .
' SELECT * FROM ' . $db->tableName( self::CHANGE_TAG ),
1032 $tables = (array)$tables;
1035 if ( in_array(
'recentchanges', $tables ) ) {
1036 $join_cond =
'ct_rc_id=rc_id';
1037 } elseif ( in_array(
'logging', $tables ) ) {
1038 $join_cond =
'ct_log_id=log_id';
1039 } elseif ( in_array(
'revision', $tables ) ) {
1040 $join_cond =
'ct_rev_id=rev_id';
1041 } elseif ( in_array(
'archive', $tables ) ) {
1042 $join_cond =
'ct_rev_id=ar_rev_id';
1044 throw new MWException(
'Unable to determine appropriate JOIN condition for tagging.' );
1047 $tagTables = [ self::CHANGE_TAG, self::CHANGE_TAG_DEF ];
1048 $join_cond_ts_tags = [ self::CHANGE_TAG_DEF => [
'JOIN',
'ct_tag_id=ctd_id' ] ];
1049 $field =
'ctd_name';
1052 ',', $tagTables, $field, $join_cond, $join_cond_ts_tags
1071 $context = RequestContext::getMain();
1074 $config = $context->getConfig();
1075 if ( !$config->get( MainConfigNames::UseTagFilter ) ||
1076 !count( self::listDefinedTags() ) ) {
1082 foreach ( $tags as $tagInfo ) {
1083 $autocomplete[ $tagInfo[
'label'] ] = $tagInfo[
'name'];
1089 [
'for' =>
'tagfilter' ],
1090 $context->msg(
'tag-filter' )->parse()
1095 $options = Xml::listDropDownOptionsOoui( $autocomplete );
1097 $data[] =
new OOUI\ComboBoxInputWidget( [
1098 'id' =>
'tagfilter',
1099 'name' =>
'tagfilter',
1100 'value' => $selected,
1101 'classes' =>
'mw-tagfilter-input',
1102 'options' => $options,
1105 $datalist =
new XmlSelect(
false,
'tagfilter-datalist' );
1106 $datalist->setTagName(
'datalist' );
1107 $datalist->addOptions( $autocomplete );
1109 $data[] = Xml::input(
1114 'class' =>
'mw-tagfilter-input mw-ui-input mw-ui-input-inline',
1115 'id' =>
'tagfilter',
1116 'list' =>
'tagfilter-datalist',
1118 ) . $datalist->getHTML();
1136 'ctd_user_defined' => 1,
1140 self::CHANGE_TAG_DEF,
1143 [
'ctd_user_defined' => 1 ],
1163 self::CHANGE_TAG_DEF,
1164 [
'ctd_user_defined' => 0 ],
1165 [
'ctd_name' => $tag ],
1170 self::CHANGE_TAG_DEF,
1171 [
'ctd_name' => $tag,
'ctd_count' => 0 ],
1194 UserIdentity $user, $tagCount =
null, array $logEntryTags = []
1199 $logEntry->setPerformer( $user );
1202 $logEntry->setTarget( Title::newFromText(
'Special:Tags' ) );
1203 $logEntry->setComment( $reason );
1205 $params = [
'4::tag' => $tag ];
1206 if ( $tagCount !==
null ) {
1207 $params[
'5:number:count'] = $tagCount;
1209 $logEntry->setParameters( $params );
1210 $logEntry->setRelations( [
'Tag' => $tag ] );
1211 $logEntry->addTags( $logEntryTags );
1213 $logId = $logEntry->insert( $dbw );
1214 $logEntry->publish( $logId );
1228 if ( $performer !==
null ) {
1229 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1230 return Status::newFatal(
'tags-manage-no-permission' );
1232 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1233 return Status::newFatal(
1234 'tags-manage-blocked',
1235 $performer->getUser()->getName()
1244 if ( in_array( $tag, $definedTags ) ) {
1245 return Status::newFatal(
'tags-activate-not-allowed', $tag );
1250 if ( !isset( $tagUsage[$tag] ) ) {
1251 return Status::newFatal(
'tags-activate-not-found', $tag );
1254 return Status::newGood();
1275 bool $ignoreWarnings =
false, array $logEntryTags = []
1279 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1280 $result->value =
null;
1289 null, $logEntryTags );
1291 return Status::newGood( $logId );
1304 if ( $performer !==
null ) {
1305 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1306 return Status::newFatal(
'tags-manage-no-permission' );
1308 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1309 return Status::newFatal(
1310 'tags-manage-blocked',
1311 $performer->getUser()->getName()
1318 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
1319 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
1321 return Status::newGood();
1342 bool $ignoreWarnings =
false, array $logEntryTags = []
1346 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1347 $result->value =
null;
1356 $performer->
getUser(),
null, $logEntryTags );
1358 return Status::newGood( $logId );
1370 if ( $tag ===
'' ) {
1371 return Status::newFatal(
'tags-create-no-name' );
1378 if ( strpos( $tag,
',' ) !==
false || strpos( $tag,
'|' ) !==
false
1379 || strpos( $tag,
'/' ) !==
false ) {
1380 return Status::newFatal(
'tags-create-invalid-chars' );
1386 return Status::newFatal(
'tags-create-invalid-title-chars' );
1389 return Status::newGood();
1406 if ( $performer !==
null ) {
1407 if ( !$performer->isAllowed(
'managechangetags' ) ) {
1408 return Status::newFatal(
'tags-manage-no-permission' );
1410 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1411 return Status::newFatal(
1412 'tags-manage-blocked',
1413 $performer->getUser()->getName()
1417 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
1421 if ( !$status->isGood() ) {
1427 if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
1428 return Status::newFatal(
'tags-create-already-exists', $tag );
1432 $canCreateResult = Status::newGood();
1433 Hooks::runner()->onChangeTagCanCreate( $tag, $user, $canCreateResult );
1434 return $canCreateResult;
1457 bool $ignoreWarnings =
false, array $logEntryTags = []
1461 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1462 $result->value =
null;
1471 $performer->
getUser(),
null, $logEntryTags );
1473 return Status::newGood( $logId );
1490 $dbw->startAtomic( __METHOD__ );
1493 $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
1499 $dbw->delete( self::CHANGE_TAG, [
'ct_tag_id' => $tagId ], __METHOD__ );
1500 $dbw->delete( self::CHANGE_TAG_DEF, [
'ctd_name' => $tag ], __METHOD__ );
1501 $dbw->endAtomic( __METHOD__ );
1504 $status = Status::newGood();
1505 Hooks::runner()->onChangeTagAfterDelete( $tag, $status );
1507 if ( !$status->isOK() ) {
1508 wfDebug(
'ChangeTagAfterDelete error condition downgraded to warning' );
1509 $status->setOK(
true );
1533 if ( $performer !==
null ) {
1534 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
1535 return Status::newFatal(
'tags-delete-no-permission' );
1537 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
1538 return Status::newFatal(
1539 'tags-manage-blocked',
1540 $performer->getUser()->getName()
1544 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $performer );
1547 if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
1548 return Status::newFatal(
'tags-delete-not-found', $tag );
1551 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1552 isset( $tagUsage[$tag] ) &&
1553 $tagUsage[$tag] > self::MAX_DELETE_USES
1555 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1559 if ( in_array( $tag, $softwareDefined ) ) {
1562 $status = Status::newFatal(
'tags-delete-not-allowed' );
1565 $status = Status::newGood();
1568 Hooks::runner()->onChangeTagCanDelete( $tag, $user, $status );
1590 bool $ignoreWarnings =
false, array $logEntryTags = []
1594 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1595 $result->value =
null;
1601 $hitcount = $tagUsage[$tag] ?? 0;
1605 if ( !$deleteResult->isOK() ) {
1606 return $deleteResult;
1611 $hitcount, $logEntryTags );
1613 $deleteResult->value = $logId;
1614 return $deleteResult;
1626 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1627 if ( !$hookContainer->isRegistered(
'ChangeTagsListActive' ) ) {
1630 $hookRunner =
new HookRunner( $hookContainer );
1631 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1632 return $cache->getWithSetCallback(
1633 $cache->makeKey(
'active-tags' ),
1634 WANObjectCache::TTL_MINUTE * 5,
1635 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1639 $hookRunner->onChangeTagsListActive( $tags );
1643 'checkKeys' => [
$cache->makeKey(
'active-tags' ) ],
1644 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1645 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1660 return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
1672 $fname = __METHOD__;
1674 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1675 return $cache->getWithSetCallback(
1676 $cache->makeKey(
'valid-tags-db' ),
1677 WANObjectCache::TTL_MINUTE * 5,
1678 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1681 $setOpts += Database::getCacheSetOptions(
$dbr );
1682 $tags =
$dbr->newSelectQueryBuilder()
1683 ->select(
'ctd_name' )
1684 ->from( self::CHANGE_TAG_DEF )
1685 ->where( [
'ctd_user_defined' => 1 ] )
1687 ->fetchFieldValues();
1689 return array_unique( $tags );
1692 'checkKeys' => [
$cache->makeKey(
'valid-tags-db' ) ],
1693 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1694 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1711 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1712 if ( !$hookContainer->isRegistered(
'ListDefinedTags' ) ) {
1715 $hookRunner =
new HookRunner( $hookContainer );
1716 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1717 return $cache->getWithSetCallback(
1718 $cache->makeKey(
'valid-tags-hook' ),
1719 WANObjectCache::TTL_MINUTE * 5,
1720 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1723 $hookRunner->onListDefinedTags( $tags );
1724 return array_unique( $tags );
1727 'checkKeys' => [
$cache->makeKey(
'valid-tags-hook' ) ],
1728 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1729 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1740 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1742 $cache->touchCheckKey(
$cache->makeKey(
'active-tags' ) );
1743 $cache->touchCheckKey(
$cache->makeKey(
'valid-tags-db' ) );
1744 $cache->touchCheckKey(
$cache->makeKey(
'valid-tags-hook' ) );
1745 $cache->touchCheckKey(
$cache->makeKey(
'tags-usage-statistics' ) );
1747 MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
1757 $fname = __METHOD__;
1759 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1760 return $cache->getWithSetCallback(
1761 $cache->makeKey(
'tags-usage-statistics' ),
1762 WANObjectCache::TTL_MINUTE * 5,
1763 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1765 $res =
$dbr->newSelectQueryBuilder()
1766 ->select( [
'ctd_name',
'ctd_count' ] )
1767 ->from( self::CHANGE_TAG_DEF )
1768 ->orderBy(
'ctd_count', SelectQueryBuilder::SORT_DESC )
1773 foreach (
$res as $row ) {
1774 $out[$row->ctd_name] = $row->ctd_count;
1780 'checkKeys' => [
$cache->makeKey(
'tags-usage-statistics' ) ],
1781 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1782 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1791 private const TAG_DESC_CHARACTER_LIMIT = 120;
1818 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1819 return $cache->getWithSetCallback(
1820 $cache->makeKey(
'tags-list-summary',
$lang->getCode() ),
1821 WANObjectCache::TTL_DAY,
1822 static function ( $oldValue, &$ttl, array &$setOpts ) use ( $localizer ) {
1827 foreach ( self::listDefinedTags() as $tagName ) {
1829 $hits = $tagHitCounts[$tagName] ?? 0;
1839 'labelMsg' => (bool)$labelMsg,
1840 'label' => $labelMsg ? $labelMsg->plain() : $tagName,
1841 'descriptionMsg' => (bool)$descriptionMsg,
1842 'description' => $descriptionMsg ? $descriptionMsg->plain() :
'',
1843 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
1865 foreach ( $tags as &$tagInfo ) {
1866 if ( $tagInfo[
'labelMsg'] ) {
1868 $labelMsg =
new RawMessage( $tagInfo[
'label'] );
1869 $tagInfo[
'label'] = Sanitizer::stripAllTags( $localizer->
msg( $labelMsg )->parse() );
1871 $tagInfo[
'label'] = $localizer->
msg(
'tag-hidden', $tagInfo[
'name'] )->text();
1873 if ( $tagInfo[
'descriptionMsg'] ) {
1874 $descriptionMsg =
new RawMessage( $tagInfo[
'description'] );
1875 $tagInfo[
'description'] =
$lang->truncateForVisual(
1876 Sanitizer::stripAllTags( $localizer->
msg( $descriptionMsg )->parse() ),
1877 self::TAG_DESC_CHARACTER_LIMIT
1880 unset( $tagInfo[
'labelMsg'] );
1881 unset( $tagInfo[
'descriptionMsg'] );
1885 usort( $tags,
static function ( $a, $b ) {
1886 return strcasecmp( $a[
'label'], $b[
'label'] );
1906 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.
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)
Variant of the Message class.
Utility class for creating new RC entries.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
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,...
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.
if(!isset( $args[0])) $lang