Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialBrokenRedirects
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 11
306
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
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 29
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 / 47
0.00% covered (danger)
0.00%
0 / 1
56
 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 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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 Wikimedia\Rdbms\IConnectionProvider;
29use Wikimedia\Rdbms\IDatabase;
30use Wikimedia\Rdbms\IResultWrapper;
31
32/**
33 * List of redirects to non-existent pages.
34 *
35 * Editors are encouraged to fix these by editing them to redirect to
36 * an existing page instead.
37 *
38 * @ingroup SpecialPage
39 */
40class SpecialBrokenRedirects extends QueryPage {
41
42    private IContentHandlerFactory $contentHandlerFactory;
43
44    /**
45     * @param IContentHandlerFactory $contentHandlerFactory
46     * @param IConnectionProvider $dbProvider
47     * @param LinkBatchFactory $linkBatchFactory
48     */
49    public function __construct(
50        IContentHandlerFactory $contentHandlerFactory,
51        IConnectionProvider $dbProvider,
52        LinkBatchFactory $linkBatchFactory
53    ) {
54        parent::__construct( 'BrokenRedirects' );
55        $this->contentHandlerFactory = $contentHandlerFactory;
56        $this->setDatabaseProvider( $dbProvider );
57        $this->setLinkBatchFactory( $linkBatchFactory );
58    }
59
60    public function isExpensive() {
61        return true;
62    }
63
64    public function isSyndicated() {
65        return false;
66    }
67
68    protected function sortDescending() {
69        return false;
70    }
71
72    protected function getPageHeader() {
73        return $this->msg( 'brokenredirectstext' )->parseAsBlock();
74    }
75
76    public function getQueryInfo() {
77        $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
78
79        return [
80            'tables' => [
81                'redirect',
82                'p1' => 'page',
83                'p2' => 'page',
84            ],
85            'fields' => [
86                'namespace' => 'p1.page_namespace',
87                'title' => 'p1.page_title',
88                'rd_namespace',
89                'rd_title',
90                'rd_fragment',
91            ],
92            'conds' => [
93                // Exclude pages that don't exist locally as wiki pages, but aren't "broken" either: special
94                // pages and interwiki links.
95                $dbr->expr( 'rd_namespace', '>=', 0 ),
96                'rd_interwiki' => '',
97                'p2.page_namespace' => null,
98            ],
99            'join_conds' => [
100                'p1' => [ 'JOIN', [
101                    'rd_from=p1.page_id',
102                ] ],
103                'p2' => [ 'LEFT JOIN', [
104                    'rd_namespace=p2.page_namespace',
105                    'rd_title=p2.page_title'
106                ] ],
107            ],
108        ];
109    }
110
111    /**
112     * @return array
113     */
114    protected function getOrderFields() {
115        return [ 'rd_namespace', 'rd_title', 'rd_from' ];
116    }
117
118    /**
119     * @param Skin $skin
120     * @param \stdClass $result Result row
121     * @return string
122     */
123    public function formatResult( $skin, $result ) {
124        $fromObj = Title::makeTitle( $result->namespace, $result->title );
125        if ( isset( $result->rd_title ) ) {
126            $toObj = Title::makeTitle(
127                $result->rd_namespace,
128                $result->rd_title,
129                $result->rd_fragment
130            );
131        } else {
132            $toObj = false;
133        }
134
135        $linkRenderer = $this->getLinkRenderer();
136
137        // $toObj may very easily be false if the $result list is cached
138        if ( !is_object( $toObj ) ) {
139            return '<del>' . $linkRenderer->makeLink( $fromObj ) . '</del>';
140        }
141
142        $from = $linkRenderer->makeKnownLink(
143            $fromObj,
144            null,
145            [],
146            [ 'redirect' => 'no' ]
147        );
148        $links = [];
149        // if the page is editable, add an edit link
150        if (
151            // check user permissions
152            $this->getAuthority()->isAllowed( 'edit' ) &&
153            // check, if the content model is editable through action=edit
154            $this->contentHandlerFactory->getContentHandler( $fromObj->getContentModel() )
155                ->supportsDirectEditing()
156        ) {
157            $links[] = $linkRenderer->makeKnownLink(
158                $fromObj,
159                $this->msg( 'brokenredirects-edit' )->text(),
160                [],
161                [ 'action' => 'edit' ]
162            );
163        }
164        $to = $linkRenderer->makeBrokenLink( $toObj, $toObj->getFullText() );
165        $arr = $this->getLanguage()->getArrow();
166
167        $out = $from . $this->msg( 'word-separator' )->escaped();
168
169        if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
170            $links[] = $linkRenderer->makeKnownLink(
171                $fromObj,
172                $this->msg( 'brokenredirects-delete' )->text(),
173                [],
174                [
175                    'action' => 'delete',
176                    'wpReason' => $this->msg( 'brokenredirects-delete-reason' )
177                        ->inContentLanguage()
178                        ->text()
179                ]
180            );
181        }
182
183        if ( $links ) {
184            $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
185                ->pipeList( $links ) )->escaped();
186        }
187        $out .= " {$arr} {$to}";
188
189        return $out;
190    }
191
192    public function execute( $par ) {
193        $this->addHelpLink( 'Help:Redirects' );
194        parent::execute( $par );
195    }
196
197    /**
198     * Cache page content model for performance
199     *
200     * @param IDatabase $db
201     * @param IResultWrapper $res
202     */
203    public function preprocessResults( $db, $res ) {
204        $this->executeLBFromResultWrapper( $res );
205    }
206
207    protected function getGroupName() {
208        return 'maintenance';
209    }
210}
211
212/** @deprecated class alias since 1.41 */
213class_alias( SpecialBrokenRedirects::class, 'SpecialBrokenRedirects' );