MediaWiki  master
ChangeTags.php
Go to the documentation of this file.
1 <?php
29 
30 class ChangeTags {
36  private const MAX_DELETE_USES = 5000;
37 
41  public const BYPASS_MAX_USAGE_CHECK = 1;
42 
46  private static $definedSoftwareTags = [
47  'mw-contentmodelchange',
48  'mw-new-redirect',
49  'mw-removed-redirect',
50  'mw-changed-redirect-target',
51  'mw-blank',
52  'mw-replace',
53  'mw-rollback',
54  'mw-undo',
55  ];
56 
64  public static function getSoftwareTags( $all = false ) {
65  global $wgSoftwareTags;
66  $softwareTags = [];
67 
68  if ( !is_array( $wgSoftwareTags ) ) {
69  wfWarn( 'wgSoftwareTags should be associative array of enabled tags.
70  Please refer to documentation for the list of tags you can enable' );
71  return $softwareTags;
72  }
73 
74  $availableSoftwareTags = !$all ?
75  array_keys( array_filter( $wgSoftwareTags ) ) :
76  array_keys( $wgSoftwareTags );
77 
78  $softwareTags = array_intersect(
79  $availableSoftwareTags,
80  self::$definedSoftwareTags
81  );
82 
83  return $softwareTags;
84  }
85 
100  public static function formatSummaryRow( $tags, $page, IContextSource $context = null ) {
101  if ( !$tags ) {
102  return [ '', [] ];
103  }
104  if ( !$context ) {
105  $context = RequestContext::getMain();
106  }
107 
108  $classes = [];
109 
110  $tags = explode( ',', $tags );
111  $displayTags = [];
112  foreach ( $tags as $tag ) {
113  if ( !$tag ) {
114  continue;
115  }
116  $description = self::tagDescription( $tag, $context );
117  if ( $description === false ) {
118  continue;
119  }
120  $displayTags[] = Xml::tags(
121  'span',
122  [ 'class' => 'mw-tag-marker ' .
123  Sanitizer::escapeClass( "mw-tag-marker-$tag" ) ],
124  $description
125  );
126  $classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
127  }
128 
129  if ( !$displayTags ) {
130  return [ '', [] ];
131  }
132 
133  $markers = $context->msg( 'tag-list-wrapper' )
134  ->numParams( count( $displayTags ) )
135  ->rawParams( implode( ' ', $displayTags ) )
136  ->parse();
137  $markers = Xml::tags( 'span', [ 'class' => 'mw-tag-markers' ], $markers );
138 
139  return [ $markers, $classes ];
140  }
141 
155  public static function tagShortDescriptionMessage( $tag, MessageLocalizer $context ) {
156  $msg = $context->msg( "tag-$tag" );
157  if ( !$msg->exists() ) {
158  // No such message
159  return ( new RawMessage( '$1', [ Message::plaintextParam( $tag ) ] ) )
160  // HACK MessageLocalizer doesn't have a way to set the right language on a RawMessage,
161  // so extract the language from $msg and use that.
162  // The language doesn't really matter, but we need to set it to avoid requesting
163  // the user's language from session-less entry points (T227233)
164  ->inLanguage( $msg->getLanguage() );
165 
166  }
167  if ( $msg->isDisabled() ) {
168  // The message exists but is disabled, hide the tag.
169  return false;
170  }
171 
172  // Message exists and isn't disabled, use it.
173  return $msg;
174  }
175 
189  public static function tagDescription( $tag, MessageLocalizer $context ) {
190  $msg = self::tagShortDescriptionMessage( $tag, $context );
191  return $msg ? $msg->parse() : false;
192  }
193 
206  public static function tagLongDescriptionMessage( $tag, MessageLocalizer $context ) {
207  $msg = $context->msg( "tag-$tag-description" );
208  if ( !$msg->exists() ) {
209  return false;
210  }
211  if ( $msg->isDisabled() ) {
212  // The message exists but is disabled, hide the description.
213  return false;
214  }
215 
216  // Message exists and isn't disabled, use it.
217  return $msg;
218  }
219 
230  public static function truncateTagDescription( $tag, $length, IContextSource $context ) {
231  wfDeprecated( __METHOD__, '1.35' );
232  // FIXME: Make this accept MessageLocalizer and Language instead of IContextSource
233 
234  $originalDesc = self::tagLongDescriptionMessage( $tag, $context );
235  // If there is no tag description, return empty string
236  if ( !$originalDesc ) {
237  return '';
238  }
239 
240  $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
241 
242  return $context->getLanguage()->truncateForVisual( $taglessDesc, $length );
243  }
244 
259  public static function addTags( $tags, $rc_id = null, $rev_id = null,
260  $log_id = null, $params = null, RecentChange $rc = null
261  ) {
262  $result = self::updateTags( $tags, null, $rc_id, $rev_id, $log_id, $params, $rc );
263  return (bool)$result[0];
264  }
265 
296  public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null,
297  &$rev_id = null, &$log_id = null, $params = null, RecentChange $rc = null,
298  User $user = null
299  ) {
300  $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
301  $tagsToRemove = array_filter( (array)$tagsToRemove );
302 
303  if ( !$rc_id && !$rev_id && !$log_id ) {
304  throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' .
305  'specified when adding or removing a tag from a change!' );
306  }
307 
308  $dbw = wfGetDB( DB_MASTER );
309 
310  // Might as well look for rcids and so on.
311  if ( !$rc_id ) {
312  // Info might be out of date, somewhat fractionally, on replica DB.
313  // LogEntry/LogPage and WikiPage match rev/log/rc timestamps,
314  // so use that relation to avoid full table scans.
315  if ( $log_id ) {
316  $rc_id = $dbw->selectField(
317  [ 'logging', 'recentchanges' ],
318  'rc_id',
319  [
320  'log_id' => $log_id,
321  'rc_timestamp = log_timestamp',
322  'rc_logid = log_id'
323  ],
324  __METHOD__
325  );
326  } elseif ( $rev_id ) {
327  $rc_id = $dbw->selectField(
328  [ 'revision', 'recentchanges' ],
329  'rc_id',
330  [
331  'rev_id' => $rev_id,
332  'rc_this_oldid = rev_id'
333  ],
334  __METHOD__
335  );
336  }
337  } elseif ( !$log_id && !$rev_id ) {
338  // Info might be out of date, somewhat fractionally, on replica DB.
339  $log_id = $dbw->selectField(
340  'recentchanges',
341  'rc_logid',
342  [ 'rc_id' => $rc_id ],
343  __METHOD__
344  );
345  $rev_id = $dbw->selectField(
346  'recentchanges',
347  'rc_this_oldid',
348  [ 'rc_id' => $rc_id ],
349  __METHOD__
350  );
351  }
352 
353  if ( $log_id && !$rev_id ) {
354  $rev_id = $dbw->selectField(
355  'log_search',
356  'ls_value',
357  [ 'ls_field' => 'associated_rev_id', 'ls_log_id' => $log_id ],
358  __METHOD__
359  );
360  } elseif ( !$log_id && $rev_id ) {
361  $log_id = $dbw->selectField(
362  'log_search',
363  'ls_log_id',
364  [ 'ls_field' => 'associated_rev_id', 'ls_value' => (string)$rev_id ],
365  __METHOD__
366  );
367  }
368 
369  $prevTags = self::getTags( $dbw, $rc_id, $rev_id, $log_id );
370 
371  // add tags
372  $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
373  $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
374 
375  // remove tags
376  $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
377  $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
378 
379  sort( $prevTags );
380  sort( $newTags );
381  if ( $prevTags == $newTags ) {
382  return [ [], [], $prevTags ];
383  }
384 
385  // insert a row into change_tag for each new tag
386  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
387  if ( count( $tagsToAdd ) ) {
388  $changeTagMapping = [];
389  foreach ( $tagsToAdd as $tag ) {
390  $changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
391  }
392  $fname = __METHOD__;
393  // T207881: update the counts at the end of the transaction
394  $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $tagsToAdd, $fname ) {
395  $dbw->update(
396  'change_tag_def',
397  [ 'ctd_count = ctd_count + 1' ],
398  [ 'ctd_name' => $tagsToAdd ],
399  $fname
400  );
401  }, $fname );
402 
403  $tagsRows = [];
404  foreach ( $tagsToAdd as $tag ) {
405  // Filter so we don't insert NULLs as zero accidentally.
406  // Keep in mind that $rc_id === null means "I don't care/know about the
407  // rc_id, just delete $tag on this revision/log entry". It doesn't
408  // mean "only delete tags on this revision/log WHERE rc_id IS NULL".
409  $tagsRows[] = array_filter(
410  [
411  'ct_rc_id' => $rc_id,
412  'ct_log_id' => $log_id,
413  'ct_rev_id' => $rev_id,
414  'ct_params' => $params,
415  'ct_tag_id' => $changeTagMapping[$tag] ?? null,
416  ]
417  );
418 
419  }
420 
421  $dbw->insert( 'change_tag', $tagsRows, __METHOD__, [ 'IGNORE' ] );
422  }
423 
424  // delete from change_tag
425  if ( count( $tagsToRemove ) ) {
426  $fname = __METHOD__;
427  foreach ( $tagsToRemove as $tag ) {
428  $conds = array_filter(
429  [
430  'ct_rc_id' => $rc_id,
431  'ct_log_id' => $log_id,
432  'ct_rev_id' => $rev_id,
433  'ct_tag_id' => $changeTagDefStore->getId( $tag ),
434  ]
435  );
436  $dbw->delete( 'change_tag', $conds, __METHOD__ );
437  if ( $dbw->affectedRows() ) {
438  // T207881: update the counts at the end of the transaction
439  $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $tag, $fname ) {
440  $dbw->update(
441  'change_tag_def',
442  [ 'ctd_count = ctd_count - 1' ],
443  [ 'ctd_name' => $tag ],
444  $fname
445  );
446 
447  $dbw->delete(
448  'change_tag_def',
449  [ 'ctd_name' => $tag, 'ctd_count' => 0, 'ctd_user_defined' => 0 ],
450  $fname
451  );
452  }, $fname );
453  }
454  }
455  }
456 
457  Hooks::runner()->onChangeTagsAfterUpdateTags( $tagsToAdd, $tagsToRemove, $prevTags,
458  $rc_id, $rev_id, $log_id, $params, $rc, $user );
459 
460  return [ $tagsToAdd, $tagsToRemove, $prevTags ];
461  }
462 
473  public static function getTags( IDatabase $db, $rc_id = null, $rev_id = null, $log_id = null ) {
474  $conds = array_filter(
475  [
476  'ct_rc_id' => $rc_id,
477  'ct_rev_id' => $rev_id,
478  'ct_log_id' => $log_id,
479  ]
480  );
481 
482  $tagIds = $db->selectFieldValues(
483  'change_tag',
484  'ct_tag_id',
485  $conds,
486  __METHOD__
487  );
488 
489  $tags = [];
490  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
491  foreach ( $tagIds as $tagId ) {
492  $tags[] = $changeTagDefStore->getName( (int)$tagId );
493  }
494 
495  return $tags;
496  }
497 
508  protected static function restrictedTagError( $msgOne, $msgMulti, $tags ) {
509  $lang = RequestContext::getMain()->getLanguage();
510  $tags = array_values( $tags );
511  $count = count( $tags );
512  $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
513  $lang->commaList( $tags ), $count );
514  $status->value = $tags;
515  return $status;
516  }
517 
531  public static function canAddTagsAccompanyingChange( array $tags, User $user = null ) {
532  if ( $user !== null ) {
533  if ( !MediaWikiServices::getInstance()->getPermissionManager()
534  ->userHasRight( $user, 'applychangetags' )
535  ) {
536  return Status::newFatal( 'tags-apply-no-permission' );
537  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
538  return Status::newFatal( 'tags-apply-blocked', $user->getName() );
539  }
540  }
541 
542  // to be applied, a tag has to be explicitly defined
543  $allowedTags = self::listExplicitlyDefinedTags();
544  Hooks::runner()->onChangeTagsAllowedAdd( $allowedTags, $tags, $user );
545  $disallowedTags = array_diff( $tags, $allowedTags );
546  if ( $disallowedTags ) {
547  return self::restrictedTagError( 'tags-apply-not-allowed-one',
548  'tags-apply-not-allowed-multi', $disallowedTags );
549  }
550 
551  return Status::newGood();
552  }
553 
574  public static function addTagsAccompanyingChangeWithChecks(
575  array $tags, $rc_id, $rev_id, $log_id, $params, User $user
576  ) {
577  // are we allowed to do this?
578  $result = self::canAddTagsAccompanyingChange( $tags, $user );
579  if ( !$result->isOK() ) {
580  $result->value = null;
581  return $result;
582  }
583 
584  // do it!
585  self::addTags( $tags, $rc_id, $rev_id, $log_id, $params );
586 
587  return Status::newGood( true );
588  }
589 
604  public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove,
605  User $user = null
606  ) {
607  if ( $user !== null ) {
608  if ( !MediaWikiServices::getInstance()->getPermissionManager()
609  ->userHasRight( $user, 'changetags' )
610  ) {
611  return Status::newFatal( 'tags-update-no-permission' );
612  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
613  return Status::newFatal( 'tags-update-blocked', $user->getName() );
614  }
615  }
616 
617  if ( $tagsToAdd ) {
618  // to be added, a tag has to be explicitly defined
619  // @todo Allow extensions to define tags that can be applied by users...
620  $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
621  $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
622  if ( $diff ) {
623  return self::restrictedTagError( 'tags-update-add-not-allowed-one',
624  'tags-update-add-not-allowed-multi', $diff );
625  }
626  }
627 
628  if ( $tagsToRemove ) {
629  // to be removed, a tag must not be defined by an extension, or equivalently it
630  // has to be either explicitly defined or not defined at all
631  // (assuming no edge case of a tag both explicitly-defined and extension-defined)
632  $softwareDefinedTags = self::listSoftwareDefinedTags();
633  $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
634  if ( $intersect ) {
635  return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
636  'tags-update-remove-not-allowed-multi', $intersect );
637  }
638  }
639 
640  return Status::newGood();
641  }
642 
673  public static function updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
674  $rc_id, $rev_id, $log_id, $params, $reason, User $user
675  ) {
676  if ( $tagsToAdd === null ) {
677  $tagsToAdd = [];
678  }
679  if ( $tagsToRemove === null ) {
680  $tagsToRemove = [];
681  }
682  if ( !$tagsToAdd && !$tagsToRemove ) {
683  // no-op, don't bother
684  return Status::newGood( (object)[
685  'logId' => null,
686  'addedTags' => [],
687  'removedTags' => [],
688  ] );
689  }
690 
691  // are we allowed to do this?
692  $result = self::canUpdateTags( $tagsToAdd, $tagsToRemove, $user );
693  if ( !$result->isOK() ) {
694  $result->value = null;
695  return $result;
696  }
697 
698  // basic rate limiting
699  if ( $user->pingLimiter( 'changetag' ) ) {
700  return Status::newFatal( 'actionthrottledtext' );
701  }
702 
703  // do it!
704  list( $tagsAdded, $tagsRemoved, $initialTags ) = self::updateTags( $tagsToAdd,
705  $tagsToRemove, $rc_id, $rev_id, $log_id, $params, null, $user );
706  if ( !$tagsAdded && !$tagsRemoved ) {
707  // no-op, don't log it
708  return Status::newGood( (object)[
709  'logId' => null,
710  'addedTags' => [],
711  'removedTags' => [],
712  ] );
713  }
714 
715  // log it
716  $logEntry = new ManualLogEntry( 'tag', 'update' );
717  $logEntry->setPerformer( $user );
718  $logEntry->setComment( $reason );
719 
720  // find the appropriate target page
721  if ( $rev_id ) {
722  $revisionRecord = MediaWikiServices::getInstance()
723  ->getRevisionLookup()
724  ->getRevisionById( $rev_id );
725  if ( $revisionRecord ) {
726  $logEntry->setTarget( $revisionRecord->getPageAsLinkTarget() );
727  }
728  } elseif ( $log_id ) {
729  // This function is from revision deletion logic and has nothing to do with
730  // change tags, but it appears to be the only other place in core where we
731  // perform logged actions on log items.
732  $logEntry->setTarget( RevDelLogList::suggestTarget( null, [ $log_id ] ) );
733  }
734 
735  if ( !$logEntry->getTarget() ) {
736  // target is required, so we have to set something
737  $logEntry->setTarget( SpecialPage::getTitleFor( 'Tags' ) );
738  }
739 
740  $logParams = [
741  '4::revid' => $rev_id,
742  '5::logid' => $log_id,
743  '6:list:tagsAdded' => $tagsAdded,
744  '7:number:tagsAddedCount' => count( $tagsAdded ),
745  '8:list:tagsRemoved' => $tagsRemoved,
746  '9:number:tagsRemovedCount' => count( $tagsRemoved ),
747  'initialTags' => $initialTags,
748  ];
749  $logEntry->setParameters( $logParams );
750  $logEntry->setRelations( [ 'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
751 
752  $dbw = wfGetDB( DB_MASTER );
753  $logId = $logEntry->insert( $dbw );
754  // Only send this to UDP, not RC, similar to patrol events
755  $logEntry->publish( $logId, 'udp' );
756 
757  return Status::newGood( (object)[
758  'logId' => $logId,
759  'addedTags' => $tagsAdded,
760  'removedTags' => $tagsRemoved,
761  ] );
762  }
763 
784  public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
785  &$join_conds, &$options, $filter_tag = ''
786  ) {
787  global $wgUseTagFilter;
788 
789  // Normalize to arrays
790  $tables = (array)$tables;
791  $fields = (array)$fields;
792  $conds = (array)$conds;
793  $options = (array)$options;
794 
795  $fields['ts_tags'] = self::makeTagSummarySubquery( $tables );
796 
797  // Figure out which ID field to use
798  if ( in_array( 'recentchanges', $tables ) ) {
799  $join_cond = 'ct_rc_id=rc_id';
800  } elseif ( in_array( 'logging', $tables ) ) {
801  $join_cond = 'ct_log_id=log_id';
802  } elseif ( in_array( 'revision', $tables ) ) {
803  $join_cond = 'ct_rev_id=rev_id';
804  } elseif ( in_array( 'archive', $tables ) ) {
805  $join_cond = 'ct_rev_id=ar_rev_id';
806  } else {
807  throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
808  }
809 
810  if ( $wgUseTagFilter && $filter_tag ) {
811  // Somebody wants to filter on a tag.
812  // Add an INNER JOIN on change_tag
813 
814  $tables[] = 'change_tag';
815  $join_conds['change_tag'] = [ 'JOIN', $join_cond ];
816  $filterTagIds = [];
817  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
818  foreach ( (array)$filter_tag as $filterTagName ) {
819  try {
820  $filterTagIds[] = $changeTagDefStore->getId( $filterTagName );
821  } catch ( NameTableAccessException $exception ) {
822  // Return nothing.
823  $conds[] = '0=1';
824  break;
825  }
826  }
827 
828  if ( $filterTagIds !== [] ) {
829  $conds['ct_tag_id'] = $filterTagIds;
830  }
831 
832  if (
833  is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
834  !in_array( 'DISTINCT', $options )
835  ) {
836  $options[] = 'DISTINCT';
837  }
838  }
839  }
840 
849  public static function makeTagSummarySubquery( $tables ) {
850  // Normalize to arrays
851  $tables = (array)$tables;
852 
853  // Figure out which ID field to use
854  if ( in_array( 'recentchanges', $tables ) ) {
855  $join_cond = 'ct_rc_id=rc_id';
856  } elseif ( in_array( 'logging', $tables ) ) {
857  $join_cond = 'ct_log_id=log_id';
858  } elseif ( in_array( 'revision', $tables ) ) {
859  $join_cond = 'ct_rev_id=rev_id';
860  } elseif ( in_array( 'archive', $tables ) ) {
861  $join_cond = 'ct_rev_id=ar_rev_id';
862  } else {
863  throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
864  }
865 
866  $tagTables = [ 'change_tag', 'change_tag_def' ];
867  $join_cond_ts_tags = [ 'change_tag_def' => [ 'JOIN', 'ct_tag_id=ctd_id' ] ];
868  $field = 'ctd_name';
869 
870  return wfGetDB( DB_REPLICA )->buildGroupConcatField(
871  ',', $tagTables, $field, $join_cond, $join_cond_ts_tags
872  );
873  }
874 
886  public static function buildTagFilterSelector(
887  $selected = '', $ooui = false, IContextSource $context = null
888  ) {
889  if ( !$context ) {
890  $context = RequestContext::getMain();
891  }
892 
893  $config = $context->getConfig();
894  if ( !$config->get( 'UseTagFilter' ) || !count( self::listDefinedTags() ) ) {
895  return [];
896  }
897 
898  $data = [
900  'label',
901  [ 'for' => 'tagfilter' ],
902  $context->msg( 'tag-filter' )->parse()
903  )
904  ];
905 
906  if ( $ooui ) {
907  $data[] = new OOUI\TextInputWidget( [
908  'id' => 'tagfilter',
909  'name' => 'tagfilter',
910  'value' => $selected,
911  'classes' => 'mw-tagfilter-input',
912  ] );
913  } else {
914  $data[] = Xml::input(
915  'tagfilter',
916  20,
917  $selected,
918  [ 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' ]
919  );
920  }
921 
922  return $data;
923  }
924 
933  public static function defineTag( $tag ) {
934  $dbw = wfGetDB( DB_MASTER );
935  $tagDef = [
936  'ctd_name' => $tag,
937  'ctd_user_defined' => 1,
938  'ctd_count' => 0
939  ];
940  $dbw->upsert(
941  'change_tag_def',
942  $tagDef,
943  'ctd_name',
944  [ 'ctd_user_defined' => 1 ],
945  __METHOD__
946  );
947 
948  // clear the memcache of defined tags
950  }
951 
960  public static function undefineTag( $tag ) {
961  $dbw = wfGetDB( DB_MASTER );
962 
963  $dbw->update(
964  'change_tag_def',
965  [ 'ctd_user_defined' => 0 ],
966  [ 'ctd_name' => $tag ],
967  __METHOD__
968  );
969 
970  $dbw->delete(
971  'change_tag_def',
972  [ 'ctd_name' => $tag, 'ctd_count' => 0 ],
973  __METHOD__
974  );
975 
976  // clear the memcache of defined tags
978  }
979 
994  protected static function logTagManagementAction( $action, $tag, $reason,
995  User $user, $tagCount = null, array $logEntryTags = []
996  ) {
997  $dbw = wfGetDB( DB_MASTER );
998 
999  $logEntry = new ManualLogEntry( 'managetags', $action );
1000  $logEntry->setPerformer( $user );
1001  // target page is not relevant, but it has to be set, so we just put in
1002  // the title of Special:Tags
1003  $logEntry->setTarget( Title::newFromText( 'Special:Tags' ) );
1004  $logEntry->setComment( $reason );
1005 
1006  $params = [ '4::tag' => $tag ];
1007  if ( $tagCount !== null ) {
1008  $params['5:number:count'] = $tagCount;
1009  }
1010  $logEntry->setParameters( $params );
1011  $logEntry->setRelations( [ 'Tag' => $tag ] );
1012  $logEntry->addTags( $logEntryTags );
1013 
1014  $logId = $logEntry->insert( $dbw );
1015  $logEntry->publish( $logId );
1016  return $logId;
1017  }
1018 
1028  public static function canActivateTag( $tag, User $user = null ) {
1029  if ( $user !== null ) {
1030  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1031  ->userHasRight( $user, 'managechangetags' )
1032  ) {
1033  return Status::newFatal( 'tags-manage-no-permission' );
1034  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1035  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1036  }
1037  }
1038 
1039  // defined tags cannot be activated (a defined tag is either extension-
1040  // defined, in which case the extension chooses whether or not to active it;
1041  // or user-defined, in which case it is considered active)
1042  $definedTags = self::listDefinedTags();
1043  if ( in_array( $tag, $definedTags ) ) {
1044  return Status::newFatal( 'tags-activate-not-allowed', $tag );
1045  }
1046 
1047  // non-existing tags cannot be activated
1048  $tagUsage = self::tagUsageStatistics();
1049  if ( !isset( $tagUsage[$tag] ) ) { // we already know the tag is undefined
1050  return Status::newFatal( 'tags-activate-not-found', $tag );
1051  }
1052 
1053  return Status::newGood();
1054  }
1055 
1073  public static function activateTagWithChecks( $tag, $reason, User $user,
1074  $ignoreWarnings = false, array $logEntryTags = []
1075  ) {
1076  // are we allowed to do this?
1077  $result = self::canActivateTag( $tag, $user );
1078  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1079  $result->value = null;
1080  return $result;
1081  }
1082 
1083  // do it!
1084  self::defineTag( $tag );
1085 
1086  // log it
1087  $logId = self::logTagManagementAction( 'activate', $tag, $reason, $user,
1088  null, $logEntryTags );
1089 
1090  return Status::newGood( $logId );
1091  }
1092 
1102  public static function canDeactivateTag( $tag, User $user = null ) {
1103  if ( $user !== null ) {
1104  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1105  ->userHasRight( $user, 'managechangetags' )
1106  ) {
1107  return Status::newFatal( 'tags-manage-no-permission' );
1108  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1109  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1110  }
1111  }
1112 
1113  // only explicitly-defined tags can be deactivated
1114  $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
1115  if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
1116  return Status::newFatal( 'tags-deactivate-not-allowed', $tag );
1117  }
1118  return Status::newGood();
1119  }
1120 
1138  public static function deactivateTagWithChecks( $tag, $reason, User $user,
1139  $ignoreWarnings = false, array $logEntryTags = []
1140  ) {
1141  // are we allowed to do this?
1142  $result = self::canDeactivateTag( $tag, $user );
1143  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1144  $result->value = null;
1145  return $result;
1146  }
1147 
1148  // do it!
1149  self::undefineTag( $tag );
1150 
1151  // log it
1152  $logId = self::logTagManagementAction( 'deactivate', $tag, $reason, $user,
1153  null, $logEntryTags );
1154 
1155  return Status::newGood( $logId );
1156  }
1157 
1165  public static function isTagNameValid( $tag ) {
1166  // no empty tags
1167  if ( $tag === '' ) {
1168  return Status::newFatal( 'tags-create-no-name' );
1169  }
1170 
1171  // tags cannot contain commas (used to be used as a delimiter in tag_summary table),
1172  // pipe (used as a delimiter between multiple tags in
1173  // SpecialRecentchanges and friends), or slashes (would break tag description messages in
1174  // MediaWiki namespace)
1175  if ( strpos( $tag, ',' ) !== false || strpos( $tag, '|' ) !== false
1176  || strpos( $tag, '/' ) !== false ) {
1177  return Status::newFatal( 'tags-create-invalid-chars' );
1178  }
1179 
1180  // could the MediaWiki namespace description messages be created?
1181  $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Tag-$tag-description" );
1182  if ( $title === null ) {
1183  return Status::newFatal( 'tags-create-invalid-title-chars' );
1184  }
1185 
1186  return Status::newGood();
1187  }
1188 
1201  public static function canCreateTag( $tag, User $user = null ) {
1202  if ( $user !== null ) {
1203  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1204  ->userHasRight( $user, 'managechangetags' )
1205  ) {
1206  return Status::newFatal( 'tags-manage-no-permission' );
1207  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1208  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1209  }
1210  }
1211 
1212  $status = self::isTagNameValid( $tag );
1213  if ( !$status->isGood() ) {
1214  return $status;
1215  }
1216 
1217  // does the tag already exist?
1218  $tagUsage = self::tagUsageStatistics();
1219  if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
1220  return Status::newFatal( 'tags-create-already-exists', $tag );
1221  }
1222 
1223  // check with hooks
1224  $canCreateResult = Status::newGood();
1225  Hooks::runner()->onChangeTagCanCreate( $tag, $user, $canCreateResult );
1226  return $canCreateResult;
1227  }
1228 
1248  public static function createTagWithChecks( $tag, $reason, User $user,
1249  $ignoreWarnings = false, array $logEntryTags = []
1250  ) {
1251  // are we allowed to do this?
1252  $result = self::canCreateTag( $tag, $user );
1253  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1254  $result->value = null;
1255  return $result;
1256  }
1257 
1258  // do it!
1259  self::defineTag( $tag );
1260 
1261  // log it
1262  $logId = self::logTagManagementAction( 'create', $tag, $reason, $user,
1263  null, $logEntryTags );
1264 
1265  return Status::newGood( $logId );
1266  }
1267 
1280  public static function deleteTagEverywhere( $tag ) {
1281  $dbw = wfGetDB( DB_MASTER );
1282  $dbw->startAtomic( __METHOD__ );
1283 
1284  // fetch tag id, this must be done before calling undefineTag(), see T225564
1285  $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
1286 
1287  // set ctd_user_defined = 0
1288  self::undefineTag( $tag );
1289 
1290  // delete from change_tag
1291  $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ );
1292  $dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
1293  $dbw->endAtomic( __METHOD__ );
1294 
1295  // give extensions a chance
1296  $status = Status::newGood();
1297  Hooks::runner()->onChangeTagAfterDelete( $tag, $status );
1298  // let's not allow error results, as the actual tag deletion succeeded
1299  if ( !$status->isOK() ) {
1300  wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' );
1301  $status->setOK( true );
1302  }
1303 
1304  // clear the memcache of defined tags
1306 
1307  return $status;
1308  }
1309 
1322  public static function canDeleteTag( $tag, User $user = null, int $flags = 0 ) {
1323  $tagUsage = self::tagUsageStatistics();
1324 
1325  if ( $user !== null ) {
1326  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1327  ->userHasRight( $user, 'deletechangetags' )
1328  ) {
1329  return Status::newFatal( 'tags-delete-no-permission' );
1330  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1331  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1332  }
1333  }
1334 
1335  if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
1336  return Status::newFatal( 'tags-delete-not-found', $tag );
1337  }
1338 
1339  if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1340  isset( $tagUsage[$tag] ) &&
1341  $tagUsage[$tag] > self::MAX_DELETE_USES
1342  ) {
1343  return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1344  }
1345 
1346  $softwareDefined = self::listSoftwareDefinedTags();
1347  if ( in_array( $tag, $softwareDefined ) ) {
1348  // extension-defined tags can't be deleted unless the extension
1349  // specifically allows it
1350  $status = Status::newFatal( 'tags-delete-not-allowed' );
1351  } else {
1352  // user-defined tags are deletable unless otherwise specified
1353  $status = Status::newGood();
1354  }
1355 
1356  Hooks::runner()->onChangeTagCanDelete( $tag, $user, $status );
1357  return $status;
1358  }
1359 
1377  public static function deleteTagWithChecks( $tag, $reason, User $user,
1378  $ignoreWarnings = false, array $logEntryTags = []
1379  ) {
1380  // are we allowed to do this?
1381  $result = self::canDeleteTag( $tag, $user );
1382  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1383  $result->value = null;
1384  return $result;
1385  }
1386 
1387  // store the tag usage statistics
1388  $tagUsage = self::tagUsageStatistics();
1389  $hitcount = $tagUsage[$tag] ?? 0;
1390 
1391  // do it!
1392  $deleteResult = self::deleteTagEverywhere( $tag );
1393  if ( !$deleteResult->isOK() ) {
1394  return $deleteResult;
1395  }
1396 
1397  // log it
1398  $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user,
1399  $hitcount, $logEntryTags );
1400 
1401  $deleteResult->value = $logId;
1402  return $deleteResult;
1403  }
1404 
1411  public static function listSoftwareActivatedTags() {
1412  // core active tags
1413  $tags = self::getSoftwareTags();
1414  $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1415  if ( !$hookContainer->isRegistered( 'ChangeTagsListActive' ) ) {
1416  return $tags;
1417  }
1418  $hookRunner = new HookRunner( $hookContainer );
1419  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1420  return $cache->getWithSetCallback(
1421  $cache->makeKey( 'active-tags' ),
1422  WANObjectCache::TTL_MINUTE * 5,
1423  function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1424  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1425 
1426  // Ask extensions which tags they consider active
1427  $hookRunner->onChangeTagsListActive( $tags );
1428  return $tags;
1429  },
1430  [
1431  'checkKeys' => [ $cache->makeKey( 'active-tags' ) ],
1432  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1433  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1434  ]
1435  );
1436  }
1437 
1444  public static function listDefinedTags() {
1446  $tags2 = self::listSoftwareDefinedTags();
1447  return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
1448  }
1449 
1458  public static function listExplicitlyDefinedTags() {
1459  $fname = __METHOD__;
1460 
1461  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1462  return $cache->getWithSetCallback(
1463  $cache->makeKey( 'valid-tags-db' ),
1464  WANObjectCache::TTL_MINUTE * 5,
1465  function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1466  $dbr = wfGetDB( DB_REPLICA );
1467 
1468  $setOpts += Database::getCacheSetOptions( $dbr );
1469 
1470  $tags = $dbr->selectFieldValues(
1471  'change_tag_def',
1472  'ctd_name',
1473  [ 'ctd_user_defined' => 1 ],
1474  $fname
1475  );
1476 
1477  return array_filter( array_unique( $tags ) );
1478  },
1479  [
1480  'checkKeys' => [ $cache->makeKey( 'valid-tags-db' ) ],
1481  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1482  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1483  ]
1484  );
1485  }
1486 
1496  public static function listSoftwareDefinedTags() {
1497  // core defined tags
1498  $tags = self::getSoftwareTags( true );
1499  $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1500  if ( !$hookContainer->isRegistered( 'ListDefinedTags' ) ) {
1501  return $tags;
1502  }
1503  $hookRunner = new HookRunner( $hookContainer );
1504  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1505  return $cache->getWithSetCallback(
1506  $cache->makeKey( 'valid-tags-hook' ),
1507  WANObjectCache::TTL_MINUTE * 5,
1508  function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags, $hookRunner ) {
1509  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1510 
1511  $hookRunner->onListDefinedTags( $tags );
1512  return array_filter( array_unique( $tags ) );
1513  },
1514  [
1515  'checkKeys' => [ $cache->makeKey( 'valid-tags-hook' ) ],
1516  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1517  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1518  ]
1519  );
1520  }
1521 
1527  public static function purgeTagCacheAll() {
1528  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1529 
1530  $cache->touchCheckKey( $cache->makeKey( 'active-tags' ) );
1531  $cache->touchCheckKey( $cache->makeKey( 'valid-tags-db' ) );
1532  $cache->touchCheckKey( $cache->makeKey( 'valid-tags-hook' ) );
1533  $cache->touchCheckKey( $cache->makeKey( 'tags-usage-statistics' ) );
1534 
1535  MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
1536  }
1537 
1544  public static function tagUsageStatistics() {
1545  $fname = __METHOD__;
1546 
1547  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1548  return $cache->getWithSetCallback(
1549  $cache->makeKey( 'tags-usage-statistics' ),
1550  WANObjectCache::TTL_MINUTE * 5,
1551  function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1552  $dbr = wfGetDB( DB_REPLICA );
1553  $res = $dbr->select(
1554  'change_tag_def',
1555  [ 'ctd_name', 'ctd_count' ],
1556  [],
1557  $fname,
1558  [ 'ORDER BY' => 'ctd_count DESC' ]
1559  );
1560 
1561  $out = [];
1562  foreach ( $res as $row ) {
1563  $out[$row->ctd_name] = $row->ctd_count;
1564  }
1565 
1566  return $out;
1567  },
1568  [
1569  'checkKeys' => [ $cache->makeKey( 'tags-usage-statistics' ) ],
1570  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1571  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1572  ]
1573  );
1574  }
1575 
1590  public static function showTagEditingUI( User $user ) {
1591  return MediaWikiServices::getInstance()->getPermissionManager()
1592  ->userHasRight( $user, 'changetags' ) &&
1593  (bool)self::listExplicitlyDefinedTags();
1594  }
1595 }
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:50
ChangeTags\makeTagSummarySubquery
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
Definition: ChangeTags.php:849
$wgUseTagFilter
$wgUseTagFilter
Allow filtering by change tag in recentchanges, history, etc Has no effect if no tags are defined in ...
Definition: DefaultSettings.php:7448
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:329
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
ChangeTags\purgeTagCacheAll
static purgeTagCacheAll()
Invalidates the short-term cache of defined tags used by the list*DefinedTags functions,...
Definition: ChangeTags.php:1527
Sanitizer\stripAllTags
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1860
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:152
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:72
ChangeTags\logTagManagementAction
static logTagManagementAction( $action, $tag, $reason, User $user, $tagCount=null, array $logEntryTags=[])
Writes a tag action into the tag management log.
Definition: ChangeTags.php:994
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:90
Sanitizer\escapeClass
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1264
ChangeTags\showTagEditingUI
static showTagEditingUI(User $user)
Indicate whether change tag editing UI is relevant.
Definition: ChangeTags.php:1590
ChangeTags\buildTagFilterSelector
static buildTagFilterSelector( $selected='', $ooui=false, IContextSource $context=null)
Build a text box to select a change tag.
Definition: ChangeTags.php:886
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1760
$res
$res
Definition: testCompression.php:57
ChangeTags\restrictedTagError
static restrictedTagError( $msgOne, $msgMulti, $tags)
Helper function to generate a fatal status with a 'not-allowed' type error.
Definition: ChangeTags.php:508
ChangeTags\isTagNameValid
static isTagNameValid( $tag)
Is the tag name valid?
Definition: ChangeTags.php:1165
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MessageLocalizer
Interface for localizing messages in MediaWiki.
Definition: MessageLocalizer.php:27
ChangeTags\BYPASS_MAX_USAGE_CHECK
const BYPASS_MAX_USAGE_CHECK
Flag for canDeleteTag().
Definition: ChangeTags.php:41
$dbr
$dbr
Definition: testCompression.php:54
ChangeTags\truncateTagDescription
static truncateTagDescription( $tag, $length, IContextSource $context)
Get truncated message for the tag's long description.
Definition: ChangeTags.php:230
ChangeTags\tagShortDescriptionMessage
static tagShortDescriptionMessage( $tag, MessageLocalizer $context)
Get the message object for the tag's short description.
Definition: ChangeTags.php:155
ChangeTags\listSoftwareDefinedTags
static listSoftwareDefinedTags()
Lists tags defined by core or extensions using the ListDefinedTags hook.
Definition: ChangeTags.php:1496
ChangeTags\deleteTagEverywhere
static deleteTagEverywhere( $tag)
Permanently removes all traces of a tag from the DB.
Definition: ChangeTags.php:1280
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:784
ChangeTags\getTags
static getTags(IDatabase $db, $rc_id=null, $rev_id=null, $log_id=null)
Return all the tags associated with the given recent change ID, revision ID, and/or log entry ID.
Definition: ChangeTags.php:473
MWException
MediaWiki exception.
Definition: MWException.php:26
ChangeTags\undefineTag
static undefineTag( $tag)
Update ctd_user_defined = 0 field in change_tag_def.
Definition: ChangeTags.php:960
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1029
MessageLocalizer\msg
msg( $key,... $params)
This is the method for getting translated interface messages.
ChangeTags
Definition: ChangeTags.php:30
getPermissionManager
getPermissionManager()
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2464
ChangeTags\listDefinedTags
static listDefinedTags()
Basically lists defined tags which count even if they aren't applied to anything.
Definition: ChangeTags.php:1444
ChangeTags\getSoftwareTags
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:64
$title
$title
Definition: testCompression.php:38
ChangeTags\MAX_DELETE_USES
const MAX_DELETE_USES
Can't delete tags with more than this many uses.
Definition: ChangeTags.php:36
ChangeTags\defineTag
static defineTag( $tag)
Set ctd_user_defined = 1 in change_tag_def without checking that the tag name is valid.
Definition: ChangeTags.php:933
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
$wgSoftwareTags
array $wgSoftwareTags
List of core tags to enable.
Definition: DefaultSettings.php:7464
DB_MASTER
const DB_MASTER
Definition: defines.php:26
ChangeTags\$definedSoftwareTags
static $definedSoftwareTags
A list of tags defined and used by MediaWiki itself.
Definition: ChangeTags.php:46
ChangeTags\canActivateTag
static canActivateTag( $tag, User $user=null)
Is it OK to allow the user to activate this tag?
Definition: ChangeTags.php:1028
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:912
ChangeTags\listExplicitlyDefinedTags
static listExplicitlyDefinedTags()
Lists tags explicitly defined in the change_tag_def table of the database.
Definition: ChangeTags.php:1458
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:618
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
Message\plaintextParam
static plaintextParam( $plaintext)
Definition: Message.php:1112
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:130
ChangeTags\canDeleteTag
static canDeleteTag( $tag, User $user=null, int $flags=0)
Is it OK to allow the user to delete this tag?
Definition: ChangeTags.php:1322
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
ChangeTags\deactivateTagWithChecks
static deactivateTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Deactivates a tag, checking whether it is allowed first, and adding a log entry afterwards.
Definition: ChangeTags.php:1138
RevDelLogList\suggestTarget
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
Definition: RevDelLogList.php:45
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
ChangeTags\listSoftwareActivatedTags
static listSoftwareActivatedTags()
Lists those tags which core or extensions report as being "active".
Definition: ChangeTags.php:1411
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:531
$cache
$cache
Definition: mcc.php:33
ChangeTags\canCreateTag
static canCreateTag( $tag, User $user=null)
Is it OK to allow the user to create this tag?
Definition: ChangeTags.php:1201
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
ChangeTags\updateTags
static updateTags( $tagsToAdd, $tagsToRemove, &$rc_id=null, &$rev_id=null, &$log_id=null, $params=null, RecentChange $rc=null, User $user=null)
Add and remove tags to/from a change given its rc_id, rev_id and/or log_id, without verifying that th...
Definition: ChangeTags.php:296
MediaWiki\Storage\NameTableAccessException
Exception representing a failure to look up a row from a name table.
Definition: NameTableAccessException.php:32
Wikimedia\Rdbms\IDatabase\selectFieldValues
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:38
ChangeTags\tagUsageStatistics
static tagUsageStatistics()
Returns a map of any tags used on the wiki to number of edits tagged with them, ordered descending by...
Definition: ChangeTags.php:1544
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:278
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1076
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:77
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:571
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
ChangeTags\addTagsAccompanyingChangeWithChecks
static addTagsAccompanyingChangeWithChecks(array $tags, $rc_id, $rev_id, $log_id, $params, User $user)
Adds tags to a given change, checking whether it is allowed first, but without adding a log entry.
Definition: ChangeTags.php:574
ChangeTags\createTagWithChecks
static createTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Creates a tag by adding it to change_tag_def table.
Definition: ChangeTags.php:1248
ChangeTags\canUpdateTags
static canUpdateTags(array $tagsToAdd, array $tagsToRemove, User $user=null)
Is it OK to allow the user to adds and remove the given tags tags to/from a change?
Definition: ChangeTags.php:604
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:54
ChangeTags\tagLongDescriptionMessage
static tagLongDescriptionMessage( $tag, MessageLocalizer $context)
Get the message object for the tag's long description.
Definition: ChangeTags.php:206
ChangeTags\deleteTagWithChecks
static deleteTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Deletes a tag, checking whether it is allowed first, and adding a log entry afterwards.
Definition: ChangeTags.php:1377
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:100
ChangeTags\updateTagsWithChecks
static updateTagsWithChecks( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id, $params, $reason, User $user)
Adds and/or removes tags to/from a given change, checking whether it is allowed first,...
Definition: ChangeTags.php:673
ChangeTags\addTags
static addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params=null, RecentChange $rc=null)
Add tags to a change given its rc_id, rev_id and/or log_id.
Definition: ChangeTags.php:259
ChangeTags\tagDescription
static tagDescription( $tag, MessageLocalizer $context)
Get a short description for a tag.
Definition: ChangeTags.php:189
IContextSource\getLanguage
getLanguage()
ChangeTags\activateTagWithChecks
static activateTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Activates a tag, checking whether it is allowed first, and adding a log entry afterwards.
Definition: ChangeTags.php:1073
ChangeTags\canDeactivateTag
static canDeactivateTag( $tag, User $user=null)
Is it OK to allow the user to deactivate this tag?
Definition: ChangeTags.php:1102