From 8deda5c25ad978604789d5f2b43f797ab4bfac63 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Sat, 30 Sep 2006 08:06:27 +0000 Subject: [PATCH] * API: revisions & pageset cleanup --- includes/api/ApiBase.php | 173 +++++++++++++++-------------- includes/api/ApiPageSet.php | 96 +++++++++++++--- includes/api/ApiQuery.php | 58 ++++------ includes/api/ApiQueryRevisions.php | 121 +++++++++++--------- 4 files changed, 268 insertions(+), 180 deletions(-) diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 1f87592ce9..c3ce8e511f 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -92,16 +92,9 @@ abstract class ApiBase { $msg = $lnPrfx . implode($lnPrfx, $msg) . "\n"; // Parameters - $params = $this->getAllowedParams(); - if ($params !== false) { - $paramsDescription = $this->getParamDescription(); - $msg .= "Parameters:\n"; - foreach (array_keys($params) as $paramName) { - $desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : ''; - if (is_array($desc)) - $desc = implode("\n" . str_repeat(' ', 19), $desc); - $msg .= sprintf(" %-14s - %s\n", $paramName, $desc); - } + $paramsMsg = $this->makeHelpMsgParameters(); + if ($paramsMsg !== false) { + $msg .= "Parameters:\n$paramsMsg"; } // Examples @@ -119,6 +112,25 @@ abstract class ApiBase { return $msg; } + public function makeHelpMsgParameters() { + $params = $this->getAllowedParams(); + if ($params !== false) { + + $paramsDescription = $this->getParamDescription(); + $msg = ''; + foreach (array_keys($params) as $paramName) { + $desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : ''; + if (is_array($desc)) + $desc = implode("\n" . str_repeat(' ', 19), $desc); + $msg .= sprintf(" %-14s - %s\n", $paramName, $desc); + } + return $msg; + + } + else + return false; + } + /** * Returns the description string for this module */ @@ -153,89 +165,88 @@ abstract class ApiBase { * This method can be used to generate local variables using extract(). */ public function extractRequestParams() { - global $wgRequest; - $params = $this->getAllowedParams(); $results = array (); - foreach ($params as $param => $enumParams) { - - if (!is_array($enumParams)) { - $default = $enumParams; - $multi = false; - $type = gettype($enumParams); - } else { - $default = isset ($enumParams[GN_ENUM_DFLT]) ? $enumParams[GN_ENUM_DFLT] : null; - $multi = isset ($enumParams[GN_ENUM_ISMULTI]) ? $enumParams[GN_ENUM_ISMULTI] : false; - $type = isset ($enumParams[GN_ENUM_TYPE]) ? $enumParams[GN_ENUM_TYPE] : null; - - // When type is not given, and no choices, the type is the same as $default - if (!isset ($type)) { - if (isset ($default)) - $type = gettype($default); - else - $type = 'NULL'; // allow everything - } - } + foreach ($params as $paramName => $paramSettings) + $results[$paramName] = $this->getParameter($paramName, $paramSettings); - if ($type == 'boolean') { - if (!isset ($default)) - $default = false; + return $results; + } - if ($default !== false) { - // Having a default value of anything other than 'false' is pointless - $this->dieDebug("Boolean param $param's default is set to '$default'"); - } + public function getParameter($paramName, $paramSettings){ + global $wgRequest; + + if (!is_array($paramSettings)) { + $default = $paramSettings; + $multi = false; + $type = gettype($paramSettings); + } else { + $default = isset ($paramSettings[GN_ENUM_DFLT]) ? $paramSettings[GN_ENUM_DFLT] : null; + $multi = isset ($paramSettings[GN_ENUM_ISMULTI]) ? $paramSettings[GN_ENUM_ISMULTI] : false; + $type = isset ($paramSettings[GN_ENUM_TYPE]) ? $paramSettings[GN_ENUM_TYPE] : null; + + // When type is not given, and no choices, the type is the same as $default + if (!isset ($type)) { + if (isset ($default)) + $type = gettype($default); + else + $type = 'NULL'; // allow everything } + } - $value = $wgRequest->getVal($param, $default); - - if (isset ($value) && ($multi || is_array($type))) - $value = $this->parseMultiValue($param, $value, $multi, is_array($type) ? $type : null); - - // More validation only when choices were not given - // choices were validated in parseMultiValue() - if (!is_array($type) && isset ($value)) { - - switch ($type) { - case 'NULL' : // nothing to do - break; - case 'string' : // nothing to do - break; - case 'integer' : // Force everything using intval() - $value = is_array($value) ? array_map('intval', $value) : intval($value); - break; - case 'limit' : - if (!isset ($enumParams[GN_ENUM_MAX1]) || !isset ($enumParams[GN_ENUM_MAX2])) - $this->dieDebug("MAX1 or MAX2 are not defined for the limit $param"); - if ($multi) - $this->dieDebug("Multi-values not supported for $param"); - $min = isset ($enumParams[GN_ENUM_MIN]) ? $enumParams[GN_ENUM_MIN] : 0; - $value = intval($value); - $this->validateLimit($param, $value, $min, $enumParams[GN_ENUM_MAX1], $enumParams[GN_ENUM_MAX2]); - break; - case 'boolean' : - if ($multi) - $this->dieDebug("Multi-values not supported for $param"); - $value = isset ($value); - break; - case 'timestamp' : - if ($multi) - $this->dieDebug("Multi-values not supported for $param"); - $value = $this->prepareTimestamp($value); // Adds quotes around timestamp - break; - default : - $this->dieDebug("Param $param's type is unknown - $type"); - - } + if ($type == 'boolean') { + if (isset ($default) && $default !== false) { + // Having a default value of anything other than 'false' is pointless + $this->dieDebug("Boolean param $paramName's default is set to '$default'"); } - $results[$param] = $value; + $value = $wgRequest->getCheck($paramName); + } else + $value = $wgRequest->getVal($paramName, $default); + + if (isset ($value) && ($multi || is_array($type))) + $value = $this->parseMultiValue($paramName, $value, $multi, is_array($type) ? $type : null); + + // More validation only when choices were not given + // choices were validated in parseMultiValue() + if (!is_array($type) && isset ($value)) { + + switch ($type) { + case 'NULL' : // nothing to do + break; + case 'string' : // nothing to do + break; + case 'integer' : // Force everything using intval() + $value = is_array($value) ? array_map('intval', $value) : intval($value); + break; + case 'limit' : + if (!isset ($paramSettings[GN_ENUM_MAX1]) || !isset ($paramSettings[GN_ENUM_MAX2])) + $this->dieDebug("MAX1 or MAX2 are not defined for the limit $paramName"); + if ($multi) + $this->dieDebug("Multi-values not supported for $paramName"); + $min = isset ($paramSettings[GN_ENUM_MIN]) ? $paramSettings[GN_ENUM_MIN] : 0; + $value = intval($value); + $this->validateLimit($paramName, $value, $min, $paramSettings[GN_ENUM_MAX1], $paramSettings[GN_ENUM_MAX2]); + break; + case 'boolean' : + if ($multi) + $this->dieDebug("Multi-values not supported for $paramName"); + break; + case 'timestamp' : + if ($multi) + $this->dieDebug("Multi-values not supported for $paramName"); + $value = $this->prepareTimestamp($value); // Adds quotes around timestamp + break; + default : + $this->dieDebug("Param $paramName's type is unknown - $type"); + + } } - return $results; + return $value; } - + /** * Return an array of values that were given in a "a|b|c" notation, * after it optionally validates them against the list allowed values. diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 7854e94cd0..eaa97ae56d 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -32,12 +32,10 @@ if (!defined('MEDIAWIKI')) { class ApiPageSet extends ApiQueryBase { private $mAllPages; // [ns][dbkey] => page_id or 0 when missing - private $mResolveRedirs; private $mGoodTitles, $mMissingTitles, $mRedirectTitles, $mNormalizedTitles; - public function __construct($query, $resolveRedirs) { + public function __construct($query) { parent :: __construct($query, __CLASS__); - $this->mResolveRedirs = $resolveRedirs; $this->mAllPages = array (); $this->mGoodTitles = array (); @@ -82,9 +80,23 @@ class ApiPageSet extends ApiQueryBase { /** * Returns the number of unique pages (not revisions) in the set. */ - public function getPageCount() { + public function getGoodTitleCount() { return count($this->getGoodTitles()); } + + /** + * Get the list of revision IDs (requested with revids= parameter) + */ + public function getRevisionIDs() { + $this->dieUsage(__FUNCTION__ . " is not implemented", 'notimplemented'); + } + + /** + * Returns the number of revisions (requested with revids= parameter) + */ + public function getRevisionCount() { + return 0; // TODO: implement + } /** * This method populates internal variables with page information @@ -100,19 +112,19 @@ class ApiPageSet extends ApiQueryBase { * #5 Substitute the original LinkBatch object with the new list * #6 Repeat from step #1 */ - public function populateTitles($titles) { - $this->profileIn(); + private function populateTitles($titles, $redirects) { + $pageFlds = array ( 'page_id', 'page_namespace', 'page_title' ); - if ($this->mResolveRedirs) { + if ($redirects) { $pageFlds[] = 'page_is_redirect'; } // Get validated and normalized title objects - $linkBatch = $this->processTitlesStrings($titles); + $linkBatch = $this->processTitlesStrArray($titles); $db = $this->getDB(); @@ -138,7 +150,7 @@ class ApiPageSet extends ApiQueryBase { $title = Title :: makeTitle($row->page_namespace, $row->page_title); $this->mAllPages[$row->page_namespace][$row->page_title] = $row->page_id; - if ($this->mResolveRedirs && $row->page_is_redirect == '1') { + if ($redirects && $row->page_is_redirect == '1') { $redirectIds[$row->page_id] = $title; } else { $this->mGoodTitles[$row->page_id] = $title; @@ -156,7 +168,7 @@ class ApiPageSet extends ApiQueryBase { } } - if (!$this->mResolveRedirs || empty ($redirectIds)) + if (!$redirects || empty ($redirectIds)) break; // @@ -199,7 +211,6 @@ class ApiPageSet extends ApiQueryBase { } $db->freeResult($res); } - $this->profileOut(); } /** @@ -209,7 +220,7 @@ class ApiPageSet extends ApiQueryBase { * * @return LinkBatch of title objects. */ - private function processTitlesStrings($titles) { + private function processTitlesStrArray($titles) { $linkBatch = new LinkBatch(); @@ -237,12 +248,69 @@ class ApiPageSet extends ApiQueryBase { return $linkBatch; } - public function populatePageIDs($pageids) { + private function populatePageIDs($pageids) { $this->dieUsage(__FUNCTION__ . " is not implemented", 'notimplemented'); } public function execute() { - $this->dieDebug("execute() is not supported on this object"); + $titles = $pageids = $revids = $redirects = null; + extract($this->extractRequestParams()); + + // Only one of the titles/pageids/revids is allowed at the same time + $dataSource = null; + if (isset ($titles)) + $dataSource = 'titles'; + if (isset ($pageids)) { + if (isset ($dataSource)) + $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource'); + $dataSource = 'pageids'; + } + if (isset ($revids)) { + if (isset ($dataSource)) + $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource'); + $dataSource = 'revids'; + } + + switch ($dataSource) { + case 'titles' : + $this->populateTitles($titles, $redirects); + break; + case 'pageids' : + $this->populatePageIDs($pageids, $redirects); + break; + case 'revids' : + $this->populateRevIDs($revids); + break; + default : + // Do nothing - some queries do not need any of the data sources. + break; + } + } + + protected function getAllowedParams() { + return array ( + 'titles' => array ( + GN_ENUM_ISMULTI => true + ), + 'pageids' => array ( + GN_ENUM_TYPE => 'integer', + GN_ENUM_ISMULTI => true + ), + 'revids' => array ( + GN_ENUM_TYPE => 'integer', + GN_ENUM_ISMULTI => true + ), + 'redirects' => false + ); + } + + protected function getParamDescription() { + return array ( + 'titles' => 'A list of titles to work on', + 'pageids' => 'A list of page IDs to work on', + 'revids' => 'A list of revision IDs to work on', + 'redirects' => 'Automatically resolve redirects' + ); } } ?> \ No newline at end of file diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 5bddbacf7b..d882a53da1 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -96,30 +96,22 @@ class ApiQuery extends ApiBase { * #5 Execute all requested modules */ public function execute() { - $meta = $prop = $list = $generator = $titles = $pageids = null; - $redirects = null; + $meta = $prop = $list = $generator = null; extract($this->extractRequestParams()); // // Create and initialize PageSet // - $dataSource = null; - if (isset ($titles) && isset($pageids)) - $this->dieUsage("At present you may not use titles= and pageids= at the same time", 'multisource'); - - $this->mData = new ApiPageSet($this, $redirects); - - if (isset($titles)) - $this->mData->populateTitles($titles); - - if (isset($pageids)) - $this->mData->populatePageIDs($pageids); + $this->mData = new ApiPageSet($this); + $this->mData->profileIn(); + $this->mData->execute(); + $this->mData->profileOut(); // // If generator is provided, get a new dataset to work on // if (isset ($generator)) - $this->executeGenerator($generator, $redirects); + $this->executeGenerator($generator); // Instantiate required modules // During instantiation, modules may optimize data requests through the $this->mData object @@ -145,14 +137,12 @@ class ApiQuery extends ApiBase { } // Show redirect information - if ($redirects) { - foreach ($this->mData->getRedirectTitles() as $titleStrFrom => $titleStrTo) { - $this->getResult()->addMessage('query', 'redirects', array ( - 'from' => $titleStrFrom, - 'to' => $titleStrTo, - '*' => '' - ), 'r'); - } + foreach ($this->mData->getRedirectTitles() as $titleStrFrom => $titleStrTo) { + $this->getResult()->addMessage('query', 'redirects', array ( + 'from' => $titleStrFrom, + 'to' => $titleStrTo, + '*' => '' + ), 'r'); } // Execute all requested modules. @@ -163,7 +153,7 @@ class ApiQuery extends ApiBase { } } - protected function executeGenerator($generator, $redirects) { + protected function executeGenerator($generator) { // Find class that implements requested generator if (isset ($this->mQueryListModules[$generator])) @@ -195,18 +185,10 @@ class ApiQuery extends ApiBase { 'list' => array ( GN_ENUM_ISMULTI => true, GN_ENUM_TYPE => $this->mListModuleNames - ), + ) // 'generator' => array ( // GN_ENUM_TYPE => $this->mAllowedGenerators // ), - 'titles' => array ( - GN_ENUM_ISMULTI => true - ), - // 'pageids' => array ( - // GN_ENUM_TYPE => 'integer', - // GN_ENUM_ISMULTI => true - // ), - 'redirects' => false ); } @@ -250,15 +232,21 @@ class ApiQuery extends ApiBase { return $msg; } + /** + * Override to add extra parameters from PageSet + */ + public function makeHelpMsgParameters() { + $module = new ApiPageSet($this); + return $module->makeHelpMsgParameters() . parent :: makeHelpMsgParameters(); + } + + protected function getParamDescription() { return array ( 'meta' => 'Which meta data to get about the site', 'prop' => 'Which properties to get for the titles/revisions/pageids', 'list' => 'Which lists to get', 'generator' => 'Use the output of a list as the input for other prop/list/meta items', - 'titles' => 'A list of titles to work on', - 'pageids' => 'A list of page IDs to work on', - 'redirects' => 'Automatically resolve redirects' ); } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 4be5d7a85a..83771b9e63 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -36,40 +36,33 @@ class ApiQueryRevisions extends ApiQueryBase { } public function execute() { - $rvrevids = $rvlimit = $rvstartid = $rvendid = $rvstart = $rvend = $rvdir = $rvprop = null; + $rvlimit = $rvstartid = $rvendid = $rvstart = $rvend = $rvdir = $rvprop = null; extract($this->extractRequestParams()); - // - // Parameter validation - // - // true when ordered by timestamp from older to newer, false otherwise $dirNewer = ($rvdir === 'newer'); - // If any of those parameters are used, we can only work with a single page + // If any of those parameters are used, work in "enumeration" mode. + // Enum mode can only be used when exactly one page is provided. // Enumerating revisions on multiple pages make it extremelly // difficult to manage continuations and require additional sql indexes $enumRevMode = ($rvlimit !== 0 || $rvstartid !== 0 || $rvendid !== 0 || $dirNewer || isset ($rvstart) || isset ($rvend)); - if ($rvstartid !== 0 || $rvendid !== 0) - $this->dieUsage('rvstartid/rvendid not implemented', 'notimplemented'); - $data = $this->getData(); - $pageCount = $data->getPageCount(); + $pageCount = $data->getGoodTitleCount(); + $revCount = $data->getRevisionCount(); - if (!empty ($rvrevids)) { - if ($pageCount > 0) - $this->dieUsage('The rvrevids= parameter may not be used with titles, pageids, and generator options.', 'rv_rvrevids'); + if ($revCount > 0 && $pageCount > 0) + $this->dieUsage('The rvrevids= parameter may not be used with titles, pageids, and generator options.', 'rv_rvrevids'); - if ($enumRevMode) - $this->dieUsage('The rvrevids= parameter may not be used with the list options (rvlimit, rvstartid, rvendid, dirNewer, rvstart, rvend).', 'rv_rvrevids'); - } else { - if ($pageCount < 1) - $this->dieUsage('No pages were given. Please use titles, pageids or a generator to provide page(s) to work on.', 'rv_no_pages'); + if ($revCount > 0 && $enumRevMode) + $this->dieUsage('The rvrevids= parameter may not be used with the list options (rvlimit, rvstartid, rvendid, dirNewer, rvstart, rvend).', 'rv_rvrevids'); - if ($enumRevMode && $pageCount > 1) - $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the rvlimit, rvstartid, rvendid, dirNewer, rvstart, and rvend parameters may only be used on a single page.', 'rv_multpages'); - } + if ($revCount === 0 && $pageCount === 0) + $this->dieUsage('No pages were given. Please use titles, pageids or a generator to provide page(s) to work on.', 'rv_no_pages'); + + if ($revCount === 0 && $pageCount > 1 && $enumRevMode) + $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the rvlimit, rvstartid, rvendid, dirNewer, rvstart, and rvend parameters may only be used on a single page.', 'rv_multpages'); $tables = array ( 'revision' @@ -111,7 +104,7 @@ class ApiQueryRevisions extends ApiQueryBase { $fields[] = 'old_id'; $fields[] = 'old_text'; $fields[] = 'old_flags'; - $showContent = true; + $showContent = true; break; default : $this->dieDebug("unknown rvprop $prop"); @@ -124,37 +117,60 @@ class ApiQueryRevisions extends ApiQueryBase { if ($enumRevMode) { + // This is mostly to prevent parameter errors (and optimize sql?) + if ($rvstartid !== 0 && isset ($rvstart)) + $this->dieUsage('rvstart and rvstartid cannot be used together', 'rv_badparams'); + + if ($rvendid !== 0 && isset ($rvend)) + $this->dieUsage('rvend and rvend cannot be used together', 'rv_badparams'); + + $options['ORDER BY'] = 'rev_timestamp' . ($dirNewer ? '' : ' DESC'); + $before = ($dirNewer ? '<=' : '>='); + $after = ($dirNewer ? '>=' : '<='); + + if ($rvstartid !== 0) + $conds[] = 'rev_id' . $after . intval($rvstartid); + if ($rvendid !== 0) + $conds[] = 'rev_id' . $before . intval($rvendid); if (isset ($rvstart)) - $conds[] = 'rev_timestamp >= ' . $this->prepareTimestamp($rvstart); + $conds[] = 'rev_timestamp' . $after . $this->prepareTimestamp($rvstart); if (isset ($rvend)) - $conds[] = 'rev_timestamp <= ' . $this->prepareTimestamp($rvend); + $conds[] = 'rev_timestamp' . $before . $this->prepareTimestamp($rvend); // must manually initialize unset rvlimit if (!isset ($rvlimit)) $rvlimit = 10; - $options['ORDER BY'] = 'rev_timestamp' . ($dirNewer ? '' : ' DESC'); - $this->validateLimit('rvlimit', $rvlimit, 1, $userMax, $botMax); - // There is only one ID - $conds['rev_page'] = array_keys($data->getGoodTitles()); - - } else { - // When working in multi-page non-enumeration mode, - // limit to the latest revision only - $tables[] = 'page'; - $conds[] = 'page_id=rev_page'; - $conds[] = 'page_latest=rev_id'; - $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); - - // Get all page IDs - $conds['page_id'] = array_keys($data->getGoodTitles()); - - $rvlimit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. - } + // There is only one ID, use it + $conds['rev_page'] = array_pop(array_keys($data->getGoodTitles())); + + } else + if ($pageCount > 0) { + // When working in multi-page non-enumeration mode, + // limit to the latest revision only + $tables[] = 'page'; + $conds[] = 'page_id=rev_page'; + $conds[] = 'page_latest=rev_id'; + $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); + + // Get all page IDs + $conds['page_id'] = array_keys($data->getGoodTitles()); + + $rvlimit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. + } else + if ($revCount > 0) { + $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); + + // Get all revision IDs + $conds['rev_id'] = array_keys($data->getRevisionIDs()); - $options['LIMIT'] = $rvlimit +1; + $rvlimit = $revCount; // assumption testing -- we should never get more then $revCount rows. + } else + $this->dieDebug('param validation?'); + + $options['LIMIT'] = $rvlimit +1; $db = $this->getDB(); $this->profileDBIn(); @@ -167,9 +183,9 @@ class ApiQueryRevisions extends ApiQueryBase { if (++ $count > $rvlimit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... - if (!$enumRevMode) - $this->dieDebug('Got more rows then expected'); // bug report - + if (!$enumRevMode) + $this->dieDebug('Got more rows then expected'); // bug report + $startStr = 'rvstartid=' . $row->rev_id; $msg = array ( 'continue' => $startStr @@ -179,9 +195,8 @@ class ApiQueryRevisions extends ApiQueryBase { } $vals = array ( - 'revid' => intval($row->rev_id), - 'oldid' => intval($row->rev_text_id - )); + 'revid' => intval($row->rev_id + ), 'oldid' => intval($row->rev_text_id)); if ($row->rev_minor_edit) { $vals['minor'] = ''; @@ -255,7 +270,13 @@ class ApiQueryRevisions extends ApiQueryBase { } protected function getDescription() { - return 'module a'; + return array ( + 'Get revision information.', + 'This module may be used in several ways:', + ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.', + ' 2) Get revisions for one given page, by using titles/pageids with rvstart*/rvend*/rvlimit params.', + ' 3) Get data about a set of revisions by setting their IDs with revids parameter.' + ); } protected function getExamples() { -- 2.20.1