@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * http://www.gnu.org/copyleft/gpl.html */ if (!defined('MEDIAWIKI')) { // Eclipse helper - will be ignored in production require_once ('ApiQueryBase.php'); } /** * A query module to show basic page information. * * @ingroup API */ class ApiQueryInfo extends ApiQueryBase { public function __construct($query, $moduleName) { parent :: __construct($query, $moduleName, 'in'); } public function requestExtraData($pageSet) { $pageSet->requestField('page_restrictions'); $pageSet->requestField('page_is_redirect'); $pageSet->requestField('page_is_new'); $pageSet->requestField('page_counter'); $pageSet->requestField('page_touched'); $pageSet->requestField('page_latest'); $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; // If we're in JSON callback mode, no tokens can be obtained if(!is_null($this->getMain()->getRequest()->getVal('callback'))) return array(); $this->tokenFunctions = array( 'edit' => array( 'ApiQueryInfo', 'getEditToken' ), 'delete' => array( 'ApiQueryInfo', 'getDeleteToken' ), 'protect' => array( 'ApiQueryInfo', 'getProtectToken' ), 'move' => array( 'ApiQueryInfo', 'getMoveToken' ), 'block' => array( 'ApiQueryInfo', 'getBlockToken' ), 'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ), 'email' => array( 'ApiQueryInfo', 'getEmailToken' ), ); 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 static function getEmailToken($pageid, $title) { global $wgUser; if(!$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser()) return false; static $cachedEmailToken = null; if(!is_null($cachedEmailToken)) return $cachedEmailToken; $cachedEmailToken = $wgUser->editToken(); return $cachedEmailToken; } public function execute() { global $wgUser; $params = $this->extractRequestParams(); $fld_protection = $fld_talkid = $fld_subjectid = $fld_url = $fld_readable = false; if(!is_null($params['prop'])) { $prop = array_flip($params['prop']); $fld_protection = isset($prop['protection']); $fld_talkid = isset($prop['talkid']); $fld_subjectid = isset($prop['subjectid']); $fld_url = isset($prop['url']); $fld_readable = isset($prop['readable']); } $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles(); $missing = $pageSet->getMissingTitles(); $result = $this->getResult(); $pageRestrictions = $pageSet->getCustomField('page_restrictions'); $pageIsRedir = $pageSet->getCustomField('page_is_redirect'); $pageIsNew = $pageSet->getCustomField('page_is_new'); $pageCounter = $pageSet->getCustomField('page_counter'); $pageTouched = $pageSet->getCustomField('page_touched'); $pageLatest = $pageSet->getCustomField('page_latest'); $pageLength = $pageSet->getCustomField('page_len'); $db = $this->getDB(); if ($fld_protection && count($titles)) { $this->addTables('page_restrictions'); $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade')); $this->addWhereFld('pr_page', array_keys($titles)); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) ); if($row->pr_cascade) $a['cascade'] = ''; $protections[$row->pr_page][] = $a; # Also check old restrictions if($pageRestrictions[$row->pr_page]) { foreach(explode(':', trim($pageRestrictions[$pageid])) as $restrict) { $temp = explode('=', trim($restrict)); if(count($temp) == 1) { // old old format should be treated as edit/move restriction $restriction = trim( $temp[0] ); if($restriction == '') continue; $protections[$row->pr_page][] = array( 'type' => 'edit', 'level' => $restriction, 'expiry' => 'infinity', ); $protections[$row->pr_page][] = array( 'type' => 'move', 'level' => $restriction, 'expiry' => 'infinity', ); } else { $restriction = trim( $temp[1] ); if($restriction == '') continue; $protections[$row->pr_page][] = array( 'type' => $temp[0], 'level' => $restriction, 'expiry' => 'infinity', ); } } } } $db->freeResult($res); $imageIds = array(); foreach ($titles as $id => $title) if ($title->getNamespace() == NS_FILE) $imageIds[] = $id; // To avoid code duplication $cascadeTypes = array( array( 'prefix' => 'tl', 'table' => 'templatelinks', 'ns' => 'tl_namespace', 'title' => 'tl_title', 'ids' => array_diff(array_keys($titles), $imageIds) ), array( 'prefix' => 'il', 'table' => 'imagelinks', 'ns' => NS_FILE, 'title' => 'il_to', 'ids' => $imageIds ) ); foreach ($cascadeTypes as $type) { if (count($type['ids']) != 0) { $this->resetQueryParams(); $this->addTables(array('page_restrictions', $type['table'])); $this->addTables('page', 'page_source'); $this->addTables('page', 'page_target'); $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 'page_target.page_id AS page_target_id', 'page_source.page_namespace AS page_source_namespace', 'page_source.page_title AS page_source_title')); $this->addWhere(array("{$type['prefix']}_from = pr_page", 'page_target.page_namespace = '.$type['ns'], 'page_target.page_title = '.$type['title'], 'page_source.page_id = pr_page' )); $this->addWhereFld('pr_cascade', 1); $this->addWhereFld('page_target.page_id', $type['ids']); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { $source = Title::makeTitle($row->page_source_namespace, $row->page_source_title); $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); $protections[$row->page_target_id][] = $a; } $db->freeResult($res); } } } // We don't need to check for pt stuff if there are no nonexistent titles if($fld_protection && count($missing)) { $this->resetQueryParams(); // Construct a custom WHERE clause that matches all titles in $missing $lb = new LinkBatch($missing); $this->addTables('protected_titles'); $this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry')); $this->addWhere($lb->constructSet('pt', $db)); $res = $this->select(__METHOD__); $prottitles = array(); while($row = $db->fetchObject($res)) { $prottitles[$row->pt_namespace][$row->pt_title][] = array( 'type' => 'create', 'level' => $row->pt_create_perm, 'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601) ); } $db->freeResult($res); $images = array(); $others = array(); foreach ($missing as $title) if ($title->getNamespace() == NS_FILE) $images[] = $title->getDBKey(); else $others[] = $title; if (count($others) != 0) { $lb = new LinkBatch($others); $this->resetQueryParams(); $this->addTables(array('page_restrictions', 'page', 'templatelinks')); $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 'page_title', 'page_namespace', 'tl_title', 'tl_namespace')); $this->addWhere($lb->constructSet('tl', $db)); $this->addWhere('pr_page = page_id'); $this->addWhere('pr_page = tl_from'); $this->addWhereFld('pr_cascade', 1); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { $source = Title::makeTitle($row->page_namespace, $row->page_title); $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); $prottitles[$row->tl_namespace][$row->tl_title][] = $a; } $db->freeResult($res); } if (count($images) != 0) { $this->resetQueryParams(); $this->addTables(array('page_restrictions', 'page', 'imagelinks')); $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 'page_title', 'page_namespace', 'il_to')); $this->addWhere('pr_page = page_id'); $this->addWhere('pr_page = il_from'); $this->addWhereFld('pr_cascade', 1); $this->addWhereFld('il_to', $images); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { $source = Title::makeTitle($row->page_namespace, $row->page_title); $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); $prottitles[NS_FILE][$row->il_to][] = $a; } $db->freeResult($res); } } // Run the talkid/subjectid query if($fld_talkid || $fld_subjectid) { $talktitles = $subjecttitles = $talkids = $subjectids = array(); $everything = array_merge($titles, $missing); foreach($everything as $t) { if(MWNamespace::isTalk($t->getNamespace())) { if($fld_subjectid) $subjecttitles[] = $t->getSubjectPage(); } else if($fld_talkid) $talktitles[] = $t->getTalkPage(); } if(count($talktitles) || count($subjecttitles)) { // Construct a custom WHERE clause that matches // all titles in $talktitles and $subjecttitles $lb = new LinkBatch(array_merge($talktitles, $subjecttitles)); $this->resetQueryParams(); $this->addTables('page'); $this->addFields(array('page_title', 'page_namespace', 'page_id')); $this->addWhere($lb->constructSet('page', $db)); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { if(MWNamespace::isTalk($row->page_namespace)) $talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = $row->page_id; else $subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = $row->page_id; } } } foreach ( $titles as $pageid => $title ) { $pageInfo = array ( 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), 'lastrevid' => intval($pageLatest[$pageid]), 'counter' => intval($pageCounter[$pageid]), 'length' => intval($pageLength[$pageid]), ); if ($pageIsRedir[$pageid]) $pageInfo['redirect'] = ''; if ($pageIsNew[$pageid]) $pageInfo['new'] = ''; if (!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); 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) { $pageInfo['protection'] = array(); if (isset($protections[$pageid])) { $pageInfo['protection'] = $protections[$pageid]; $result->setIndexedTagName($pageInfo['protection'], 'pr'); } } if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; if($fld_url) { $pageInfo['fullurl'] = $title->getFullURL(); $pageInfo['editurl'] = $title->getFullURL('action=edit'); } if($fld_readable) if($title->userCanRead()) $pageInfo['readable'] = ''; $result->addValue(array ( 'query', 'pages' ), $pageid, $pageInfo); } // Get properties for missing titles if requested if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid || $fld_url || $fld_readable) { $res = &$result->getData(); foreach($missing as $pageid => $title) { if(!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); $res['query']['pages'][$pageid]['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); 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 $res['query']['pages'][$pageid][$t . 'token'] = $val; } } if($fld_protection) { // Apparently the XML formatting code doesn't like array(null) // This is painful to fix, so we'll just work around it if(isset($prottitles[$title->getNamespace()][$title->getDBkey()])) $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()]; else $res['query']['pages'][$pageid]['protection'] = array(); $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr'); } if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; if($fld_url) { $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL(); $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit'); } if($fld_readable) if($title->userCanRead()) $res['query']['pages'][$pageid]['readable'] = ''; } } } public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_DFLT => NULL, ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array ( 'protection', 'talkid', 'subjectid', 'url', 'readable', )), 'token' => array ( ApiBase :: PARAM_DFLT => NULL, ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()) ) ); } public function getParamDescription() { return array ( 'prop' => array ( 'Which additional properties to get:', ' "protection" - List the protection level of each page', ' "talkid" - The page ID of the talk page for each non-talk page', ' "subjectid" - The page ID of the parent page for each talk page' ), 'token' => 'Request a token to perform a data-modifying action on a page', ); } public function getDescription() { return 'Get basic page information such as namespace, title, last touched date, ...'; } protected function getExamples() { return array ( 'api.php?action=query&prop=info&titles=Main%20Page', 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page' ); } public function getVersion() { return __CLASS__ . ': $Id: ApiQueryInfo.php 45683 2009-01-12 19:10:42Z raymond $'; } }