ApiFeedContributions.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. /**
  3. * Copyright © 2011 Sam Reed
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. use MediaWiki\MediaWikiServices;
  23. use MediaWiki\Revision\RevisionAccessException;
  24. use MediaWiki\Revision\RevisionRecord;
  25. use MediaWiki\Revision\RevisionStore;
  26. use MediaWiki\Revision\SlotRecord;
  27. /**
  28. * @ingroup API
  29. */
  30. class ApiFeedContributions extends ApiBase {
  31. /** @var RevisionStore */
  32. private $revisionStore;
  33. /** @var TitleParser */
  34. private $titleParser;
  35. /**
  36. * This module uses a custom feed wrapper printer.
  37. *
  38. * @return ApiFormatFeedWrapper
  39. */
  40. public function getCustomPrinter() {
  41. return new ApiFormatFeedWrapper( $this->getMain() );
  42. }
  43. public function execute() {
  44. $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
  45. $this->titleParser = MediaWikiServices::getInstance()->getTitleParser();
  46. $params = $this->extractRequestParams();
  47. $config = $this->getConfig();
  48. if ( !$config->get( 'Feed' ) ) {
  49. $this->dieWithError( 'feed-unavailable' );
  50. }
  51. $feedClasses = $config->get( 'FeedClasses' );
  52. if ( !isset( $feedClasses[$params['feedformat']] ) ) {
  53. $this->dieWithError( 'feed-invalid' );
  54. }
  55. if ( $params['showsizediff'] && $this->getConfig()->get( 'MiserMode' ) ) {
  56. $this->dieWithError( 'apierror-sizediffdisabled' );
  57. }
  58. $msg = wfMessage( 'Contributions' )->inContentLanguage()->text();
  59. $feedTitle = $config->get( 'Sitename' ) . ' - ' . $msg .
  60. ' [' . $config->get( 'LanguageCode' ) . ']';
  61. $feedUrl = SpecialPage::getTitleFor( 'Contributions', $params['user'] )->getFullURL();
  62. try {
  63. $target = $this->titleParser
  64. ->parseTitle( $params['user'], NS_USER )
  65. ->getText();
  66. } catch ( MalformedTitleException $e ) {
  67. $this->dieWithError(
  68. [ 'apierror-baduser', 'user', wfEscapeWikiText( $params['user'] ) ],
  69. 'baduser_' . $this->encodeParamName( 'user' )
  70. );
  71. }
  72. $feed = new $feedClasses[$params['feedformat']] (
  73. $feedTitle,
  74. htmlspecialchars( $msg ),
  75. $feedUrl
  76. );
  77. // Convert year/month parameters to end parameter
  78. $params['start'] = '';
  79. $params['end'] = '';
  80. $params = ContribsPager::processDateFilter( $params );
  81. $pager = new ContribsPager( $this->getContext(), [
  82. 'target' => $target,
  83. 'namespace' => $params['namespace'],
  84. 'start' => $params['start'],
  85. 'end' => $params['end'],
  86. 'tagFilter' => $params['tagfilter'],
  87. 'deletedOnly' => $params['deletedonly'],
  88. 'topOnly' => $params['toponly'],
  89. 'newOnly' => $params['newonly'],
  90. 'hideMinor' => $params['hideminor'],
  91. 'showSizeDiff' => $params['showsizediff'],
  92. ] );
  93. $feedLimit = $this->getConfig()->get( 'FeedLimit' );
  94. if ( $pager->getLimit() > $feedLimit ) {
  95. $pager->setLimit( $feedLimit );
  96. }
  97. $feedItems = [];
  98. if ( $pager->getNumRows() > 0 ) {
  99. $count = 0;
  100. $limit = $pager->getLimit();
  101. foreach ( $pager->mResult as $row ) {
  102. // ContribsPager selects one more row for navigation, skip that row
  103. if ( ++$count > $limit ) {
  104. break;
  105. }
  106. $item = $this->feedItem( $row );
  107. if ( $item !== null ) {
  108. $feedItems[] = $item;
  109. }
  110. }
  111. }
  112. ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
  113. }
  114. protected function feedItem( $row ) {
  115. // This hook is the api contributions equivalent to the
  116. // ContributionsLineEnding hook. Hook implementers may cancel
  117. // the hook to signal the user is not allowed to read this item.
  118. $feedItem = null;
  119. $hookResult = Hooks::run(
  120. 'ApiFeedContributions::feedItem',
  121. [ $row, $this->getContext(), &$feedItem ]
  122. );
  123. // Hook returned a valid feed item
  124. if ( $feedItem instanceof FeedItem ) {
  125. return $feedItem;
  126. // Hook was canceled and did not return a valid feed item
  127. } elseif ( !$hookResult ) {
  128. return null;
  129. }
  130. // Hook completed and did not return a valid feed item
  131. $title = Title::makeTitle( (int)$row->page_namespace, $row->page_title );
  132. $user = $this->getUser();
  133. if ( $title && $this->getPermissionManager()->userCan( 'read', $user, $title ) ) {
  134. $date = $row->rev_timestamp;
  135. $comments = $title->getTalkPage()->getFullURL();
  136. $revision = $this->revisionStore->newRevisionFromRow( $row );
  137. return new FeedItem(
  138. $title->getPrefixedText(),
  139. $this->feedItemDesc( $revision ),
  140. $title->getFullURL( [ 'diff' => $revision->getId() ] ),
  141. $date,
  142. $this->feedItemAuthor( $revision ),
  143. $comments
  144. );
  145. }
  146. return null;
  147. }
  148. /**
  149. * @since 1.32, takes a RevisionRecord instead of a Revision
  150. * @param RevisionRecord $revision
  151. * @return string
  152. */
  153. protected function feedItemAuthor( RevisionRecord $revision ) {
  154. $user = $revision->getUser();
  155. return $user ? $user->getName() : '';
  156. }
  157. /**
  158. * @since 1.32, takes a RevisionRecord instead of a Revision
  159. * @param RevisionRecord $revision
  160. * @return string
  161. */
  162. protected function feedItemDesc( RevisionRecord $revision ) {
  163. $msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
  164. try {
  165. $content = $revision->getContent( SlotRecord::MAIN );
  166. } catch ( RevisionAccessException $e ) {
  167. $content = null;
  168. }
  169. if ( $content instanceof TextContent ) {
  170. // only textual content has a "source view".
  171. $html = nl2br( htmlspecialchars( $content->getText() ) );
  172. } else {
  173. // XXX: we could get an HTML representation of the content via getParserOutput, but that may
  174. // contain JS magic and generally may not be suitable for inclusion in a feed.
  175. // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
  176. // Compare also FeedUtils::formatDiffRow.
  177. $html = '';
  178. }
  179. $comment = $revision->getComment();
  180. return '<p>' . htmlspecialchars( $this->feedItemAuthor( $revision ) ) . $msg .
  181. htmlspecialchars( FeedItem::stripComment( $comment ? $comment->text : '' ) ) .
  182. "</p>\n<hr />\n<div>" . $html . '</div>';
  183. }
  184. public function getAllowedParams() {
  185. $feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
  186. $ret = [
  187. 'feedformat' => [
  188. ApiBase::PARAM_DFLT => 'rss',
  189. ApiBase::PARAM_TYPE => $feedFormatNames
  190. ],
  191. 'user' => [
  192. ApiBase::PARAM_TYPE => 'user',
  193. ApiBase::PARAM_REQUIRED => true,
  194. ],
  195. 'namespace' => [
  196. ApiBase::PARAM_TYPE => 'namespace'
  197. ],
  198. 'year' => [
  199. ApiBase::PARAM_TYPE => 'integer'
  200. ],
  201. 'month' => [
  202. ApiBase::PARAM_TYPE => 'integer'
  203. ],
  204. 'tagfilter' => [
  205. ApiBase::PARAM_ISMULTI => true,
  206. ApiBase::PARAM_TYPE => array_values( ChangeTags::listDefinedTags() ),
  207. ApiBase::PARAM_DFLT => '',
  208. ],
  209. 'deletedonly' => false,
  210. 'toponly' => false,
  211. 'newonly' => false,
  212. 'hideminor' => false,
  213. 'showsizediff' => [
  214. ApiBase::PARAM_DFLT => false,
  215. ],
  216. ];
  217. if ( $this->getConfig()->get( 'MiserMode' ) ) {
  218. $ret['showsizediff'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
  219. }
  220. return $ret;
  221. }
  222. protected function getExamplesMessages() {
  223. return [
  224. 'action=feedcontributions&user=Example'
  225. => 'apihelp-feedcontributions-example-simple',
  226. ];
  227. }
  228. }