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