MediaWiki master
SpecialBrokenRedirects.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
14use stdClass;
18
49
51 private array $redirectTargets = [];
52
53 public function __construct(
54 private readonly IContentHandlerFactory $contentHandlerFactory,
55 IConnectionProvider $dbProvider,
56 LinkBatchFactory $linkBatchFactory
57 ) {
58 parent::__construct( 'BrokenRedirects' );
59 $this->setDatabaseProvider( $dbProvider );
60 $this->setLinkBatchFactory( $linkBatchFactory );
61 }
62
64 public function isExpensive() {
65 return true;
66 }
67
69 public function isSyndicated() {
70 return false;
71 }
72
74 protected function sortDescending() {
75 return false;
76 }
77
79 protected function getPageHeader() {
80 return $this->msg( 'brokenredirectstext' )->parseAsBlock();
81 }
82
84 public function getQueryInfo() {
85 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
86
87 return [
88 'tables' => [
89 'redirect',
90 'p1' => 'page',
91 'p2' => 'page',
92 ],
93 'fields' => [
94 'namespace' => 'p1.page_namespace',
95 'title' => 'p1.page_title',
96 'rd_namespace',
97 'rd_title',
98 'rd_fragment',
99 ],
100 'conds' => [
101 // Exclude pages that don't exist locally as wiki pages, but aren't "broken" either: special
102 // pages and interwiki links.
103 $dbr->expr( 'rd_namespace', '>=', 0 ),
104 'rd_interwiki' => '',
105 'p2.page_namespace' => null,
106 ],
107 'join_conds' => [
108 'p1' => [ 'JOIN', [
109 'rd_from=p1.page_id',
110 ] ],
111 'p2' => [ 'LEFT JOIN', [
112 'rd_namespace=p2.page_namespace',
113 'rd_title=p2.page_title'
114 ] ],
115 ],
116 ];
117 }
118
122 protected function getOrderFields() {
123 return [ 'rd_namespace', 'rd_title', 'rd_from' ];
124 }
125
132 public function preprocessResults( $db, $res ) {
133 if ( !$res->numRows() ) {
134 return;
135 }
136
137 $batch = $this->getLinkBatchFactory()->newLinkBatch()->setCaller( __METHOD__ );
138 $cached = $this->isCached();
139 foreach ( $res as $row ) {
140 // Preload LinkRenderer data for source links
141 $batch->add( $row->namespace, $row->title );
142 if ( !$cached ) {
143 // Preload LinkRenderer data for destination links
144 $batch->add( $row->rd_namespace, $row->rd_title );
145 }
146 }
147 if ( $cached ) {
148 // Preload redirect targets and LinkRenderer data for destination links
149 $rdRes = $db->newSelectQueryBuilder()
150 ->select( [ 'page_namespace', 'page_title', 'rd_namespace', 'rd_title', 'rd_fragment' ] )
151 ->from( 'page' )
152 ->join( 'redirect', null, 'page_id = rd_from' )
153 ->where( $batch->constructSet( 'page', $db ) )
154 ->caller( __METHOD__ )
155 ->fetchResultSet();
156
157 foreach ( $rdRes as $rdRow ) {
158 $batch->add( $rdRow->rd_namespace, $rdRow->rd_title );
159 $this->redirectTargets[$rdRow->page_namespace][$rdRow->page_title] =
160 Title::makeTitle( $rdRow->rd_namespace, $rdRow->rd_title, $rdRow->rd_fragment );
161 }
162 }
163 $batch->execute();
164 // Rewind for display
165 $res->seek( 0 );
166 }
167
168 protected function getRedirectTarget( stdClass $result ): ?Title {
169 if ( isset( $result->rd_title ) ) {
170 return Title::makeTitle(
171 $result->rd_namespace,
172 $result->rd_title,
173 $result->rd_fragment
174 );
175 } else {
176 return $this->redirectTargets[$result->namespace][$result->title] ?? null;
177 }
178 }
179
185 public function formatResult( $skin, $result ) {
186 $fromObj = Title::makeTitle( $result->namespace, $result->title );
187 $toObj = $this->getRedirectTarget( $result );
188
189 $linkRenderer = $this->getLinkRenderer();
190
191 if ( $toObj === null || $toObj->isKnown() ) {
192 return '<del>' . $linkRenderer->makeLink( $fromObj ) . '</del>';
193 }
194
195 $from = $linkRenderer->makeKnownLink(
196 $fromObj,
197 null,
198 [],
199 [ 'redirect' => 'no' ]
200 );
201 $links = [];
202 // if the page is editable, add an edit link
203 if (
204 // check user permissions
205 $this->getAuthority()->isAllowed( 'edit' ) &&
206 // check, if the content model is editable through action=edit
207 $this->contentHandlerFactory->getContentHandler( $fromObj->getContentModel() )
208 ->supportsDirectEditing()
209 ) {
210 $links[] = $linkRenderer->makeKnownLink(
211 $fromObj,
212 $this->msg( 'brokenredirects-edit' )->text(),
213 [],
214 [ 'action' => 'edit' ]
215 );
216 }
217 $to = $linkRenderer->makeBrokenLink( $toObj, $toObj->getFullText() );
218 $arr = $this->getLanguage()->getArrow();
219
220 $out = $from . $this->msg( 'word-separator' )->escaped();
221
222 if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
223 $links[] = $linkRenderer->makeKnownLink(
224 $fromObj,
225 $this->msg( 'brokenredirects-delete' )->text(),
226 [],
227 [
228 'action' => 'delete',
229 'wpReason' => $this->msg( 'brokenredirects-delete-reason' )
230 ->inContentLanguage()
231 ->text()
232 ]
233 );
234 }
235
236 if ( $links ) {
237 $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
238 ->pipeList( $links ) )->escaped();
239 }
240 $out .= " {$arr} {$to}";
241
242 return $out;
243 }
244
246 public function execute( $par ) {
247 $this->addHelpLink( 'Help:Redirects' );
248 parent::execute( $par );
249 }
250
252 protected function getGroupName() {
253 return 'maintenance';
254 }
255}
256
258class_alias( SpecialBrokenRedirects::class, 'SpecialBrokenRedirects' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Factory for LinkBatch objects to batch query page metadata.
The base class for all skins.
Definition Skin.php:52
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:77
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?If the query for this page is considere...
isSyndicated()
Sometimes we don't want to build rss / atom feeds.to override bool
sortDescending()
Override to sort by increasing values.to override bool
__construct(private readonly IContentHandlerFactory $contentHandlerFactory, IConnectionProvider $dbProvider, LinkBatchFactory $linkBatchFactory)
getPageHeader()
The content returned by this function will be output before any result.to override string
execute( $par)
This is the actual workhorse.It does everything needed to make a real, honest-to-gosh query page....
Represents a title within MediaWiki.
Definition Title.php:69
Provide primary and replica IDatabase connections.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.