MediaWiki  master
ChangeTags.php
Go to the documentation of this file.
1 <?php
28 
29 class ChangeTags {
35  const MAX_DELETE_USES = 5000;
36 
40  public const BYPASS_MAX_USAGE_CHECK = 1;
41 
45  private static $definedSoftwareTags = [
46  'mw-contentmodelchange',
47  'mw-new-redirect',
48  'mw-removed-redirect',
49  'mw-changed-redirect-target',
50  'mw-blank',
51  'mw-replace',
52  'mw-rollback',
53  'mw-undo',
54  ];
55 
63  public static function getSoftwareTags( $all = false ) {
64  global $wgSoftwareTags;
65  $softwareTags = [];
66 
67  if ( !is_array( $wgSoftwareTags ) ) {
68  wfWarn( 'wgSoftwareTags should be associative array of enabled tags.
69  Please refer to documentation for the list of tags you can enable' );
70  return $softwareTags;
71  }
72 
73  $availableSoftwareTags = !$all ?
74  array_keys( array_filter( $wgSoftwareTags ) ) :
75  array_keys( $wgSoftwareTags );
76 
77  $softwareTags = array_intersect(
78  $availableSoftwareTags,
79  self::$definedSoftwareTags
80  );
81 
82  return $softwareTags;
83  }
84 
99  public static function formatSummaryRow( $tags, $page, IContextSource $context = null ) {
100  if ( !$tags ) {
101  return [ '', [] ];
102  }
103  if ( !$context ) {
105  }
106 
107  $classes = [];
108 
109  $tags = explode( ',', $tags );
110  $displayTags = [];
111  foreach ( $tags as $tag ) {
112  if ( !$tag ) {
113  continue;
114  }
115  $description = self::tagDescription( $tag, $context );
116  if ( $description === false ) {
117  continue;
118  }
119  $displayTags[] = Xml::tags(
120  'span',
121  [ 'class' => 'mw-tag-marker ' .
122  Sanitizer::escapeClass( "mw-tag-marker-$tag" ) ],
123  $description
124  );
125  $classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
126  }
127 
128  if ( !$displayTags ) {
129  return [ '', [] ];
130  }
131 
132  $markers = $context->msg( 'tag-list-wrapper' )
133  ->numParams( count( $displayTags ) )
134  ->rawParams( implode( ' ', $displayTags ) )
135  ->parse();
136  $markers = Xml::tags( 'span', [ 'class' => 'mw-tag-markers' ], $markers );
137 
138  return [ $markers, $classes ];
139  }
140 
154  public static function tagShortDescriptionMessage( $tag, MessageLocalizer $context ) {
155  $msg = $context->msg( "tag-$tag" );
156  if ( !$msg->exists() ) {
157  // No such message
158  return ( new RawMessage( '$1', [ Message::plaintextParam( $tag ) ] ) )
159  // HACK MessageLocalizer doesn't have a way to set the right language on a RawMessage,
160  // so extract the language from $msg and use that.
161  // The language doesn't really matter, but we need to set it to avoid requesting
162  // the user's language from session-less entry points (T227233)
163  ->inLanguage( $msg->getLanguage() );
164 
165  }
166  if ( $msg->isDisabled() ) {
167  // The message exists but is disabled, hide the tag.
168  return false;
169  }
170 
171  // Message exists and isn't disabled, use it.
172  return $msg;
173  }
174 
188  public static function tagDescription( $tag, MessageLocalizer $context ) {
190  return $msg ? $msg->parse() : false;
191  }
192 
205  public static function tagLongDescriptionMessage( $tag, MessageLocalizer $context ) {
206  $msg = $context->msg( "tag-$tag-description" );
207  if ( !$msg->exists() ) {
208  return false;
209  }
210  if ( $msg->isDisabled() ) {
211  // The message exists but is disabled, hide the description.
212  return false;
213  }
214 
215  // Message exists and isn't disabled, use it.
216  return $msg;
217  }
218 
228  public static function truncateTagDescription( $tag, $length, IContextSource $context ) {
229  // FIXME: Make this accept MessageLocalizer and Language instead of IContextSource
230 
231  $originalDesc = self::tagLongDescriptionMessage( $tag, $context );
232  // If there is no tag description, return empty string
233  if ( !$originalDesc ) {
234  return '';
235  }
236 
237  $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
238 
239  return $context->getLanguage()->truncateForVisual( $taglessDesc, $length );
240  }
241 
256  public static function addTags( $tags, $rc_id = null, $rev_id = null,
257  $log_id = null, $params = null, RecentChange $rc = null
258  ) {
259  $result = self::updateTags( $tags, null, $rc_id, $rev_id, $log_id, $params, $rc );
260  return (bool)$result[0];
261  }
262 
293  public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null,
294  &$rev_id = null, &$log_id = null, $params = null, RecentChange $rc = null,
295  User $user = null
296  ) {
297  $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
298  $tagsToRemove = array_filter( (array)$tagsToRemove );
299 
300  if ( !$rc_id && !$rev_id && !$log_id ) {
301  throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' .
302  'specified when adding or removing a tag from a change!' );
303  }
304 
305  $dbw = wfGetDB( DB_MASTER );
306 
307  // Might as well look for rcids and so on.
308  if ( !$rc_id ) {
309  // Info might be out of date, somewhat fractionally, on replica DB.
310  // LogEntry/LogPage and WikiPage match rev/log/rc timestamps,
311  // so use that relation to avoid full table scans.
312  if ( $log_id ) {
313  $rc_id = $dbw->selectField(
314  [ 'logging', 'recentchanges' ],
315  'rc_id',
316  [
317  'log_id' => $log_id,
318  'rc_timestamp = log_timestamp',
319  'rc_logid = log_id'
320  ],
321  __METHOD__
322  );
323  } elseif ( $rev_id ) {
324  $rc_id = $dbw->selectField(
325  [ 'revision', 'recentchanges' ],
326  'rc_id',
327  [
328  'rev_id' => $rev_id,
329  'rc_this_oldid = rev_id'
330  ],
331  __METHOD__
332  );
333  }
334  } elseif ( !$log_id && !$rev_id ) {
335  // Info might be out of date, somewhat fractionally, on replica DB.
336  $log_id = $dbw->selectField(
337  'recentchanges',
338  'rc_logid',
339  [ 'rc_id' => $rc_id ],
340  __METHOD__
341  );
342  $rev_id = $dbw->selectField(
343  'recentchanges',
344  'rc_this_oldid',
345  [ 'rc_id' => $rc_id ],
346  __METHOD__
347  );
348  }
349 
350  if ( $log_id && !$rev_id ) {
351  $rev_id = $dbw->selectField(
352  'log_search',
353  'ls_value',
354  [ 'ls_field' => 'associated_rev_id', 'ls_log_id' => $log_id ],
355  __METHOD__
356  );
357  } elseif ( !$log_id && $rev_id ) {
358  $log_id = $dbw->selectField(
359  'log_search',
360  'ls_log_id',
361  [ 'ls_field' => 'associated_rev_id', 'ls_value' => (string)$rev_id ],
362  __METHOD__
363  );
364  }
365 
366  $prevTags = self::getTags( $dbw, $rc_id, $rev_id, $log_id );
367 
368  // add tags
369  $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
370  $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
371 
372  // remove tags
373  $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
374  $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
375 
376  sort( $prevTags );
377  sort( $newTags );
378  if ( $prevTags == $newTags ) {
379  return [ [], [], $prevTags ];
380  }
381 
382  // insert a row into change_tag for each new tag
383  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
384  if ( count( $tagsToAdd ) ) {
385  $changeTagMapping = [];
386  foreach ( $tagsToAdd as $tag ) {
387  $changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
388  }
389  $fname = __METHOD__;
390  // T207881: update the counts at the end of the transaction
391  $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $tagsToAdd, $fname ) {
392  $dbw->update(
393  'change_tag_def',
394  [ 'ctd_count = ctd_count + 1' ],
395  [ 'ctd_name' => $tagsToAdd ],
396  $fname
397  );
398  } );
399 
400  $tagsRows = [];
401  foreach ( $tagsToAdd as $tag ) {
402  // Filter so we don't insert NULLs as zero accidentally.
403  // Keep in mind that $rc_id === null means "I don't care/know about the
404  // rc_id, just delete $tag on this revision/log entry". It doesn't
405  // mean "only delete tags on this revision/log WHERE rc_id IS NULL".
406  $tagsRows[] = array_filter(
407  [
408  'ct_rc_id' => $rc_id,
409  'ct_log_id' => $log_id,
410  'ct_rev_id' => $rev_id,
411  'ct_params' => $params,
412  'ct_tag_id' => $changeTagMapping[$tag] ?? null,
413  ]
414  );
415 
416  }
417 
418  $dbw->insert( 'change_tag', $tagsRows, __METHOD__, [ 'IGNORE' ] );
419  }
420 
421  // delete from change_tag
422  if ( count( $tagsToRemove ) ) {
423  $fname = __METHOD__;
424  foreach ( $tagsToRemove as $tag ) {
425  $conds = array_filter(
426  [
427  'ct_rc_id' => $rc_id,
428  'ct_log_id' => $log_id,
429  'ct_rev_id' => $rev_id,
430  'ct_tag_id' => $changeTagDefStore->getId( $tag ),
431  ]
432  );
433  $dbw->delete( 'change_tag', $conds, __METHOD__ );
434  if ( $dbw->affectedRows() ) {
435  // T207881: update the counts at the end of the transaction
436  $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $tag, $fname ) {
437  $dbw->update(
438  'change_tag_def',
439  [ 'ctd_count = ctd_count - 1' ],
440  [ 'ctd_name' => $tag ],
441  $fname
442  );
443 
444  $dbw->delete(
445  'change_tag_def',
446  [ 'ctd_name' => $tag, 'ctd_count' => 0, 'ctd_user_defined' => 0 ],
447  $fname
448  );
449  } );
450  }
451  }
452  }
453 
454  Hooks::run( 'ChangeTagsAfterUpdateTags', [ $tagsToAdd, $tagsToRemove, $prevTags,
455  $rc_id, $rev_id, $log_id, $params, $rc, $user ] );
456 
457  return [ $tagsToAdd, $tagsToRemove, $prevTags ];
458  }
459 
470  public static function getTags( IDatabase $db, $rc_id = null, $rev_id = null, $log_id = null ) {
471  $conds = array_filter(
472  [
473  'ct_rc_id' => $rc_id,
474  'ct_rev_id' => $rev_id,
475  'ct_log_id' => $log_id,
476  ]
477  );
478 
479  $tagIds = $db->selectFieldValues(
480  'change_tag',
481  'ct_tag_id',
482  $conds,
483  __METHOD__
484  );
485 
486  $tags = [];
487  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
488  foreach ( $tagIds as $tagId ) {
489  $tags[] = $changeTagDefStore->getName( (int)$tagId );
490  }
491 
492  return $tags;
493  }
494 
505  protected static function restrictedTagError( $msgOne, $msgMulti, $tags ) {
506  $lang = RequestContext::getMain()->getLanguage();
507  $tags = array_values( $tags );
508  $count = count( $tags );
509  $status = Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
510  $lang->commaList( $tags ), $count );
511  $status->value = $tags;
512  return $status;
513  }
514 
528  public static function canAddTagsAccompanyingChange( array $tags, User $user = null ) {
529  if ( $user !== null ) {
530  if ( !MediaWikiServices::getInstance()->getPermissionManager()
531  ->userHasRight( $user, 'applychangetags' )
532  ) {
533  return Status::newFatal( 'tags-apply-no-permission' );
534  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
535  return Status::newFatal( 'tags-apply-blocked', $user->getName() );
536  }
537  }
538 
539  // to be applied, a tag has to be explicitly defined
540  $allowedTags = self::listExplicitlyDefinedTags();
541  Hooks::run( 'ChangeTagsAllowedAdd', [ &$allowedTags, $tags, $user ] );
542  $disallowedTags = array_diff( $tags, $allowedTags );
543  if ( $disallowedTags ) {
544  return self::restrictedTagError( 'tags-apply-not-allowed-one',
545  'tags-apply-not-allowed-multi', $disallowedTags );
546  }
547 
548  return Status::newGood();
549  }
550 
571  public static function addTagsAccompanyingChangeWithChecks(
572  array $tags, $rc_id, $rev_id, $log_id, $params, User $user
573  ) {
574  // are we allowed to do this?
575  $result = self::canAddTagsAccompanyingChange( $tags, $user );
576  if ( !$result->isOK() ) {
577  $result->value = null;
578  return $result;
579  }
580 
581  // do it!
582  self::addTags( $tags, $rc_id, $rev_id, $log_id, $params );
583 
584  return Status::newGood( true );
585  }
586 
601  public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove,
602  User $user = null
603  ) {
604  if ( $user !== null ) {
605  if ( !MediaWikiServices::getInstance()->getPermissionManager()
606  ->userHasRight( $user, 'changetags' )
607  ) {
608  return Status::newFatal( 'tags-update-no-permission' );
609  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
610  return Status::newFatal( 'tags-update-blocked', $user->getName() );
611  }
612  }
613 
614  if ( $tagsToAdd ) {
615  // to be added, a tag has to be explicitly defined
616  // @todo Allow extensions to define tags that can be applied by users...
617  $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
618  $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
619  if ( $diff ) {
620  return self::restrictedTagError( 'tags-update-add-not-allowed-one',
621  'tags-update-add-not-allowed-multi', $diff );
622  }
623  }
624 
625  if ( $tagsToRemove ) {
626  // to be removed, a tag must not be defined by an extension, or equivalently it
627  // has to be either explicitly defined or not defined at all
628  // (assuming no edge case of a tag both explicitly-defined and extension-defined)
629  $softwareDefinedTags = self::listSoftwareDefinedTags();
630  $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
631  if ( $intersect ) {
632  return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
633  'tags-update-remove-not-allowed-multi', $intersect );
634  }
635  }
636 
637  return Status::newGood();
638  }
639 
670  public static function updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
671  $rc_id, $rev_id, $log_id, $params, $reason, User $user
672  ) {
673  if ( $tagsToAdd === null ) {
674  $tagsToAdd = [];
675  }
676  if ( $tagsToRemove === null ) {
677  $tagsToRemove = [];
678  }
679  if ( !$tagsToAdd && !$tagsToRemove ) {
680  // no-op, don't bother
681  return Status::newGood( (object)[
682  'logId' => null,
683  'addedTags' => [],
684  'removedTags' => [],
685  ] );
686  }
687 
688  // are we allowed to do this?
689  $result = self::canUpdateTags( $tagsToAdd, $tagsToRemove, $user );
690  if ( !$result->isOK() ) {
691  $result->value = null;
692  return $result;
693  }
694 
695  // basic rate limiting
696  if ( $user->pingLimiter( 'changetag' ) ) {
697  return Status::newFatal( 'actionthrottledtext' );
698  }
699 
700  // do it!
701  list( $tagsAdded, $tagsRemoved, $initialTags ) = self::updateTags( $tagsToAdd,
702  $tagsToRemove, $rc_id, $rev_id, $log_id, $params, null, $user );
703  if ( !$tagsAdded && !$tagsRemoved ) {
704  // no-op, don't log it
705  return Status::newGood( (object)[
706  'logId' => null,
707  'addedTags' => [],
708  'removedTags' => [],
709  ] );
710  }
711 
712  // log it
713  $logEntry = new ManualLogEntry( 'tag', 'update' );
714  $logEntry->setPerformer( $user );
715  $logEntry->setComment( $reason );
716 
717  // find the appropriate target page
718  if ( $rev_id ) {
719  $rev = Revision::newFromId( $rev_id );
720  if ( $rev ) {
721  $logEntry->setTarget( $rev->getTitle() );
722  }
723  } elseif ( $log_id ) {
724  // This function is from revision deletion logic and has nothing to do with
725  // change tags, but it appears to be the only other place in core where we
726  // perform logged actions on log items.
727  $logEntry->setTarget( RevDelLogList::suggestTarget( null, [ $log_id ] ) );
728  }
729 
730  if ( !$logEntry->getTarget() ) {
731  // target is required, so we have to set something
732  $logEntry->setTarget( SpecialPage::getTitleFor( 'Tags' ) );
733  }
734 
735  $logParams = [
736  '4::revid' => $rev_id,
737  '5::logid' => $log_id,
738  '6:list:tagsAdded' => $tagsAdded,
739  '7:number:tagsAddedCount' => count( $tagsAdded ),
740  '8:list:tagsRemoved' => $tagsRemoved,
741  '9:number:tagsRemovedCount' => count( $tagsRemoved ),
742  'initialTags' => $initialTags,
743  ];
744  $logEntry->setParameters( $logParams );
745  $logEntry->setRelations( [ 'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
746 
747  $dbw = wfGetDB( DB_MASTER );
748  $logId = $logEntry->insert( $dbw );
749  // Only send this to UDP, not RC, similar to patrol events
750  $logEntry->publish( $logId, 'udp' );
751 
752  return Status::newGood( (object)[
753  'logId' => $logId,
754  'addedTags' => $tagsAdded,
755  'removedTags' => $tagsRemoved,
756  ] );
757  }
758 
779  public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
780  &$join_conds, &$options, $filter_tag = ''
781  ) {
782  global $wgUseTagFilter;
783 
784  // Normalize to arrays
785  $tables = (array)$tables;
786  $fields = (array)$fields;
787  $conds = (array)$conds;
788  $options = (array)$options;
789 
790  $fields['ts_tags'] = self::makeTagSummarySubquery( $tables );
791 
792  // Figure out which ID field to use
793  if ( in_array( 'recentchanges', $tables ) ) {
794  $join_cond = 'ct_rc_id=rc_id';
795  } elseif ( in_array( 'logging', $tables ) ) {
796  $join_cond = 'ct_log_id=log_id';
797  } elseif ( in_array( 'revision', $tables ) ) {
798  $join_cond = 'ct_rev_id=rev_id';
799  } elseif ( in_array( 'archive', $tables ) ) {
800  $join_cond = 'ct_rev_id=ar_rev_id';
801  } else {
802  throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
803  }
804 
805  if ( $wgUseTagFilter && $filter_tag ) {
806  // Somebody wants to filter on a tag.
807  // Add an INNER JOIN on change_tag
808 
809  $tables[] = 'change_tag';
810  $join_conds['change_tag'] = [ 'JOIN', $join_cond ];
811  $filterTagIds = [];
812  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
813  foreach ( (array)$filter_tag as $filterTagName ) {
814  try {
815  $filterTagIds[] = $changeTagDefStore->getId( $filterTagName );
816  } catch ( NameTableAccessException $exception ) {
817  // Return nothing.
818  $conds[] = '0=1';
819  break;
820  }
821  }
822 
823  if ( $filterTagIds !== [] ) {
824  $conds['ct_tag_id'] = $filterTagIds;
825  }
826 
827  if (
828  is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
829  !in_array( 'DISTINCT', $options )
830  ) {
831  $options[] = 'DISTINCT';
832  }
833  }
834  }
835 
844  public static function makeTagSummarySubquery( $tables ) {
845  // Normalize to arrays
846  $tables = (array)$tables;
847 
848  // Figure out which ID field to use
849  if ( in_array( 'recentchanges', $tables ) ) {
850  $join_cond = 'ct_rc_id=rc_id';
851  } elseif ( in_array( 'logging', $tables ) ) {
852  $join_cond = 'ct_log_id=log_id';
853  } elseif ( in_array( 'revision', $tables ) ) {
854  $join_cond = 'ct_rev_id=rev_id';
855  } elseif ( in_array( 'archive', $tables ) ) {
856  $join_cond = 'ct_rev_id=ar_rev_id';
857  } else {
858  throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
859  }
860 
861  $tagTables = [ 'change_tag', 'change_tag_def' ];
862  $join_cond_ts_tags = [ 'change_tag_def' => [ 'JOIN', 'ct_tag_id=ctd_id' ] ];
863  $field = 'ctd_name';
864 
865  return wfGetDB( DB_REPLICA )->buildGroupConcatField(
866  ',', $tagTables, $field, $join_cond, $join_cond_ts_tags
867  );
868  }
869 
881  public static function buildTagFilterSelector(
882  $selected = '', $ooui = false, IContextSource $context = null
883  ) {
884  if ( !$context ) {
886  }
887 
888  $config = $context->getConfig();
889  if ( !$config->get( 'UseTagFilter' ) || !count( self::listDefinedTags() ) ) {
890  return [];
891  }
892 
893  $data = [
894  Html::rawElement(
895  'label',
896  [ 'for' => 'tagfilter' ],
897  $context->msg( 'tag-filter' )->parse()
898  )
899  ];
900 
901  if ( $ooui ) {
902  $data[] = new OOUI\TextInputWidget( [
903  'id' => 'tagfilter',
904  'name' => 'tagfilter',
905  'value' => $selected,
906  'classes' => 'mw-tagfilter-input',
907  ] );
908  } else {
909  $data[] = Xml::input(
910  'tagfilter',
911  20,
912  $selected,
913  [ 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' ]
914  );
915  }
916 
917  return $data;
918  }
919 
928  public static function defineTag( $tag ) {
929  $dbw = wfGetDB( DB_MASTER );
930  $tagDef = [
931  'ctd_name' => $tag,
932  'ctd_user_defined' => 1,
933  'ctd_count' => 0
934  ];
935  $dbw->upsert(
936  'change_tag_def',
937  $tagDef,
938  'ctd_name',
939  [ 'ctd_user_defined' => 1 ],
940  __METHOD__
941  );
942 
943  // clear the memcache of defined tags
945  }
946 
955  public static function undefineTag( $tag ) {
956  $dbw = wfGetDB( DB_MASTER );
957 
958  $dbw->update(
959  'change_tag_def',
960  [ 'ctd_user_defined' => 0 ],
961  [ 'ctd_name' => $tag ],
962  __METHOD__
963  );
964 
965  $dbw->delete(
966  'change_tag_def',
967  [ 'ctd_name' => $tag, 'ctd_count' => 0 ],
968  __METHOD__
969  );
970 
971  // clear the memcache of defined tags
973  }
974 
989  protected static function logTagManagementAction( $action, $tag, $reason,
990  User $user, $tagCount = null, array $logEntryTags = []
991  ) {
992  $dbw = wfGetDB( DB_MASTER );
993 
994  $logEntry = new ManualLogEntry( 'managetags', $action );
995  $logEntry->setPerformer( $user );
996  // target page is not relevant, but it has to be set, so we just put in
997  // the title of Special:Tags
998  $logEntry->setTarget( Title::newFromText( 'Special:Tags' ) );
999  $logEntry->setComment( $reason );
1000 
1001  $params = [ '4::tag' => $tag ];
1002  if ( $tagCount !== null ) {
1003  $params['5:number:count'] = $tagCount;
1004  }
1005  $logEntry->setParameters( $params );
1006  $logEntry->setRelations( [ 'Tag' => $tag ] );
1007  $logEntry->addTags( $logEntryTags );
1008 
1009  $logId = $logEntry->insert( $dbw );
1010  $logEntry->publish( $logId );
1011  return $logId;
1012  }
1013 
1023  public static function canActivateTag( $tag, User $user = null ) {
1024  if ( $user !== null ) {
1025  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1026  ->userHasRight( $user, 'managechangetags' )
1027  ) {
1028  return Status::newFatal( 'tags-manage-no-permission' );
1029  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1030  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1031  }
1032  }
1033 
1034  // defined tags cannot be activated (a defined tag is either extension-
1035  // defined, in which case the extension chooses whether or not to active it;
1036  // or user-defined, in which case it is considered active)
1037  $definedTags = self::listDefinedTags();
1038  if ( in_array( $tag, $definedTags ) ) {
1039  return Status::newFatal( 'tags-activate-not-allowed', $tag );
1040  }
1041 
1042  // non-existing tags cannot be activated
1043  $tagUsage = self::tagUsageStatistics();
1044  if ( !isset( $tagUsage[$tag] ) ) { // we already know the tag is undefined
1045  return Status::newFatal( 'tags-activate-not-found', $tag );
1046  }
1047 
1048  return Status::newGood();
1049  }
1050 
1068  public static function activateTagWithChecks( $tag, $reason, User $user,
1069  $ignoreWarnings = false, array $logEntryTags = []
1070  ) {
1071  // are we allowed to do this?
1072  $result = self::canActivateTag( $tag, $user );
1073  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1074  $result->value = null;
1075  return $result;
1076  }
1077 
1078  // do it!
1079  self::defineTag( $tag );
1080 
1081  // log it
1082  $logId = self::logTagManagementAction( 'activate', $tag, $reason, $user,
1083  null, $logEntryTags );
1084 
1085  return Status::newGood( $logId );
1086  }
1087 
1097  public static function canDeactivateTag( $tag, User $user = null ) {
1098  if ( $user !== null ) {
1099  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1100  ->userHasRight( $user, 'managechangetags' )
1101  ) {
1102  return Status::newFatal( 'tags-manage-no-permission' );
1103  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1104  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1105  }
1106  }
1107 
1108  // only explicitly-defined tags can be deactivated
1109  $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
1110  if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
1111  return Status::newFatal( 'tags-deactivate-not-allowed', $tag );
1112  }
1113  return Status::newGood();
1114  }
1115 
1133  public static function deactivateTagWithChecks( $tag, $reason, User $user,
1134  $ignoreWarnings = false, array $logEntryTags = []
1135  ) {
1136  // are we allowed to do this?
1137  $result = self::canDeactivateTag( $tag, $user );
1138  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1139  $result->value = null;
1140  return $result;
1141  }
1142 
1143  // do it!
1144  self::undefineTag( $tag );
1145 
1146  // log it
1147  $logId = self::logTagManagementAction( 'deactivate', $tag, $reason, $user,
1148  null, $logEntryTags );
1149 
1150  return Status::newGood( $logId );
1151  }
1152 
1160  public static function isTagNameValid( $tag ) {
1161  // no empty tags
1162  if ( $tag === '' ) {
1163  return Status::newFatal( 'tags-create-no-name' );
1164  }
1165 
1166  // tags cannot contain commas (used to be used as a delimiter in tag_summary table),
1167  // pipe (used as a delimiter between multiple tags in
1168  // SpecialRecentchanges and friends), or slashes (would break tag description messages in
1169  // MediaWiki namespace)
1170  if ( strpos( $tag, ',' ) !== false || strpos( $tag, '|' ) !== false
1171  || strpos( $tag, '/' ) !== false ) {
1172  return Status::newFatal( 'tags-create-invalid-chars' );
1173  }
1174 
1175  // could the MediaWiki namespace description messages be created?
1176  $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Tag-$tag-description" );
1177  if ( $title === null ) {
1178  return Status::newFatal( 'tags-create-invalid-title-chars' );
1179  }
1180 
1181  return Status::newGood();
1182  }
1183 
1196  public static function canCreateTag( $tag, User $user = null ) {
1197  if ( $user !== null ) {
1198  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1199  ->userHasRight( $user, 'managechangetags' )
1200  ) {
1201  return Status::newFatal( 'tags-manage-no-permission' );
1202  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1203  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1204  }
1205  }
1206 
1207  $status = self::isTagNameValid( $tag );
1208  if ( !$status->isGood() ) {
1209  return $status;
1210  }
1211 
1212  // does the tag already exist?
1213  $tagUsage = self::tagUsageStatistics();
1214  if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
1215  return Status::newFatal( 'tags-create-already-exists', $tag );
1216  }
1217 
1218  // check with hooks
1219  $canCreateResult = Status::newGood();
1220  Hooks::run( 'ChangeTagCanCreate', [ $tag, $user, &$canCreateResult ] );
1221  return $canCreateResult;
1222  }
1223 
1243  public static function createTagWithChecks( $tag, $reason, User $user,
1244  $ignoreWarnings = false, array $logEntryTags = []
1245  ) {
1246  // are we allowed to do this?
1247  $result = self::canCreateTag( $tag, $user );
1248  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1249  $result->value = null;
1250  return $result;
1251  }
1252 
1253  // do it!
1254  self::defineTag( $tag );
1255 
1256  // log it
1257  $logId = self::logTagManagementAction( 'create', $tag, $reason, $user,
1258  null, $logEntryTags );
1259 
1260  return Status::newGood( $logId );
1261  }
1262 
1275  public static function deleteTagEverywhere( $tag ) {
1276  $dbw = wfGetDB( DB_MASTER );
1277  $dbw->startAtomic( __METHOD__ );
1278 
1279  // fetch tag id, this must be done before calling undefineTag(), see T225564
1280  $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
1281 
1282  // set ctd_user_defined = 0
1283  self::undefineTag( $tag );
1284 
1285  // delete from change_tag
1286  $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ );
1287  $dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
1288  $dbw->endAtomic( __METHOD__ );
1289 
1290  // give extensions a chance
1291  $status = Status::newGood();
1292  Hooks::run( 'ChangeTagAfterDelete', [ $tag, &$status ] );
1293  // let's not allow error results, as the actual tag deletion succeeded
1294  if ( !$status->isOK() ) {
1295  wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' );
1296  $status->setOK( true );
1297  }
1298 
1299  // clear the memcache of defined tags
1301 
1302  return $status;
1303  }
1304 
1317  public static function canDeleteTag( $tag, User $user = null, int $flags = 0 ) {
1318  $tagUsage = self::tagUsageStatistics();
1319 
1320  if ( $user !== null ) {
1321  if ( !MediaWikiServices::getInstance()->getPermissionManager()
1322  ->userHasRight( $user, 'deletechangetags' )
1323  ) {
1324  return Status::newFatal( 'tags-delete-no-permission' );
1325  } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
1326  return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1327  }
1328  }
1329 
1330  if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
1331  return Status::newFatal( 'tags-delete-not-found', $tag );
1332  }
1333 
1334  if ( $flags !== self::BYPASS_MAX_USAGE_CHECK &&
1335  isset( $tagUsage[$tag] ) &&
1336  $tagUsage[$tag] > self::MAX_DELETE_USES
1337  ) {
1338  return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1339  }
1340 
1341  $softwareDefined = self::listSoftwareDefinedTags();
1342  if ( in_array( $tag, $softwareDefined ) ) {
1343  // extension-defined tags can't be deleted unless the extension
1344  // specifically allows it
1345  $status = Status::newFatal( 'tags-delete-not-allowed' );
1346  } else {
1347  // user-defined tags are deletable unless otherwise specified
1348  $status = Status::newGood();
1349  }
1350 
1351  Hooks::run( 'ChangeTagCanDelete', [ $tag, $user, &$status ] );
1352  return $status;
1353  }
1354 
1372  public static function deleteTagWithChecks( $tag, $reason, User $user,
1373  $ignoreWarnings = false, array $logEntryTags = []
1374  ) {
1375  // are we allowed to do this?
1376  $result = self::canDeleteTag( $tag, $user );
1377  if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1378  $result->value = null;
1379  return $result;
1380  }
1381 
1382  // store the tag usage statistics
1383  $tagUsage = self::tagUsageStatistics();
1384  $hitcount = $tagUsage[$tag] ?? 0;
1385 
1386  // do it!
1387  $deleteResult = self::deleteTagEverywhere( $tag );
1388  if ( !$deleteResult->isOK() ) {
1389  return $deleteResult;
1390  }
1391 
1392  // log it
1393  $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user,
1394  $hitcount, $logEntryTags );
1395 
1396  $deleteResult->value = $logId;
1397  return $deleteResult;
1398  }
1399 
1406  public static function listSoftwareActivatedTags() {
1407  // core active tags
1408  $tags = self::getSoftwareTags();
1409  if ( !Hooks::isRegistered( 'ChangeTagsListActive' ) ) {
1410  return $tags;
1411  }
1412  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1413  return $cache->getWithSetCallback(
1414  $cache->makeKey( 'active-tags' ),
1415  WANObjectCache::TTL_MINUTE * 5,
1416  function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
1417  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1418 
1419  // Ask extensions which tags they consider active
1420  Hooks::run( 'ChangeTagsListActive', [ &$tags ] );
1421  return $tags;
1422  },
1423  [
1424  'checkKeys' => [ $cache->makeKey( 'active-tags' ) ],
1425  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1426  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1427  ]
1428  );
1429  }
1430 
1437  public static function listDefinedTags() {
1439  $tags2 = self::listSoftwareDefinedTags();
1440  return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
1441  }
1442 
1451  public static function listExplicitlyDefinedTags() {
1452  $fname = __METHOD__;
1453 
1454  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1455  return $cache->getWithSetCallback(
1456  $cache->makeKey( 'valid-tags-db' ),
1457  WANObjectCache::TTL_MINUTE * 5,
1458  function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1459  $dbr = wfGetDB( DB_REPLICA );
1460 
1461  $setOpts += Database::getCacheSetOptions( $dbr );
1462 
1463  $tags = $dbr->selectFieldValues(
1464  'change_tag_def',
1465  'ctd_name',
1466  [ 'ctd_user_defined' => 1 ],
1467  $fname
1468  );
1469 
1470  return array_filter( array_unique( $tags ) );
1471  },
1472  [
1473  'checkKeys' => [ $cache->makeKey( 'valid-tags-db' ) ],
1474  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1475  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1476  ]
1477  );
1478  }
1479 
1489  public static function listSoftwareDefinedTags() {
1490  // core defined tags
1491  $tags = self::getSoftwareTags( true );
1492  if ( !Hooks::isRegistered( 'ListDefinedTags' ) ) {
1493  return $tags;
1494  }
1495  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1496  return $cache->getWithSetCallback(
1497  $cache->makeKey( 'valid-tags-hook' ),
1498  WANObjectCache::TTL_MINUTE * 5,
1499  function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
1500  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1501 
1502  Hooks::run( 'ListDefinedTags', [ &$tags ] );
1503  return array_filter( array_unique( $tags ) );
1504  },
1505  [
1506  'checkKeys' => [ $cache->makeKey( 'valid-tags-hook' ) ],
1507  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1508  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1509  ]
1510  );
1511  }
1512 
1518  public static function purgeTagCacheAll() {
1519  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1520 
1521  $cache->touchCheckKey( $cache->makeKey( 'active-tags' ) );
1522  $cache->touchCheckKey( $cache->makeKey( 'valid-tags-db' ) );
1523  $cache->touchCheckKey( $cache->makeKey( 'valid-tags-hook' ) );
1524  $cache->touchCheckKey( $cache->makeKey( 'tags-usage-statistics' ) );
1525 
1526  MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
1527  }
1528 
1535  public static function tagUsageStatistics() {
1536  $fname = __METHOD__;
1537 
1538  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1539  return $cache->getWithSetCallback(
1540  $cache->makeKey( 'tags-usage-statistics' ),
1541  WANObjectCache::TTL_MINUTE * 5,
1542  function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1543  $dbr = wfGetDB( DB_REPLICA );
1544  $res = $dbr->select(
1545  'change_tag_def',
1546  [ 'ctd_name', 'ctd_count' ],
1547  [],
1548  $fname,
1549  [ 'ORDER BY' => 'ctd_count DESC' ]
1550  );
1551 
1552  $out = [];
1553  foreach ( $res as $row ) {
1554  $out[$row->ctd_name] = $row->ctd_count;
1555  }
1556 
1557  return $out;
1558  },
1559  [
1560  'checkKeys' => [ $cache->makeKey( 'tags-usage-statistics' ) ],
1561  'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1562  'pcTTL' => WANObjectCache::TTL_PROC_LONG
1563  ]
1564  );
1565  }
1566 
1581  public static function showTagEditingUI( User $user ) {
1582  return MediaWikiServices::getInstance()->getPermissionManager()
1583  ->userHasRight( $user, 'changetags' ) &&
1584  (bool)self::listExplicitlyDefinedTags();
1585  }
1586 }
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
ChangeTags\makeTagSummarySubquery
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
Definition: ChangeTags.php:844
$wgUseTagFilter
$wgUseTagFilter
Allow filtering by change tag in recentchanges, history, etc Has no effect if no tags are defined in ...
Definition: DefaultSettings.php:7309
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:332
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:1518
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:119
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:71
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:989
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:83
ChangeTags\showTagEditingUI
static showTagEditingUI(User $user)
Indicate whether change tag editing UI is relevant.
Definition: ChangeTags.php:1581
ChangeTags\buildTagFilterSelector
static buildTagFilterSelector( $selected='', $ooui=false, IContextSource $context=null)
Build a text box to select a change tag.
Definition: ChangeTags.php:881
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:1869
$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:505
ChangeTags\isTagNameValid
static isTagNameValid( $tag)
Is the tag name valid?
Definition: ChangeTags.php:1160
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:40
$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:228
ChangeTags\tagShortDescriptionMessage
static tagShortDescriptionMessage( $tag, MessageLocalizer $context)
Get the message object for the tag's short description.
Definition: ChangeTags.php:154
ChangeTags\listSoftwareDefinedTags
static listSoftwareDefinedTags()
Lists tags defined by core or extensions using the ListDefinedTags hook.
Definition: ChangeTags.php:1489
ChangeTags\deleteTagEverywhere
static deleteTagEverywhere( $tag)
Permanently removes all traces of a tag from the DB.
Definition: ChangeTags.php:1275
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:779
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:470
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:955
ChangeTags
Definition: ChangeTags.php:29
getPermissionManager
getPermissionManager()
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2500
ChangeTags\listDefinedTags
static listDefinedTags()
Basically lists defined tags which count even if they aren't applied to anything.
Definition: ChangeTags.php:1437
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:63
$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:35
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:928
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
$wgSoftwareTags
array $wgSoftwareTags
List of core tags to enable.
Definition: DefaultSettings.php:7325
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:45
ChangeTags\canActivateTag
static canActivateTag( $tag, User $user=null)
Is it OK to allow the user to activate this tag?
Definition: ChangeTags.php:1023
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:913
ChangeTags\listExplicitlyDefinedTags
static listExplicitlyDefinedTags()
Lists tags explicitly defined in the change_tag_def table of the database.
Definition: ChangeTags.php:1451
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:621
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
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:1317
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:1133
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:1406
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
$context
$context
Definition: load.php:43
Hooks\isRegistered
static isRegistered( $name)
Returns true if a hook has a function registered to it.
Definition: Hooks.php:80
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:528
$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:1196
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:293
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:1535
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:276
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:1054
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:77
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:571
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:1243
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:601
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
ChangeTags\tagLongDescriptionMessage
static tagLongDescriptionMessage( $tag, MessageLocalizer $context)
Get the message object for the tag's long description.
Definition: ChangeTags.php:205
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:1372
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:99
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:670
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:256
ChangeTags\tagDescription
static tagDescription( $tag, MessageLocalizer $context)
Get a short description for a tag.
Definition: ChangeTags.php:188
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:1068
ChangeTags\canDeactivateTag
static canDeactivateTag( $tag, User $user=null)
Is it OK to allow the user to deactivate this tag?
Definition: ChangeTags.php:1097