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