MediaWiki master
SpecialBrokenRedirects.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
28use stdClass;
32
63
64 private IContentHandlerFactory $contentHandlerFactory;
66 private array $redirectTargets = [];
67
68 public function __construct(
69 IContentHandlerFactory $contentHandlerFactory,
70 IConnectionProvider $dbProvider,
71 LinkBatchFactory $linkBatchFactory
72 ) {
73 parent::__construct( 'BrokenRedirects' );
74 $this->contentHandlerFactory = $contentHandlerFactory;
75 $this->setDatabaseProvider( $dbProvider );
76 $this->setLinkBatchFactory( $linkBatchFactory );
77 }
78
79 public function isExpensive() {
80 return true;
81 }
82
83 public function isSyndicated() {
84 return false;
85 }
86
87 protected function sortDescending() {
88 return false;
89 }
90
91 protected function getPageHeader() {
92 return $this->msg( 'brokenredirectstext' )->parseAsBlock();
93 }
94
95 public function getQueryInfo() {
96 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
97
98 return [
99 'tables' => [
100 'redirect',
101 'p1' => 'page',
102 'p2' => 'page',
103 ],
104 'fields' => [
105 'namespace' => 'p1.page_namespace',
106 'title' => 'p1.page_title',
107 'rd_namespace',
108 'rd_title',
109 'rd_fragment',
110 ],
111 'conds' => [
112 // Exclude pages that don't exist locally as wiki pages, but aren't "broken" either: special
113 // pages and interwiki links.
114 $dbr->expr( 'rd_namespace', '>=', 0 ),
115 'rd_interwiki' => '',
116 'p2.page_namespace' => null,
117 ],
118 'join_conds' => [
119 'p1' => [ 'JOIN', [
120 'rd_from=p1.page_id',
121 ] ],
122 'p2' => [ 'LEFT JOIN', [
123 'rd_namespace=p2.page_namespace',
124 'rd_title=p2.page_title'
125 ] ],
126 ],
127 ];
128 }
129
133 protected function getOrderFields() {
134 return [ 'rd_namespace', 'rd_title', 'rd_from' ];
135 }
136
143 public function preprocessResults( $db, $res ) {
144 if ( !$res->numRows() ) {
145 return;
146 }
147
148 $batch = $this->getLinkBatchFactory()->newLinkBatch()->setCaller( __METHOD__ );
149 $cached = $this->isCached();
150 foreach ( $res as $row ) {
151 // Preload LinkRenderer data for source links
152 $batch->add( $row->namespace, $row->title );
153 if ( !$cached ) {
154 // Preload LinkRenderer data for destination links
155 $batch->add( $row->rd_namespace, $row->rd_title );
156 }
157 }
158 if ( $cached ) {
159 // Preload redirect targets and LinkRenderer data for destination links
160 $rdRes = $db->newSelectQueryBuilder()
161 ->select( [ 'page_namespace', 'page_title', 'rd_namespace', 'rd_title', 'rd_fragment' ] )
162 ->from( 'page' )
163 ->join( 'redirect', null, 'page_id = rd_from' )
164 ->where( $batch->constructSet( 'page', $db ) )
165 ->caller( __METHOD__ )
166 ->fetchResultSet();
167
168 foreach ( $rdRes as $rdRow ) {
169 $batch->add( $rdRow->rd_namespace, $rdRow->rd_title );
170 $this->redirectTargets[$rdRow->page_namespace][$rdRow->page_title] =
171 Title::makeTitle( $rdRow->rd_namespace, $rdRow->rd_title, $rdRow->rd_fragment );
172 }
173 }
174 $batch->execute();
175 // Rewind for display
176 $res->seek( 0 );
177 }
178
179 protected function getRedirectTarget( stdClass $result ): ?Title {
180 if ( isset( $result->rd_title ) ) {
181 return Title::makeTitle(
182 $result->rd_namespace,
183 $result->rd_title,
184 $result->rd_fragment
185 );
186 } else {
187 return $this->redirectTargets[$result->namespace][$result->title] ?? null;
188 }
189 }
190
196 public function formatResult( $skin, $result ) {
197 $fromObj = Title::makeTitle( $result->namespace, $result->title );
198 $toObj = $this->getRedirectTarget( $result );
199
200 $linkRenderer = $this->getLinkRenderer();
201
202 if ( $toObj === null || $toObj->isKnown() ) {
203 return '<del>' . $linkRenderer->makeLink( $fromObj ) . '</del>';
204 }
205
206 $from = $linkRenderer->makeKnownLink(
207 $fromObj,
208 null,
209 [],
210 [ 'redirect' => 'no' ]
211 );
212 $links = [];
213 // if the page is editable, add an edit link
214 if (
215 // check user permissions
216 $this->getAuthority()->isAllowed( 'edit' ) &&
217 // check, if the content model is editable through action=edit
218 $this->contentHandlerFactory->getContentHandler( $fromObj->getContentModel() )
219 ->supportsDirectEditing()
220 ) {
221 $links[] = $linkRenderer->makeKnownLink(
222 $fromObj,
223 $this->msg( 'brokenredirects-edit' )->text(),
224 [],
225 [ 'action' => 'edit' ]
226 );
227 }
228 $to = $linkRenderer->makeBrokenLink( $toObj, $toObj->getFullText() );
229 $arr = $this->getLanguage()->getArrow();
230
231 $out = $from . $this->msg( 'word-separator' )->escaped();
232
233 if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
234 $links[] = $linkRenderer->makeKnownLink(
235 $fromObj,
236 $this->msg( 'brokenredirects-delete' )->text(),
237 [],
238 [
239 'action' => 'delete',
240 'wpReason' => $this->msg( 'brokenredirects-delete-reason' )
241 ->inContentLanguage()
242 ->text()
243 ]
244 );
245 }
246
247 if ( $links ) {
248 $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
249 ->pipeList( $links ) )->escaped();
250 }
251 $out .= " {$arr} {$to}";
252
253 return $out;
254 }
255
256 public function execute( $par ) {
257 $this->addHelpLink( 'Help:Redirects' );
258 parent::execute( $par );
259 }
260
261 protected function getGroupName() {
262 return 'maintenance';
263 }
264}
265
267class_alias( SpecialBrokenRedirects::class, 'SpecialBrokenRedirects' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
The base class for all skins.
Definition Skin.php:58
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
Definition QueryPage.php:87
setDatabaseProvider(IConnectionProvider $databaseProvider)
isCached()
Whether or not the output of the page in question is retrieved from the database cache.
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
List of redirects to non-existent pages.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
preprocessResults( $db, $res)
Preload LinkRenderer for source and destination.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
isExpensive()
Should this query page only be updated offline on large wikis?
__construct(IContentHandlerFactory $contentHandlerFactory, IConnectionProvider $dbProvider, LinkBatchFactory $linkBatchFactory)
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
sortDescending()
Override to sort by increasing values.
getPageHeader()
The content returned by this function will be output before any result.
execute( $par)
This is the actual workhorse.
Represents a title within MediaWiki.
Definition Title.php:78
Provide primary and replica IDatabase connections.
Interface to a relational database.
Definition IDatabase.php:45
Result wrapper for grabbing data queried from an IDatabase object.