MediaWiki REL1_35
SpecialRecentChangesLinked.php
Go to the documentation of this file.
1<?php
31 protected $rclTargetTitle;
32
33 public function __construct() {
34 parent::__construct( 'Recentchangeslinked' );
35 }
36
37 public function getDefaultOptions() {
38 $opts = parent::getDefaultOptions();
39 $opts->add( 'target', '' );
40 $opts->add( 'showlinkedto', false );
41
42 return $opts;
43 }
44
45 public function parseParameters( $par, FormOptions $opts ) {
46 $opts['target'] = $par;
47 }
48
52 protected function doMainQuery( $tables, $select, $conds, $query_options,
53 $join_conds, FormOptions $opts
54 ) {
55 $target = $opts['target'];
56 $showlinkedto = $opts['showlinkedto'];
57 $limit = $opts['limit'];
58
59 if ( $target === '' ) {
60 return false;
61 }
62 $outputPage = $this->getOutput();
63 $title = Title::newFromText( $target );
64 if ( !$title || $title->isExternal() ) {
65 $outputPage->addHTML(
66 Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse() )
67 );
68 return false;
69 }
70
71 $outputPage->setPageTitle( $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
72
73 /*
74 * Ordinary links are in the pagelinks table, while transclusions are
75 * in the templatelinks table, categorizations in categorylinks and
76 * image use in imagelinks. We need to somehow combine all these.
77 * Special:Whatlinkshere does this by firing multiple queries and
78 * merging the results, but the code we inherit from our parent class
79 * expects only one result set so we use UNION instead.
80 */
81
82 $dbr = wfGetDB( DB_REPLICA, 'recentchangeslinked' );
83 $id = $title->getArticleID();
84 $ns = $title->getNamespace();
85 $dbkey = $title->getDBkey();
86
87 $rcQuery = RecentChange::getQueryInfo();
88 $tables = array_merge( $tables, $rcQuery['tables'] );
89 $select = array_merge( $rcQuery['fields'], $select );
90 $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
91
92 // Join with watchlist and watchlist_expiry tables to highlight watched rows.
93 $this->addWatchlistJoins( $dbr, $tables, $select, $join_conds, $conds );
94
95 // JOIN on page, used for 'last revision' filter highlight
96 $tables[] = 'page';
97 $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
98 $select[] = 'page_latest';
99
100 $tagFilter = $opts['tagfilter'] !== '' ? explode( '|', $opts['tagfilter'] ) : [];
102 $tables,
103 $select,
104 $conds,
105 $join_conds,
106 $query_options,
107 $tagFilter
108 );
109
110 if ( $dbr->unionSupportsOrderAndLimit() ) {
111 if ( count( $tagFilter ) > 1 ) {
112 // ChangeTags::modifyDisplayQuery() will have added DISTINCT.
113 // To prevent this from causing query performance problems, we need to add
114 // a GROUP BY, and add rc_id to the ORDER BY.
115 $order = [
116 'GROUP BY' => [ 'rc_timestamp', 'rc_id' ],
117 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ]
118 ];
119 } else {
120 $order = [ 'ORDER BY' => 'rc_timestamp DESC' ];
121 }
122 } else {
123 $order = [];
124 }
125
126 if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
127 $opts )
128 ) {
129 return false;
130 }
131
132 if ( $ns == NS_CATEGORY && !$showlinkedto ) {
133 // special handling for categories
134 // XXX: should try to make this less kludgy
135 $link_tables = [ 'categorylinks' ];
136 $showlinkedto = true;
137 } else {
138 // for now, always join on these tables; really should be configurable as in whatlinkshere
139 $link_tables = [ 'pagelinks', 'templatelinks' ];
140 // imagelinks only contains links to pages in NS_FILE
141 if ( $ns == NS_FILE || !$showlinkedto ) {
142 $link_tables[] = 'imagelinks';
143 }
144 }
145
146 if ( $id == 0 && !$showlinkedto ) {
147 return false; // nonexistent pages can't link to any pages
148 }
149
150 // field name prefixes for all the various tables we might want to join with
151 $prefix = [
152 'pagelinks' => 'pl',
153 'templatelinks' => 'tl',
154 'categorylinks' => 'cl',
155 'imagelinks' => 'il'
156 ];
157
158 $subsql = []; // SELECT statements to combine with UNION
159
160 foreach ( $link_tables as $link_table ) {
161 $pfx = $prefix[$link_table];
162
163 // imagelinks and categorylinks tables have no xx_namespace field,
164 // and have xx_to instead of xx_title
165 if ( $link_table == 'imagelinks' ) {
166 $link_ns = NS_FILE;
167 } elseif ( $link_table == 'categorylinks' ) {
168 $link_ns = NS_CATEGORY;
169 } else {
170 $link_ns = 0;
171 }
172
173 if ( $showlinkedto ) {
174 // find changes to pages linking to this page
175 if ( $link_ns ) {
176 if ( $ns != $link_ns ) {
177 continue;
178 } // should never happen, but check anyway
179 $subconds = [ "{$pfx}_to" => $dbkey ];
180 } else {
181 $subconds = [ "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey ];
182 }
183 $subjoin = "rc_cur_id = {$pfx}_from";
184 } else {
185 // find changes to pages linked from this page
186 $subconds = [ "{$pfx}_from" => $id ];
187 if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
188 $subconds["rc_namespace"] = $link_ns;
189 $subjoin = "rc_title = {$pfx}_to";
190 } else {
191 $subjoin = [ "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ];
192 }
193 }
194
195 $query = $dbr->selectSQLText(
196 array_merge( $tables, [ $link_table ] ),
197 $select,
198 $conds + $subconds,
199 __METHOD__,
200 $order + $query_options,
201 $join_conds + [ $link_table => [ 'JOIN', $subjoin ] ]
202 );
203
204 if ( $dbr->unionSupportsOrderAndLimit() ) {
205 $query = $dbr->limitResult( $query, $limit );
206 }
207
208 $subsql[] = $query;
209 }
210
211 if ( count( $subsql ) == 0 ) {
212 return false; // should never happen
213 }
214 if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
215 $sql = $subsql[0];
216 } else {
217 // need to resort and relimit after union
218 $sql = $dbr->unionQueries( $subsql, $dbr::UNION_DISTINCT ) .
219 ' ORDER BY rc_timestamp DESC';
220 $sql = $dbr->limitResult( $sql, $limit, false );
221 }
222 return $dbr->query( $sql, __METHOD__ );
223 }
224
225 public function setTopText( FormOptions $opts ) {
226 $target = $this->getTargetTitle();
227 if ( $target ) {
228 $this->getOutput()->addBacklinkSubtitle( $target );
229 $this->getSkin()->setRelevantTitle( $target );
230 }
231 }
232
239 public function getExtraOptions( $opts ) {
240 $extraOpts = parent::getExtraOptions( $opts );
241
242 $opts->consumeValues( [ 'showlinkedto', 'target' ] );
243
244 $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
245 Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
246 Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
247 Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
248
249 $this->addHelpLink( 'Help:Related changes' );
250 return $extraOpts;
251 }
252
256 private function getTargetTitle() {
257 if ( $this->rclTargetTitle === null ) {
258 $opts = $this->getOptions();
259 if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
260 $this->rclTargetTitle = Title::newFromText( $opts['target'] );
261 } else {
262 $this->rclTargetTitle = false;
263 }
264 }
265
267 }
268
277 public function prefixSearchSubpages( $search, $limit, $offset ) {
278 return $this->prefixSearchString( $search, $limit, $offset );
279 }
280
281 protected function outputNoResults() {
282 $targetTitle = $this->getTargetTitle();
283 if ( $targetTitle === false ) {
284 $this->getOutput()->addHTML(
285 Html::rawElement(
286 'div',
287 [ 'class' => 'mw-changeslist-empty mw-changeslist-notargetpage' ],
288 $this->msg( 'recentchanges-notargetpage' )->parse()
289 )
290 );
291 } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
292 $this->getOutput()->addHTML(
293 Html::rawElement(
294 'div',
295 [ 'class' => 'mw-changeslist-empty mw-changeslist-invalidtargetpage' ],
296 $this->msg( 'allpagesbadtitle' )->parse()
297 )
298 );
299 } else {
300 parent::outputNoResults();
301 }
302 }
303}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
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.
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.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
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.
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...
Represents a title within MediaWiki.
Definition Title.php:42
const NS_FILE
Definition Defines.php:76
const NS_CATEGORY
Definition Defines.php:84
const DB_REPLICA
Definition defines.php:25