MediaWiki master
SpecialDoubleRedirects.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
9use MediaWiki\Cache\LinkBatchFactory;
15use stdClass;
19
29
30 private IContentHandlerFactory $contentHandlerFactory;
31 private LinkBatchFactory $linkBatchFactory;
32
33 public function __construct(
34 IContentHandlerFactory $contentHandlerFactory,
35 LinkBatchFactory $linkBatchFactory,
36 IConnectionProvider $dbProvider
37 ) {
38 parent::__construct( 'DoubleRedirects' );
39 $this->contentHandlerFactory = $contentHandlerFactory;
40 $this->linkBatchFactory = $linkBatchFactory;
41 $this->setDatabaseProvider( $dbProvider );
42 }
43
45 public function isExpensive() {
46 return true;
47 }
48
50 public function isSyndicated() {
51 return false;
52 }
53
55 protected function sortDescending() {
56 return false;
57 }
58
60 protected function getPageHeader() {
61 return $this->msg( 'doubleredirectstext' )->parseAsBlock();
62 }
63
64 private function reallyGetQueryInfo( ?int $namespace = null, ?string $title = null ): array {
65 $limitToTitle = !( $namespace === null && $title === null );
66 $retval = [
67 'tables' => [
68 'ra' => 'redirect',
69 'rb' => 'redirect',
70 'pa' => 'page',
71 'pb' => 'page'
72 ],
73 'fields' => [
74 'namespace' => 'pa.page_namespace',
75 'title' => 'pa.page_title',
76
77 'b_namespace' => 'pb.page_namespace',
78 'b_title' => 'pb.page_title',
79 'b_fragment' => 'ra.rd_fragment',
80
81 // Select fields from redirect instead of page. Because there may
82 // not actually be a page table row for this target (e.g. for interwiki redirects)
83 'c_namespace' => 'rb.rd_namespace',
84 'c_title' => 'rb.rd_title',
85 'c_fragment' => 'rb.rd_fragment',
86 'c_interwiki' => 'rb.rd_interwiki',
87 ],
88 'conds' => [
89 'ra.rd_from = pa.page_id',
90
91 // Filter out redirects where the target goes interwiki (T42353).
92 // This isn't an optimization, it is required for correct results,
93 // otherwise a non-double redirect like Bar -> w:Foo will show up
94 // like "Bar -> Foo -> w:Foo".
95 'ra.rd_interwiki' => '',
96
97 'pb.page_namespace = ra.rd_namespace',
98 'pb.page_title = ra.rd_title',
99
100 'rb.rd_from = pb.page_id',
101 ]
102 ];
103
104 if ( $limitToTitle ) {
105 $retval['conds']['pa.page_namespace'] = $namespace;
106 $retval['conds']['pa.page_title'] = $title;
107 }
108
109 return $retval;
110 }
111
113 public function getQueryInfo() {
114 return $this->reallyGetQueryInfo();
115 }
116
118 protected function getOrderFields() {
119 return [ 'ra.rd_namespace', 'ra.rd_title' ];
120 }
121
127 public function formatResult( $skin, $result ) {
128 // If no Title B or C is in the query, it means this came from
129 // querycache (which only saves the 3 columns for title A).
130 // That does save the bulk of the query cost, but now we need to
131 // get a little more detail about each individual entry quickly
132 // using the filter of reallyGetQueryInfo.
133 $deep = false;
134 if ( $result ) {
135 if ( isset( $result->b_namespace ) ) {
136 $deep = $result;
137 } else {
138 $qi = $this->reallyGetQueryInfo(
139 $result->namespace,
140 $result->title
141 );
142 $deep = $this->getDatabaseProvider()->getReplicaDatabase()->newSelectQueryBuilder()
143 ->queryInfo( $qi )
144 ->caller( __METHOD__ )
145 ->fetchRow();
146 }
147 }
148
149 $titleA = Title::makeTitle( $result->namespace, $result->title );
150
151 $linkRenderer = $this->getLinkRenderer();
152 if ( !$deep ) {
153 return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
154 }
155
156 // if the page is editable, add an edit link
157 if (
158 // check user permissions
159 $this->getAuthority()->isAllowed( 'edit' ) &&
160 // check, if the content model is editable through action=edit
161 $this->contentHandlerFactory->getContentHandler( $titleA->getContentModel() )
162 ->supportsDirectEditing()
163 ) {
164 $edit = $linkRenderer->makeKnownLink(
165 $titleA,
166 $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
167 [],
168 [ 'action' => 'edit' ]
169 );
170 } else {
171 $edit = '';
172 }
173
174 $arrow = $this->getLanguage()->getArrow();
175 $contentLanguage = $this->getContentLanguage();
176 $bdiAttrs = [
177 'dir' => $contentLanguage->getDir(),
178 'lang' => $contentLanguage->getHtmlCode(),
179 ];
180 $linkA = Html::rawElement( 'bdi', $bdiAttrs, $linkRenderer->makeKnownLink(
181 $titleA,
182 null,
183 [],
184 [ 'redirect' => 'no' ]
185 ) );
186
187 $titleB = Title::makeTitle( $deep->b_namespace, $deep->b_title );
188 // We show fragment, but don't link to it, as it probably doesn't exist anymore.
189 $titleBFrag = Title::makeTitle( $deep->b_namespace, $deep->b_title, $deep->b_fragment );
190 $linkB = Html::rawElement( 'bdi', $bdiAttrs, $linkRenderer->makeKnownLink(
191 $titleB,
192 $titleBFrag->getFullText(),
193 [],
194 [ 'redirect' => 'no' ]
195 ) );
196
197 $titleC = Title::makeTitle(
198 $deep->c_namespace,
199 $deep->c_title,
200 $deep->c_fragment,
201 $deep->c_interwiki
202 );
203 $linkC = Html::rawElement( 'bdi', $bdiAttrs,
204 $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() )
205 );
206
207 return ( "{$linkA} {$edit} {$arrow} {$linkB} {$arrow} {$linkC}" );
208 }
209
211 public function execute( $par ) {
212 $this->addHelpLink( 'Help:Redirects' );
213 parent::execute( $par );
214 }
215
222 public function preprocessResults( $db, $res ) {
223 if ( !$res->numRows() ) {
224 return;
225 }
226
227 $batch = $this->linkBatchFactory->newLinkBatch()->setCaller( __METHOD__ );
228 foreach ( $res as $row ) {
229 $batch->add( $row->namespace, $row->title );
230 if ( isset( $row->b_namespace ) ) {
231 // lazy loaded when using cached results
232 $batch->add( $row->b_namespace, $row->b_title );
233 }
234 if ( isset( $row->c_interwiki ) && !$row->c_interwiki ) {
235 // lazy loaded when using cached result, not added when interwiki link
236 $batch->add( $row->c_namespace, $row->c_title );
237 }
238 }
239 $batch->execute();
240
241 // Back to start for display
242 $res->seek( 0 );
243 }
244
246 protected function getGroupName() {
247 return 'maintenance';
248 }
249}
250
252class_alias( SpecialDoubleRedirects::class, 'SpecialDoubleRedirects' );
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
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)
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
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.to override string
execute( $par)
This is the actual workhorse.It does everything needed to make a real, honest-to-gosh query page....
isSyndicated()
Sometimes we don't want to build rss / atom feeds.to override bool
__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.Don't append DESC to the field names,...
preprocessResults( $db, $res)
Cache page content model and gender distinction for performance.
sortDescending()
Override to sort by increasing values.to override bool
isExpensive()
Should this query page only be updated offline on large wikis?If the query for this page is considere...
Represents a title within MediaWiki.
Definition Title.php:70
Provide primary and replica IDatabase connections.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.