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