MediaWiki  master
SpecialTags.php
Go to the documentation of this file.
1 <?php
29 class SpecialTags extends SpecialPage {
30 
35 
40 
45 
46  public function __construct() {
47  parent::__construct( 'Tags' );
48  }
49 
50  public function execute( $par ) {
51  $this->setHeaders();
52  $this->outputHeader();
53  $this->addHelpLink( 'Manual:Tags' );
54 
55  $request = $this->getRequest();
56  switch ( $par ) {
57  case 'delete':
58  $this->showDeleteTagForm( $request->getVal( 'tag' ) );
59  break;
60  case 'activate':
61  $this->showActivateDeactivateForm( $request->getVal( 'tag' ), true );
62  break;
63  case 'deactivate':
64  $this->showActivateDeactivateForm( $request->getVal( 'tag' ), false );
65  break;
66  case 'create':
67  // fall through, thanks to HTMLForm's logic
68  default:
69  $this->showTagList();
70  break;
71  }
72  }
73 
74  private function showTagList() {
75  $out = $this->getOutput();
76  $out->setPageTitle( $this->msg( 'tags-title' ) );
77  $out->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
78 
79  $authority = $this->getAuthority();
80  $userCanManage = $authority->isAllowed( 'managechangetags' );
81  $userCanDelete = $authority->isAllowed( 'deletechangetags' );
82  $userCanEditInterface = $authority->isAllowed( 'editinterface' );
83 
84  // Show form to create a tag
85  if ( $userCanManage ) {
86  $fields = [
87  'Tag' => [
88  'type' => 'text',
89  'label' => $this->msg( 'tags-create-tag-name' )->plain(),
90  'required' => true,
91  ],
92  'Reason' => [
93  'type' => 'text',
95  'label' => $this->msg( 'tags-create-reason' )->plain(),
96  'size' => 50,
97  ],
98  'IgnoreWarnings' => [
99  'type' => 'hidden',
100  ],
101  ];
102 
103  HTMLForm::factory( 'ooui', $fields, $this->getContext() )
104  ->setAction( $this->getPageTitle( 'create' )->getLocalURL() )
105  ->setWrapperLegendMsg( 'tags-create-heading' )
106  ->setHeaderText( $this->msg( 'tags-create-explanation' )->parseAsBlock() )
107  ->setSubmitCallback( [ $this, 'processCreateTagForm' ] )
108  ->setSubmitTextMsg( 'tags-create-submit' )
109  ->show();
110 
111  // If processCreateTagForm generated a redirect, there's no point
112  // continuing with this, as the user is just going to end up getting sent
113  // somewhere else. Additionally, if we keep going here, we end up
114  // populating the memcache of tag data (see ChangeTags::listDefinedTags)
115  // with out-of-date data from the replica DB, because the replica DB hasn't caught
116  // up to the fact that a new tag has been created as part of an implicit,
117  // as yet uncommitted transaction on primary DB.
118  if ( $out->getRedirect() !== '' ) {
119  return;
120  }
121  }
122 
123  // Used to get hitcounts for #doTagRow()
124  $tagStats = ChangeTags::tagUsageStatistics();
125 
126  // Used in #doTagRow()
127  $this->explicitlyDefinedTags = array_fill_keys(
129  $this->softwareDefinedTags = array_fill_keys(
131 
132  // List all defined tags, even if they were never applied
133  $definedTags = array_keys( $this->explicitlyDefinedTags + $this->softwareDefinedTags );
134 
135  // Show header only if there exists atleast one tag
136  if ( !$tagStats && !$definedTags ) {
137  return;
138  }
139 
140  // Write the headers
141  $thead = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) .
142  Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
143  Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
144  Xml::tags( 'th', null, $this->msg( 'tags-source-header' )->parse() ) .
145  Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
146  Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() ) .
147  ( ( $userCanManage || $userCanDelete ) ?
148  Xml::tags( 'th', [ 'class' => 'unsortable' ],
149  $this->msg( 'tags-actions-header' )->parse() ) :
150  '' )
151  );
152 
153  $tbody = '';
154  // Used in #doTagRow()
155  $this->softwareActivatedTags = array_fill_keys(
157 
158  // Insert tags that have been applied at least once
159  foreach ( $tagStats as $tag => $hitcount ) {
160  $tbody .= $this->doTagRow( $tag, $hitcount, $userCanManage,
161  $userCanDelete, $userCanEditInterface );
162  }
163  // Insert tags defined somewhere but never applied
164  foreach ( $definedTags as $tag ) {
165  if ( !isset( $tagStats[$tag] ) ) {
166  $tbody .= $this->doTagRow( $tag, 0, $userCanManage, $userCanDelete, $userCanEditInterface );
167  }
168  }
169 
170  $out->addModuleStyles( [
171  'jquery.tablesorter.styles',
172  'mediawiki.pager.tablePager'
173  ] );
174  $out->addModules( 'jquery.tablesorter' );
175  $out->addHTML( Xml::tags(
176  'table',
177  [ 'class' => 'mw-datatable sortable mw-tags-table' ],
178  Xml::tags( 'thead', null, $thead ) .
179  Xml::tags( 'tbody', null, $tbody )
180  ) );
181  }
182 
183  private function doTagRow(
184  $tag, $hitcount, $showManageActions, $showDeleteActions, $showEditLinks
185  ) {
186  $newRow = '';
187  $newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
188 
189  $linkRenderer = $this->getLinkRenderer();
190  $disp = ChangeTags::tagDescription( $tag, $this->getContext() );
191  if ( $disp === false ) {
192  $disp = Xml::element( 'em', null, $this->msg( 'tags-hidden' )->text() );
193  }
194  if ( $showEditLinks ) {
195  $disp .= ' ';
196  $editLink = $linkRenderer->makeLink(
197  $this->msg( "tag-$tag" )->inContentLanguage()->getTitle(),
198  $this->msg( 'tags-edit' )->text(),
199  [],
200  [ 'action' => 'edit' ]
201  );
202  $disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped();
203  }
204  $newRow .= Xml::tags( 'td', null, $disp );
205 
206  $msg = $this->msg( "tag-$tag-description" );
207  $desc = !$msg->exists() ? '' : $msg->parse();
208  if ( $showEditLinks ) {
209  $desc .= ' ';
210  $editDescLink = $linkRenderer->makeLink(
211  $this->msg( "tag-$tag-description" )->inContentLanguage()->getTitle(),
212  $this->msg( 'tags-edit' )->text(),
213  [],
214  [ 'action' => 'edit' ]
215  );
216  $desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped();
217  }
218  $newRow .= Xml::tags( 'td', null, $desc );
219 
220  $sourceMsgs = [];
221  $isSoftware = isset( $this->softwareDefinedTags[$tag] );
222  $isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
223  if ( $isSoftware ) {
224  // TODO: Rename this message
225  $sourceMsgs[] = $this->msg( 'tags-source-extension' )->escaped();
226  }
227  if ( $isExplicit ) {
228  $sourceMsgs[] = $this->msg( 'tags-source-manual' )->escaped();
229  }
230  if ( !$sourceMsgs ) {
231  $sourceMsgs[] = $this->msg( 'tags-source-none' )->escaped();
232  }
233  $newRow .= Xml::tags( 'td', null, implode( Xml::element( 'br' ), $sourceMsgs ) );
234 
235  $isActive = $isExplicit || isset( $this->softwareActivatedTags[$tag] );
236  $activeMsg = ( $isActive ? 'tags-active-yes' : 'tags-active-no' );
237  $newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
238 
239  $hitcountLabelMsg = $this->msg( 'tags-hitcount' )->numParams( $hitcount );
240  if ( $this->getConfig()->get( 'UseTagFilter' ) ) {
241  $hitcountLabel = $linkRenderer->makeLink(
242  SpecialPage::getTitleFor( 'Recentchanges' ),
243  $hitcountLabelMsg->text(),
244  [],
245  [ 'tagfilter' => $tag ]
246  );
247  } else {
248  $hitcountLabel = $hitcountLabelMsg->escaped();
249  }
250 
251  // add raw $hitcount for sorting, because tags-hitcount contains numbers and letters
252  $newRow .= Xml::tags( 'td', [ 'data-sort-value' => $hitcount ], $hitcountLabel );
253 
254  $actionLinks = [];
255 
256  if ( $showDeleteActions && ChangeTags::canDeleteTag( $tag )->isOK() ) {
257  $actionLinks[] = $linkRenderer->makeKnownLink(
258  $this->getPageTitle( 'delete' ),
259  $this->msg( 'tags-delete' )->text(),
260  [],
261  [ 'tag' => $tag ] );
262  }
263 
264  if ( $showManageActions ) { // we've already checked that the user had the requisite userright
265  if ( ChangeTags::canActivateTag( $tag )->isOK() ) {
266  $actionLinks[] = $linkRenderer->makeKnownLink(
267  $this->getPageTitle( 'activate' ),
268  $this->msg( 'tags-activate' )->text(),
269  [],
270  [ 'tag' => $tag ] );
271  }
272 
273  if ( ChangeTags::canDeactivateTag( $tag )->isOK() ) {
274  $actionLinks[] = $linkRenderer->makeKnownLink(
275  $this->getPageTitle( 'deactivate' ),
276  $this->msg( 'tags-deactivate' )->text(),
277  [],
278  [ 'tag' => $tag ] );
279  }
280  }
281 
282  if ( $showDeleteActions || $showManageActions ) {
283  $newRow .= Xml::tags( 'td', null, $this->getLanguage()->pipeList( $actionLinks ) );
284  }
285 
286  return Xml::tags( 'tr', null, $newRow ) . "\n";
287  }
288 
289  public function processCreateTagForm( array $data, HTMLForm $form ) {
290  $context = $form->getContext();
291  $out = $context->getOutput();
292 
293  $tag = trim( strval( $data['Tag'] ) );
294  $ignoreWarnings = isset( $data['IgnoreWarnings'] ) && $data['IgnoreWarnings'] === '1';
295  $status = ChangeTags::createTagWithChecks( $tag, $data['Reason'],
296  $context->getUser(), $ignoreWarnings );
297 
298  if ( $status->isGood() ) {
299  $out->redirect( $this->getPageTitle()->getLocalURL() );
300  return true;
301  } elseif ( $status->isOK() ) {
302  // we have some warnings, so we show a confirmation form
303  $fields = [
304  'Tag' => [
305  'type' => 'hidden',
306  'default' => $data['Tag'],
307  ],
308  'Reason' => [
309  'type' => 'hidden',
310  'default' => $data['Reason'],
311  ],
312  'IgnoreWarnings' => [
313  'type' => 'hidden',
314  'default' => '1',
315  ],
316  ];
317 
318  // fool HTMLForm into thinking the form hasn't been submitted yet. Otherwise
319  // we get into an infinite loop!
320  $context->getRequest()->unsetVal( 'wpEditToken' );
321 
322  $headerText = $this->msg( 'tags-create-warnings-above', $tag,
323  count( $status->getWarningsArray() ) )->parseAsBlock() .
324  $out->parseAsInterface( $status->getWikiText() ) .
325  $this->msg( 'tags-create-warnings-below' )->parseAsBlock();
326 
327  HTMLForm::factory( 'ooui', $fields, $this->getContext() )
328  ->setAction( $this->getPageTitle( 'create' )->getLocalURL() )
329  ->setWrapperLegendMsg( 'tags-create-heading' )
330  ->setHeaderText( $headerText )
331  ->setSubmitCallback( [ $this, 'processCreateTagForm' ] )
332  ->setSubmitTextMsg( 'htmlform-yes' )
333  ->show();
334 
335  $out->addBacklinkSubtitle( $this->getPageTitle() );
336  return true;
337  } else {
338  $out->wrapWikiTextAsInterface( 'error', $status->getWikiText() );
339  return false;
340  }
341  }
342 
343  protected function showDeleteTagForm( $tag ) {
344  $authority = $this->getAuthority();
345  if ( !$authority->isAllowed( 'deletechangetags' ) ) {
346  throw new PermissionsError( 'deletechangetags' );
347  }
348 
349  $out = $this->getOutput();
350  $out->setPageTitle( $this->msg( 'tags-delete-title' ) );
351  $out->addBacklinkSubtitle( $this->getPageTitle() );
352 
353  // is the tag actually able to be deleted?
354  $canDeleteResult = ChangeTags::canDeleteTag( $tag, $authority );
355  if ( !$canDeleteResult->isGood() ) {
356  $out->wrapWikiTextAsInterface( 'error', $canDeleteResult->getWikiText() );
357  if ( !$canDeleteResult->isOK() ) {
358  return;
359  }
360  }
361 
362  $preText = $this->msg( 'tags-delete-explanation-initial', $tag )->parseAsBlock();
363  $tagUsage = ChangeTags::tagUsageStatistics();
364  if ( isset( $tagUsage[$tag] ) && $tagUsage[$tag] > 0 ) {
365  $preText .= $this->msg( 'tags-delete-explanation-in-use', $tag,
366  $tagUsage[$tag] )->parseAsBlock();
367  }
368  $preText .= $this->msg( 'tags-delete-explanation-warning', $tag )->parseAsBlock();
369 
370  // see if the tag is in use
371  $this->softwareActivatedTags = array_fill_keys(
373  if ( isset( $this->softwareActivatedTags[$tag] ) ) {
374  $preText .= $this->msg( 'tags-delete-explanation-active', $tag )->parseAsBlock();
375  }
376 
377  $fields = [];
378  $fields['Reason'] = [
379  'type' => 'text',
380  'label' => $this->msg( 'tags-delete-reason' )->plain(),
381  'size' => 50,
382  ];
383  $fields['HiddenTag'] = [
384  'type' => 'hidden',
385  'name' => 'tag',
386  'default' => $tag,
387  'required' => true,
388  ];
389 
390  HTMLForm::factory( 'ooui', $fields, $this->getContext() )
391  ->setAction( $this->getPageTitle( 'delete' )->getLocalURL() )
392  ->setSubmitCallback( function ( $data, $form ) {
393  return $this->processTagForm( $data, $form, 'delete' );
394  } )
395  ->setSubmitTextMsg( 'tags-delete-submit' )
396  ->setSubmitDestructive()
397  ->addPreText( $preText )
398  ->show();
399  }
400 
401  protected function showActivateDeactivateForm( $tag, $activate ) {
402  $actionStr = $activate ? 'activate' : 'deactivate';
403 
404  $authority = $this->getAuthority();
405  if ( !$authority->isAllowed( 'managechangetags' ) ) {
406  throw new PermissionsError( 'managechangetags' );
407  }
408 
409  $out = $this->getOutput();
410  // tags-activate-title, tags-deactivate-title
411  $out->setPageTitle( $this->msg( "tags-$actionStr-title" ) );
412  $out->addBacklinkSubtitle( $this->getPageTitle() );
413 
414  // is it possible to do this?
415  if ( $activate ) {
416  $result = ChangeTags::canActivateTag( $tag, $authority );
417  } else {
418  $result = ChangeTags::canDeactivateTag( $tag, $authority );
419  }
420  if ( !$result->isGood() ) {
421  $out->wrapWikiTextAsInterface( 'error', $result->getWikiText() );
422  if ( !$result->isOK() ) {
423  return;
424  }
425  }
426 
427  // tags-activate-question, tags-deactivate-question
428  $preText = $this->msg( "tags-$actionStr-question", $tag )->parseAsBlock();
429 
430  $fields = [];
431  // tags-activate-reason, tags-deactivate-reason
432  $fields['Reason'] = [
433  'type' => 'text',
434  'label' => $this->msg( "tags-$actionStr-reason" )->plain(),
435  'size' => 50,
436  ];
437  $fields['HiddenTag'] = [
438  'type' => 'hidden',
439  'name' => 'tag',
440  'default' => $tag,
441  'required' => true,
442  ];
443 
444  HTMLForm::factory( 'ooui', $fields, $this->getContext() )
445  ->setAction( $this->getPageTitle( $actionStr )->getLocalURL() )
446  ->setSubmitCallback( function ( $data, $form ) use ( $actionStr ) {
447  return $this->processTagForm( $data, $form, $actionStr );
448  } )
449  // tags-activate-submit, tags-deactivate-submit
450  ->setSubmitTextMsg( "tags-$actionStr-submit" )
451  ->addPreText( $preText )
452  ->show();
453  }
454 
461  public function processTagForm( array $data, HTMLForm $form, string $action ) {
462  $context = $form->getContext();
463  $out = $context->getOutput();
464 
465  $tag = $data['HiddenTag'];
466  // activateTagWithChecks, deactivateTagWithChecks, deleteTagWithChecks
467  $status = call_user_func( [ ChangeTags::class, "{$action}TagWithChecks" ],
468  $tag, $data['Reason'], $context->getUser(), true );
469 
470  if ( $status->isGood() ) {
471  $out->redirect( $this->getPageTitle()->getLocalURL() );
472  return true;
473  } elseif ( $status->isOK() && $action === 'delete' ) {
474  // deletion succeeded, but hooks raised a warning
475  $out->addWikiTextAsInterface( $this->msg( 'tags-delete-warnings-after-delete', $tag,
476  count( $status->getWarningsArray() ) )->text() . "\n" .
477  $status->getWikitext() );
478  $out->addReturnTo( $this->getPageTitle() );
479  return true;
480  } else {
481  $out->wrapWikiTextAsInterface( 'error', $status->getWikitext() );
482  return false;
483  }
484  }
485 
491  public function getSubpagesForPrefixSearch() {
492  // The subpages does not have an own form, so not listing it at the moment
493  return [
494  // 'delete',
495  // 'activate',
496  // 'deactivate',
497  // 'create',
498  ];
499  }
500 
501  protected function getGroupName() {
502  return 'changes';
503  }
504 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialTags
A special page that lists tags for edits.
Definition: SpecialTags.php:29
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:744
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:912
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:47
ChangeTags\createTagWithChecks
static createTagWithChecks( $tag, $reason, Authority $performer, $ignoreWarnings=false, array $logEntryTags=[])
Creates a tag by adding it to change_tag_def table.
Definition: ChangeTags.php:1391
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:790
ChangeTags\canActivateTag
static canActivateTag( $tag, Authority $performer=null)
Is it OK to allow the user to activate this tag?
Definition: ChangeTags.php:1162
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:107
SpecialTags\processCreateTagForm
processCreateTagForm(array $data, HTMLForm $form)
Definition: SpecialTags.php:289
SpecialPage\getAuthority
getAuthority()
Shortcut to get the Authority executing this instance.
Definition: SpecialPage.php:810
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:32
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:830
SpecialTags\getSubpagesForPrefixSearch
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept.
Definition: SpecialTags.php:491
ChangeTags\listSoftwareDefinedTags
static listSoftwareDefinedTags()
Lists tags defined by core or extensions using the ListDefinedTags hook.
Definition: ChangeTags.php:1644
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:948
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:878
ChangeTags\canDeactivateTag
static canDeactivateTag( $tag, Authority $performer=null)
Is it OK to allow the user to deactivate this tag?
Definition: ChangeTags.php:1238
SpecialTags\$explicitlyDefinedTags
array $explicitlyDefinedTags
List of explicitly defined tags.
Definition: SpecialTags.php:34
SpecialTags\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialTags.php:501
SpecialTags\__construct
__construct()
Definition: SpecialTags.php:46
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:618
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:764
ChangeTags\listExplicitlyDefinedTags
static listExplicitlyDefinedTags()
Lists tags explicitly defined in the change_tag_def table of the database.
Definition: ChangeTags.php:1606
SpecialTags\showTagList
showTagList()
Definition: SpecialTags.php:74
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
SpecialTags\showActivateDeactivateForm
showActivateDeactivateForm( $tag, $activate)
Definition: SpecialTags.php:401
SpecialTags\doTagRow
doTagRow( $tag, $hitcount, $showManageActions, $showDeleteActions, $showEditLinks)
Definition: SpecialTags.php:183
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:132
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:780
ChangeTags\listSoftwareActivatedTags
static listSoftwareActivatedTags()
Lists those tags which core or extensions report as being "active".
Definition: ChangeTags.php:1558
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:48
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1028
SpecialTags\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialTags.php:50
SpecialTags\$softwareDefinedTags
array $softwareDefinedTags
List of software defined tags.
Definition: SpecialTags.php:39
getTitle
getTitle()
Definition: RevisionSearchResultTrait.php:81
SpecialTags\$softwareActivatedTags
array $softwareActivatedTags
List of software activated tags.
Definition: SpecialTags.php:44
SpecialTags\showDeleteTagForm
showDeleteTagForm( $tag)
Definition: SpecialTags.php:343
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:1692
SpecialTags\processTagForm
processTagForm(array $data, HTMLForm $form, string $action)
Definition: SpecialTags.php:461
HTMLForm\factory
static factory( $displayFormat,... $arguments)
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:326
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:709
ChangeTags\tagDescription
static tagDescription( $tag, MessageLocalizer $context)
Get a short description for a tag.
Definition: ChangeTags.php:283
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:143
ChangeTags\canDeleteTag
static canDeleteTag( $tag, Authority $performer=null, int $flags=0)
Is it OK to allow the user to delete this tag?
Definition: ChangeTags.php:1465