MediaWiki  master
SpecialDoubleRedirects.php
Go to the documentation of this file.
1 <?php
28 
36 
38  private $contentHandlerFactory;
39 
41  private $linkBatchFactory;
42 
44  private $dbr;
45 
51  public function __construct(
52  IContentHandlerFactory $contentHandlerFactory,
53  LinkBatchFactory $linkBatchFactory,
54  ILoadBalancer $loadBalancer
55  ) {
56  parent::__construct( 'DoubleRedirects' );
57  $this->contentHandlerFactory = $contentHandlerFactory;
58  $this->linkBatchFactory = $linkBatchFactory;
59  $this->setDBLoadBalancer( $loadBalancer );
60  $this->dbr = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
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 IS NULL OR ra.rd_interwiki = ' . $this->dbr->addQuotes( '' ),
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->dbr->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 }
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:42
setDBLoadBalancer(ILoadBalancer $loadBalancer)
Definition: QueryPage.php:916
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.
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
Create and track the database connections and transactions for a given database cluster.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition: defines.php:26
if(!isset( $args[0])) $lang