Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialDoubleRedirects
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 12
600
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
 isExpensive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isSyndicated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sortDescending
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPageHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 reallyGetQueryInfo
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
12
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOrderFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatResult
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
42
 execute
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 preprocessResults
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Specials;
22
23use MediaWiki\Cache\LinkBatchFactory;
24use MediaWiki\Content\IContentHandlerFactory;
25use MediaWiki\SpecialPage\QueryPage;
26use MediaWiki\Title\Title;
27use Skin;
28use stdClass;
29use Wikimedia\Rdbms\IConnectionProvider;
30use Wikimedia\Rdbms\IDatabase;
31use Wikimedia\Rdbms\IResultWrapper;
32
33/**
34 * List of redirects to another redirecting page.
35 *
36 * The software will by default not follow double redirects, to prevent loops.
37 * Editors are encouraged to fix these, and can discover them via this page.
38 *
39 * @ingroup SpecialPage
40 */
41class SpecialDoubleRedirects extends QueryPage {
42
43    private IContentHandlerFactory $contentHandlerFactory;
44    private LinkBatchFactory $linkBatchFactory;
45
46    /**
47     * @param IContentHandlerFactory $contentHandlerFactory
48     * @param LinkBatchFactory $linkBatchFactory
49     * @param IConnectionProvider $dbProvider
50     */
51    public function __construct(
52        IContentHandlerFactory $contentHandlerFactory,
53        LinkBatchFactory $linkBatchFactory,
54        IConnectionProvider $dbProvider
55    ) {
56        parent::__construct( 'DoubleRedirects' );
57        $this->contentHandlerFactory = $contentHandlerFactory;
58        $this->linkBatchFactory = $linkBatchFactory;
59        $this->setDatabaseProvider( $dbProvider );
60    }
61
62    public function isExpensive() {
63        return true;
64    }
65
66    public function isSyndicated() {
67        return false;
68    }
69
70    protected function sortDescending() {
71        return false;
72    }
73
74    protected function getPageHeader() {
75        return $this->msg( 'doubleredirectstext' )->parseAsBlock();
76    }
77
78    private function reallyGetQueryInfo( $namespace = null, $title = null ) {
79        $limitToTitle = !( $namespace === null && $title === null );
80        $retval = [
81            'tables' => [
82                'ra' => 'redirect',
83                'rb' => 'redirect',
84                'pa' => 'page',
85                'pb' => 'page'
86            ],
87            'fields' => [
88                'namespace' => 'pa.page_namespace',
89                'title' => 'pa.page_title',
90
91                'b_namespace' => 'pb.page_namespace',
92                'b_title' => 'pb.page_title',
93                'b_fragment' => 'ra.rd_fragment',
94
95                // Select fields from redirect instead of page. Because there may
96                // not actually be a page table row for this target (e.g. for interwiki redirects)
97                'c_namespace' => 'rb.rd_namespace',
98                'c_title' => 'rb.rd_title',
99                'c_fragment' => 'rb.rd_fragment',
100                'c_interwiki' => 'rb.rd_interwiki',
101            ],
102            'conds' => [
103                'ra.rd_from = pa.page_id',
104
105                // Filter out redirects where the target goes interwiki (T42353).
106                // This isn't an optimization, it is required for correct results,
107                // otherwise a non-double redirect like Bar -> w:Foo will show up
108                // like "Bar -> Foo -> w:Foo".
109                'ra.rd_interwiki' => '',
110
111                'pb.page_namespace = ra.rd_namespace',
112                'pb.page_title = ra.rd_title',
113
114                'rb.rd_from = pb.page_id',
115            ]
116        ];
117
118        if ( $limitToTitle ) {
119            $retval['conds']['pa.page_namespace'] = $namespace;
120            $retval['conds']['pa.page_title'] = $title;
121        }
122
123        return $retval;
124    }
125
126    public function getQueryInfo() {
127        return $this->reallyGetQueryInfo();
128    }
129
130    protected function getOrderFields() {
131        return [ 'ra.rd_namespace', 'ra.rd_title' ];
132    }
133
134    /**
135     * @param Skin $skin
136     * @param stdClass $result Result row
137     * @return string
138     */
139    public function formatResult( $skin, $result ) {
140        // If no Title B or C is in the query, it means this came from
141        // querycache (which only saves the 3 columns for title A).
142        // That does save the bulk of the query cost, but now we need to
143        // get a little more detail about each individual entry quickly
144        // using the filter of reallyGetQueryInfo.
145        $deep = false;
146        if ( $result ) {
147            if ( isset( $result->b_namespace ) ) {
148                $deep = $result;
149            } else {
150                $qi = $this->reallyGetQueryInfo(
151                    $result->namespace,
152                    $result->title
153                );
154                $deep = $this->getDatabaseProvider()->getReplicaDatabase()->newSelectQueryBuilder()
155                    ->queryInfo( $qi )
156                    ->caller( __METHOD__ )
157                    ->fetchRow();
158            }
159        }
160
161        $titleA = Title::makeTitle( $result->namespace, $result->title );
162
163        $linkRenderer = $this->getLinkRenderer();
164        if ( !$deep ) {
165            return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
166        }
167
168        // if the page is editable, add an edit link
169        if (
170            // check user permissions
171            $this->getAuthority()->isAllowed( 'edit' ) &&
172            // check, if the content model is editable through action=edit
173            $this->contentHandlerFactory->getContentHandler( $titleA->getContentModel() )
174                ->supportsDirectEditing()
175        ) {
176            $edit = $linkRenderer->makeKnownLink(
177                $titleA,
178                $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
179                [],
180                [ 'action' => 'edit' ]
181            );
182        } else {
183            $edit = '';
184        }
185
186        $linkA = $linkRenderer->makeKnownLink(
187            $titleA,
188            null,
189            [],
190            [ 'redirect' => 'no' ]
191        );
192
193        $titleB = Title::makeTitle( $deep->b_namespace, $deep->b_title );
194        // We show fragment, but don't link to it, as it probably doesn't exist anymore.
195        $titleBFrag = Title::makeTitle( $deep->b_namespace, $deep->b_title, $deep->b_fragment );
196        $linkB = $linkRenderer->makeKnownLink(
197            $titleB,
198            $titleBFrag->getFullText(),
199            [],
200            [ 'redirect' => 'no' ]
201        );
202
203        $titleC = Title::makeTitle(
204            $deep->c_namespace,
205            $deep->c_title,
206            $deep->c_fragment,
207            $deep->c_interwiki
208        );
209        $linkC = $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() );
210
211        $lang = $this->getLanguage();
212        $arr = $lang->getArrow() . $lang->getDirMark();
213
214        return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
215    }
216
217    public function execute( $par ) {
218        $this->addHelpLink( 'Help:Redirects' );
219        parent::execute( $par );
220    }
221
222    /**
223     * Cache page content model and gender distinction for performance
224     *
225     * @param IDatabase $db
226     * @param IResultWrapper $res
227     */
228    public function preprocessResults( $db, $res ) {
229        if ( !$res->numRows() ) {
230            return;
231        }
232
233        $batch = $this->linkBatchFactory->newLinkBatch();
234        foreach ( $res as $row ) {
235            $batch->add( $row->namespace, $row->title );
236            if ( isset( $row->b_namespace ) ) {
237                // lazy loaded when using cached results
238                $batch->add( $row->b_namespace, $row->b_title );
239            }
240            if ( isset( $row->c_interwiki ) && !$row->c_interwiki ) {
241                // lazy loaded when using cached result, not added when interwiki link
242                $batch->add( $row->c_namespace, $row->c_title );
243            }
244        }
245        $batch->execute();
246
247        // Back to start for display
248        $res->seek( 0 );
249    }
250
251    protected function getGroupName() {
252        return 'maintenance';
253    }
254}
255
256/** @deprecated class alias since 1.41 */
257class_alias( SpecialDoubleRedirects::class, 'SpecialDoubleRedirects' );