Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
82 / 82
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
NukeAssociatedQueryBuilder
100.00% covered (success)
100.00%
82 / 82
100.00% covered (success)
100.00%
3 / 3
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getTalkPages
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
4
 getRedirectPages
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\Nuke;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Exception\MWException;
7use MediaWiki\MainConfigNames;
8use MediaWiki\Title\NamespaceInfo;
9use MediaWiki\Title\Title;
10use Wikimedia\Rdbms\IReadableDatabase;
11use Wikimedia\Rdbms\SelectQueryBuilder;
12
13class NukeAssociatedQueryBuilder {
14
15    private IReadableDatabase $readableDatabase;
16    private Config $config;
17    private NamespaceInfo $namespaceInfo;
18
19    /**
20     * @param IReadableDatabase $readableDatabase
21     * @param Config $config
22     * @param NamespaceInfo $namespaceInfo
23     */
24    public function __construct(
25        IReadableDatabase $readableDatabase,
26        Config $config,
27        NamespaceInfo $namespaceInfo
28    ) {
29        $this->readableDatabase = $readableDatabase;
30        $this->config = $config;
31        $this->namespaceInfo = $namespaceInfo;
32    }
33
34    /**
35     * Get the associated talk pages that exist for each page in the provided
36     * list.
37     *
38     * @param Title[] $pages
39     * @return SelectQueryBuilder
40     */
41    public function getTalkPages( array $pages ): SelectQueryBuilder {
42        $byNamespace = [];
43
44        // Sort the pages by their associated talk namespaces.
45        foreach ( $pages as $page ) {
46            try {
47                $talkNamespace = $this->namespaceInfo->getTalk( $page->getNamespace() );
48            } catch ( MWException $e ) {
49                continue;
50            }
51            $byNamespace[ $talkNamespace ][] = $page->getDBkey();
52        }
53
54        // Get the talk pages that exist for each page.
55        $dbr = $this->readableDatabase;
56        $queryBuilder = $dbr->newSelectQueryBuilder()
57            ->select( [
58                'talk.page_id',
59                'talk.page_namespace',
60                'talk.page_title',
61                'actor_name',
62                'subject_page_id' => 'subject.page_id',
63            ] )
64            ->distinct()
65            ->from( 'page', 'talk' )
66            ->join( 'revision', 'first', [
67                'first.rev_id=talk.page_latest',
68                'first.rev_parent_id=0'
69            ] )
70            ->join( 'actor', null, 'actor_id=first.rev_actor' )
71            // Self-join to identify the subject page
72            ->join( 'page', 'subject', [
73                'subject.page_title=talk.page_title',
74                // Talk namespaces are always 1 greater than the subject namespace.
75                'subject.page_namespace=(talk.page_namespace - 1)'
76            ] )
77            ->setMaxExecutionTime(
78                $this->config->get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
79            );
80        $conditions = [];
81        foreach ( $byNamespace as $talkNamespace => $names ) {
82            $conditions[] = $dbr->andExpr( [
83                $dbr->expr( 'talk.page_namespace', '=', $talkNamespace ),
84                $dbr->expr( 'talk.page_title', '=', $names ),
85            ] );
86        }
87        $queryBuilder->where( [
88            $dbr->orExpr( $conditions )
89        ] );
90        return $queryBuilder;
91    }
92
93    public function getRedirectPages( array $pages ): SelectQueryBuilder {
94        $byNamespace = [];
95
96        // Sort the pages by their namespaces.
97        foreach ( $pages as $page ) {
98            $byNamespace[ $page->getNamespace() ][] = $page->getDBkey();
99        }
100
101        $dbr = $this->readableDatabase;
102        $queryBuilder = $dbr->newSelectQueryBuilder()
103            ->select( [
104                'rdpage.page_id',
105                'rdpage.page_namespace',
106                'rdpage.page_title',
107                'actor_name',
108                'target_page_id' => 'target.page_id'
109            ] )
110            ->distinct()
111            ->from( 'redirect' )
112            ->join( 'page', 'rdpage', 'rd_from=rdpage.page_id' )
113            ->join( 'revision', 'first', [
114                'first.rev_id=rdpage.page_latest',
115                'first.rev_parent_id=0'
116            ] )
117            ->join( 'actor', null, 'actor_id=first.rev_actor' )
118            // Self-join to identify the target page
119            ->join( 'page', 'target', [
120                'target.page_title=rd_title',
121                'target.page_namespace=rd_namespace'
122            ] );
123
124        $conditions = [];
125        foreach ( $byNamespace as $namespace => $names ) {
126            $conditions[] = $dbr->andExpr( [
127                $dbr->expr( 'rd_namespace', '=', $namespace ),
128                $dbr->expr( 'rd_title', '=', $names ),
129            ] );
130        }
131        $queryBuilder->where( $dbr->orExpr( $conditions ) );
132        $queryBuilder->andWhere( $dbr->orExpr( [
133            $dbr->expr( 'rd_interwiki', '=', null ),
134            $dbr->expr( 'rd_interwiki', '=', "" )
135        ] ) );
136
137        return $queryBuilder->setMaxExecutionTime(
138            $this->config->get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
139        );
140    }
141
142}