139 private const MAX_DELETE_USES = 5000;
144 private const CHANGE_TAG =
'change_tag';
188 RequestContext::getMain()->getAuthority()
208 $msg = $context->
msg(
"tag-$tag" );
209 if ( !$msg->exists() ) {
215 if ( $msg->isDisabled() ) {
239 $msg = $context->
msg(
"tag-$tag-helppage" )->inContentLanguage();
240 if ( !$msg->isDisabled() ) {
241 return Skin::makeInternalOrExternalUrl( $msg->text() ) ?:
null;
261 if ( $description ===
'' ) {
282 $msg = $context->
msg(
"tag-$tag-description" );
283 return $msg->isDisabled() ? false : $msg;
297 $tags = array_values( $tags );
298 $count = count( $tags );
299 $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
301 $status->value = $tags;
326 if ( $performer !==
null ) {
327 if ( !$performer->isAllowed(
'applychangetags' ) ) {
328 return Status::newFatal(
'tags-apply-no-permission' );
331 if ( $checkBlock && $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
332 return Status::newFatal(
333 'tags-apply-blocked',
334 $performer->getUser()->getName()
339 $user = $services->getUserFactory()->newFromAuthority( $performer );
343 $allowedTags = $services->getChangeTagsStore()->listExplicitlyDefinedTags();
344 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
345 $disallowedTags = array_diff( $tags, $allowedTags );
346 if ( $disallowedTags ) {
348 'tags-apply-not-allowed-multi', $disallowedTags );
351 return Status::newGood();
375 return self::canUpdateTagsInternal(
395 private static function canUpdateTagsInternal(
400 if ( !$performer->isDefinitelyAllowed(
'changetags' ) ) {
401 return Status::newFatal(
'tags-update-no-permission' );
404 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
405 return Status::newFatal(
406 'tags-update-blocked',
407 $performer->getUser()->getName()
415 $explicitlyDefinedTags = $changeTagsStore->listExplicitlyDefinedTags();
416 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
419 'tags-update-add-not-allowed-multi', $diff );
423 if ( $tagsToRemove ) {
426 $unviewableRestricted = array_filter(
428 static fn ( $tag ) => $changeTagsStore->isRestrictedTag( $tag )
429 && !$changeTagsStore->canViewTag( $tag, $performer )
431 if ( $unviewableRestricted ) {
433 'tags-update-remove-not-allowed-multi', $unviewableRestricted );
439 $softwareDefinedTags = $changeTagsStore->listSoftwareDefinedTags();
440 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
443 'tags-update-remove-not-allowed-multi', $intersect );
447 return Status::newGood();
480 $rc_id, $rev_id, $log_id, $params,
string $reason,
Authority $performer
482 if ( !$tagsToAdd && !$tagsToRemove ) {
484 return Status::newGood( (
object)[
492 $tagsToRemove ??= [];
495 $result = self::canUpdateTagsInternal( $tagsToAdd, $tagsToRemove, $performer );
496 if ( !$result->isOK() ) {
497 $result->value =
null;
502 $status = PermissionStatus::newEmpty();
504 return Status::wrap( $status );
509 [ $tagsAdded, $tagsRemoved, $initialTags ] = $changeTagsStore->updateTags( $tagsToAdd,
510 $tagsToRemove, $rc_id, $rev_id, $log_id, $params,
null, $performer->
getUser() );
511 if ( !$tagsAdded && !$tagsRemoved ) {
513 return Status::newGood( (
object)[
522 $logEntry->setPerformer( $performer->
getUser() );
523 $logEntry->setComment( $reason );
528 ->getRevisionLookup()
529 ->getRevisionById( $rev_id );
530 if ( $revisionRecord ) {
531 $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
533 } elseif ( $log_id ) {
540 if ( !$logEntry->getTarget() ) {
546 '4::revid' => $rev_id,
547 '5::logid' => $log_id,
548 '6:list:tagsAdded' => $tagsAdded,
549 '7:number:tagsAddedCount' => count( $tagsAdded ),
550 '8:list:tagsRemoved' => $tagsRemoved,
551 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
552 'initialTags' => $initialTags,
554 $logEntry->setParameters( $logParams );
555 $logEntry->setRelations( [
'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
558 $logId = $logEntry->insert( $dbw );
560 $logEntry->publish( $logId,
'udp' );
562 return Status::newGood( (
object)[
564 'addedTags' => $tagsAdded,
565 'removedTags' => $tagsRemoved,
585 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
586 bool $useAllTags = self::USE_ALL_TAGS
591 $ooui ?
'ooui' :
'other',
609 if ( $performer !==
null ) {
610 if ( !$performer->isAllowed(
'managechangetags' ) ) {
611 return Status::newFatal(
'tags-manage-no-permission' );
613 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
614 return Status::newFatal(
615 'tags-manage-blocked',
616 $performer->getUser()->getName()
619 if ( $changeTagsStore->filterViewableTags( [ $tag ], $performer ) === [] ) {
620 return Status::newFatal(
'tags-activate-not-found', $tag );
627 $definedTags = $changeTagsStore->listDefinedTags();
628 if ( in_array( $tag, $definedTags ) ) {
629 return Status::newFatal(
'tags-activate-not-allowed', $tag );
633 if ( !isset( $changeTagsStore->tagUsageStatistics()[$tag] ) ) {
634 return Status::newFatal(
'tags-activate-not-found', $tag );
637 return Status::newGood();
658 bool $ignoreWarnings =
false, array $logEntryTags = []
661 $result = self::canActivateTag( $tag, $performer );
662 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
663 $result->value =
null;
668 $changeTagsStore->defineTag( $tag );
670 $logId = $changeTagsStore->logTagManagementAction(
'activate', $tag, $reason, $performer->
getUser(),
671 null, $logEntryTags );
673 return Status::newGood( $logId );
687 if ( $performer !==
null ) {
688 if ( !$performer->isAllowed(
'managechangetags' ) ) {
689 return Status::newFatal(
'tags-manage-no-permission' );
691 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
692 return Status::newFatal(
693 'tags-manage-blocked',
694 $performer->getUser()->getName()
697 if ( $changeTagsStore->filterViewableTags( [ $tag ], $performer ) === [] ) {
698 return Status::newFatal(
'tags-deactivate-not-found', $tag );
703 $explicitlyDefinedTags = $changeTagsStore->listExplicitlyDefinedTags();
704 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
705 return Status::newFatal(
'tags-deactivate-not-allowed', $tag );
707 return Status::newGood();
728 bool $ignoreWarnings =
false, array $logEntryTags = []
731 $result = self::canDeactivateTag( $tag, $performer );
732 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
733 $result->value =
null;
738 $changeTagsStore->undefineTag( $tag );
740 $logId = $changeTagsStore->logTagManagementAction(
'deactivate', $tag, $reason,
741 $performer->
getUser(),
null, $logEntryTags );
743 return Status::newGood( $logId );
756 return Status::newFatal(
'tags-create-no-name' );
763 if ( str_contains( $tag,
',' ) || str_contains( $tag,
'|' ) || str_contains( $tag,
'/' ) ) {
764 return Status::newFatal(
'tags-create-invalid-chars' );
768 $title = Title::makeTitleSafe(
NS_MEDIAWIKI,
"Tag-$tag-description" );
769 if ( $title ===
null ) {
770 return Status::newFatal(
'tags-create-invalid-title-chars' );
774 if ( $changeTagsStore->isRestrictedTag( $tag ) ) {
778 return Status::newGood();
796 if ( $performer !==
null ) {
797 if ( !$performer->isAllowed(
'managechangetags' ) ) {
798 return Status::newFatal(
'tags-manage-no-permission' );
800 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
801 return Status::newFatal(
802 'tags-manage-blocked',
803 $performer->getUser()->getName()
807 $user = $services->getUserFactory()->newFromAuthority( $performer );
810 $status = self::isTagNameValid( $tag );
811 if ( !$status->isGood() ) {
816 $changeTagsStore = $services->getChangeTagsStore();
818 isset( $changeTagsStore->tagUsageStatistics()[$tag] ) ||
819 in_array( $tag, $changeTagsStore->listDefinedTags() )
821 return Status::newFatal(
'tags-create-already-exists', $tag );
825 $canCreateResult = Status::newGood();
826 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanCreate( $tag, $user, $canCreateResult );
827 return $canCreateResult;
850 bool $ignoreWarnings =
false, array $logEntryTags = []
853 $result = self::canCreateTag( $tag, $performer );
854 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
855 $result->value =
null;
860 $changeTagsStore->defineTag( $tag );
861 $logId = $changeTagsStore->logTagManagementAction(
'create', $tag, $reason,
862 $performer->
getUser(),
null, $logEntryTags );
864 return Status::newGood( $logId );
882 $changeTagsStore = $services->getChangeTagsStore();
883 if ( $performer !==
null ) {
884 if ( !$performer->isAllowed(
'deletechangetags' ) ) {
885 return Status::newFatal(
'tags-delete-no-permission' );
887 if ( $performer->getBlock() && $performer->getBlock()->isSitewide() ) {
888 return Status::newFatal(
889 'tags-manage-blocked',
890 $performer->getUser()->getName()
893 if ( $changeTagsStore->filterViewableTags( [ $tag ], $performer ) === [] ) {
894 return Status::newFatal(
'tags-delete-not-found', $tag );
897 $user = $services->getUserFactory()->newFromAuthority( $performer );
900 $tagUsage = $changeTagsStore->tagUsageStatistics();
902 !isset( $tagUsage[$tag] ) &&
903 !in_array( $tag, $changeTagsStore->listDefinedTags() )
905 return Status::newFatal(
'tags-delete-not-found', $tag );
908 if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
909 isset( $tagUsage[$tag] ) &&
910 $tagUsage[$tag] > self::MAX_DELETE_USES
912 return Status::newFatal(
'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
915 $softwareDefined = $changeTagsStore->listSoftwareDefinedTags();
916 if ( in_array( $tag, $softwareDefined ) ) {
919 $status = Status::newFatal(
'tags-delete-not-allowed' );
922 $status = Status::newGood();
925 (
new HookRunner( $services->getHookContainer() ) )->onChangeTagCanDelete( $tag, $user, $status );
947 bool $ignoreWarnings =
false, array $logEntryTags = []
951 $result = self::canDeleteTag( $tag, $performer );
952 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
953 $result->value =
null;
958 $hitcount = $changeTagsStore->tagUsageStatistics()[$tag] ?? 0;
961 $deleteResult = $changeTagsStore->deleteTagEverywhere( $tag );
962 if ( !$deleteResult->isOK() ) {
963 return $deleteResult;
968 $logId = $changeTagsStore->logTagManagementAction(
'delete', $tag, $reason, $performer->
getUser(),
969 $hitcount, $logEntryTags );
971 $deleteResult->value = $logId;
972 return $deleteResult;
1007 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
1008 bool $useAllTags = self::USE_ALL_TAGS
1013 ( $localizer instanceof
IContextSource ? $localizer : RequestContext::getMain() )->getAuthority(),
1037 bool $activeOnly = self::TAG_SET_ACTIVE_ONLY,
bool $useAllTags = self::USE_ALL_TAGS,
1043 ( $localizer instanceof
IContextSource ? $localizer : RequestContext::getMain() )->getAuthority(),
1066 return $performer->
isAllowed(
'changetags' ) && (bool)$changeTagsStore->listExplicitlyDefinedTags();
1071class_alias( ChangeTags::class,
'ChangeTags' );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Group all the pieces relevant to the context of a request into one instance.
An implementation of LocalizationContext that implements only the methods defined in the interface.
Class for creating new log entries and inserting them into the database.
Parent class for all special pages.
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,...
Interface for objects which can provide a MediaWiki context on request.