breaking change: Query watchlist shows flags only when explicitly requested with wlparam=flags, and rc_this_oldid (textid) is no longer accessible
query watchlist cleanup
bug in the integer parameter min/max validation
bug in feed formatting in error handling
some documentation
* list=allpages now returns a list instead of a map in json format
* Breaking Change: in json, revisions are now returned as a list, not as a map.
* Add: prop=info can show page is new flag, current page length, and visit counter.
+* Change: Query watchlist now shows flags only when explicitly requested with wlparam=flags
+ rc_this_oldid (textid) is no longer accessible from query watchlist
== Maintenance script changes since 1.10 ==
if ($checkMin || $checkMax) {
$min = $checkMin ? $paramSettings[self :: PARAM_MIN] : false;
$max = $checkMax ? $paramSettings[self :: PARAM_MAX] : false;
-
+
$values = is_array($value) ? $value : array($value);
foreach ($values as $v) {
- if ($checkMin && $v < $checkMin)
+ if ($checkMin && $v < $min)
$this->dieUsage("$paramName may not be less than $min (set to $v)", $paramName);
- if ($checkMax && $v > $checkMax)
+ if ($checkMax && $v > $max)
$this->dieUsage("$paramName may not be over $max (set to $v)", $paramName);
}
}
}
/**
+ * This action allows users to get their watchlist items in RSS/Atom formats.
+ * When executed, it performs a nested call to the API to get the needed data,
+ * and formats it in a proper format.
+ *
* @addtogroup API
*/
class ApiFeedWatchlist extends ApiBase {
parent :: __construct($main, $action);
}
+ /**
+ * This module uses a custom feed wrapper printer.
+ */
public function getCustomPrinter() {
return new ApiFormatFeedWrapper($this->getMain());
}
public function execute() {
- $params = $this->extractRequestParams();
-
- // limit to the number of hours going from now back
- $endTime = wfTimestamp(TS_MW, time() - intval($params['hours'] * 60 * 60));
-
- // Prepare nested request
- $fauxReq = new FauxRequest(array (
- 'action' => 'query',
- 'meta' => 'siteinfo',
- 'siprop' => 'general',
- 'list' => 'watchlist',
- 'wlprop' => 'user|comment|timestamp',
- 'wldir' => 'older', // reverse order - from newest to oldest
- 'wlend' => $endTime, // stop at this time
- 'wllimit' => 50
- ));
-
- // Execute
- $module = new ApiMain($fauxReq);
global $wgFeedClasses, $wgSitename, $wgContLanguageCode;
try {
+ $params = $this->extractRequestParams();
+
+ // limit to the number of hours going from now back
+ $endTime = wfTimestamp(TS_MW, time() - intval($params['hours'] * 60 * 60));
+
+ // Prepare nested request
+ $fauxReq = new FauxRequest(array (
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'siprop' => 'general',
+ 'list' => 'watchlist',
+ 'wlprop' => 'user|comment|timestamp',
+ 'wldir' => 'older', // reverse order - from newest to oldest
+ 'wlend' => $endTime, // stop at this time
+ 'wllimit' => 50
+ ));
+
+ // Execute
+ $module = new ApiMain($fauxReq);
$module->execute();
// Get data array
$feedTitle = $wgSitename . ' - Error - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
- $feed = new $wgFeedClasses[$params['feedformat']] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl);
+ $feedFormat = isset($params['feedformat']) ? $params['feedformat'] : 'rss';
+ $feed = new $wgFeedClasses[$feedFormat] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl);
if ($e instanceof UsageException) {
}
/**
+ * This is the abstract base class for API formatters.
+ *
* @addtogroup API
*/
abstract class ApiFormatBase extends ApiBase {
private $mIsHtml, $mFormat;
/**
- * Constructor
+ * Create a new instance of the formatter.
+ * If the format name ends with 'fm', wrap its output in the proper HTML.
*/
public function __construct($main, $format) {
parent :: __construct($main, $format);
*/
public abstract function getMimeType();
+ /**
+ * If formatter outputs data results as is, the results must first be sanitized.
+ * An XML formatter on the other hand uses special tags, such as "_element" for special handling,
+ * and thus needs to override this function to return true.
+ */
public function getNeedsRawData() {
return false;
}
}
}
+ /**
+ * The main format printing function. Call it to output the result string to the user.
+ * This function will automatically output HTML when format name ends in 'fm'.
+ */
public function printText($text) {
if ($this->getIsHtml())
echo $this->formatHTML($text);
}
/**
- * Call this method to initialize output data
+ * Call this method to initialize output data. See self::execute()
*/
public static function setResult($result, $feed, $feedItems) {
// Store output in the Result data.
return true;
}
+ /**
+ * This class expects the result data to be in a custom format set by self::setResult()
+ * $result['_feed'] - an instance of one of the $wgFeedClasses classes
+ * $result['_feeditems'] - an array of FeedItem instances
+ */
public function execute() {
$data = $this->getResultData();
if (isset ($data['_feed']) && isset ($data['_feeditems'])) {
$this->mSquidMaxage = 0;
}
- public function & getRequest() {
+ /**
+ * Return the request object that contains client's request
+ */
+ public function getRequest() {
return $this->mRequest;
}
+ /**
+ * Get the ApiResult object asscosiated with current request
+ */
public function getResult() {
return $this->mResult;
}
+ /**
+ * This method will simply cause an error if the write mode was disabled for this api.
+ */
public function requestWriteMode() {
if (!$this->mEnableWrite)
$this->dieUsage('Editing of this site is disabled. Make sure the $wgEnableWriteAPI=true; ' .
'statement is included in the site\'s LocalSettings.php file', 'readonly');
}
+ /**
+ * Set how long the response should be cached.
+ */
public function setCacheMaxAge($maxage) {
$this->mSquidMaxage = $maxage;
}
+ /**
+ * Create an instance of an output formatter by its name
+ */
public function createPrinterByName($format) {
return new $this->mFormats[$format] ($this, $format);
}
+ /**
+ * Execute api request. Any errors will be handled if the API was called by the remote client.
+ */
public function execute() {
$this->profileIn();
if ($this->mInternalMode)
$this->profileOut();
}
+ /**
+ * Execute an action, and in case of an error, erase whatever partial results
+ * have been accumulated, and replace it with an error message and a help screen.
+ */
protected function executeActionWithErrorHandling() {
// In case an error occurs during data output,
- // this clear the output buffer and print just the error information
+ // clear the output buffer and print just the error information
ob_start();
try {
header('Expires: ' . wfTimestamp(TS_RFC2822, $expires));
header('Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0');
- if($this->mPrinter->getIsHtml()) {
+ if($this->mPrinter->getIsHtml())
echo wfReportTime();
- }
ob_end_flush();
}
}
/**
- * Internal printer
+ * Print results using the current printer
*/
protected function printResult($isError) {
$printer = $this->mPrinter;
$printer->profileOut();
}
+ /**
+ * See ApiBase for description.
+ */
protected function getAllowedParams() {
return array (
'format' => array (
);
}
+ /**
+ * See ApiBase for description.
+ */
protected function getParamDescription() {
return array (
'format' => 'The format of the output',
);
}
+ /**
+ * See ApiBase for description.
+ */
protected function getDescription() {
return array (
'',
);
}
+ /**
+ * Returns an array of strings with credits for the API
+ */
protected function getCredits() {
return array(
- 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / FirstnameLastname@gmail.com',
- 'Please leave your comments and suggestions at http://meta.wikimedia.org/wiki/API'
+ 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / <FirstnameLastname>@gmail.com',
+ 'Please leave your comments and suggestions at http://www.mediawiki.org/wiki/API'
);
}
}
private $mIsBot = null;
+
+ /**
+ * Returns true if the currently logged in user is a bot, false otherwise
+ */
public function isBot() {
if (!isset ($this->mIsBot)) {
global $wgUser;
return $this->mShowVersions;
}
+ /**
+ * Returns the version information of this file, plus it includes
+ * the versions for all files that are not callable proper API modules
+ */
public function getVersion() {
$vers = array ();
$vers[] = __CLASS__ . ': $Id$';
/**
* This exception will be thrown when dieUsage is called to stop module execution.
+ * The exception handling code will print a help screen explaining how this API may be used.
+ *
* @addtogroup API
*/
class UsageException extends Exception {
$this->run($resultPageSet);
}
+ private $fld_patrol = false, $fld_flags = false, $fld_timestamp = false, $fld_user = false, $fld_comment = false;
+
private function run($resultPageSet = null) {
global $wgUser;
$allrev = $start = $end = $namespace = $dir = $limit = $prop = null;
extract($this->extractRequestParams());
- $patrol = $timestamp = $user = $comment = false;
if (!is_null($prop)) {
if (!is_null($resultPageSet))
$this->dieUsage('prop parameter may not be used in a generator', 'params');
- $user = (false !== array_search('user', $prop));
- $comment = (false !== array_search('comment', $prop));
- $timestamp = (false !== array_search('timestamp', $prop)); // TODO: $timestamp not currently being used.
- $patrol = (false !== array_search('patrol', $prop));
+ $prop = array_flip($prop);
- if ($patrol) {
+ $this->fld_flags = isset($prop['flags']);
+ $this->fld_user = isset($prop['user']);
+ $this->fld_comment = isset($prop['comment']);
+ $this->fld_timestamp = isset($prop['timestamp']);
+ $this->fld_patrol = isset($prop['patrol']);
+
+ if ($this->fld_patrol) {
global $wgUseRCPatrol, $wgUser;
if (!$wgUseRCPatrol || !$wgUser->isAllowed('patrol'))
$this->dieUsage('patrol property is not available', 'patrol');
if (is_null($resultPageSet)) {
$this->addFields(array (
'rc_cur_id',
- 'rc_this_oldid',
+ // 'rc_this_oldid', // Should this field be exposed?
'rc_namespace',
'rc_title',
- 'rc_new',
- 'rc_minor',
'rc_timestamp'
));
- $this->addFieldsIf('rc_user', $user);
- $this->addFieldsIf('rc_user_text', $user);
- $this->addFieldsIf('rc_comment', $comment);
- $this->addFieldsIf('rc_patrolled', $patrol);
+ $this->addFieldsIf('rc_new', $this->fld_flags);
+ $this->addFieldsIf('rc_minor', $this->fld_flags);
+ $this->addFieldsIf('rc_user', $this->fld_user);
+ $this->addFieldsIf('rc_user_text', $this->fld_user);
+ $this->addFieldsIf('rc_comment', $this->fld_comment);
+ $this->addFieldsIf('rc_patrolled', $this->fld_patrol);
}
elseif ($allrev) {
$this->addFields(array (
}
if (is_null($resultPageSet)) {
- $vals = $this->addRowInfo('rc', $row);
- if($vals)
+ $vals = $this->extractRowInfo($row);
+ if ($vals)
$data[] = $vals;
} else {
$title = Title :: makeTitle($row->rc_namespace, $row->rc_title);
}
}
+ private function extractRowInfo($row) {
+
+ $title = Title :: makeTitle($row->rc_namespace, $row->rc_title);
+ if (!$title->userCanRead())
+ return false;
+
+ $vals = array ();
+
+ $vals['pageid'] = intval($row->rc_cur_id);
+ // $vals['textid'] = intval($row->rc_this_oldid); // Should this field be exposed?
+
+ ApiQueryBase :: addTitleInfo($vals, $title);
+
+ if ($this->fld_user) {
+ $vals['user'] = $row->rc_user_text;
+ if (!$row->rc_user)
+ $vals['anon'] = '';
+ }
+
+ if ($this->fld_flags) {
+ if ($row->rc_new)
+ $vals['new'] = '';
+ if ($row->rc_minor)
+ $vals['minor'] = '';
+ }
+
+ if ($this->fld_patrol && isset($row->rc_patrolled))
+ $vals['patrolled'] = '';
+
+ if ($this->fld_timestamp)
+ $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp);
+
+ if ($this->fld_comment && !empty ($row->rc_comment))
+ $vals['comment'] = $row->rc_comment;
+
+ return $vals;
+ }
+
protected function getAllowedParams() {
return array (
'allrev' => false,
'prop' => array (
APIBase :: PARAM_ISMULTI => true,
APIBase :: PARAM_TYPE => array (
+ 'flags',
'user',
'comment',
'timestamp',
protected function getExamples() {
return array (
- 'api.php?action=query&list=watchlist',
- 'api.php?action=query&list=watchlist&wlallrev',
+ 'api.php?action=query&list=watchlist&wlprop=timestamp|user|comment',
+ 'api.php?action=query&list=watchlist&wlallrev&wlprop=timestamp|user|comment',
'api.php?action=query&generator=watchlist&prop=info',
'api.php?action=query&generator=watchlist&gwlallrev&prop=revisions&rvprop=timestamp|user'
);
}
/**
+ * This class represents the result of the API operations.
+ * It simply wraps a nested array() structure, adding some functions to simplify array's modifications.
+ * As various modules execute, they add different pieces of information to this result,
+ * structuring it as it will be given to the client.
+ *
+ * Each subarray may either be a dictionary - key-value pairs with unique keys,
+ * or lists, where the items are added using $data[] = $value notation.
+ *
+ * There are two special key values that change how XML output is generated:
+ * '_element' This key sets the tag name for the rest of the elements in the current array.
+ * It is only inserted if the formatter returned true for getNeedsRawData()
+ * '*' This key has special meaning only to the XML formatter, and is outputed as is
+ * for all others. In XML it becomes the content of the current element.
+ *
* @addtogroup API
*/
class ApiResult extends ApiBase {
$this->reset();
}
+ /**
+ * Clear the current result data.
+ */
public function reset() {
$this->mData = array ();
}
$this->mIsRawMode = true;
}
+ /**
+ * Returns true if the result is being created for the formatter that requested raw data.
+ */
public function getIsRawMode() {
return $this->mIsRawMode;
}
+ /**
+ * Get result's internal data array
+ */
public function & getData() {
return $this->mData;
}
}
}
- // public static function makeContentElement($tag, $value) {
- // $result = array();
- // ApiResult::setContent($result, )
- // }
- //
/**
* In case the array contains indexed values (in addition to named),
* all indexed values will have the given tag name.