ApiQueryRecentChanges.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. /*
  3. * Created on Oct 19, 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. * A query action to enumerate the recent changes that were done to the wiki.
  30. * Various filters are supported.
  31. *
  32. * @ingroup API
  33. */
  34. class ApiQueryRecentChanges extends ApiQueryBase {
  35. public function __construct($query, $moduleName) {
  36. parent :: __construct($query, $moduleName, 'rc');
  37. }
  38. private $fld_comment = false, $fld_user = false, $fld_flags = false,
  39. $fld_timestamp = false, $fld_title = false, $fld_ids = false,
  40. $fld_sizes = false;
  41. protected function getTokenFunctions() {
  42. // tokenname => function
  43. // function prototype is func($pageid, $title, $rev)
  44. // should return token or false
  45. // Don't call the hooks twice
  46. if(isset($this->tokenFunctions))
  47. return $this->tokenFunctions;
  48. // If we're in JSON callback mode, no tokens can be obtained
  49. if(!is_null($this->getMain()->getRequest()->getVal('callback')))
  50. return array();
  51. $this->tokenFunctions = array(
  52. 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
  53. );
  54. wfRunHooks('APIQueryRecentChangesTokens', array(&$this->tokenFunctions));
  55. return $this->tokenFunctions;
  56. }
  57. public static function getPatrolToken($pageid, $title, $rc)
  58. {
  59. global $wgUser;
  60. if(!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
  61. return false;
  62. // The patrol token is always the same, let's exploit that
  63. static $cachedPatrolToken = null;
  64. if(!is_null($cachedPatrolToken))
  65. return $cachedPatrolToken;
  66. $cachedPatrolToken = $wgUser->editToken();
  67. return $cachedPatrolToken;
  68. }
  69. /**
  70. * Generates and outputs the result of this query based upon the provided parameters.
  71. */
  72. public function execute() {
  73. /* Get the parameters of the request. */
  74. $params = $this->extractRequestParams();
  75. /* Build our basic query. Namely, something along the lines of:
  76. * SELECT * FROM recentchanges WHERE rc_timestamp > $start
  77. * AND rc_timestamp < $end AND rc_namespace = $namespace
  78. * AND rc_deleted = '0'
  79. */
  80. $db = $this->getDB();
  81. $this->addTables('recentchanges');
  82. $this->addOption('USE INDEX', array('recentchanges' => 'rc_timestamp'));
  83. $this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']);
  84. $this->addWhereFld('rc_namespace', $params['namespace']);
  85. $this->addWhereFld('rc_deleted', 0);
  86. if(!is_null($params['type']))
  87. $this->addWhereFld('rc_type', $this->parseRCType($params['type']));
  88. if (!is_null($params['show'])) {
  89. $show = array_flip($params['show']);
  90. /* Check for conflicting parameters. */
  91. if ((isset ($show['minor']) && isset ($show['!minor']))
  92. || (isset ($show['bot']) && isset ($show['!bot']))
  93. || (isset ($show['anon']) && isset ($show['!anon']))
  94. || (isset ($show['redirect']) && isset ($show['!redirect']))
  95. || (isset ($show['patrolled']) && isset ($show['!patrolled']))) {
  96. $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
  97. }
  98. // Check permissions
  99. global $wgUser;
  100. if((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
  101. $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
  102. /* Add additional conditions to query depending upon parameters. */
  103. $this->addWhereIf('rc_minor = 0', isset ($show['!minor']));
  104. $this->addWhereIf('rc_minor != 0', isset ($show['minor']));
  105. $this->addWhereIf('rc_bot = 0', isset ($show['!bot']));
  106. $this->addWhereIf('rc_bot != 0', isset ($show['bot']));
  107. $this->addWhereIf('rc_user = 0', isset ($show['anon']));
  108. $this->addWhereIf('rc_user != 0', isset ($show['!anon']));
  109. $this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled']));
  110. $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled']));
  111. $this->addWhereIf('page_is_redirect = 1', isset ($show['redirect']));
  112. // Don't throw log entries out the window here
  113. $this->addWhereIf('page_is_redirect = 0 OR page_is_redirect IS NULL', isset ($show['!redirect']));
  114. }
  115. /* Add the fields we're concerned with to out query. */
  116. $this->addFields(array (
  117. 'rc_timestamp',
  118. 'rc_namespace',
  119. 'rc_title',
  120. 'rc_cur_id',
  121. 'rc_type',
  122. 'rc_moved_to_ns',
  123. 'rc_moved_to_title'
  124. ));
  125. /* Determine what properties we need to display. */
  126. if (!is_null($params['prop'])) {
  127. $prop = array_flip($params['prop']);
  128. /* Set up internal members based upon params. */
  129. $this->fld_comment = isset ($prop['comment']);
  130. $this->fld_user = isset ($prop['user']);
  131. $this->fld_flags = isset ($prop['flags']);
  132. $this->fld_timestamp = isset ($prop['timestamp']);
  133. $this->fld_title = isset ($prop['title']);
  134. $this->fld_ids = isset ($prop['ids']);
  135. $this->fld_sizes = isset ($prop['sizes']);
  136. $this->fld_redirect = isset($prop['redirect']);
  137. $this->fld_patrolled = isset($prop['patrolled']);
  138. $this->fld_loginfo = isset($prop['loginfo']);
  139. global $wgUser;
  140. if($this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
  141. $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
  142. /* Add fields to our query if they are specified as a needed parameter. */
  143. $this->addFieldsIf('rc_id', $this->fld_ids);
  144. $this->addFieldsIf('rc_this_oldid', $this->fld_ids);
  145. $this->addFieldsIf('rc_last_oldid', $this->fld_ids);
  146. $this->addFieldsIf('rc_comment', $this->fld_comment);
  147. $this->addFieldsIf('rc_user', $this->fld_user);
  148. $this->addFieldsIf('rc_user_text', $this->fld_user);
  149. $this->addFieldsIf('rc_minor', $this->fld_flags);
  150. $this->addFieldsIf('rc_bot', $this->fld_flags);
  151. $this->addFieldsIf('rc_new', $this->fld_flags);
  152. $this->addFieldsIf('rc_old_len', $this->fld_sizes);
  153. $this->addFieldsIf('rc_new_len', $this->fld_sizes);
  154. $this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
  155. $this->addFieldsIf('rc_logid', $this->fld_loginfo);
  156. $this->addFieldsIf('rc_log_type', $this->fld_loginfo);
  157. $this->addFieldsIf('rc_log_action', $this->fld_loginfo);
  158. $this->addFieldsIf('rc_params', $this->fld_loginfo);
  159. if($this->fld_redirect || isset($show['redirect']) || isset($show['!redirect']))
  160. {
  161. $this->addTables('page');
  162. $this->addJoinConds(array('page' => array('LEFT JOIN', array('rc_namespace=page_namespace', 'rc_title=page_title'))));
  163. $this->addFields('page_is_redirect');
  164. }
  165. }
  166. $this->token = $params['token'];
  167. $this->addOption('LIMIT', $params['limit'] +1);
  168. $count = 0;
  169. /* Perform the actual query. */
  170. $db = $this->getDB();
  171. $res = $this->select(__METHOD__);
  172. /* Iterate through the rows, adding data extracted from them to our query result. */
  173. while ($row = $db->fetchObject($res)) {
  174. if (++ $count > $params['limit']) {
  175. // We've reached the one extra which shows that there are additional pages to be had. Stop here...
  176. $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp));
  177. break;
  178. }
  179. /* Extract the data from a single row. */
  180. $vals = $this->extractRowInfo($row);
  181. /* Add that row's data to our final output. */
  182. if(!$vals)
  183. continue;
  184. $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
  185. if(!$fit)
  186. {
  187. $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp));
  188. break;
  189. }
  190. }
  191. $db->freeResult($res);
  192. /* Format the result */
  193. $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'rc');
  194. }
  195. /**
  196. * Extracts from a single sql row the data needed to describe one recent change.
  197. *
  198. * @param $row The row from which to extract the data.
  199. * @return An array mapping strings (descriptors) to their respective string values.
  200. * @access private
  201. */
  202. private function extractRowInfo($row) {
  203. /* If page was moved somewhere, get the title of the move target. */
  204. $movedToTitle = false;
  205. if (isset($row->rc_moved_to_title) && $row->rc_moved_to_title !== '')
  206. $movedToTitle = Title :: makeTitle($row->rc_moved_to_ns, $row->rc_moved_to_title);
  207. /* Determine the title of the page that has been changed. */
  208. $title = Title :: makeTitle($row->rc_namespace, $row->rc_title);
  209. /* Our output data. */
  210. $vals = array ();
  211. $type = intval ( $row->rc_type );
  212. /* Determine what kind of change this was. */
  213. switch ( $type ) {
  214. case RC_EDIT: $vals['type'] = 'edit'; break;
  215. case RC_NEW: $vals['type'] = 'new'; break;
  216. case RC_MOVE: $vals['type'] = 'move'; break;
  217. case RC_LOG: $vals['type'] = 'log'; break;
  218. case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break;
  219. default: $vals['type'] = $type;
  220. }
  221. /* Create a new entry in the result for the title. */
  222. if ($this->fld_title) {
  223. ApiQueryBase :: addTitleInfo($vals, $title);
  224. if ($movedToTitle)
  225. ApiQueryBase :: addTitleInfo($vals, $movedToTitle, "new_");
  226. }
  227. /* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
  228. if ($this->fld_ids) {
  229. $vals['rcid'] = intval($row->rc_id);
  230. $vals['pageid'] = intval($row->rc_cur_id);
  231. $vals['revid'] = intval($row->rc_this_oldid);
  232. $vals['old_revid'] = intval( $row->rc_last_oldid );
  233. }
  234. /* Add user data and 'anon' flag, if use is anonymous. */
  235. if ($this->fld_user) {
  236. $vals['user'] = $row->rc_user_text;
  237. if(!$row->rc_user)
  238. $vals['anon'] = '';
  239. }
  240. /* Add flags, such as new, minor, bot. */
  241. if ($this->fld_flags) {
  242. if ($row->rc_bot)
  243. $vals['bot'] = '';
  244. if ($row->rc_new)
  245. $vals['new'] = '';
  246. if ($row->rc_minor)
  247. $vals['minor'] = '';
  248. }
  249. /* Add sizes of each revision. (Only available on 1.10+) */
  250. if ($this->fld_sizes) {
  251. $vals['oldlen'] = intval($row->rc_old_len);
  252. $vals['newlen'] = intval($row->rc_new_len);
  253. }
  254. /* Add the timestamp. */
  255. if ($this->fld_timestamp)
  256. $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp);
  257. /* Add edit summary / log summary. */
  258. if ($this->fld_comment && isset($row->rc_comment)) {
  259. $vals['comment'] = $row->rc_comment;
  260. }
  261. if ($this->fld_redirect)
  262. if($row->page_is_redirect)
  263. $vals['redirect'] = '';
  264. /* Add the patrolled flag */
  265. if ($this->fld_patrolled && $row->rc_patrolled == 1)
  266. $vals['patrolled'] = '';
  267. if ($this->fld_loginfo && $row->rc_type == RC_LOG) {
  268. $vals['logid'] = intval($row->rc_logid);
  269. $vals['logtype'] = $row->rc_log_type;
  270. $vals['logaction'] = $row->rc_log_action;
  271. ApiQueryLogEvents::addLogParams($this->getResult(),
  272. $vals, $row->rc_params,
  273. $row->rc_log_type, $row->rc_timestamp);
  274. }
  275. if(!is_null($this->token))
  276. {
  277. $tokenFunctions = $this->getTokenFunctions();
  278. foreach($this->token as $t)
  279. {
  280. $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id,
  281. $title, RecentChange::newFromRow($row));
  282. if($val === false)
  283. $this->setWarning("Action '$t' is not allowed for the current user");
  284. else
  285. $vals[$t . 'token'] = $val;
  286. }
  287. }
  288. return $vals;
  289. }
  290. private function parseRCType($type)
  291. {
  292. if(is_array($type))
  293. {
  294. $retval = array();
  295. foreach($type as $t)
  296. $retval[] = $this->parseRCType($t);
  297. return $retval;
  298. }
  299. switch($type)
  300. {
  301. case 'edit': return RC_EDIT;
  302. case 'new': return RC_NEW;
  303. case 'log': return RC_LOG;
  304. }
  305. }
  306. public function getAllowedParams() {
  307. return array (
  308. 'start' => array (
  309. ApiBase :: PARAM_TYPE => 'timestamp'
  310. ),
  311. 'end' => array (
  312. ApiBase :: PARAM_TYPE => 'timestamp'
  313. ),
  314. 'dir' => array (
  315. ApiBase :: PARAM_DFLT => 'older',
  316. ApiBase :: PARAM_TYPE => array (
  317. 'newer',
  318. 'older'
  319. )
  320. ),
  321. 'namespace' => array (
  322. ApiBase :: PARAM_ISMULTI => true,
  323. ApiBase :: PARAM_TYPE => 'namespace'
  324. ),
  325. 'prop' => array (
  326. ApiBase :: PARAM_ISMULTI => true,
  327. ApiBase :: PARAM_DFLT => 'title|timestamp|ids',
  328. ApiBase :: PARAM_TYPE => array (
  329. 'user',
  330. 'comment',
  331. 'flags',
  332. 'timestamp',
  333. 'title',
  334. 'ids',
  335. 'sizes',
  336. 'redirect',
  337. 'patrolled',
  338. 'loginfo',
  339. )
  340. ),
  341. 'token' => array(
  342. ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
  343. ApiBase :: PARAM_ISMULTI => true
  344. ),
  345. 'show' => array (
  346. ApiBase :: PARAM_ISMULTI => true,
  347. ApiBase :: PARAM_TYPE => array (
  348. 'minor',
  349. '!minor',
  350. 'bot',
  351. '!bot',
  352. 'anon',
  353. '!anon',
  354. 'redirect',
  355. '!redirect',
  356. 'patrolled',
  357. '!patrolled'
  358. )
  359. ),
  360. 'limit' => array (
  361. ApiBase :: PARAM_DFLT => 10,
  362. ApiBase :: PARAM_TYPE => 'limit',
  363. ApiBase :: PARAM_MIN => 1,
  364. ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
  365. ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
  366. ),
  367. 'type' => array (
  368. ApiBase :: PARAM_ISMULTI => true,
  369. ApiBase :: PARAM_TYPE => array (
  370. 'edit',
  371. 'new',
  372. 'log'
  373. )
  374. )
  375. );
  376. }
  377. public function getParamDescription() {
  378. return array (
  379. 'start' => 'The timestamp to start enumerating from.',
  380. 'end' => 'The timestamp to end enumerating.',
  381. 'dir' => 'In which direction to enumerate.',
  382. 'namespace' => 'Filter log entries to only this namespace(s)',
  383. 'prop' => 'Include additional pieces of information',
  384. 'token' => 'Which tokens to obtain for each change',
  385. 'show' => array (
  386. 'Show only items that meet this criteria.',
  387. 'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
  388. ),
  389. 'type' => 'Which types of changes to show.',
  390. 'limit' => 'How many total changes to return.'
  391. );
  392. }
  393. public function getDescription() {
  394. return 'Enumerate recent changes';
  395. }
  396. protected function getExamples() {
  397. return array (
  398. 'api.php?action=query&list=recentchanges'
  399. );
  400. }
  401. public function getVersion() {
  402. return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 50094 2009-05-01 06:24:09Z tstarling $';
  403. }
  404. }