45 parent::__construct(
'Tags' );
53 $this->
getOutput()->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
74 private function showTagList() {
76 $out->setPageTitleMsg( $this->
msg(
'tags-title' ) );
77 $out->wrapWikiMsg(
"<div class='mw-tags-intro'>\n$1\n</div>",
'tags-intro' );
80 $userCanManage = $authority->isAllowed(
'managechangetags' );
81 $userCanDelete = $authority->isAllowed(
'deletechangetags' );
82 $userCanEditInterface = $authority->isAllowed(
'editinterface' );
85 if ( $userCanManage ) {
89 'label' => $this->
msg(
'tags-create-tag-name' )->plain(),
94 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
95 'label' => $this->
msg(
'tags-create-reason' )->plain(),
103 HTMLForm::factory(
'ooui', $fields, $this->
getContext() )
104 ->setAction( $this->
getPageTitle(
'create' )->getLocalURL() )
105 ->setWrapperLegendMsg(
'tags-create-heading' )
106 ->setHeaderHtml( $this->
msg(
'tags-create-explanation' )->parseAsBlock() )
107 ->setSubmitCallback( $this->processCreateTagForm( ... ) )
108 ->setSubmitTextMsg(
'tags-create-submit' )
118 if ( $out->getRedirect() !==
'' ) {
124 $tagStats = $this->changeTagsStore->tagUsageStatistics();
125 $viewableTags = $this->changeTagsStore->filterViewableTags(
126 array_keys( $tagStats ),
129 $tagStats = array_filter(
131 static fn ( $tag ) => in_array( $tag, $viewableTags,
true ),
136 $this->explicitlyDefinedTags = array_fill_keys(
137 $this->changeTagsStore->filterViewableTags(
138 $this->changeTagsStore->listExplicitlyDefinedTags(),
139 $this->getAuthority()
143 $this->softwareDefinedTags = array_fill_keys(
144 $this->changeTagsStore->filterViewableTags(
145 $this->changeTagsStore->listSoftwareDefinedTags(),
146 $this->getAuthority()
152 $definedTags = array_keys( $this->explicitlyDefinedTags + $this->softwareDefinedTags );
155 if ( !$tagStats && !$definedTags ) {
160 $thead = Html::rawElement(
'tr', [], Html::rawElement(
'th', [], $this->
msg(
'tags-tag' )->parse() ) .
161 Html::rawElement(
'th', [], $this->
msg(
'tags-display-header' )->parse() ) .
162 Html::rawElement(
'th', [], $this->
msg(
'tags-description-header' )->parse() ) .
163 Html::rawElement(
'th', [], $this->
msg(
'tags-source-header' )->parse() ) .
164 Html::rawElement(
'th', [], $this->
msg(
'tags-active-header' )->parse() ) .
165 Html::rawElement(
'th', [], $this->
msg(
'tags-hitcount-header' )->parse() ) .
166 ( ( $userCanManage || $userCanDelete ) ?
167 Html::rawElement(
'th', [
'class' =>
'unsortable' ],
168 $this->
msg(
'tags-actions-header' )->parse() ) :
174 $this->softwareActivatedTags = array_fill_keys(
175 $this->changeTagsStore->filterViewableTags(
176 $this->changeTagsStore->listSoftwareActivatedTags(),
177 $this->getAuthority()
183 foreach ( $tagStats as $tag => $hitcount ) {
184 $tbody .= $this->doTagRow( $tag, $hitcount, $userCanManage,
185 $userCanDelete, $userCanEditInterface );
188 foreach ( $definedTags as $tag ) {
189 if ( !isset( $tagStats[$tag] ) ) {
190 $tbody .= $this->doTagRow( $tag, 0, $userCanManage, $userCanDelete, $userCanEditInterface );
194 $out->addModuleStyles( [
195 'jquery.tablesorter.styles',
196 'mediawiki.pager.styles'
198 $out->addModules(
'jquery.tablesorter' );
199 $out->addHTML( Html::rawElement(
201 [
'class' =>
'mw-datatable sortable mw-tags-table' ],
202 Html::rawElement(
'thead', [], $thead ) .
203 Html::rawElement(
'tbody', [], $tbody )
207 private function doTagRow(
208 string $tag,
int $hitcount,
bool $showManageActions,
bool $showDeleteActions,
bool $showEditLinks
211 $newRow .= Html::rawElement(
'td', [],
Html::element(
'code', [], $tag ) );
214 $disp = $this->changeTagsFormatter->getTagDescription( $tag, $this->
getContext() );
215 if ( $disp ===
'' ) {
218 if ( $showEditLinks ) {
220 $editLink = $linkRenderer->makeLink(
222 $this->
msg(
'tags-edit' )->text(),
224 [
'action' =>
'edit' ]
226 $helpEditLink = $linkRenderer->makeLink(
227 $this->
msg(
"tag-$tag-helppage" )->inContentLanguage()->
getTitle(),
228 $this->
msg(
'tags-helppage-edit' )->text(),
230 [
'action' =>
'edit' ]
232 $disp .= $this->
msg(
'parentheses' )->rawParams(
233 $this->
getLanguage()->pipeList( [ $editLink, $helpEditLink ] )
236 $newRow .= Html::rawElement(
'td', [], $disp );
238 $msg = $this->
msg(
"tag-$tag-description" );
239 $desc = !$msg->exists() ?
'' : $msg->parse();
240 if ( $showEditLinks ) {
242 $editDescLink = $linkRenderer->makeLink(
243 $this->
msg(
"tag-$tag-description" )->inContentLanguage()->
getTitle(),
244 $this->
msg(
'tags-edit' )->text(),
246 [
'action' =>
'edit' ]
248 $desc .= $this->
msg(
'parentheses' )->rawParams( $editDescLink )->escaped();
250 $newRow .= Html::rawElement(
'td', [], $desc );
253 $isSoftware = isset( $this->softwareDefinedTags[$tag] );
254 $isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
257 $sourceMsgs[] = $this->
msg(
'tags-source-extension' )->escaped();
260 $sourceMsgs[] = $this->
msg(
'tags-source-manual' )->escaped();
262 if ( !$sourceMsgs ) {
263 $sourceMsgs[] = $this->
msg(
'tags-source-none' )->escaped();
265 $newRow .= Html::rawElement(
'td', [], implode(
Html::element(
'br' ), $sourceMsgs ) );
267 $isActive = $isExplicit || isset( $this->softwareActivatedTags[$tag] );
268 $activeMsg = ( $isActive ?
'tags-active-yes' :
'tags-active-no' );
271 $hitcountLabelMsg = $this->
msg(
'tags-hitcount' )->numParams( $hitcount );
273 $hitcountLabel = $linkRenderer->makeLink(
275 $hitcountLabelMsg->text(),
277 [
'tagfilter' => $tag ]
280 $hitcountLabel = $hitcountLabelMsg->escaped();
284 $newRow .= Html::rawElement(
'td', [
'data-sort-value' => $hitcount ], $hitcountLabel );
288 if ( $showDeleteActions && ChangeTags::canDeleteTag( $tag )->isOK() ) {
289 $actionLinks[] = $linkRenderer->makeKnownLink(
291 $this->
msg(
'tags-delete' )->text(),
296 if ( $showManageActions ) {
297 if ( ChangeTags::canActivateTag( $tag )->isOK() ) {
298 $actionLinks[] = $linkRenderer->makeKnownLink(
300 $this->
msg(
'tags-activate' )->text(),
305 if ( ChangeTags::canDeactivateTag( $tag )->isOK() ) {
306 $actionLinks[] = $linkRenderer->makeKnownLink(
308 $this->
msg(
'tags-deactivate' )->text(),
314 if ( $showDeleteActions || $showManageActions ) {
315 $newRow .= Html::rawElement(
'td', [], $this->
getLanguage()->pipeList( $actionLinks ) );
318 return Html::rawElement(
'tr', [], $newRow ) .
"\n";
321 private function processCreateTagForm( array $data, HTMLForm $form ): bool {
323 $out = $context->getOutput();
325 $tag = trim( strval( $data[
'Tag'] ) );
326 $ignoreWarnings = isset( $data[
'IgnoreWarnings'] ) && $data[
'IgnoreWarnings'] ===
'1';
327 $status = ChangeTags::createTagWithChecks( $tag, $data[
'Reason'],
328 $context->getAuthority(), $ignoreWarnings );
330 if ( $status->isGood() ) {
331 $out->redirect( $this->getPageTitle()->getLocalURL() );
333 } elseif ( $status->isOK() ) {
337 'IgnoreWarnings' => [
343 $headerText = $this->
msg(
'tags-create-warnings-above', $tag,
344 count( $status->getMessages(
'warning' ) ) )->parseAsBlock() .
345 $out->parseAsInterface( $status->getWikiText() ) .
346 $this->
msg(
'tags-create-warnings-below' )->parseAsBlock();
348 $form->setHeaderHtml( $headerText )
349 ->setSubmitTextMsg(
'htmlform-yes' );
351 $out->addBacklinkSubtitle( $this->getPageTitle() );
354 foreach ( $status->getMessages() as $msg ) {
355 $out->addHTML( Html::errorBox(
356 $this->
msg( $msg )->parse()
364 $authority = $this->getAuthority();
365 if ( !$authority->isAllowed(
'deletechangetags' ) ) {
369 $out = $this->getOutput();
370 $out->setPageTitleMsg( $this->msg(
'tags-delete-title' ) );
371 $out->addBacklinkSubtitle( $this->getPageTitle() );
374 $out->addWikiMsg(
'tags-delete-not-specified' );
379 $canDeleteResult = ChangeTags::canDeleteTag( $tag, $authority );
380 if ( !$canDeleteResult->isGood() ) {
381 foreach ( $canDeleteResult->getMessages() as $msg ) {
382 $out->addHTML( Html::errorBox(
383 $this->msg( $msg )->parse()
386 if ( !$canDeleteResult->isOK() ) {
391 $preText = $this->
msg(
'tags-delete-explanation-initial', $tag )->parseAsBlock();
392 $tagUsage = $this->changeTagsStore->tagUsageStatistics();
393 if ( isset( $tagUsage[$tag] ) && $tagUsage[$tag] > 0 ) {
394 $preText .= $this->
msg(
'tags-delete-explanation-in-use', $tag,
395 $tagUsage[$tag] )->parseAsBlock();
397 $preText .= $this->
msg(
'tags-delete-explanation-warning', $tag )->parseAsBlock();
400 $this->softwareActivatedTags = array_fill_keys(
401 $this->changeTagsStore->listSoftwareActivatedTags(),
true );
402 if ( isset( $this->softwareActivatedTags[$tag] ) ) {
403 $preText .= $this->
msg(
'tags-delete-explanation-active', $tag )->parseAsBlock();
407 $fields[
'Reason'] = [
409 'label' => $this->
msg(
'tags-delete-reason' )->plain(),
412 $fields[
'HiddenTag'] = [
419 HTMLForm::factory(
'ooui', $fields, $this->
getContext() )
420 ->setAction( $this->getPageTitle(
'delete' )->getLocalURL() )
421 ->setSubmitCallback(
function ( $data, $form ) {
422 return $this->processTagForm( $data, $form,
'delete' );
424 ->setSubmitTextMsg(
'tags-delete-submit' )
425 ->setSubmitDestructive()
426 ->addPreHtml( $preText )
431 $actionStr = $activate ?
'activate' :
'deactivate';
433 $authority = $this->getAuthority();
434 if ( !$authority->isAllowed(
'managechangetags' ) ) {
438 $out = $this->getOutput();
440 $out->setPageTitleMsg( $this->msg(
"tags-$actionStr-title" ) );
441 $out->addBacklinkSubtitle( $this->getPageTitle() );
444 $out->addWikiMsg(
'tags-deactivate-or-activate-not-specified' );
450 $result = ChangeTags::canActivateTag( $tag, $authority );
452 $result = ChangeTags::canDeactivateTag( $tag, $authority );
454 if ( !$result->isGood() ) {
455 foreach ( $result->getMessages() as $msg ) {
456 $out->addHTML( Html::errorBox(
457 $this->
msg( $msg )->parse()
460 if ( !$result->isOK() ) {
466 $preText = $this->
msg(
"tags-$actionStr-question", $tag )->parseAsBlock();
470 $fields[
'Reason'] = [
472 'label' => $this->
msg(
"tags-$actionStr-reason" )->plain(),
475 $fields[
'HiddenTag'] = [
482 HTMLForm::factory(
'ooui', $fields, $this->
getContext() )
483 ->setAction( $this->getPageTitle( $actionStr )->getLocalURL() )
484 ->setSubmitCallback(
function ( $data, $form ) use ( $actionStr ) {
485 return $this->processTagForm( $data, $form, $actionStr );
488 ->setSubmitTextMsg(
"tags-$actionStr-submit" )
489 ->addPreHtml( $preText )
501 $out = $context->getOutput();
503 $tag = $data[
'HiddenTag'];
505 $method =
"{$action}TagWithChecks";
506 $status = ChangeTags::$method(
507 $tag, $data[
'Reason'], $context->getUser(),
true );
509 if ( $status->isGood() ) {
510 $out->redirect( $this->getPageTitle()->getLocalURL() );
512 } elseif ( $status->isOK() && $action ===
'delete' ) {
514 $out->addWikiTextAsInterface( $this->msg(
'tags-delete-warnings-after-delete', $tag,
515 count( $status->getMessages(
'warning' ) ) )->text() .
"\n" .
516 $status->getWikitext() );
517 $out->addReturnTo( $this->getPageTitle() );
520 foreach ( $status->getMessages() as $msg ) {
521 $out->addHTML( Html::errorBox(
522 $this->msg( $msg )->parse()