MediaWiki master
ApiFeedContributions.php
Go to the documentation of this file.
1<?php
45
50
51 private RevisionStore $revisionStore;
52 private TitleParser $titleParser;
53 private LinkRenderer $linkRenderer;
54 private LinkBatchFactory $linkBatchFactory;
55 private HookContainer $hookContainer;
56 private IConnectionProvider $dbProvider;
57 private NamespaceInfo $namespaceInfo;
58 private UserFactory $userFactory;
59 private CommentFormatter $commentFormatter;
60 private ApiHookRunner $hookRunner;
61
75 public function __construct(
76 ApiMain $main,
77 $action,
78 RevisionStore $revisionStore,
79 TitleParser $titleParser,
80 LinkRenderer $linkRenderer,
81 LinkBatchFactory $linkBatchFactory,
82 HookContainer $hookContainer,
83 IConnectionProvider $dbProvider,
84 NamespaceInfo $namespaceInfo,
85 UserFactory $userFactory,
86 CommentFormatter $commentFormatter
87 ) {
88 parent::__construct( $main, $action );
89 $this->revisionStore = $revisionStore;
90 $this->titleParser = $titleParser;
91 $this->linkRenderer = $linkRenderer;
92 $this->linkBatchFactory = $linkBatchFactory;
93 $this->hookContainer = $hookContainer;
94 $this->dbProvider = $dbProvider;
95 $this->namespaceInfo = $namespaceInfo;
96 $this->userFactory = $userFactory;
97 $this->commentFormatter = $commentFormatter;
98
99 $this->hookRunner = new ApiHookRunner( $hookContainer );
100 }
101
107 public function getCustomPrinter() {
108 return new ApiFormatFeedWrapper( $this->getMain() );
109 }
110
111 public function execute() {
112 $params = $this->extractRequestParams();
113
114 $config = $this->getConfig();
115 if ( !$config->get( MainConfigNames::Feed ) ) {
116 $this->dieWithError( 'feed-unavailable' );
117 }
118
119 $feedClasses = $config->get( MainConfigNames::FeedClasses );
120 if ( !isset( $feedClasses[$params['feedformat']] ) ) {
121 $this->dieWithError( 'feed-invalid' );
122 }
123
124 if ( $params['showsizediff'] && $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
125 $this->dieWithError( 'apierror-sizediffdisabled' );
126 }
127
128 $msg = $this->msg( 'Contributions' )->inContentLanguage()->text();
129 $feedTitle = $config->get( MainConfigNames::Sitename ) . ' - ' . $msg .
130 ' [' . $config->get( MainConfigNames::LanguageCode ) . ']';
131
132 $target = $params['user'];
133 if ( ExternalUserNames::isExternal( $target ) ) {
134 // Interwiki names make invalid titles, so put the target in the query instead.
135 $feedUrl = SpecialPage::getTitleFor( 'Contributions' )->getFullURL( [ 'target' => $target ] );
136 } else {
137 $feedUrl = SpecialPage::getTitleFor( 'Contributions', $target )->getFullURL();
138 }
139
140 $feed = new $feedClasses[$params['feedformat']] (
141 $feedTitle,
142 htmlspecialchars( $msg ),
143 $feedUrl
144 );
145
146 // Convert year/month parameters to end parameter
147 $params['start'] = '';
148 $params['end'] = '';
149 $params = ContribsPager::processDateFilter( $params );
150
151 $targetUser = $this->userFactory->newFromName( $target, UserRigorOptions::RIGOR_NONE );
152
153 $pager = new ContribsPager(
154 $this->getContext(), [
155 'target' => $target,
156 'namespace' => $params['namespace'],
157 'start' => $params['start'],
158 'end' => $params['end'],
159 'tagFilter' => $params['tagfilter'],
160 'deletedOnly' => $params['deletedonly'],
161 'topOnly' => $params['toponly'],
162 'newOnly' => $params['newonly'],
163 'hideMinor' => $params['hideminor'],
164 'showSizeDiff' => $params['showsizediff'],
165 ],
166 $this->linkRenderer,
167 $this->linkBatchFactory,
168 $this->hookContainer,
169 $this->dbProvider,
170 $this->revisionStore,
171 $this->namespaceInfo,
172 $targetUser,
173 $this->commentFormatter
174 );
175
176 $feedLimit = $this->getConfig()->get( MainConfigNames::FeedLimit );
177 if ( $pager->getLimit() > $feedLimit ) {
178 $pager->setLimit( $feedLimit );
179 }
180
181 $feedItems = [];
182 if ( $pager->getNumRows() > 0 ) {
183 $count = 0;
184 $limit = $pager->getLimit();
185 foreach ( $pager->mResult as $row ) {
186 // ContribsPager selects one more row for navigation, skip that row
187 if ( ++$count > $limit ) {
188 break;
189 }
190 $item = $this->feedItem( $row );
191 if ( $item !== null ) {
192 $feedItems[] = $item;
193 }
194 }
195 }
196
197 ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
198 }
199
200 protected function feedItem( $row ) {
201 // This hook is the api contributions equivalent to the
202 // ContributionsLineEnding hook. Hook implementers may cancel
203 // the hook to signal the user is not allowed to read this item.
204 $feedItem = null;
205 $hookResult = $this->hookRunner->onApiFeedContributions__feedItem(
206 $row, $this->getContext(), $feedItem );
207 // Hook returned a valid feed item
208 if ( $feedItem instanceof FeedItem ) {
209 return $feedItem;
210 // Hook was canceled and did not return a valid feed item
211 } elseif ( !$hookResult ) {
212 return null;
213 }
214
215 // Hook completed and did not return a valid feed item
216 $title = Title::makeTitle( (int)$row->page_namespace, $row->page_title );
217
218 if ( $title && $this->getAuthority()->authorizeRead( 'read', $title ) ) {
219 $date = $row->rev_timestamp;
220 $comments = $title->getTalkPage()->getFullURL();
221 $revision = $this->revisionStore->newRevisionFromRow( $row, 0, $title );
222
223 return new FeedItem(
224 $title->getPrefixedText(),
225 $this->feedItemDesc( $revision ),
226 $title->getFullURL( [ 'diff' => $revision->getId() ] ),
227 $date,
228 $this->feedItemAuthor( $revision ),
229 $comments
230 );
231 }
232
233 return null;
234 }
235
241 protected function feedItemAuthor( RevisionRecord $revision ) {
242 $user = $revision->getUser();
243 return $user ? $user->getName() : '';
244 }
245
251 protected function feedItemDesc( RevisionRecord $revision ) {
252 $msg = $this->msg( 'colon-separator' )->inContentLanguage()->text();
253 try {
254 $content = $revision->getContent( SlotRecord::MAIN );
255 } catch ( RevisionAccessException $e ) {
256 $content = null;
257 }
258
259 if ( $content instanceof TextContent ) {
260 // only textual content has a "source view".
261 $html = nl2br( htmlspecialchars( $content->getText(), ENT_COMPAT ) );
262 } else {
263 // XXX: we could get an HTML representation of the content via getParserOutput, but that may
264 // contain JS magic and generally may not be suitable for inclusion in a feed.
265 // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
266 // Compare also MediaWiki\Feed\FeedUtils::formatDiffRow.
267 $html = '';
268 }
269
270 $comment = $revision->getComment();
271
272 return '<p>' . htmlspecialchars( $this->feedItemAuthor( $revision ) ) . $msg .
273 htmlspecialchars( FeedItem::stripComment( $comment->text ?? '' ) ) .
274 "</p>\n<hr />\n<div>" . $html . '</div>';
275 }
276
277 public function getAllowedParams() {
278 $feedFormatNames = array_keys( $this->getConfig()->get( MainConfigNames::FeedClasses ) );
279
280 $ret = [
281 'feedformat' => [
282 ParamValidator::PARAM_DEFAULT => 'rss',
283 ParamValidator::PARAM_TYPE => $feedFormatNames
284 ],
285 'user' => [
286 ParamValidator::PARAM_TYPE => 'user',
287 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id', 'interwiki' ],
288 ParamValidator::PARAM_REQUIRED => true,
289 ],
290 'namespace' => [
291 ParamValidator::PARAM_TYPE => 'namespace'
292 ],
293 'year' => [
294 ParamValidator::PARAM_TYPE => 'integer'
295 ],
296 'month' => [
297 ParamValidator::PARAM_TYPE => 'integer'
298 ],
299 'tagfilter' => [
300 ParamValidator::PARAM_ISMULTI => true,
301 ParamValidator::PARAM_TYPE => array_values( ChangeTags::listDefinedTags() ),
302 ParamValidator::PARAM_DEFAULT => '',
303 ],
304 'deletedonly' => false,
305 'toponly' => false,
306 'newonly' => false,
307 'hideminor' => false,
308 'showsizediff' => [
309 ParamValidator::PARAM_DEFAULT => false,
310 ],
311 ];
312
313 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
314 $ret['showsizediff'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
315 }
316
317 return $ret;
318 }
319
320 protected function getExamplesMessages() {
321 return [
322 'action=feedcontributions&user=Example'
323 => 'apihelp-feedcontributions-example-simple',
324 ];
325 }
326
327 public function getHelpUrls() {
328 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Feedcontributions';
329 }
330}
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
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
__construct(ApiMain $main, $action, RevisionStore $revisionStore, TitleParser $titleParser, LinkRenderer $linkRenderer, LinkBatchFactory $linkBatchFactory, HookContainer $hookContainer, IConnectionProvider $dbProvider, NamespaceInfo $namespaceInfo, UserFactory $userFactory, CommentFormatter $commentFormatter)
feedItemDesc(RevisionRecord $revision)
feedItemAuthor(RevisionRecord $revision)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getHelpUrls()
Return links to more detailed help pages about the module.
getCustomPrinter()
This module uses a custom feed wrapper printer.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getExamplesMessages()
Returns usage examples for this module.
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:65
static listDefinedTags()
Basically lists defined tags which count even if they aren't applied to anything.
This class provides an implementation of the hook interfaces used by the core Action API,...
This is the main service interface for converting single-line comments from various DB comment fields...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
A base class for outputting syndication feeds (e.g.
Definition FeedItem.php:40
Class that generates HTML for internal links.
A class containing constants representing the names of configuration variables.
Pager for Special:Contributions.
Type definition for user types.
Definition UserDef.php:27
Exception representing a failure to look up a revision.
Page revision base class.
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.
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Parent class for all special pages.
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:78
Class to parse and build external user names.
Creates User objects.
Content object implementation for representing flat text.
Service for formatting and validating API parameters.
A title parser service for MediaWiki.
Shared interface for rigor levels when dealing with User methods.
Provide primary and replica IDatabase connections.