ApiQueryUserContributions.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /*
  3. * Created on Oct 16, 2006
  4. *
  5. * API for MediaWiki 1.8+
  6. *
  7. * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
  8. *
  9. * This program is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License along
  20. * with this program; if not, write to the Free Software Foundation, Inc.,
  21. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. * http://www.gnu.org/copyleft/gpl.html
  23. */
  24. if (!defined('MEDIAWIKI')) {
  25. // Eclipse helper - will be ignored in production
  26. require_once ('ApiQueryBase.php');
  27. }
  28. /**
  29. * This query action adds a list of a specified user's contributions to the output.
  30. *
  31. * @ingroup API
  32. */
  33. class ApiQueryContributions extends ApiQueryBase {
  34. public function __construct($query, $moduleName) {
  35. parent :: __construct($query, $moduleName, 'uc');
  36. }
  37. private $params, $username;
  38. private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
  39. $fld_comment = false, $fld_flags = false,
  40. $fld_patrolled = false;
  41. public function execute() {
  42. // Parse some parameters
  43. $this->params = $this->extractRequestParams();
  44. $prop = array_flip($this->params['prop']);
  45. $this->fld_ids = isset($prop['ids']);
  46. $this->fld_title = isset($prop['title']);
  47. $this->fld_comment = isset($prop['comment']);
  48. $this->fld_flags = isset($prop['flags']);
  49. $this->fld_timestamp = isset($prop['timestamp']);
  50. $this->fld_patrolled = isset($prop['patrolled']);
  51. // TODO: if the query is going only against the revision table, should this be done?
  52. $this->selectNamedDB('contributions', DB_SLAVE, 'contributions');
  53. $db = $this->getDB();
  54. if(isset($this->params['userprefix']))
  55. {
  56. $this->prefixMode = true;
  57. $this->multiUserMode = true;
  58. $this->userprefix = $this->params['userprefix'];
  59. }
  60. else
  61. {
  62. $this->usernames = array();
  63. if(!is_array($this->params['user']))
  64. $this->params['user'] = array($this->params['user']);
  65. foreach($this->params['user'] as $u)
  66. $this->prepareUsername($u);
  67. $this->prefixMode = false;
  68. $this->multiUserMode = (count($this->params['user']) > 1);
  69. }
  70. $this->prepareQuery();
  71. //Do the actual query.
  72. $res = $this->select( __METHOD__ );
  73. //Initialise some variables
  74. $count = 0;
  75. $limit = $this->params['limit'];
  76. //Fetch each row
  77. while ( $row = $db->fetchObject( $res ) ) {
  78. if (++ $count > $limit) {
  79. // We've reached the one extra which shows that there are additional pages to be had. Stop here...
  80. if($this->multiUserMode)
  81. $this->setContinueEnumParameter('continue', $this->continueStr($row));
  82. else
  83. $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp));
  84. break;
  85. }
  86. $vals = $this->extractRowInfo($row);
  87. $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
  88. if(!$fit)
  89. {
  90. if($this->multiUserMode)
  91. $this->setContinueEnumParameter('continue', $this->continueStr($row));
  92. else
  93. $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp));
  94. break;
  95. }
  96. }
  97. //Free the database record so the connection can get on with other stuff
  98. $db->freeResult($res);
  99. $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item');
  100. }
  101. /**
  102. * Validate the 'user' parameter and set the value to compare
  103. * against `revision`.`rev_user_text`
  104. */
  105. private function prepareUsername($user) {
  106. if( $user ) {
  107. $name = User::isIP( $user )
  108. ? $user
  109. : User::getCanonicalName( $user, 'valid' );
  110. if( $name === false ) {
  111. $this->dieUsage( "User name {$user} is not valid", 'param_user' );
  112. } else {
  113. $this->usernames[] = $name;
  114. }
  115. } else {
  116. $this->dieUsage( 'User parameter may not be empty', 'param_user' );
  117. }
  118. }
  119. /**
  120. * Prepares the query and returns the limit of rows requested
  121. */
  122. private function prepareQuery() {
  123. // We're after the revision table, and the corresponding page
  124. // row for anything we retrieve. We may also need the
  125. // recentchanges row.
  126. $tables = array('page', 'revision'); // Order may change
  127. $this->addWhere('page_id=rev_page');
  128. // Handle continue parameter
  129. if($this->multiUserMode && !is_null($this->params['continue']))
  130. {
  131. $continue = explode('|', $this->params['continue']);
  132. if(count($continue) != 2)
  133. $this->dieUsage("Invalid continue param. You should pass the original " .
  134. "value returned by the previous query", "_badcontinue");
  135. $encUser = $this->getDB()->strencode($continue[0]);
  136. $encTS = wfTimestamp(TS_MW, $continue[1]);
  137. $op = ($this->params['dir'] == 'older' ? '<' : '>');
  138. $this->addWhere("rev_user_text $op '$encUser' OR " .
  139. "(rev_user_text = '$encUser' AND " .
  140. "rev_timestamp $op= '$encTS')");
  141. }
  142. $this->addWhereFld('rev_deleted', 0);
  143. // We only want pages by the specified users.
  144. if($this->prefixMode)
  145. $this->addWhere("rev_user_text LIKE '" . $this->getDB()->escapeLike($this->userprefix) . "%'");
  146. else
  147. $this->addWhereFld('rev_user_text', $this->usernames);
  148. // ... and in the specified timeframe.
  149. // Ensure the same sort order for rev_user_text and rev_timestamp
  150. // so our query is indexed
  151. if($this->multiUserMode)
  152. $this->addWhereRange('rev_user_text', $this->params['dir'], null, null);
  153. $this->addWhereRange('rev_timestamp',
  154. $this->params['dir'], $this->params['start'], $this->params['end'] );
  155. $this->addWhereFld('page_namespace', $this->params['namespace']);
  156. $show = $this->params['show'];
  157. if (!is_null($show)) {
  158. $show = array_flip($show);
  159. if ((isset($show['minor']) && isset($show['!minor']))
  160. || (isset($show['patrolled']) && isset($show['!patrolled'])))
  161. $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
  162. $this->addWhereIf('rev_minor_edit = 0', isset($show['!minor']));
  163. $this->addWhereIf('rev_minor_edit != 0', isset($show['minor']));
  164. $this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled']));
  165. $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled']));
  166. }
  167. $this->addOption('LIMIT', $this->params['limit'] + 1);
  168. $index['revision'] = 'usertext_timestamp';
  169. // Mandatory fields: timestamp allows request continuation
  170. // ns+title checks if the user has access rights for this page
  171. // user_text is necessary if multiple users were specified
  172. $this->addFields(array(
  173. 'rev_timestamp',
  174. 'page_namespace',
  175. 'page_title',
  176. 'rev_user_text',
  177. ));
  178. if(isset($show['patrolled']) || isset($show['!patrolled']) ||
  179. $this->fld_patrolled)
  180. {
  181. global $wgUser;
  182. if(!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
  183. $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
  184. // Use a redundant join condition on both
  185. // timestamp and ID so we can use the timestamp
  186. // index
  187. $index['recentchanges'] = 'rc_user_text';
  188. if(isset($show['patrolled']) || isset($show['!patrolled']))
  189. {
  190. // Put the tables in the right order for
  191. // STRAIGHT_JOIN
  192. $tables = array('revision', 'recentchanges', 'page');
  193. $this->addOption('STRAIGHT_JOIN');
  194. $this->addWhere('rc_user_text=rev_user_text');
  195. $this->addWhere('rc_timestamp=rev_timestamp');
  196. $this->addWhere('rc_this_oldid=rev_id');
  197. }
  198. else
  199. {
  200. $tables[] = 'recentchanges';
  201. $this->addJoinConds(array('recentchanges' => array(
  202. 'LEFT JOIN', array(
  203. 'rc_user_text=rev_user_text',
  204. 'rc_timestamp=rev_timestamp',
  205. 'rc_this_oldid=rev_id'))));
  206. }
  207. }
  208. $this->addTables($tables);
  209. $this->addOption('USE INDEX', $index);
  210. $this->addFieldsIf('rev_page', $this->fld_ids);
  211. $this->addFieldsIf('rev_id', $this->fld_ids || $this->fld_flags);
  212. $this->addFieldsIf('page_latest', $this->fld_flags);
  213. // $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed?
  214. $this->addFieldsIf('rev_comment', $this->fld_comment);
  215. $this->addFieldsIf('rev_minor_edit', $this->fld_flags);
  216. $this->addFieldsIf('rev_parent_id', $this->fld_flags);
  217. $this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
  218. }
  219. /**
  220. * Extract fields from the database row and append them to a result array
  221. */
  222. private function extractRowInfo($row) {
  223. $vals = array();
  224. $vals['user'] = $row->rev_user_text;
  225. if ($this->fld_ids) {
  226. $vals['pageid'] = intval($row->rev_page);
  227. $vals['revid'] = intval($row->rev_id);
  228. // $vals['textid'] = intval($row->rev_text_id); // todo: Should this field be exposed?
  229. }
  230. if ($this->fld_title)
  231. ApiQueryBase :: addTitleInfo($vals,
  232. Title :: makeTitle($row->page_namespace, $row->page_title));
  233. if ($this->fld_timestamp)
  234. $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp);
  235. if ($this->fld_flags) {
  236. if ($row->rev_parent_id == 0)
  237. $vals['new'] = '';
  238. if ($row->rev_minor_edit)
  239. $vals['minor'] = '';
  240. if ($row->page_latest == $row->rev_id)
  241. $vals['top'] = '';
  242. }
  243. if ($this->fld_comment && isset($row->rev_comment))
  244. $vals['comment'] = $row->rev_comment;
  245. if ($this->fld_patrolled && $row->rc_patrolled)
  246. $vals['patrolled'] = '';
  247. return $vals;
  248. }
  249. private function continueStr($row)
  250. {
  251. return $row->rev_user_text . '|' .
  252. wfTimestamp(TS_ISO_8601, $row->rev_timestamp);
  253. }
  254. public function getAllowedParams() {
  255. return array (
  256. 'limit' => array (
  257. ApiBase :: PARAM_DFLT => 10,
  258. ApiBase :: PARAM_TYPE => 'limit',
  259. ApiBase :: PARAM_MIN => 1,
  260. ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
  261. ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
  262. ),
  263. 'start' => array (
  264. ApiBase :: PARAM_TYPE => 'timestamp'
  265. ),
  266. 'end' => array (
  267. ApiBase :: PARAM_TYPE => 'timestamp'
  268. ),
  269. 'continue' => null,
  270. 'user' => array (
  271. ApiBase :: PARAM_ISMULTI => true
  272. ),
  273. 'userprefix' => null,
  274. 'dir' => array (
  275. ApiBase :: PARAM_DFLT => 'older',
  276. ApiBase :: PARAM_TYPE => array (
  277. 'newer',
  278. 'older'
  279. )
  280. ),
  281. 'namespace' => array (
  282. ApiBase :: PARAM_ISMULTI => true,
  283. ApiBase :: PARAM_TYPE => 'namespace'
  284. ),
  285. 'prop' => array (
  286. ApiBase :: PARAM_ISMULTI => true,
  287. ApiBase :: PARAM_DFLT => 'ids|title|timestamp|flags|comment',
  288. ApiBase :: PARAM_TYPE => array (
  289. 'ids',
  290. 'title',
  291. 'timestamp',
  292. 'comment',
  293. 'flags',
  294. 'patrolled',
  295. )
  296. ),
  297. 'show' => array (
  298. ApiBase :: PARAM_ISMULTI => true,
  299. ApiBase :: PARAM_TYPE => array (
  300. 'minor',
  301. '!minor',
  302. 'patrolled',
  303. '!patrolled',
  304. )
  305. ),
  306. );
  307. }
  308. public function getParamDescription() {
  309. return array (
  310. 'limit' => 'The maximum number of contributions to return.',
  311. 'start' => 'The start timestamp to return from.',
  312. 'end' => 'The end timestamp to return to.',
  313. 'continue' => 'When more results are available, use this to continue.',
  314. 'user' => 'The user to retrieve contributions for.',
  315. 'userprefix' => 'Retrieve contibutions for all users whose names begin with this value. Overrides ucuser.',
  316. 'dir' => 'The direction to search (older or newer).',
  317. 'namespace' => 'Only list contributions in these namespaces',
  318. 'prop' => 'Include additional pieces of information',
  319. 'show' => array('Show only items that meet this criteria, e.g. non minor edits only: show=!minor',
  320. 'NOTE: if show=patrolled or show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown',),
  321. );
  322. }
  323. public function getDescription() {
  324. return 'Get all edits by a user';
  325. }
  326. protected function getExamples() {
  327. return array (
  328. 'api.php?action=query&list=usercontribs&ucuser=YurikBot',
  329. 'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.',
  330. );
  331. }
  332. public function getVersion() {
  333. return __CLASS__ . ': $Id: ApiQueryUserContributions.php 47037 2009-02-09 14:07:18Z catrope $';
  334. }
  335. }