MediaWiki master
ApiFeedWatchlist.php
Go to the documentation of this file.
1<?php
30
39
40 private $watchlistModule = null;
41 private $linkToSections = false;
42
43 private ParserFactory $parserFactory;
44
50 public function __construct(
51 ApiMain $main,
52 $action,
53 ParserFactory $parserFactory
54 ) {
55 parent::__construct( $main, $action );
56 $this->parserFactory = $parserFactory;
57 }
58
64 public function getCustomPrinter() {
65 return new ApiFormatFeedWrapper( $this->getMain() );
66 }
67
72 public function execute() {
73 $config = $this->getConfig();
74 $feedClasses = $config->get( MainConfigNames::FeedClasses );
75 $params = [];
76 $feedItems = [];
77 try {
79
80 if ( !$config->get( MainConfigNames::Feed ) ) {
81 $this->dieWithError( 'feed-unavailable' );
82 }
83
84 if ( !isset( $feedClasses[$params['feedformat']] ) ) {
85 $this->dieWithError( 'feed-invalid' );
86 }
87
88 // limit to the number of hours going from now back
89 $endTime = wfTimestamp( TS_MW, time() - (int)$params['hours'] * 60 * 60 );
90
91 // Prepare parameters for nested request
92 $fauxReqArr = [
93 'action' => 'query',
94 'meta' => 'siteinfo',
95 'siprop' => 'general',
96 'list' => 'watchlist',
97 'wlprop' => 'title|user|comment|timestamp|ids|loginfo',
98 'wldir' => 'older', // reverse order - from newest to oldest
99 'wlend' => $endTime, // stop at this time
100 'wllimit' => min( 50, $this->getConfig()->get( MainConfigNames::FeedLimit ) )
101 ];
102
103 if ( $params['wlowner'] !== null ) {
104 $fauxReqArr['wlowner'] = $params['wlowner'];
105 }
106 if ( $params['wltoken'] !== null ) {
107 $fauxReqArr['wltoken'] = $params['wltoken'];
108 }
109 if ( $params['wlexcludeuser'] !== null ) {
110 $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
111 }
112 if ( $params['wlshow'] !== null ) {
113 $fauxReqArr['wlshow'] = $params['wlshow'];
114 }
115 if ( $params['wltype'] !== null ) {
116 $fauxReqArr['wltype'] = $params['wltype'];
117 }
118
119 // Support linking directly to sections when possible
120 // (possible only if section name is present in comment)
121 if ( $params['linktosections'] ) {
122 $this->linkToSections = true;
123 }
124
125 // Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
126 if ( $params['allrev'] ) {
127 $fauxReqArr['wlallrev'] = '';
128 }
129
130 $fauxReq = new FauxRequest( $fauxReqArr );
131
132 $module = new ApiMain( $fauxReq );
133 $module->execute();
134
135 $data = $module->getResult()->getResultData( [ 'query', 'watchlist' ] );
136 foreach ( (array)$data as $key => $info ) {
137 if ( ApiResult::isMetadataKey( $key ) ) {
138 continue;
139 }
140 $feedItem = $this->createFeedItem( $info );
141 if ( $feedItem ) {
142 $feedItems[] = $feedItem;
143 }
144 }
145
146 $msg = $this->msg( 'watchlist' )->inContentLanguage()->text();
147
148 $feedTitle = $this->getConfig()->get( MainConfigNames::Sitename ) . ' - ' . $msg .
149 ' [' . $this->getConfig()->get( MainConfigNames::LanguageCode ) . ']';
150 $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
151
152 $feed = new $feedClasses[$params['feedformat']] (
153 $feedTitle,
154 htmlspecialchars( $msg ),
155 $feedUrl
156 );
157
158 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
159 } catch ( Exception $e ) {
160 // Error results should not be cached
161 $this->getMain()->setCacheMaxAge( 0 );
162
163 // @todo FIXME: Localise brackets
164 $feedTitle = $this->getConfig()->get( MainConfigNames::Sitename ) . ' - Error - ' .
165 $this->msg( 'watchlist' )->inContentLanguage()->text() .
166 ' [' . $this->getConfig()->get( MainConfigNames::LanguageCode ) . ']';
167 $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
168
169 $feedFormat = $params['feedformat'] ?? 'rss';
170 $msg = $this->msg( 'watchlist' )->inContentLanguage()->escaped();
171 $feed = new $feedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
172
173 if ( $e instanceof ApiUsageException ) {
174 foreach ( $e->getStatusValue()->getErrors() as $error ) {
175 // @phan-suppress-next-line PhanUndeclaredMethod
176 $msg = ApiMessage::create( $error )
177 ->inLanguage( $this->getLanguage() );
178 $errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
179 $errorText = $msg->text();
180 $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
181 }
182 } else {
183 // Something is seriously wrong
184 $errorCode = 'internal_api_error';
185 $errorTitle = $this->msg( 'api-feed-error-title', $errorCode );
186 $errorText = $e->getMessage();
187 $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
188 }
189
190 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
191 }
192 }
193
198 private function createFeedItem( $info ) {
199 if ( !isset( $info['title'] ) ) {
200 // Probably a revdeled log entry, skip it.
201 return null;
202 }
203
204 $titleStr = $info['title'];
205 $title = Title::newFromText( $titleStr );
206 $curidParam = [];
207 if ( !$title || $title->isExternal() ) {
208 // Probably a formerly-valid title that's now conflicting with an
209 // interwiki prefix or the like.
210 if ( isset( $info['pageid'] ) ) {
211 $title = Title::newFromID( $info['pageid'] );
212 $curidParam = [ 'curid' => $info['pageid'] ];
213 }
214 if ( !$title || $title->isExternal() ) {
215 return null;
216 }
217 }
218 if ( isset( $info['revid'] ) ) {
219 if ( $info['revid'] === 0 && isset( $info['logid'] ) ) {
220 $logTitle = Title::makeTitle( NS_SPECIAL, 'Log' );
221 $titleUrl = $logTitle->getFullURL( [ 'logid' => $info['logid'] ] );
222 } else {
223 $titleUrl = $title->getFullURL( [ 'diff' => $info['revid'] ] );
224 }
225 } else {
226 $titleUrl = $title->getFullURL( $curidParam );
227 }
228 $comment = $info['comment'] ?? null;
229
230 // Create an anchor to section.
231 // The anchor won't work for sections that have dupes on page
232 // as there's no way to strip that info from ApiWatchlist (apparently?).
233 // RegExp in the line below is equal to MediaWiki\CommentFormatter\CommentParser::doSectionLinks().
234 if ( $this->linkToSections && $comment !== null &&
235 preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches )
236 ) {
237 $titleUrl .= $this->parserFactory->getMainInstance()->guessSectionNameFromWikiText( $matches[ 2 ] );
238 }
239
240 $timestamp = $info['timestamp'];
241
242 if ( isset( $info['user'] ) ) {
243 $user = $info['user'];
244 $completeText = "$comment ($user)";
245 } else {
246 $user = '';
247 $completeText = (string)$comment;
248 }
249
250 return new FeedItem( $titleStr, $completeText, $titleUrl, $timestamp, $user );
251 }
252
253 private function getWatchlistModule() {
254 $this->watchlistModule ??= $this->getMain()->getModuleManager()->getModule( 'query' )
255 ->getModuleManager()->getModule( 'watchlist' );
256
257 return $this->watchlistModule;
258 }
259
260 public function getAllowedParams( $flags = 0 ) {
261 $feedFormatNames = array_keys( $this->getConfig()->get( MainConfigNames::FeedClasses ) );
262 $ret = [
263 'feedformat' => [
264 ParamValidator::PARAM_DEFAULT => 'rss',
265 ParamValidator::PARAM_TYPE => $feedFormatNames
266 ],
267 'hours' => [
268 ParamValidator::PARAM_DEFAULT => 24,
269 ParamValidator::PARAM_TYPE => 'integer',
270 IntegerDef::PARAM_MIN => 1,
271 IntegerDef::PARAM_MAX => 72,
272 ],
273 'linktosections' => false,
274 ];
275
276 $copyParams = [
277 'allrev' => 'allrev',
278 'owner' => 'wlowner',
279 'token' => 'wltoken',
280 'show' => 'wlshow',
281 'type' => 'wltype',
282 'excludeuser' => 'wlexcludeuser',
283 ];
284 // @phan-suppress-next-line PhanParamTooMany
285 $wlparams = $this->getWatchlistModule()->getAllowedParams( $flags );
286 foreach ( $copyParams as $from => $to ) {
287 $p = $wlparams[$from];
288 if ( !is_array( $p ) ) {
289 $p = [ ParamValidator::PARAM_DEFAULT => $p ];
290 }
291 if ( !isset( $p[ApiBase::PARAM_HELP_MSG] ) ) {
292 $p[ApiBase::PARAM_HELP_MSG] = "apihelp-query+watchlist-param-$from";
293 }
294 if ( isset( $p[ParamValidator::PARAM_TYPE] ) && is_array( $p[ParamValidator::PARAM_TYPE] ) &&
296 ) {
297 foreach ( $p[ParamValidator::PARAM_TYPE] as $v ) {
298 if ( !isset( $p[ApiBase::PARAM_HELP_MSG_PER_VALUE][$v] ) ) {
299 $p[ApiBase::PARAM_HELP_MSG_PER_VALUE][$v] = "apihelp-query+watchlist-paramvalue-$from-$v";
300 }
301 }
302 }
303 $ret[$to] = $p;
304 }
305
306 return $ret;
307 }
308
309 protected function getExamplesMessages() {
310 return [
311 'action=feedwatchlist'
312 => 'apihelp-feedwatchlist-example-default',
313 'action=feedwatchlist&allrev=&hours=6'
314 => 'apihelp-feedwatchlist-example-all6hrs',
315 ];
316 }
317
318 public function getHelpUrls() {
319 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Watchlist_feed';
320 }
321}
const NS_SPECIAL
Definition Defines.php:54
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:64
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1542
getMain()
Get the main module.
Definition ApiBase.php:559
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:211
getResult()
Get the result object.
Definition ApiBase.php:680
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:820
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
This action allows users to get their watchlist items in RSS/Atom formats.
getAllowedParams( $flags=0)
getExamplesMessages()
Returns usage examples for this module.
getHelpUrls()
Return links to more detailed help pages about the module.
getCustomPrinter()
This module uses a custom feed wrapper printer.
__construct(ApiMain $main, $action, ParserFactory $parserFactory)
execute()
Make a nested call to the API to request watchlist items in the last $hours.
This printer is used to wrap an instance of the Feed class.
static setResult( $result, $feed, $feedItems)
Call this method to initialize output data.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:66
Exception used to abort API execution with an error.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
A base class for outputting syndication feeds (e.g.
Definition FeedItem.php:40
A class containing constants representing the names of configuration variables.
WebRequest clone which takes values from a provided array.
Parent class for all special pages.
Represents a title within MediaWiki.
Definition Title.php:78
Service for formatting and validating API parameters.
Type definition for integer types.