Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialWantedCategories
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 6
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 getRecacheDB
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 preprocessResults
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 formatResult
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2005 Ævar Arnfjörð Bjarmason
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Specials;
10
11use MediaWiki\Deferred\LinksUpdate\CategoryLinksTable;
12use MediaWiki\Language\ILanguageConverter;
13use MediaWiki\Languages\LanguageConverterFactory;
14use MediaWiki\Linker\LinksMigration;
15use MediaWiki\Page\LinkBatchFactory;
16use MediaWiki\Skin\Skin;
17use MediaWiki\SpecialPage\WantedQueryPage;
18use MediaWiki\Title\Title;
19use stdClass;
20use Wikimedia\HtmlArmor\HtmlArmor;
21use Wikimedia\Rdbms\IConnectionProvider;
22
23/**
24 * List of the most wanted categories
25 *
26 * @ingroup SpecialPage
27 */
28class SpecialWantedCategories extends WantedQueryPage {
29    /** @var int[] */
30    private $currentCategoryCounts;
31
32    private ILanguageConverter $languageConverter;
33
34    public function __construct(
35        IConnectionProvider $dbProvider,
36        LinkBatchFactory $linkBatchFactory,
37        LanguageConverterFactory $languageConverterFactory,
38        private readonly LinksMigration $linksMigration
39    ) {
40        parent::__construct( 'Wantedcategories' );
41        $this->setDatabaseProvider( $dbProvider );
42        $this->setLinkBatchFactory( $linkBatchFactory );
43        $this->languageConverter = $languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
44    }
45
46    /** @inheritDoc */
47    public function getQueryInfo() {
48        $queryInfo = $this->linksMigration->getQueryInfo( 'categorylinks' );
49        $titleField = $this->linksMigration->getTitleFields( 'categorylinks' )[1];
50
51        return [
52            'tables' => array_merge( $queryInfo['tables'], [ 'page' ] ),
53            'fields' => [
54                'namespace' => NS_CATEGORY,
55                'title' => $titleField,
56                'value' => 'COUNT(*)'
57            ],
58            'conds' => [ 'page_title' => null ],
59            'options' => [ 'GROUP BY' => $titleField ],
60            'join_conds' => array_merge( $queryInfo['joins'],
61                [ 'page' => [ 'LEFT JOIN',
62                    [ 'page_title = ' . $titleField,
63                        'page_namespace' => NS_CATEGORY ] ] ] )
64        ];
65    }
66
67    /** @inheritDoc */
68    protected function getRecacheDB() {
69        return $this->getDatabaseProvider()->getReplicaDatabase(
70            CategoryLinksTable::VIRTUAL_DOMAIN,
71            'vslow'
72        );
73    }
74
75    /** @inheritDoc */
76    public function preprocessResults( $db, $res ) {
77        parent::preprocessResults( $db, $res );
78
79        $this->currentCategoryCounts = [];
80
81        if ( !$res->numRows() || !$this->isCached() ) {
82            return;
83        }
84
85        // Fetch (hopefully) up-to-date numbers of pages in each category.
86        // This should be fast enough as we limit the list to a reasonable length.
87
88        $allCategories = [];
89        foreach ( $res as $row ) {
90            $allCategories[] = $row->title;
91        }
92
93        $categoryRes = $db->newSelectQueryBuilder()
94            ->select( [ 'cat_title', 'cat_pages' ] )
95            ->from( 'category' )
96            ->where( [ 'cat_title' => $allCategories ] )
97            ->caller( __METHOD__ )->fetchResultSet();
98        foreach ( $categoryRes as $row ) {
99            $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
100        }
101
102        // Back to start for display
103        $res->seek( 0 );
104    }
105
106    /**
107     * @param Skin $skin
108     * @param stdClass $result Result row
109     * @return string
110     */
111    public function formatResult( $skin, $result ) {
112        $nt = Title::makeTitle( $result->namespace, $result->title );
113
114        $text = new HtmlArmor( $this->languageConverter->convertHtml( $nt->getText() ) );
115
116        if ( !$this->isCached() ) {
117            // We can assume the freshest data
118            $plink = $this->getLinkRenderer()->makeBrokenLink(
119                $nt,
120                $text
121            );
122            $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
123        } else {
124            $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
125
126            $currentValue = $this->currentCategoryCounts[$result->title] ?? 0;
127            $cachedValue = intval( $result->value ); // T76910
128
129            // If the category has been created or emptied since the list was refreshed, strike it
130            if ( $nt->isKnown() || $currentValue === 0 ) {
131                $plink = "<del>$plink</del>";
132            }
133
134            // Show the current number of category entries if it changed
135            if ( $currentValue !== $cachedValue ) {
136                $nlinks = $this->msg( 'nmemberschanged' )
137                    ->numParams( $cachedValue, $currentValue )->escaped();
138            } else {
139                $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
140            }
141        }
142
143        return $this->getLanguage()->specialList( $plink, $nlinks );
144    }
145
146    /** @inheritDoc */
147    protected function getGroupName() {
148        return 'maintenance';
149    }
150}
151
152/**
153 * Retain the old class name for backwards compatibility.
154 * @deprecated since 1.41
155 */
156class_alias( SpecialWantedCategories::class, 'SpecialWantedCategories' );