Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.08% covered (danger)
48.08%
50 / 104
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiFeedRecentChanges
48.08% covered (danger)
48.08%
50 / 104
28.57% covered (danger)
28.57%
2 / 7
57.46
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getCustomPrinter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
56
 getFeedObject
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 getAllowedParams
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @since 1.23
20 */
21
22use MediaWiki\Feed\ChannelFeed;
23use MediaWiki\MainConfigNames;
24use MediaWiki\Request\DerivativeRequest;
25use MediaWiki\SpecialPage\SpecialPage;
26use MediaWiki\SpecialPage\SpecialPageFactory;
27use MediaWiki\Title\Title;
28use MediaWiki\User\TempUser\TempUserConfig;
29use Wikimedia\ParamValidator\ParamValidator;
30use Wikimedia\ParamValidator\TypeDef\IntegerDef;
31
32/**
33 * Recent changes feed.
34 *
35 * @ingroup API
36 */
37class ApiFeedRecentChanges extends ApiBase {
38
39    private $params;
40
41    private SpecialPageFactory $specialPageFactory;
42    private TempUserConfig $tempUserConfig;
43
44    /**
45     * @param ApiMain $mainModule
46     * @param string $moduleName
47     * @param SpecialPageFactory $specialPageFactory
48     * @param TempUserConfig $tempUserConfig
49     */
50    public function __construct(
51        ApiMain $mainModule,
52        string $moduleName,
53        SpecialPageFactory $specialPageFactory,
54        TempUserConfig $tempUserConfig
55    ) {
56        parent::__construct( $mainModule, $moduleName );
57        $this->specialPageFactory = $specialPageFactory;
58        $this->tempUserConfig = $tempUserConfig;
59    }
60
61    /**
62     * This module uses a custom feed wrapper printer.
63     *
64     * @return ApiFormatFeedWrapper
65     */
66    public function getCustomPrinter() {
67        return new ApiFormatFeedWrapper( $this->getMain() );
68    }
69
70    /**
71     * Format the rows (generated by SpecialRecentchanges or SpecialRecentchangeslinked)
72     * as an RSS/Atom feed.
73     */
74    public function execute() {
75        $config = $this->getConfig();
76
77        $this->params = $this->extractRequestParams();
78
79        if ( !$config->get( MainConfigNames::Feed ) ) {
80            $this->dieWithError( 'feed-unavailable' );
81        }
82
83        $feedClasses = $config->get( MainConfigNames::FeedClasses );
84        if ( !isset( $feedClasses[$this->params['feedformat']] ) ) {
85            $this->dieWithError( 'feed-invalid' );
86        }
87
88        $this->getMain()->setCacheMode( 'public' );
89        if ( !$this->getMain()->getParameter( 'smaxage' ) ) {
90            // T65249: This page gets hit a lot, cache at least 15 seconds.
91            $this->getMain()->setCacheMaxAge( 15 );
92        }
93
94        $feedFormat = $this->params['feedformat'];
95        $specialPageName = $this->params['target'] !== null
96            ? 'Recentchangeslinked'
97            : 'Recentchanges';
98
99        $formatter = $this->getFeedObject( $feedFormat, $specialPageName );
100
101        // Parameters are passed via the request in the context… :(
102        $context = new DerivativeContext( $this );
103        $context->setRequest( new DerivativeRequest(
104            $this->getRequest(),
105            $this->params,
106            $this->getRequest()->wasPosted()
107        ) );
108
109        // The row-getting functionality should be factored out of ChangesListSpecialPage too…
110        $rc = $this->specialPageFactory->getPage( $specialPageName );
111        if ( $rc === null ) {
112            throw new RuntimeException( __METHOD__ . ' not able to instance special page ' . $specialPageName );
113        }
114        '@phan-var \MediaWiki\SpecialPage\ChangesListSpecialPage $rc';
115        $rc->setContext( $context );
116        $rows = $rc->getRows();
117
118        $feedItems = $rows ? ChangesFeed::buildItems( $rows ) : [];
119
120        ApiFormatFeedWrapper::setResult( $this->getResult(), $formatter, $feedItems );
121    }
122
123    /**
124     * Return a MediaWiki\Feed\ChannelFeed object.
125     *
126     * @param string $feedFormat Feed's format (either 'rss' or 'atom')
127     * @param string $specialPageName Relevant special page name (either 'Recentchanges' or
128     *     'Recentchangeslinked')
129     * @return ChannelFeed
130     */
131    private function getFeedObject( $feedFormat, $specialPageName ) {
132        if ( $specialPageName === 'Recentchangeslinked' ) {
133            $title = Title::newFromText( $this->params['target'] );
134            if ( !$title || $title->isExternal() ) {
135                $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $this->params['target'] ) ] );
136            }
137
138            $feed = new ChangesFeed( $feedFormat );
139            $feedObj = $feed->getFeedObject(
140                $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() )
141                    ->inContentLanguage()->text(),
142                $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
143                SpecialPage::getTitleFor( 'Recentchangeslinked' )->getFullURL()
144            );
145        } else {
146            $feed = new ChangesFeed( $feedFormat );
147            $feedObj = $feed->getFeedObject(
148                $this->msg( 'recentchanges' )->inContentLanguage()->text(),
149                $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
150                SpecialPage::getTitleFor( 'Recentchanges' )->getFullURL()
151            );
152        }
153
154        return $feedObj;
155    }
156
157    public function getAllowedParams() {
158        $config = $this->getConfig();
159        $feedFormatNames = array_keys( $config->get( MainConfigNames::FeedClasses ) );
160
161        return [
162            'feedformat' => [
163                ParamValidator::PARAM_DEFAULT => 'rss',
164                ParamValidator::PARAM_TYPE => $feedFormatNames,
165            ],
166
167            'namespace' => [
168                ParamValidator::PARAM_TYPE => 'namespace',
169            ],
170            // TODO: Rename this option to 'invertnamespaces'?
171            'invert' => false,
172            'associated' => false,
173
174            'days' => [
175                ParamValidator::PARAM_DEFAULT => 7,
176                IntegerDef::PARAM_MIN => 1,
177                ParamValidator::PARAM_TYPE => 'integer',
178            ],
179            'limit' => [
180                ParamValidator::PARAM_DEFAULT => 50,
181                IntegerDef::PARAM_MIN => 1,
182                IntegerDef::PARAM_MAX => $config->get( MainConfigNames::FeedLimit ),
183                ParamValidator::PARAM_TYPE => 'integer',
184            ],
185            'from' => [
186                ParamValidator::PARAM_TYPE => 'timestamp',
187            ],
188
189            'hideminor' => false,
190            'hidebots' => false,
191            'hideanons' => [
192                ParamValidator::PARAM_DEFAULT => false,
193                ApiBase::PARAM_HELP_MSG => $this->tempUserConfig->isEnabled() ?
194                    'apihelp-feedrecentchanges-param-hideanons-temp' :
195                    'apihelp-feedrecentchanges-param-hideanons',
196            ],
197            'hideliu' => false,
198            'hidepatrolled' => false,
199            'hidemyself' => false,
200            'hidecategorization' => false,
201
202            'tagfilter' => [
203                ParamValidator::PARAM_TYPE => 'string',
204            ],
205            'inverttags' => false,
206
207            'target' => [
208                ParamValidator::PARAM_TYPE => 'string',
209            ],
210            'showlinkedto' => false,
211        ];
212    }
213
214    protected function getExamplesMessages() {
215        return [
216            'action=feedrecentchanges'
217                => 'apihelp-feedrecentchanges-example-simple',
218            'action=feedrecentchanges&days=30'
219                => 'apihelp-feedrecentchanges-example-30days',
220        ];
221    }
222
223    public function getHelpUrls() {
224        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Feedrecentchanges';
225    }
226}