MediaWiki master
ApiFeedContributions.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
11use MediaWiki\Cache\LinkBatchFactory;
20use MediaWiki\Pager\ContribsPager;
32use stdClass;
35
40
41 private RevisionStore $revisionStore;
42 private LinkRenderer $linkRenderer;
43 private LinkBatchFactory $linkBatchFactory;
44 private HookContainer $hookContainer;
45 private IConnectionProvider $dbProvider;
46 private NamespaceInfo $namespaceInfo;
47 private UserFactory $userFactory;
48 private CommentFormatter $commentFormatter;
49 private ApiHookRunner $hookRunner;
50
51 public function __construct(
52 ApiMain $main,
53 string $action,
54 RevisionStore $revisionStore,
55 LinkRenderer $linkRenderer,
56 LinkBatchFactory $linkBatchFactory,
57 HookContainer $hookContainer,
58 IConnectionProvider $dbProvider,
59 NamespaceInfo $namespaceInfo,
60 UserFactory $userFactory,
61 CommentFormatter $commentFormatter
62 ) {
63 parent::__construct( $main, $action );
64 $this->revisionStore = $revisionStore;
65 $this->linkRenderer = $linkRenderer;
66 $this->linkBatchFactory = $linkBatchFactory;
67 $this->hookContainer = $hookContainer;
68 $this->dbProvider = $dbProvider;
69 $this->namespaceInfo = $namespaceInfo;
70 $this->userFactory = $userFactory;
71 $this->commentFormatter = $commentFormatter;
72
73 $this->hookRunner = new ApiHookRunner( $hookContainer );
74 }
75
81 public function getCustomPrinter() {
82 return new ApiFormatFeedWrapper( $this->getMain() );
83 }
84
85 public function execute() {
86 $params = $this->extractRequestParams();
87
88 $config = $this->getConfig();
89 if ( !$config->get( MainConfigNames::Feed ) ) {
90 $this->dieWithError( 'feed-unavailable' );
91 }
92
93 $feedClasses = $config->get( MainConfigNames::FeedClasses );
94 '@phan-var array<string,class-string<ChannelFeed>> $feedClasses';
95 if ( !isset( $feedClasses[$params['feedformat']] ) ) {
96 $this->dieWithError( 'feed-invalid' );
97 }
98
99 if ( $params['showsizediff'] && $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
100 $this->dieWithError( 'apierror-sizediffdisabled' );
101 }
102
103 $msg = $this->msg( 'Contributions' )->inContentLanguage()->escaped();
104 $feedTitle = $config->get( MainConfigNames::Sitename ) . ' - ' . $msg .
105 ' [' . $config->get( MainConfigNames::LanguageCode ) . ']';
106
107 $target = $params['user'];
108 if ( ExternalUserNames::isExternal( $target ) ) {
109 // Interwiki names make invalid titles, so put the target in the query instead.
110 $feedUrl = SpecialPage::getTitleFor( 'Contributions' )->getFullURL( [ 'target' => $target ] );
111 } else {
112 $feedUrl = SpecialPage::getTitleFor( 'Contributions', $target )->getFullURL();
113 }
114
115 $feed = new $feedClasses[$params['feedformat']](
116 $feedTitle,
117 $msg,
118 $feedUrl
119 );
120
121 // Convert year/month parameters to end parameter
122 $params['start'] = '';
123 $params['end'] = '';
124 $params = ContribsPager::processDateFilter( $params );
125
126 $targetUser = $this->userFactory->newFromName( $target, UserRigorOptions::RIGOR_NONE );
127
128 $pager = new ContribsPager(
129 $this->getContext(), [
130 'target' => $target,
131 'namespace' => $params['namespace'],
132 'start' => $params['start'],
133 'end' => $params['end'],
134 'tagFilter' => $params['tagfilter'],
135 'deletedOnly' => $params['deletedonly'],
136 'topOnly' => $params['toponly'],
137 'newOnly' => $params['newonly'],
138 'hideMinor' => $params['hideminor'],
139 'showSizeDiff' => $params['showsizediff'],
140 ],
141 $this->linkRenderer,
142 $this->linkBatchFactory,
143 $this->hookContainer,
144 $this->dbProvider,
145 $this->revisionStore,
146 $this->namespaceInfo,
147 $targetUser,
148 $this->commentFormatter
149 );
150
151 $feedLimit = $this->getConfig()->get( MainConfigNames::FeedLimit );
152 if ( $pager->getLimit() > $feedLimit ) {
153 $pager->setLimit( $feedLimit );
154 }
155
156 $feedItems = [];
157 if ( $pager->getNumRows() > 0 ) {
158 $count = 0;
159 $limit = $pager->getLimit();
160 foreach ( $pager->mResult as $row ) {
161 // ContribsPager selects one more row for navigation, skip that row
162 if ( ++$count > $limit ) {
163 break;
164 }
165 $item = $this->feedItem( $row );
166 if ( $item !== null ) {
167 $feedItems[] = $item;
168 }
169 }
170 }
171
172 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
173 }
174
179 protected function feedItem( $row ): ?FeedItem {
180 // This hook is the api contributions equivalent to the
181 // ContributionsLineEnding hook. Hook implementers may cancel
182 // the hook to signal the user is not allowed to read this item.
183 $feedItem = null;
184 $hookResult = $this->hookRunner->onApiFeedContributions__feedItem(
185 $row, $this->getContext(), $feedItem );
186 // Hook returned a valid feed item
187 if ( $feedItem instanceof FeedItem ) {
188 return $feedItem;
189 // Hook was canceled and did not return a valid feed item
190 } elseif ( !$hookResult ) {
191 return null;
192 }
193
194 // Hook completed and did not return a valid feed item
195 $title = Title::makeTitle( (int)$row->page_namespace, $row->page_title );
196
197 if ( $this->getAuthority()->authorizeRead( 'read', $title ) ) {
198 $date = $row->rev_timestamp;
199 $comments = $title->getTalkPage()->getFullURL();
200 $revision = $this->revisionStore->newRevisionFromRow( $row, 0, $title );
201
202 return new FeedItem(
203 $title->getPrefixedText(),
204 $this->feedItemDesc( $revision ),
205 $title->getFullURL( [ 'diff' => $revision->getId() ] ),
206 $date,
207 $this->feedItemAuthor( $revision ),
208 $comments
209 );
210 }
211
212 return null;
213 }
214
220 protected function feedItemAuthor( RevisionRecord $revision ) {
221 $user = $revision->getUser();
222 return $user ? $user->getName() : '';
223 }
224
230 protected function feedItemDesc( RevisionRecord $revision ) {
231 $msg = $this->msg( 'colon-separator' )->inContentLanguage()->escaped();
232 try {
233 $content = $revision->getContent( SlotRecord::MAIN );
234 } catch ( RevisionAccessException ) {
235 $content = null;
236 }
237
238 if ( $content instanceof TextContent ) {
239 // only textual content has a "source view".
240 $html = nl2br( htmlspecialchars( $content->getText(), ENT_COMPAT ) );
241 } else {
242 // XXX: we could get an HTML representation of the content via getParserOutput, but that may
243 // contain JS magic and generally may not be suitable for inclusion in a feed.
244 // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
245 // Compare also MediaWiki\Feed\FeedUtils::formatDiffRow.
246 $html = '';
247 }
248
249 $comment = $revision->getComment();
250
251 return '<p>' . htmlspecialchars( $this->feedItemAuthor( $revision ) ) . $msg .
252 htmlspecialchars( FeedItem::stripComment( $comment->text ?? '' ) ) .
253 "</p>\n<hr />\n<div>" . $html . '</div>';
254 }
255
257 public function getAllowedParams() {
258 $feedFormatNames = array_keys( $this->getConfig()->get( MainConfigNames::FeedClasses ) );
259
260 $ret = [
261 'feedformat' => [
262 ParamValidator::PARAM_DEFAULT => 'rss',
263 ParamValidator::PARAM_TYPE => $feedFormatNames
264 ],
265 'user' => [
266 ParamValidator::PARAM_TYPE => 'user',
267 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id', 'interwiki' ],
268 ParamValidator::PARAM_REQUIRED => true,
269 ],
270 'namespace' => [
271 ParamValidator::PARAM_TYPE => 'namespace'
272 ],
273 'year' => [
274 ParamValidator::PARAM_TYPE => 'integer'
275 ],
276 'month' => [
277 ParamValidator::PARAM_TYPE => 'integer'
278 ],
279 'tagfilter' => [
280 ParamValidator::PARAM_ISMULTI => true,
281 ParamValidator::PARAM_TYPE => array_values( MediaWikiServices::getInstance()
282 ->getChangeTagsStore()->listDefinedTags()
283 ),
284 ParamValidator::PARAM_DEFAULT => '',
285 ],
286 'deletedonly' => false,
287 'toponly' => false,
288 'newonly' => false,
289 'hideminor' => false,
290 'showsizediff' => [
291 ParamValidator::PARAM_DEFAULT => false,
292 ],
293 ];
294
295 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
296 $ret['showsizediff'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
297 }
298
299 return $ret;
300 }
301
303 protected function getExamplesMessages() {
304 return [
305 'action=feedcontributions&user=Example'
306 => 'apihelp-feedcontributions-example-simple',
307 ];
308 }
309
311 public function getHelpUrls() {
312 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Feedcontributions';
313 }
314}
315
317class_alias( ApiFeedContributions::class, 'ApiFeedContributions' );
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:61
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1511
getMain()
Get the main module.
Definition ApiBase.php:561
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
feedItem( $row)
TODO: use stdClass type hint without T398925.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
__construct(ApiMain $main, string $action, RevisionStore $revisionStore, LinkRenderer $linkRenderer, LinkBatchFactory $linkBatchFactory, HookContainer $hookContainer, IConnectionProvider $dbProvider, NamespaceInfo $namespaceInfo, UserFactory $userFactory, CommentFormatter $commentFormatter)
getCustomPrinter()
This module uses a custom feed wrapper printer.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
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 class provides an implementation of the hook interfaces used by the core Action API,...
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:65
This is the main service interface for converting single-line comments from various DB comment fields...
Content object implementation for representing flat text.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Class to support the outputting of syndication feeds in Atom and RSS format.
A base class for outputting syndication feeds (e.g.
Definition FeedItem.php:26
Class that generates HTML for internal links.
A class containing constants representing the names of configuration variables.
const FeedClasses
Name constant for the FeedClasses setting, for use with Config::get()
const Feed
Name constant for the Feed setting, for use with Config::get()
const Sitename
Name constant for the Sitename setting, for use with Config::get()
const LanguageCode
Name constant for the LanguageCode setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
const FeedLimit
Name constant for the FeedLimit setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Type definition for user types.
Definition UserDef.php:27
Exception representing a failure to look up a revision.
Page revision base class.
getContent( $role, $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Returns the Content of the given slot of this revision.
getUser( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
getComment( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision comment, if it's available to the specified audience.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Parent class for all special pages.
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,...
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:69
Class to parse and build external user names.
Create User objects.
Service for formatting and validating API parameters.
Shared interface for rigor levels when dealing with User methods.
Provide primary and replica IDatabase connections.