function showTagList() { $out = $this->getOutput(); $out->setPageTitle($this->msg('tags-title')); $out->wrapWikiMsg("<div class='mw-tags-intro'>\n\$1\n</div>", 'tags-intro'); $user = $this->getUser(); $userCanManage = $user->isAllowed('managechangetags'); $userCanDelete = $user->isAllowed('deletechangetags'); $userCanEditInterface = $user->isAllowed('editinterface'); // Show form to create a tag if ($userCanManage) { $fields = ['Tag' => ['type' => 'text', 'label' => $this->msg('tags-create-tag-name')->plain(), 'required' => true], 'Reason' => ['type' => 'text', 'label' => $this->msg('tags-create-reason')->plain(), 'size' => 50], 'IgnoreWarnings' => ['type' => 'hidden']]; $form = new HTMLForm($fields, $this->getContext()); $form->setAction($this->getPageTitle('create')->getLocalURL()); $form->setWrapperLegendMsg('tags-create-heading'); $form->setHeaderText($this->msg('tags-create-explanation')->parseAsBlock()); $form->setSubmitCallback([$this, 'processCreateTagForm']); $form->setSubmitTextMsg('tags-create-submit'); $form->show(); // If processCreateTagForm generated a redirect, there's no point // continuing with this, as the user is just going to end up getting sent // somewhere else. Additionally, if we keep going here, we end up // populating the memcache of tag data (see ChangeTags::listDefinedTags) // with out-of-date data from the replica DB, because the replica DB hasn't caught // up to the fact that a new tag has been created as part of an implicit, // as yet uncommitted transaction on master. if ($out->getRedirect() !== '') { return; } } // Used to get hitcounts for #doTagRow() $tagStats = ChangeTags::tagUsageStatistics(); // Used in #doTagRow() $this->explicitlyDefinedTags = array_fill_keys(ChangeTags::listExplicitlyDefinedTags(), true); $this->softwareDefinedTags = array_fill_keys(ChangeTags::listSoftwareDefinedTags(), true); // List all defined tags, even if they were never applied $definedTags = array_keys($this->explicitlyDefinedTags + $this->softwareDefinedTags); // Show header only if there exists atleast one tag if (!$tagStats && !$definedTags) { return; } // Write the headers $html = Xml::tags('tr', null, Xml::tags('th', null, $this->msg('tags-tag')->parse()) . Xml::tags('th', null, $this->msg('tags-display-header')->parse()) . Xml::tags('th', null, $this->msg('tags-description-header')->parse()) . Xml::tags('th', null, $this->msg('tags-source-header')->parse()) . Xml::tags('th', null, $this->msg('tags-active-header')->parse()) . Xml::tags('th', null, $this->msg('tags-hitcount-header')->parse()) . ($userCanManage || $userCanDelete ? Xml::tags('th', ['class' => 'unsortable'], $this->msg('tags-actions-header')->parse()) : '')); // Used in #doTagRow() $this->softwareActivatedTags = array_fill_keys(ChangeTags::listSoftwareActivatedTags(), true); // Insert tags that have been applied at least once foreach ($tagStats as $tag => $hitcount) { $html .= $this->doTagRow($tag, $hitcount, $userCanManage, $userCanDelete, $userCanEditInterface); } // Insert tags defined somewhere but never applied foreach ($definedTags as $tag) { if (!isset($tagStats[$tag])) { $html .= $this->doTagRow($tag, 0, $userCanManage, $userCanDelete, $userCanEditInterface); } } $out->addHTML(Xml::tags('table', ['class' => 'mw-datatable sortable mw-tags-table'], $html)); }
public function execute() { $params = $this->extractRequestParams(); $prop = array_flip($params['prop']); $fld_displayname = isset($prop['displayname']); $fld_description = isset($prop['description']); $fld_hitcount = isset($prop['hitcount']); $fld_defined = isset($prop['defined']); $fld_source = isset($prop['source']); $fld_active = isset($prop['active']); $limit = $params['limit']; $result = $this->getResult(); $softwareDefinedTags = array_fill_keys(ChangeTags::listSoftwareDefinedTags(), 0); $explicitlyDefinedTags = array_fill_keys(ChangeTags::listExplicitlyDefinedTags(), 0); $softwareActivatedTags = array_fill_keys(ChangeTags::listSoftwareActivatedTags(), 0); $definedTags = array_merge($softwareDefinedTags, $explicitlyDefinedTags); # Fetch defined tags that aren't past the continuation if ($params['continue'] !== null) { $cont = $params['continue']; $tags = array_filter(array_keys($definedTags), function ($v) use($cont) { return $v >= $cont; }); $tags = array_fill_keys($tags, 0); } else { $tags = $definedTags; } # Merge in all used tags $this->addTables('change_tag'); $this->addFields('ct_tag'); $this->addFields(['hitcount' => $fld_hitcount ? 'COUNT(*)' : '0']); $this->addOption('LIMIT', $limit + 1); $this->addOption('GROUP BY', 'ct_tag'); $this->addWhereRange('ct_tag', 'newer', $params['continue'], null); $res = $this->select(__METHOD__); foreach ($res as $row) { $tags[$row->ct_tag] = (int) $row->hitcount; } # Now make sure the array is sorted for proper continuation ksort($tags); $count = 0; foreach ($tags as $tagName => $hitcount) { if (++$count > $limit) { $this->setContinueEnumParameter('continue', $tagName); break; } $tag = []; $tag['name'] = $tagName; if ($fld_displayname) { $tag['displayname'] = ChangeTags::tagDescription($tagName, $this); } if ($fld_description) { $msg = $this->msg("tag-{$tagName}-description"); $tag['description'] = $msg->exists() ? $msg->text() : ''; } if ($fld_hitcount) { $tag['hitcount'] = $hitcount; } $isSoftware = isset($softwareDefinedTags[$tagName]); $isExplicit = isset($explicitlyDefinedTags[$tagName]); if ($fld_defined) { $tag['defined'] = $isSoftware || $isExplicit; } if ($fld_source) { $tag['source'] = []; if ($isSoftware) { // TODO: Can we change this to 'software'? $tag['source'][] = 'extension'; } if ($isExplicit) { $tag['source'][] = 'manual'; } } if ($fld_active) { $tag['active'] = $isExplicit || isset($softwareActivatedTags[$tagName]); } $fit = $result->addValue(['query', $this->getModuleName()], null, $tag); if (!$fit) { $this->setContinueEnumParameter('continue', $tagName); break; } } $result->addIndexedTagName(['query', $this->getModuleName()], 'tag'); }