]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/specials/SpecialRecentchangeslinked.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / specials / SpecialRecentchangeslinked.php
1 <?php
2 /**
3  * Implements Special:Recentchangeslinked
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup SpecialPage
22  */
23
24 /**
25  * This is to display changes made to all articles linked in an article.
26  *
27  * @ingroup SpecialPage
28  */
29 class SpecialRecentChangesLinked extends SpecialRecentChanges {
30         /** @var bool|Title */
31         protected $rclTargetTitle;
32
33         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
49         /**
50          * @inheritDoc
51          */
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( '<div class="errorbox">' . $this->msg( 'allpagesbadtitle' )
66                                         ->parse() . '</div>' );
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                 $tables[] = 'recentchanges';
88                 $select = array_merge( RecentChange::selectFields(), $select );
89
90                 // left join with watchlist table to highlight watched rows
91                 $uid = $this->getUser()->getId();
92                 if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
93                         $tables[] = 'watchlist';
94                         $select[] = 'wl_user';
95                         $join_conds['watchlist'] = [ 'LEFT JOIN', [
96                                 'wl_user' => $uid,
97                                 'wl_title=rc_title',
98                                 'wl_namespace=rc_namespace'
99                         ] ];
100                 }
101
102                 // JOIN on page, used for 'last revision' filter highlight
103                 $tables[] = 'page';
104                 $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
105                 $select[] = 'page_latest';
106
107                 $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
108                 ChangeTags::modifyDisplayQuery(
109                         $tables,
110                         $select,
111                         $conds,
112                         $join_conds,
113                         $query_options,
114                         $tagFilter
115                 );
116
117                 if ( $dbr->unionSupportsOrderAndLimit() ) {
118                         if ( count( $tagFilter ) > 1 ) {
119                                 // ChangeTags::modifyDisplayQuery() will have added DISTINCT.
120                                 // To prevent this from causing query performance problems, we need to add
121                                 // a GROUP BY, and add rc_id to the ORDER BY.
122                                 $order = [
123                                         'GROUP BY' => 'rc_timestamp, rc_id',
124                                         'ORDER BY' => 'rc_timestamp DESC, rc_id DESC'
125                                 ];
126                         } else {
127                                 $order = [ 'ORDER BY' => 'rc_timestamp DESC' ];
128                         }
129                 } else {
130                         $order = [];
131                 }
132
133                 if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
134                         $opts )
135                 ) {
136                         return false;
137                 }
138
139                 if ( $ns == NS_CATEGORY && !$showlinkedto ) {
140                         // special handling for categories
141                         // XXX: should try to make this less kludgy
142                         $link_tables = [ 'categorylinks' ];
143                         $showlinkedto = true;
144                 } else {
145                         // for now, always join on these tables; really should be configurable as in whatlinkshere
146                         $link_tables = [ 'pagelinks', 'templatelinks' ];
147                         // imagelinks only contains links to pages in NS_FILE
148                         if ( $ns == NS_FILE || !$showlinkedto ) {
149                                 $link_tables[] = 'imagelinks';
150                         }
151                 }
152
153                 if ( $id == 0 && !$showlinkedto ) {
154                         return false; // nonexistent pages can't link to any pages
155                 }
156
157                 // field name prefixes for all the various tables we might want to join with
158                 $prefix = [
159                         'pagelinks' => 'pl',
160                         'templatelinks' => 'tl',
161                         'categorylinks' => 'cl',
162                         'imagelinks' => 'il'
163                 ];
164
165                 $subsql = []; // SELECT statements to combine with UNION
166
167                 foreach ( $link_tables as $link_table ) {
168                         $pfx = $prefix[$link_table];
169
170                         // imagelinks and categorylinks tables have no xx_namespace field,
171                         // and have xx_to instead of xx_title
172                         if ( $link_table == 'imagelinks' ) {
173                                 $link_ns = NS_FILE;
174                         } elseif ( $link_table == 'categorylinks' ) {
175                                 $link_ns = NS_CATEGORY;
176                         } else {
177                                 $link_ns = 0;
178                         }
179
180                         if ( $showlinkedto ) {
181                                 // find changes to pages linking to this page
182                                 if ( $link_ns ) {
183                                         if ( $ns != $link_ns ) {
184                                                 continue;
185                                         } // should never happen, but check anyway
186                                         $subconds = [ "{$pfx}_to" => $dbkey ];
187                                 } else {
188                                         $subconds = [ "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey ];
189                                 }
190                                 $subjoin = "rc_cur_id = {$pfx}_from";
191                         } else {
192                                 // find changes to pages linked from this page
193                                 $subconds = [ "{$pfx}_from" => $id ];
194                                 if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
195                                         $subconds["rc_namespace"] = $link_ns;
196                                         $subjoin = "rc_title = {$pfx}_to";
197                                 } else {
198                                         $subjoin = [ "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ];
199                                 }
200                         }
201
202                         $query = $dbr->selectSQLText(
203                                 array_merge( $tables, [ $link_table ] ),
204                                 $select,
205                                 $conds + $subconds,
206                                 __METHOD__,
207                                 $order + $query_options,
208                                 $join_conds + [ $link_table => [ 'INNER JOIN', $subjoin ] ]
209                         );
210
211                         if ( $dbr->unionSupportsOrderAndLimit() ) {
212                                 $query = $dbr->limitResult( $query, $limit );
213                         }
214
215                         $subsql[] = $query;
216                 }
217
218                 if ( count( $subsql ) == 0 ) {
219                         return false; // should never happen
220                 }
221                 if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
222                         $sql = $subsql[0];
223                 } else {
224                         // need to resort and relimit after union
225                         $sql = $dbr->unionQueries( $subsql, false ) . ' ORDER BY rc_timestamp DESC';
226                         $sql = $dbr->limitResult( $sql, $limit, false );
227                 }
228
229                 $res = $dbr->query( $sql, __METHOD__ );
230
231                 if ( $res->numRows() == 0 ) {
232                         $this->mResultEmpty = true;
233                 }
234
235                 return $res;
236         }
237
238         function setTopText( FormOptions $opts ) {
239                 $target = $this->getTargetTitle();
240                 if ( $target ) {
241                         $this->getOutput()->addBacklinkSubtitle( $target );
242                         $this->getSkin()->setRelevantTitle( $target );
243                 }
244         }
245
246         /**
247          * Get options to be displayed in a form
248          *
249          * @param FormOptions $opts
250          * @return array
251          */
252         function getExtraOptions( $opts ) {
253                 $extraOpts = parent::getExtraOptions( $opts );
254
255                 $opts->consumeValues( [ 'showlinkedto', 'target' ] );
256
257                 $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
258                         Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
259                         Xml::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
260                         Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
261
262                 $this->addHelpLink( 'Help:Related changes' );
263                 return $extraOpts;
264         }
265
266         /**
267          * @return Title
268          */
269         function getTargetTitle() {
270                 if ( $this->rclTargetTitle === null ) {
271                         $opts = $this->getOptions();
272                         if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
273                                 $this->rclTargetTitle = Title::newFromText( $opts['target'] );
274                         } else {
275                                 $this->rclTargetTitle = false;
276                         }
277                 }
278
279                 return $this->rclTargetTitle;
280         }
281
282         /**
283          * Return an array of subpages beginning with $search that this special page will accept.
284          *
285          * @param string $search Prefix to search for
286          * @param int $limit Maximum number of results to return (usually 10)
287          * @param int $offset Number of results to skip (usually 0)
288          * @return string[] Matching subpages
289          */
290         public function prefixSearchSubpages( $search, $limit, $offset ) {
291                 return $this->prefixSearchString( $search, $limit, $offset );
292         }
293 }