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