From 0104fa1f0c2782a5463cf3feb43b773d1e0e4e57 Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Fri, 4 Jul 2008 12:07:02 +0000 Subject: [PATCH] Rewritten the way the API handles tokens: * Instead of hardcoding stuff all over the place, use callbacks * Extensions can now add their own tokens to prop=info or prop=revisions using hooks --- RELEASE-NOTES | 3 + docs/hooks.txt | 22 +++++ includes/api/ApiQueryInfo.php | 154 +++++++++++++++++++++++------ includes/api/ApiQueryRevisions.php | 54 +++++++--- 4 files changed, 187 insertions(+), 46 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 836d501dfd..b578d154c2 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -495,6 +495,9 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * Added clprop=timestamp to prop=categories * (bug 14678) API errors now respects $wgShowExceptionDetails and $wgShowSQLErrors * (bug 14723) Added time zone and writing direction to meta=siteinfo +* Added APIQueryInfoTokens and APIQueryRevisionsTokens hooks so extensions + can add their own tokens +* Added block and unblock tokens to prop=info as well === Languages updated in 1.13 === diff --git a/docs/hooks.txt b/docs/hooks.txt index ab7ccdc899..6f76b5c4ed 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -281,6 +281,28 @@ $EditPage : the EditPage object $text : the new text of the article (has yet to be saved) $resultArr : data in this array will be added to the API result +'APIQueryInfoTokens': use this hook to add custom tokens to prop=info. +Every token has an action, which will be used in the intoken parameter +and in the output (actiontoken="..."), and a callback function which +should return the token, or false if the user isn't allowed to obtain +it. The prototype of the callback function is func($pageid, $title) +where $pageid is the page ID of the page the token is requested for +and $title is the associated Title object. In the hook, just add +your callback to the $tokenFunctions array and return true (returning +false makes no sense) +$tokenFunctions: array(action => callback) + +'APIQueryRevisionsTokens': use this hook to add custom tokens to prop=revisions. +Every token has an action, which will be used in the rvtoken parameter +and in the output (actiontoken="..."), and a callback function which +should return the token, or false if the user isn't allowed to obtain +it. The prototype of the callback function is func($pageid, $title, $rev) +where $pageid is the page ID of the page associated to the revision the +token is requested for, $title the associated Title object and $rev the +associated Revision object. In the hook, just add your callback to the +$tokenFunctions array and return true (returning false makes no sense) +$tokenFunctions: array(action => callback) + 'ArticleAfterFetchContent': after fetching content of an article from the database $article: the article (object) being loaded from the database $content: the content (string) of the article diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 1cde5515bf..8fea5835d4 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -49,6 +49,106 @@ class ApiQueryInfo extends ApiQueryBase { $pageSet->requestField('page_len'); } + protected function getTokenFunctions() { + // tokenname => function + // function prototype is func($pageid, $title) + // should return token or false + + // Don't call the hooks twice + if(isset($this->tokenFunctions)) + return $this->tokenFunctions; + + $this->tokenFunctions = array( + 'edit' => 'ApiQueryInfo::getEditToken', + 'delete' => 'ApiQueryInfo::getDeleteToken', + 'protect' => 'ApiQueryInfo::getProtectToken', + 'move' => 'ApiQueryInfo::getMoveToken', + 'block' => 'ApiQueryInfo::getBlockToken', + 'unblock' => 'ApiQueryInfo::getUnblockToken' + ); + wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions)); + return $this->tokenFunctions; + } + + public static function getEditToken($pageid, $title) + { + // We could check for $title->userCan('edit') here, + // but that's too expensive for this purpose + global $wgUser; + if(!$wgUser->isAllowed('edit')) + return false; + + // The edit token is always the same, let's exploit that + static $cachedEditToken = null; + if(!is_null($cachedEditToken)) + return $cachedEditToken; + + $cachedEditToken = $wgUser->editToken(); + return $cachedEditToken; + } + + public static function getDeleteToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->isAllowed('delete')) + return false; + + static $cachedDeleteToken = null; + if(!is_null($cachedDeleteToken)) + return $cachedDeleteToken; + + $cachedDeleteToken = $wgUser->editToken(); + return $cachedDeleteToken; + } + + public static function getProtectToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->isAllowed('protect')) + return false; + + static $cachedProtectToken = null; + if(!is_null($cachedProtectToken)) + return $cachedProtectToken; + + $cachedProtectToken = $wgUser->editToken(); + return $cachedProtectToken; + } + + public static function getMoveToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->isAllowed('move')) + return false; + + static $cachedMoveToken = null; + if(!is_null($cachedMoveToken)) + return $cachedMoveToken; + + $cachedMoveToken = $wgUser->editToken(); + return $cachedMoveToken; + } + + public static function getBlockToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->isAllowed('block')) + return false; + + static $cachedBlockToken = null; + if(!is_null($cachedBlockToken)) + return $cachedBlockToken; + + $cachedBlockToken = $wgUser->editToken(); + return $cachedBlockToken; + } + + public static function getUnblockToken($pageid, $title) + { + // Currently, this is exactly the same as the block token + return self::getBlockToken($pageid, $title); + } + public function execute() { global $wgUser; @@ -61,16 +161,6 @@ class ApiQueryInfo extends ApiQueryBase { $fld_talkid = isset($prop['talkid']); $fld_subjectid = isset($prop['subjectid']); } - if(!is_null($params['token'])) { - $token = $params['token']; - $tok_edit = $this->getTokenFlag($token, 'edit'); - $tok_delete = $this->getTokenFlag($token, 'delete'); - $tok_protect = $this->getTokenFlag($token, 'protect'); - $tok_move = $this->getTokenFlag($token, 'move'); - } - else - // Fix E_NOTICEs about unset variables - $token = $tok_edit = $tok_delete = $tok_protect = $tok_move = null; $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles(); @@ -290,16 +380,16 @@ class ApiQueryInfo extends ApiQueryBase { if ($pageIsNew[$pageid]) $pageInfo['new'] = ''; - if (!is_null($token)) { - // Currently all tokens are generated the same way, but it might change - if ($tok_edit) - $pageInfo['edittoken'] = $wgUser->editToken(); - if ($tok_delete) - $pageInfo['deletetoken'] = $wgUser->editToken(); - if ($tok_protect) - $pageInfo['protecttoken'] = $wgUser->editToken(); - if ($tok_move) - $pageInfo['movetoken'] = $wgUser->editToken(); + if (!is_null($params['token'])) { + $tokenFunctions = $this->getTokenFunctions(); + foreach($params['token'] as $t) + { + $val = call_user_func($tokenFunctions[$t], $pageid, $title); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $pageInfo[$t . 'token'] = $val; + } } if($fld_protection) { @@ -352,14 +442,20 @@ class ApiQueryInfo extends ApiQueryBase { // Get edit/protect tokens and protection data for missing titles if requested // Delete and move tokens are N/A for missing titles anyway - if($tok_edit || $tok_protect || $fld_protection || $fld_talkid || $fld_subjectid) + if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid) { $res = &$result->getData(); foreach($missing as $pageid => $title) { - if($tok_edit) - $res['query']['pages'][$pageid]['edittoken'] = $wgUser->editToken(); - if($tok_protect) - $res['query']['pages'][$pageid]['protecttoken'] = $wgUser->editToken(); + if(!is_null($params['token'])) + { + $tokenFunctions = $this->getTokenFunctions(); + foreach($params['token'] as $t) + { + $val = call_user_func($tokenFunctions[$t], $pageid, $title); + if($val !== false) + $res['query']['pages'][$pageid][$t . 'token'] = $val; + } + } if($fld_protection) { // Apparently the XML formatting code doesn't like array(null) @@ -391,12 +487,8 @@ class ApiQueryInfo extends ApiQueryBase { 'token' => array ( ApiBase :: PARAM_DFLT => NULL, ApiBase :: PARAM_ISMULTI => true, - ApiBase :: PARAM_TYPE => array ( - 'edit', - 'delete', - 'protect', - 'move', - )), + ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()) + ) ); } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 0c4932c054..fb14a4c49f 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -44,6 +44,30 @@ class ApiQueryRevisions extends ApiQueryBase { private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_comment = false, $fld_user = false, $fld_content = false; + protected function getTokenFunctions() { + // tokenname => function + // function prototype is func($pageid, $title, $rev) + // should return token or false + + // Don't call the hooks twice + if(isset($this->tokenFunctions)) + return $this->tokenFunctions; + $this->tokenFunctions = array( + 'rollback' => 'ApiQueryRevisions::getRollbackToken' + ); + wfRunHooks('APIQueryRevisionsTokens', array(&$this->tokenFunctions)); + return $this->tokenFunctions; + } + + public static function getRollbackToken($pageid, $title, $rev) + { + global $wgUser; + if(!$wgUser->isAllowed('rollback')) + return false; + return $wgUser->editToken($title->getPrefixedText(), + $rev->getUserText()); + } + public function execute() { $limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = $expandtemplates = $section = $token = null; extract($this->extractRequestParams(false)); @@ -81,12 +105,10 @@ class ApiQueryRevisions extends ApiQueryBase { $this->fld_timestamp = isset ($prop['timestamp']); $this->fld_comment = isset ($prop['comment']); $this->fld_size = isset ($prop['size']); - $this->tok_rollback = false; // Prevent PHP undefined property notice - if(!is_null($token)) - $this->tok_rollback = $this->getTokenFlag($token, 'rollback'); $this->fld_user = isset ($prop['user']); + $this->token = $token; - if ( $this->tok_rollback || ( $this->fld_content && $this->expandTemplates ) || $pageCount > 0) { + if ( !is_null($this->token) || ( $this->fld_content && $this->expandTemplates ) || $pageCount > 0) { $this->addTables( 'page' ); $this->addWhere('page_id=rev_page'); $this->addFields( Revision::selectPageFields() ); @@ -258,18 +280,22 @@ class ApiQueryRevisions extends ApiQueryBase { $vals['comment'] = $comment; } - if($this->tok_rollback || ($this->fld_content && $this->expandTemplates)) + if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates)) $title = $revision->getTitle(); - if($this->tok_rollback) { - global $wgUser; - $vals['rollbacktoken'] = $wgUser->editToken( array( - $title->getPrefixedText(), - $revision->getUserText(), - ) ); + if(!is_null($this->token)) + { + $tokenFunctions = $this->getTokenFunctions(); + foreach($this->token as $t) + { + $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $vals[$t . 'token'] = $val; + } } - if ($this->fld_content) { global $wgParser; $text = $revision->getText(); @@ -341,9 +367,7 @@ class ApiQueryRevisions extends ApiQueryBase { ApiBase :: PARAM_TYPE => 'integer' ), 'token' => array( - ApiBase :: PARAM_TYPE => array( - 'rollback' - ), + ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), ApiBase :: PARAM_ISMULTI => true ), ); -- 2.20.1