MediaWiki REL1_39
ApiFeedWatchlist.php
Go to the documentation of this file.
1<?php
26
35
36 private $watchlistModule = null;
37 private $linkToSections = false;
38
40 private $parser;
41
47 public function __construct(
48 ApiMain $main,
49 $action,
50 Parser $parser
51 ) {
52 parent::__construct( $main, $action );
53 $this->parser = $parser;
54 }
55
61 public function getCustomPrinter() {
62 return new ApiFormatFeedWrapper( $this->getMain() );
63 }
64
69 public function execute() {
70 $config = $this->getConfig();
71 $feedClasses = $config->get( MainConfigNames::FeedClasses );
72 $params = [];
73 $feedItems = [];
74 try {
75 $params = $this->extractRequestParams();
76
77 if ( !$config->get( MainConfigNames::Feed ) ) {
78 $this->dieWithError( 'feed-unavailable' );
79 }
80
81 if ( !isset( $feedClasses[$params['feedformat']] ) ) {
82 $this->dieWithError( 'feed-invalid' );
83 }
84
85 // limit to the number of hours going from now back
86 $endTime = wfTimestamp( TS_MW, time() - (int)$params['hours'] * 60 * 60 );
87
88 // Prepare parameters for nested request
89 $fauxReqArr = [
90 'action' => 'query',
91 'meta' => 'siteinfo',
92 'siprop' => 'general',
93 'list' => 'watchlist',
94 'wlprop' => 'title|user|comment|timestamp|ids|loginfo',
95 'wldir' => 'older', // reverse order - from newest to oldest
96 'wlend' => $endTime, // stop at this time
97 'wllimit' => min( 50, $this->getConfig()->get( MainConfigNames::FeedLimit ) )
98 ];
99
100 if ( $params['wlowner'] !== null ) {
101 $fauxReqArr['wlowner'] = $params['wlowner'];
102 }
103 if ( $params['wltoken'] !== null ) {
104 $fauxReqArr['wltoken'] = $params['wltoken'];
105 }
106 if ( $params['wlexcludeuser'] !== null ) {
107 $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
108 }
109 if ( $params['wlshow'] !== null ) {
110 $fauxReqArr['wlshow'] = ParamValidator::implodeMultiValue( $params['wlshow'] );
111 }
112 if ( $params['wltype'] !== null ) {
113 $fauxReqArr['wltype'] = ParamValidator::implodeMultiValue( $params['wltype'] );
114 }
115
116 // Support linking directly to sections when possible
117 // (possible only if section name is present in comment)
118 if ( $params['linktosections'] ) {
119 $this->linkToSections = true;
120 }
121
122 // Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
123 if ( $params['allrev'] ) {
124 $fauxReqArr['wlallrev'] = '';
125 }
126
127 $fauxReq = new FauxRequest( $fauxReqArr );
128
129 $module = new ApiMain( $fauxReq );
130 $module->execute();
131
132 $data = $module->getResult()->getResultData( [ 'query', 'watchlist' ] );
133 foreach ( (array)$data as $key => $info ) {
134 if ( ApiResult::isMetadataKey( $key ) ) {
135 continue;
136 }
137 $feedItem = $this->createFeedItem( $info );
138 if ( $feedItem ) {
139 $feedItems[] = $feedItem;
140 }
141 }
142
143 $msg = $this->msg( 'watchlist' )->inContentLanguage()->text();
144
145 $feedTitle = $this->getConfig()->get( MainConfigNames::Sitename ) . ' - ' . $msg .
146 ' [' . $this->getConfig()->get( MainConfigNames::LanguageCode ) . ']';
147 $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
148
149 $feed = new $feedClasses[$params['feedformat']] (
150 $feedTitle,
151 htmlspecialchars( $msg ),
152 $feedUrl
153 );
154
155 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
156 } catch ( Exception $e ) {
157 // Error results should not be cached
158 $this->getMain()->setCacheMaxAge( 0 );
159
160 // @todo FIXME: Localise brackets
161 $feedTitle = $this->getConfig()->get( MainConfigNames::Sitename ) . ' - Error - ' .
162 $this->msg( 'watchlist' )->inContentLanguage()->text() .
163 ' [' . $this->getConfig()->get( MainConfigNames::LanguageCode ) . ']';
164 $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
165
166 $feedFormat = $params['feedformat'] ?? 'rss';
167 $msg = $this->msg( 'watchlist' )->inContentLanguage()->escaped();
168 $feed = new $feedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
169
170 if ( $e instanceof ApiUsageException ) {
171 foreach ( $e->getStatusValue()->getErrors() as $error ) {
172 // @phan-suppress-next-line PhanUndeclaredMethod
173 $msg = ApiMessage::create( $error )
174 ->inLanguage( $this->getLanguage() );
175 $errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
176 $errorText = $msg->text();
177 $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
178 }
179 } else {
180 // Something is seriously wrong
181 $errorCode = 'internal_api_error';
182 $errorTitle = $this->msg( 'api-feed-error-title', $errorCode );
183 $errorText = $e->getMessage();
184 $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
185 }
186
187 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
188 }
189 }
190
195 private function createFeedItem( $info ) {
196 if ( !isset( $info['title'] ) ) {
197 // Probably a revdeled log entry, skip it.
198 return null;
199 }
200
201 $titleStr = $info['title'];
202 $title = Title::newFromText( $titleStr );
203 $curidParam = [];
204 if ( !$title || $title->isExternal() ) {
205 // Probably a formerly-valid title that's now conflicting with an
206 // interwiki prefix or the like.
207 if ( isset( $info['pageid'] ) ) {
208 $title = Title::newFromID( $info['pageid'] );
209 $curidParam = [ 'curid' => $info['pageid'] ];
210 }
211 if ( !$title || $title->isExternal() ) {
212 return null;
213 }
214 }
215 if ( isset( $info['revid'] ) ) {
216 if ( $info['revid'] === 0 && isset( $info['logid'] ) ) {
217 $logTitle = Title::makeTitle( NS_SPECIAL, 'Log' );
218 $titleUrl = $logTitle->getFullURL( [ 'logid' => $info['logid'] ] );
219 } else {
220 $titleUrl = $title->getFullURL( [ 'diff' => $info['revid'] ] );
221 }
222 } else {
223 $titleUrl = $title->getFullURL( $curidParam );
224 }
225 $comment = $info['comment'] ?? null;
226
227 // Create an anchor to section.
228 // The anchor won't work for sections that have dupes on page
229 // as there's no way to strip that info from ApiWatchlist (apparently?).
230 // RegExp in the line below is equal to Linker::formatAutocomments().
231 if ( $this->linkToSections && $comment !== null &&
232 preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches )
233 ) {
234 $titleUrl .= $this->parser->guessSectionNameFromWikiText( $matches[ 2 ] );
235 }
236
237 $timestamp = $info['timestamp'];
238
239 if ( isset( $info['user'] ) ) {
240 $user = $info['user'];
241 $completeText = "$comment ($user)";
242 } else {
243 $user = '';
244 $completeText = (string)$comment;
245 }
246
247 return new FeedItem( $titleStr, $completeText, $titleUrl, $timestamp, $user );
248 }
249
250 private function getWatchlistModule() {
251 if ( $this->watchlistModule === null ) {
252 $this->watchlistModule = $this->getMain()->getModuleManager()->getModule( 'query' )
253 ->getModuleManager()->getModule( 'watchlist' );
254 }
255
256 return $this->watchlistModule;
257 }
258
259 public function getAllowedParams( $flags = 0 ) {
260 $feedFormatNames = array_keys( $this->getConfig()->get( MainConfigNames::FeedClasses ) );
261 $ret = [
262 'feedformat' => [
263 ParamValidator::PARAM_DEFAULT => 'rss',
264 ParamValidator::PARAM_TYPE => $feedFormatNames
265 ],
266 'hours' => [
267 ParamValidator::PARAM_DEFAULT => 24,
268 ParamValidator::PARAM_TYPE => 'integer',
269 IntegerDef::PARAM_MIN => 1,
270 IntegerDef::PARAM_MAX => 72,
271 ],
272 'linktosections' => false,
273 ];
274
275 $copyParams = [
276 'allrev' => 'allrev',
277 'owner' => 'wlowner',
278 'token' => 'wltoken',
279 'show' => 'wlshow',
280 'type' => 'wltype',
281 'excludeuser' => 'wlexcludeuser',
282 ];
283 if ( $flags ) {
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 } else {
306 foreach ( $copyParams as $to ) {
307 $ret[$to] = null;
308 }
309 }
310
311 return $ret;
312 }
313
314 protected function getExamplesMessages() {
315 return [
316 'action=feedwatchlist'
317 => 'apihelp-feedwatchlist-example-default',
318 'action=feedwatchlist&allrev=&hours=6'
319 => 'apihelp-feedwatchlist-example-all6hrs',
320 ];
321 }
322
323 public function getHelpUrls() {
324 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Watchlist_feed';
325 }
326}
const NS_SPECIAL
Definition Defines.php:53
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:56
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1454
getMain()
Get the main module.
Definition ApiBase.php:514
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition ApiBase.php:196
getResult()
Get the result object.
Definition ApiBase.php:629
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:765
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:163
This action allows users to get their watchlist items in RSS/Atom formats.
getAllowedParams( $flags=0)
__construct(ApiMain $main, $action, Parser $parser)
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.
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:52
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()
WebRequest clone which takes values from a provided array.
A base class for outputting syndication feeds (e.g.
Definition FeedItem.php:36
A class containing constants representing the names of configuration variables.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:96
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:518
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:370
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:638
Service for formatting and validating API parameters.
Type definition for integer types.