MediaWiki  master
SpecialDoubleRedirects.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Specials;
25 
30 use Skin;
31 use stdClass;
35 
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 
111  // Need to check both NULL and "" for some reason,
112  // apparently either can be stored for non-iw entries.
113  'ra.rd_interwiki' => [ null, '' ],
114 
115  'pb.page_namespace = ra.rd_namespace',
116  'pb.page_title = ra.rd_title',
117 
118  'rb.rd_from = pb.page_id',
119  ]
120  ];
121 
122  if ( $limitToTitle ) {
123  $retval['conds']['pa.page_namespace'] = $namespace;
124  $retval['conds']['pa.page_title'] = $title;
125  }
126 
127  return $retval;
128  }
129 
130  public function getQueryInfo() {
131  return $this->reallyGetQueryInfo();
132  }
133 
134  protected function getOrderFields() {
135  return [ 'ra.rd_namespace', 'ra.rd_title' ];
136  }
137 
143  public function formatResult( $skin, $result ) {
144  // If no Title B or C is in the query, it means this came from
145  // querycache (which only saves the 3 columns for title A).
146  // That does save the bulk of the query cost, but now we need to
147  // get a little more detail about each individual entry quickly
148  // using the filter of reallyGetQueryInfo.
149  $deep = false;
150  if ( $result ) {
151  if ( isset( $result->b_namespace ) ) {
152  $deep = $result;
153  } else {
154  $qi = $this->reallyGetQueryInfo(
155  $result->namespace,
156  $result->title
157  );
158  $deep = $this->getDatabaseProvider()->getReplicaDatabase()->selectRow(
159  $qi['tables'],
160  $qi['fields'],
161  $qi['conds'],
162  __METHOD__
163  );
164  }
165  }
166 
167  $titleA = Title::makeTitle( $result->namespace, $result->title );
168 
169  $linkRenderer = $this->getLinkRenderer();
170  if ( !$deep ) {
171  return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
172  }
173 
174  // if the page is editable, add an edit link
175  if (
176  // check user permissions
177  $this->getAuthority()->isAllowed( 'edit' ) &&
178  // check, if the content model is editable through action=edit
179  $this->contentHandlerFactory->getContentHandler( $titleA->getContentModel() )
180  ->supportsDirectEditing()
181  ) {
182  $edit = $linkRenderer->makeKnownLink(
183  $titleA,
184  $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
185  [],
186  [ 'action' => 'edit' ]
187  );
188  } else {
189  $edit = '';
190  }
191 
192  $linkA = $linkRenderer->makeKnownLink(
193  $titleA,
194  null,
195  [],
196  [ 'redirect' => 'no' ]
197  );
198 
199  $titleB = Title::makeTitle( $deep->b_namespace, $deep->b_title );
200  // We show fragment, but don't link to it, as it probably doesn't exist anymore.
201  $titleBFrag = Title::makeTitle( $deep->b_namespace, $deep->b_title, $deep->b_fragment );
202  $linkB = $linkRenderer->makeKnownLink(
203  $titleB,
204  $titleBFrag->getFullText(),
205  [],
206  [ 'redirect' => 'no' ]
207  );
208 
209  $titleC = Title::makeTitle(
210  $deep->c_namespace,
211  $deep->c_title,
212  $deep->c_fragment,
213  $deep->c_interwiki
214  );
215  $linkC = $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() );
216 
217  $lang = $this->getLanguage();
218  $arr = $lang->getArrow() . $lang->getDirMark();
219 
220  return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$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 
265 class_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:88
setDatabaseProvider(IConnectionProvider $databaseProvider)
Definition: QueryPage.php:985
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.
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...
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:76
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
The base class for all skins.
Definition: Skin.php:60
Provide primary and replica IDatabase connections.
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:36
Result wrapper for grabbing data queried from an IDatabase object.