MediaWiki  master
SpecialRecentChangesLinked.php
Go to the documentation of this file.
1 <?php
26 
34  protected $rclTargetTitle;
35 
37  private $loadBalancer;
38 
41 
49  public function __construct(
55  ) {
56  parent::__construct(
61  );
62  $this->mName = 'Recentchangeslinked';
63  $this->loadBalancer = $loadBalancer;
64  $this->searchEngineFactory = $searchEngineFactory;
65  }
66 
67  public function getDefaultOptions() {
68  $opts = parent::getDefaultOptions();
69  $opts->add( 'target', '' );
70  $opts->add( 'showlinkedto', false );
71 
72  return $opts;
73  }
74 
75  public function parseParameters( $par, FormOptions $opts ) {
76  $opts['target'] = $par;
77  }
78 
82  protected function doMainQuery( $tables, $select, $conds, $query_options,
83  $join_conds, FormOptions $opts
84  ) {
85  $target = $opts['target'];
86  $showlinkedto = $opts['showlinkedto'];
87  $limit = $opts['limit'];
88 
89  if ( $target === '' ) {
90  return false;
91  }
92  $outputPage = $this->getOutput();
93  $title = Title::newFromText( $target );
94  if ( !$title || $title->isExternal() ) {
95  $outputPage->addHTML(
96  Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse(), '', 'mw-recentchangeslinked-errorbox' )
97  );
98  return false;
99  }
100 
101  $outputPage->setPageTitle( $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
102 
103  /*
104  * Ordinary links are in the pagelinks table, while transclusions are
105  * in the templatelinks table, categorizations in categorylinks and
106  * image use in imagelinks. We need to somehow combine all these.
107  * Special:Whatlinkshere does this by firing multiple queries and
108  * merging the results, but the code we inherit from our parent class
109  * expects only one result set so we use UNION instead.
110  */
111 
112  $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'recentchangeslinked' );
113  $id = $title->getArticleID();
114  $ns = $title->getNamespace();
115  $dbkey = $title->getDBkey();
116 
117  $rcQuery = RecentChange::getQueryInfo();
118  $tables = array_merge( $rcQuery['tables'], $tables );
119  $select = array_merge( $rcQuery['fields'], $select );
120  $join_conds = array_merge( $rcQuery['joins'], $join_conds );
121 
122  // Join with watchlist and watchlist_expiry tables to highlight watched rows.
123  $this->addWatchlistJoins( $dbr, $tables, $select, $join_conds, $conds );
124 
125  // JOIN on page, used for 'last revision' filter highlight
126  $tables[] = 'page';
127  $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
128  $select[] = 'page_latest';
129 
130  $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
132  $tables,
133  $select,
134  $conds,
135  $join_conds,
136  $query_options,
137  $tagFilter
138  );
139 
140  if ( $dbr->unionSupportsOrderAndLimit() ) {
141  if ( count( $tagFilter ) > 1 ) {
142  // ChangeTags::modifyDisplayQuery() will have added DISTINCT.
143  // To prevent this from causing query performance problems, we need to add
144  // a GROUP BY, and add rc_id to the ORDER BY.
145  $order = [
146  'GROUP BY' => [ 'rc_timestamp', 'rc_id' ],
147  'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ]
148  ];
149  } else {
150  $order = [ 'ORDER BY' => 'rc_timestamp DESC' ];
151  }
152  } else {
153  $order = [];
154  }
155 
156  if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
157  $opts )
158  ) {
159  return false;
160  }
161 
162  if ( $ns === NS_CATEGORY && !$showlinkedto ) {
163  // special handling for categories
164  // XXX: should try to make this less kludgy
165  $link_tables = [ 'categorylinks' ];
166  $showlinkedto = true;
167  } else {
168  // for now, always join on these tables; really should be configurable as in whatlinkshere
169  $link_tables = [ 'pagelinks', 'templatelinks' ];
170  // imagelinks only contains links to pages in NS_FILE
171  if ( $ns === NS_FILE || !$showlinkedto ) {
172  $link_tables[] = 'imagelinks';
173  }
174  }
175 
176  if ( $id == 0 && !$showlinkedto ) {
177  return false; // nonexistent pages can't link to any pages
178  }
179 
180  // field name prefixes for all the various tables we might want to join with
181  $prefix = [
182  'pagelinks' => 'pl',
183  'templatelinks' => 'tl',
184  'categorylinks' => 'cl',
185  'imagelinks' => 'il'
186  ];
187 
188  $subsql = []; // SELECT statements to combine with UNION
189 
190  foreach ( $link_tables as $link_table ) {
191  $pfx = $prefix[$link_table];
192 
193  // imagelinks and categorylinks tables have no xx_namespace field,
194  // and have xx_to instead of xx_title
195  if ( $link_table == 'imagelinks' ) {
196  $link_ns = NS_FILE;
197  } elseif ( $link_table == 'categorylinks' ) {
198  $link_ns = NS_CATEGORY;
199  } else {
200  $link_ns = 0;
201  }
202 
203  if ( $showlinkedto ) {
204  // find changes to pages linking to this page
205  if ( $link_ns ) {
206  if ( $ns != $link_ns ) {
207  continue;
208  } // should never happen, but check anyway
209  $subconds = [ "{$pfx}_to" => $dbkey ];
210  } else {
211  $subconds = [ "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey ];
212  }
213  $subjoin = "rc_cur_id = {$pfx}_from";
214  } else {
215  // find changes to pages linked from this page
216  $subconds = [ "{$pfx}_from" => $id ];
217  if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
218  $subconds["rc_namespace"] = $link_ns;
219  $subjoin = "rc_title = {$pfx}_to";
220  } else {
221  $subjoin = [ "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ];
222  }
223  }
224 
225  $query = $dbr->selectSQLText(
226  array_merge( $tables, [ $link_table ] ),
227  $select,
228  $conds + $subconds,
229  __METHOD__,
230  $order + $query_options,
231  $join_conds + [ $link_table => [ 'JOIN', $subjoin ] ]
232  );
233 
234  if ( $dbr->unionSupportsOrderAndLimit() ) {
235  $query = $dbr->limitResult( $query, $limit );
236  }
237 
238  $subsql[] = $query;
239  }
240 
241  if ( count( $subsql ) == 0 ) {
242  return false; // should never happen
243  }
244  if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
245  $sql = $subsql[0];
246  } else {
247  // need to resort and relimit after union
248  $sql = $dbr->unionQueries( $subsql, $dbr::UNION_DISTINCT ) .
249  ' ORDER BY rc_timestamp DESC';
250  $sql = $dbr->limitResult( $sql, $limit, false );
251  }
252 
253  return $dbr->query( $sql, __METHOD__ );
254  }
255 
256  public function setTopText( FormOptions $opts ) {
257  $target = $this->getTargetTitle();
258  if ( $target ) {
259  $this->getOutput()->addBacklinkSubtitle( $target );
260  $this->getSkin()->setRelevantTitle( $target );
261  }
262  }
263 
270  public function getExtraOptions( $opts ) {
271  $extraOpts = parent::getExtraOptions( $opts );
272 
273  $opts->consumeValues( [ 'showlinkedto', 'target' ] );
274 
275  $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
276  Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
277  Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
278  Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
279 
280  $this->addHelpLink( 'Help:Related changes' );
281  return $extraOpts;
282  }
283 
287  private function getTargetTitle() {
288  if ( $this->rclTargetTitle === null ) {
289  $opts = $this->getOptions();
290  if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
291  $this->rclTargetTitle = Title::newFromText( $opts['target'] );
292  } else {
293  $this->rclTargetTitle = false;
294  }
295  }
296 
297  return $this->rclTargetTitle;
298  }
299 
308  public function prefixSearchSubpages( $search, $limit, $offset ) {
309  return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
310  }
311 
312  protected function outputNoResults() {
313  $targetTitle = $this->getTargetTitle();
314  if ( $targetTitle === false ) {
315  $this->getOutput()->addHTML(
317  'div',
318  [ 'class' => 'mw-changeslist-empty mw-changeslist-notargetpage' ],
319  $this->msg( 'recentchanges-notargetpage' )->parse()
320  )
321  );
322  } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
323  $this->getOutput()->addHTML(
325  'div',
326  [ 'class' => 'mw-changeslist-empty mw-changeslist-invalidtargetpage' ],
327  $this->msg( 'allpagesbadtitle' )->parse()
328  )
329  );
330  } else {
331  parent::outputNoResults();
332  }
333  }
334 }
SpecialRecentChangesLinked\getExtraOptions
getExtraOptions( $opts)
Get options to be displayed in a form.
Definition: SpecialRecentChangesLinked.php:270
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:257
SpecialRecentChangesLinked\outputNoResults
outputNoResults()
Add the "no results" message to the output.
Definition: SpecialRecentChangesLinked.php:312
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:912
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:383
SpecialRecentChangesLinked\parseParameters
parseParameters( $par, FormOptions $opts)
Process $par and put options found in $opts.
Definition: SpecialRecentChangesLinked.php:75
SpecialRecentChangesLinked\__construct
__construct(WatchedItemStoreInterface $watchedItemStore, MessageCache $messageCache, ILoadBalancer $loadBalancer, UserOptionsLookup $userOptionsLookup, SearchEngineFactory $searchEngineFactory)
Definition: SpecialRecentChangesLinked.php:49
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:790
SpecialRecentChangesLinked
This is to display changes made to all articles linked in an article.
Definition: SpecialRecentChangesLinked.php:32
Xml\label
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition: Xml.php:364
SpecialRecentChanges\$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: SpecialRecentChanges.php:53
SpecialRecentChangesLinked\getTargetTitle
getTargetTitle()
Definition: SpecialRecentChangesLinked.php:287
SpecialRecentChangesLinked\setTopText
setTopText(FormOptions $opts)
Send the text to be displayed above the options.
Definition: SpecialRecentChangesLinked.php:256
SpecialRecentChanges\addWatchlistJoins
addWatchlistJoins(IDatabase $dbr, &$tables, &$fields, &$joinConds, &$conds)
Add required values to a query's $tables, $fields, $joinConds, and $conds arrays to join to the watch...
Definition: SpecialRecentChanges.php:323
SearchEngineFactory
Factory class for SearchEngine.
Definition: SearchEngineFactory.php:12
SpecialPage\getSkin
getSkin()
Shortcut to get the skin being used for this instance.
Definition: SpecialPage.php:820
$dbr
$dbr
Definition: testCompression.php:54
SpecialRecentChangesLinked\$searchEngineFactory
SearchEngineFactory $searchEngineFactory
Definition: SpecialRecentChangesLinked.php:40
SpecialPage\prefixSearchString
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
Definition: SpecialPage.php:577
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:891
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:948
ChangesListSpecialPage\runMainQueryHook
runMainQueryHook(&$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts)
Definition: ChangesListSpecialPage.php:1633
Xml\check
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:329
$title
$title
Definition: testCompression.php:38
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Html\errorBox
static errorBox( $html, $heading='', $className='')
Return an error box.
Definition: Html.php:768
SpecialRecentChangesLinked\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialRecentChangesLinked.php:308
SpecialRecentChangesLinked\doMainQuery
doMainQuery( $tables, $select, $conds, $query_options, $join_conds, FormOptions $opts)
Process the query.Array of tables; see IDatabase::select $table Array of fields; see IDatabase::selec...
Definition: SpecialRecentChangesLinked.php:82
SpecialRecentChanges\$watchedItemStore
WatchedItemStoreInterface $watchedItemStore
Definition: SpecialRecentChanges.php:44
SpecialRecentChanges
A special page that lists last changes made to the wiki.
Definition: SpecialRecentChanges.php:35
SpecialRecentChanges\$messageCache
MessageCache $messageCache
Definition: SpecialRecentChanges.php:47
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
Title
Represents a title within MediaWiki.
Definition: Title.php:48
SpecialRecentChangesLinked\getDefaultOptions
getDefaultOptions()
Get a FormOptions object containing the default options.
Definition: SpecialRecentChangesLinked.php:67
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:280
FormOptions
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
NS_FILE
const NS_FILE
Definition: Defines.php:70
SpecialRecentChangesLinked\$rclTargetTitle
bool Title $rclTargetTitle
Definition: SpecialRecentChangesLinked.php:34
WatchedItemStoreInterface
Definition: WatchedItemStoreInterface.php:31
MessageCache
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
Definition: MessageCache.php:52
ChangesListSpecialPage\getOptions
getOptions()
Get the current FormOptions for this request.
Definition: ChangesListSpecialPage.php:1032
SpecialRecentChangesLinked\$loadBalancer
ILoadBalancer $loadBalancer
Definition: SpecialRecentChangesLinked.php:37
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81