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