MediaWiki REL1_37
SpecialRecentChangesLinked.php
Go to the documentation of this file.
1<?php
26
34 protected $rclTargetTitle;
35
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 return $dbr->query( $sql, __METHOD__ );
253 }
254
255 public function setTopText( FormOptions $opts ) {
256 $target = $this->getTargetTitle();
257 if ( $target ) {
258 $this->getOutput()->addBacklinkSubtitle( $target );
259 $this->getSkin()->setRelevantTitle( $target );
260 }
261 }
262
269 public function getExtraOptions( $opts ) {
270 $extraOpts = parent::getExtraOptions( $opts );
271
272 $opts->consumeValues( [ 'showlinkedto', 'target' ] );
273
274 $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
275 Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
276 Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
277 Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
278
279 $this->addHelpLink( 'Help:Related changes' );
280 return $extraOpts;
281 }
282
286 private function getTargetTitle() {
287 if ( $this->rclTargetTitle === null ) {
288 $opts = $this->getOptions();
289 if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
290 $this->rclTargetTitle = Title::newFromText( $opts['target'] );
291 } else {
292 $this->rclTargetTitle = false;
293 }
294 }
295
297 }
298
307 public function prefixSearchSubpages( $search, $limit, $offset ) {
308 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
309 }
310
311 protected function outputNoResults() {
312 $targetTitle = $this->getTargetTitle();
313 if ( $targetTitle === false ) {
314 $this->getOutput()->addHTML(
315 Html::rawElement(
316 'div',
317 [ 'class' => 'mw-changeslist-empty mw-changeslist-notargetpage' ],
318 $this->msg( 'recentchanges-notargetpage' )->parse()
319 )
320 );
321 } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
322 $this->getOutput()->addHTML(
323 Html::rawElement(
324 'div',
325 [ 'class' => 'mw-changeslist-empty mw-changeslist-invalidtargetpage' ],
326 $this->msg( 'allpagesbadtitle' )->parse()
327 )
328 );
329 } else {
330 parent::outputNoResults();
331 }
332 }
333}
const NS_FILE
Definition Defines.php:70
const NS_CATEGORY
Definition Defines.php:78
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
runMainQueryHook(&$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts)
getOptions()
Get the current FormOptions for this request.
Helper class to keep track of options when mixing links and form elements.
Provides access to user options.
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
Factory class for SearchEngine.
getOutput()
Get the OutputPage being used for this instance.
getSkin()
Shortcut to get the skin being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
This is to display changes made to all articles linked in an article.
setTopText(FormOptions $opts)
Send the text to be displayed above the options.
doMainQuery( $tables, $select, $conds, $query_options, $join_conds, FormOptions $opts)
Process the query.bool|IResultWrapper Result or false
parseParameters( $par, FormOptions $opts)
Process $par and put options found in $opts.
getDefaultOptions()
Get a FormOptions object containing the default options.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
outputNoResults()
Add the "no results" message to the output.
__construct(WatchedItemStoreInterface $watchedItemStore, MessageCache $messageCache, ILoadBalancer $loadBalancer, UserOptionsLookup $userOptionsLookup, SearchEngineFactory $searchEngineFactory)
getExtraOptions( $opts)
Get options to be displayed in a form.
A special page that lists last changes made to the wiki.
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...
UserOptionsLookup $userOptionsLookup
WatchedItemStoreInterface $watchedItemStore
Represents a title within MediaWiki.
Definition Title.php:48
Database cluster connection, tracking, load balancing, and transaction manager interface.