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