MediaWiki master
SpecialDoubleRedirects.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
27use Skin;
28use stdClass;
32
42
43 private IContentHandlerFactory $contentHandlerFactory;
44 private LinkBatchFactory $linkBatchFactory;
45
51 public function __construct(
52 IContentHandlerFactory $contentHandlerFactory,
53 LinkBatchFactory $linkBatchFactory,
54 IConnectionProvider $dbProvider
55 ) {
56 parent::__construct( 'DoubleRedirects' );
57 $this->contentHandlerFactory = $contentHandlerFactory;
58 $this->linkBatchFactory = $linkBatchFactory;
59 $this->setDatabaseProvider( $dbProvider );
60 }
61
62 public function isExpensive() {
63 return true;
64 }
65
66 public function isSyndicated() {
67 return false;
68 }
69
70 protected function sortDescending() {
71 return false;
72 }
73
74 protected function getPageHeader() {
75 return $this->msg( 'doubleredirectstext' )->parseAsBlock();
76 }
77
78 private function reallyGetQueryInfo( $namespace = null, $title = null ) {
79 $limitToTitle = !( $namespace === null && $title === null );
80 $retval = [
81 'tables' => [
82 'ra' => 'redirect',
83 'rb' => 'redirect',
84 'pa' => 'page',
85 'pb' => 'page'
86 ],
87 'fields' => [
88 'namespace' => 'pa.page_namespace',
89 'title' => 'pa.page_title',
90
91 'b_namespace' => 'pb.page_namespace',
92 'b_title' => 'pb.page_title',
93 'b_fragment' => 'ra.rd_fragment',
94
95 // Select fields from redirect instead of page. Because there may
96 // not actually be a page table row for this target (e.g. for interwiki redirects)
97 'c_namespace' => 'rb.rd_namespace',
98 'c_title' => 'rb.rd_title',
99 'c_fragment' => 'rb.rd_fragment',
100 'c_interwiki' => 'rb.rd_interwiki',
101 ],
102 'conds' => [
103 'ra.rd_from = pa.page_id',
104
105 // Filter out redirects where the target goes interwiki (T42353).
106 // This isn't an optimization, it is required for correct results,
107 // otherwise a non-double redirect like Bar -> w:Foo will show up
108 // like "Bar -> Foo -> w:Foo".
109 'ra.rd_interwiki' => '',
110
111 'pb.page_namespace = ra.rd_namespace',
112 'pb.page_title = ra.rd_title',
113
114 'rb.rd_from = pb.page_id',
115 ]
116 ];
117
118 if ( $limitToTitle ) {
119 $retval['conds']['pa.page_namespace'] = $namespace;
120 $retval['conds']['pa.page_title'] = $title;
121 }
122
123 return $retval;
124 }
125
126 public function getQueryInfo() {
127 return $this->reallyGetQueryInfo();
128 }
129
130 protected function getOrderFields() {
131 return [ 'ra.rd_namespace', 'ra.rd_title' ];
132 }
133
139 public function formatResult( $skin, $result ) {
140 // If no Title B or C is in the query, it means this came from
141 // querycache (which only saves the 3 columns for title A).
142 // That does save the bulk of the query cost, but now we need to
143 // get a little more detail about each individual entry quickly
144 // using the filter of reallyGetQueryInfo.
145 $deep = false;
146 if ( $result ) {
147 if ( isset( $result->b_namespace ) ) {
148 $deep = $result;
149 } else {
150 $qi = $this->reallyGetQueryInfo(
151 $result->namespace,
152 $result->title
153 );
154 $deep = $this->getDatabaseProvider()->getReplicaDatabase()->newSelectQueryBuilder()
155 ->queryInfo( $qi )
156 ->caller( __METHOD__ )
157 ->fetchRow();
158 }
159 }
160
161 $titleA = Title::makeTitle( $result->namespace, $result->title );
162
163 $linkRenderer = $this->getLinkRenderer();
164 if ( !$deep ) {
165 return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
166 }
167
168 // if the page is editable, add an edit link
169 if (
170 // check user permissions
171 $this->getAuthority()->isAllowed( 'edit' ) &&
172 // check, if the content model is editable through action=edit
173 $this->contentHandlerFactory->getContentHandler( $titleA->getContentModel() )
174 ->supportsDirectEditing()
175 ) {
176 $edit = $linkRenderer->makeKnownLink(
177 $titleA,
178 $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
179 [],
180 [ 'action' => 'edit' ]
181 );
182 } else {
183 $edit = '';
184 }
185
186 $linkA = $linkRenderer->makeKnownLink(
187 $titleA,
188 null,
189 [],
190 [ 'redirect' => 'no' ]
191 );
192
193 $titleB = Title::makeTitle( $deep->b_namespace, $deep->b_title );
194 // We show fragment, but don't link to it, as it probably doesn't exist anymore.
195 $titleBFrag = Title::makeTitle( $deep->b_namespace, $deep->b_title, $deep->b_fragment );
196 $linkB = $linkRenderer->makeKnownLink(
197 $titleB,
198 $titleBFrag->getFullText(),
199 [],
200 [ 'redirect' => 'no' ]
201 );
202
203 $titleC = Title::makeTitle(
204 $deep->c_namespace,
205 $deep->c_title,
206 $deep->c_fragment,
207 $deep->c_interwiki
208 );
209 $linkC = $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() );
210
211 $lang = $this->getLanguage();
212 $arr = $lang->getArrow() . $lang->getDirMark();
213
214 return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
215 }
216
217 public function execute( $par ) {
218 $this->addHelpLink( 'Help:Redirects' );
219 parent::execute( $par );
220 }
221
228 public function preprocessResults( $db, $res ) {
229 if ( !$res->numRows() ) {
230 return;
231 }
232
233 $batch = $this->linkBatchFactory->newLinkBatch();
234 foreach ( $res as $row ) {
235 $batch->add( $row->namespace, $row->title );
236 if ( isset( $row->b_namespace ) ) {
237 // lazy loaded when using cached results
238 $batch->add( $row->b_namespace, $row->b_title );
239 }
240 if ( isset( $row->c_interwiki ) && !$row->c_interwiki ) {
241 // lazy loaded when using cached result, not added when interwiki link
242 $batch->add( $row->c_namespace, $row->c_title );
243 }
244 }
245 $batch->execute();
246
247 // Back to start for display
248 $res->seek( 0 );
249 }
250
251 protected function getGroupName() {
252 return 'maintenance';
253 }
254}
255
257class_alias( SpecialDoubleRedirects::class, 'SpecialDoubleRedirects' );
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:89
setDatabaseProvider(IConnectionProvider $databaseProvider)
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getAuthority()
Shortcut to get the Authority executing this instance.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
List of redirects to another redirecting page.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
getPageHeader()
The content returned by this function will be output before any result.
execute( $par)
This is the actual workhorse.
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
__construct(IContentHandlerFactory $contentHandlerFactory, LinkBatchFactory $linkBatchFactory, IConnectionProvider $dbProvider)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getOrderFields()
Subclasses return an array of fields to order by here.
preprocessResults( $db, $res)
Cache page content model and gender distinction for performance.
sortDescending()
Override to sort by increasing values.
isExpensive()
Should this query page only be updated offline on large wikis?
Represents a title within MediaWiki.
Definition Title.php:79
The base class for all skins.
Definition Skin.php:59
Provide primary and replica IDatabase connections.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
Result wrapper for grabbing data queried from an IDatabase object.