MediaWiki 1.40.4
SpecialDoubleRedirects.php
Go to the documentation of this file.
1<?php
29
37
39 private $contentHandlerFactory;
40
42 private $linkBatchFactory;
43
45 private $dbr;
46
52 public function __construct(
53 IContentHandlerFactory $contentHandlerFactory,
54 LinkBatchFactory $linkBatchFactory,
55 ILoadBalancer $loadBalancer
56 ) {
57 parent::__construct( 'DoubleRedirects' );
58 $this->contentHandlerFactory = $contentHandlerFactory;
59 $this->linkBatchFactory = $linkBatchFactory;
60 $this->setDBLoadBalancer( $loadBalancer );
61 $this->dbr = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
62 }
63
64 public function isExpensive() {
65 return true;
66 }
67
68 public function isSyndicated() {
69 return false;
70 }
71
72 protected function sortDescending() {
73 return false;
74 }
75
76 protected function getPageHeader() {
77 return $this->msg( 'doubleredirectstext' )->parseAsBlock();
78 }
79
80 private function reallyGetQueryInfo( $namespace = null, $title = null ) {
81 $limitToTitle = !( $namespace === null && $title === null );
82 $retval = [
83 'tables' => [
84 'ra' => 'redirect',
85 'rb' => 'redirect',
86 'pa' => 'page',
87 'pb' => 'page'
88 ],
89 'fields' => [
90 'namespace' => 'pa.page_namespace',
91 'title' => 'pa.page_title',
92
93 'b_namespace' => 'pb.page_namespace',
94 'b_title' => 'pb.page_title',
95 'b_fragment' => 'ra.rd_fragment',
96
97 // Select fields from redirect instead of page. Because there may
98 // not actually be a page table row for this target (e.g. for interwiki redirects)
99 'c_namespace' => 'rb.rd_namespace',
100 'c_title' => 'rb.rd_title',
101 'c_fragment' => 'rb.rd_fragment',
102 'c_interwiki' => 'rb.rd_interwiki',
103 ],
104 'conds' => [
105 'ra.rd_from = pa.page_id',
106
107 // Filter out redirects where the target goes interwiki (T42353).
108 // This isn't an optimization, it is required for correct results,
109 // otherwise a non-double redirect like Bar -> w:Foo will show up
110 // like "Bar -> Foo -> w:Foo".
111
112 // Need to check both NULL and "" for some reason,
113 // apparently either can be stored for non-iw entries.
114 'ra.rd_interwiki IS NULL OR ra.rd_interwiki = ' . $this->dbr->addQuotes( '' ),
115
116 'pb.page_namespace = ra.rd_namespace',
117 'pb.page_title = ra.rd_title',
118
119 'rb.rd_from = pb.page_id',
120 ]
121 ];
122
123 if ( $limitToTitle ) {
124 $retval['conds']['pa.page_namespace'] = $namespace;
125 $retval['conds']['pa.page_title'] = $title;
126 }
127
128 return $retval;
129 }
130
131 public function getQueryInfo() {
132 return $this->reallyGetQueryInfo();
133 }
134
135 protected function getOrderFields() {
136 return [ 'ra.rd_namespace', 'ra.rd_title' ];
137 }
138
144 public function formatResult( $skin, $result ) {
145 // If no Title B or C is in the query, it means this came from
146 // querycache (which only saves the 3 columns for title A).
147 // That does save the bulk of the query cost, but now we need to
148 // get a little more detail about each individual entry quickly
149 // using the filter of reallyGetQueryInfo.
150 $deep = false;
151 if ( $result ) {
152 if ( isset( $result->b_namespace ) ) {
153 $deep = $result;
154 } else {
155 $qi = $this->reallyGetQueryInfo(
156 $result->namespace,
157 $result->title
158 );
159 $deep = $this->dbr->selectRow(
160 $qi['tables'],
161 $qi['fields'],
162 $qi['conds'],
163 __METHOD__
164 );
165 }
166 }
167
168 $titleA = Title::makeTitle( $result->namespace, $result->title );
169
170 $linkRenderer = $this->getLinkRenderer();
171 if ( !$deep ) {
172 return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
173 }
174
175 // if the page is editable, add an edit link
176 if (
177 // check user permissions
178 $this->getAuthority()->isAllowed( 'edit' ) &&
179 // check, if the content model is editable through action=edit
180 $this->contentHandlerFactory->getContentHandler( $titleA->getContentModel() )
181 ->supportsDirectEditing()
182 ) {
183 $edit = $linkRenderer->makeKnownLink(
184 $titleA,
185 $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
186 [],
187 [ 'action' => 'edit' ]
188 );
189 } else {
190 $edit = '';
191 }
192
193 $linkA = $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 = $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 = $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() );
217
218 $lang = $this->getLanguage();
219 $arr = $lang->getArrow() . $lang->getDirMark();
220
221 return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
222 }
223
224 public function execute( $par ) {
225 $this->addHelpLink( 'Help:Redirects' );
226 parent::execute( $par );
227 }
228
235 public function preprocessResults( $db, $res ) {
236 if ( !$res->numRows() ) {
237 return;
238 }
239
240 $batch = $this->linkBatchFactory->newLinkBatch();
241 foreach ( $res as $row ) {
242 $batch->add( $row->namespace, $row->title );
243 if ( isset( $row->b_namespace ) ) {
244 // lazy loaded when using cached results
245 $batch->add( $row->b_namespace, $row->b_title );
246 }
247 if ( isset( $row->c_interwiki ) && !$row->c_interwiki ) {
248 // lazy loaded when using cached result, not added when interwiki link
249 $batch->add( $row->c_namespace, $row->c_title );
250 }
251 }
252 $batch->execute();
253
254 // Back to start for display
255 $res->seek( 0 );
256 }
257
258 protected function getGroupName() {
259 return 'maintenance';
260 }
261}
Represents a title within MediaWiki.
Definition Title.php:82
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:45
setDBLoadBalancer(ILoadBalancer $loadBalancer)
A special page listing redirects to redirecting page.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
preprocessResults( $db, $res)
Cache page content model and gender distinction for performance.
execute( $par)
This is the actual workhorse.
sortDescending()
Override to sort by increasing values.
isExpensive()
Should this query page only be updated offline on large wikis?
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
__construct(IContentHandlerFactory $contentHandlerFactory, LinkBatchFactory $linkBatchFactory, ILoadBalancer $loadBalancer)
getPageHeader()
The content returned by this function will be output before any result.
getOrderFields()
Subclasses return an array of fields to order by here.
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
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.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
This class is a delegate to ILBFactory for a given database cluster.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Result wrapper for grabbing data queried from an IDatabase object.
if(!isset( $args[0])) $lang