4 * Created on Sep 25, 2006
6 * API for MediaWiki 1.8+
8 * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 * http://www.gnu.org/copyleft/gpl.html
26 if (!defined('MEDIAWIKI')) {
27 // Eclipse helper - will be ignored in production
28 require_once ('ApiQueryBase.php');
32 * A query module to show basic page information.
36 class ApiQueryInfo
extends ApiQueryBase
{
38 public function __construct($query, $moduleName) {
39 parent
:: __construct($query, $moduleName, 'in');
42 public function requestExtraData($pageSet) {
43 $pageSet->requestField('page_restrictions');
44 $pageSet->requestField('page_is_redirect');
45 $pageSet->requestField('page_is_new');
46 $pageSet->requestField('page_counter');
47 $pageSet->requestField('page_touched');
48 $pageSet->requestField('page_latest');
49 $pageSet->requestField('page_len');
52 protected function getTokenFunctions() {
53 // tokenname => function
54 // function prototype is func($pageid, $title)
55 // should return token or false
57 // Don't call the hooks twice
58 if(isset($this->tokenFunctions
))
59 return $this->tokenFunctions
;
61 // If we're in JSON callback mode, no tokens can be obtained
62 if(!is_null($this->getMain()->getRequest()->getVal('callback')))
65 $this->tokenFunctions
= array(
66 'edit' => array( 'ApiQueryInfo', 'getEditToken' ),
67 'delete' => array( 'ApiQueryInfo', 'getDeleteToken' ),
68 'protect' => array( 'ApiQueryInfo', 'getProtectToken' ),
69 'move' => array( 'ApiQueryInfo', 'getMoveToken' ),
70 'block' => array( 'ApiQueryInfo', 'getBlockToken' ),
71 'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
72 'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
74 wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions
));
75 return $this->tokenFunctions
;
78 public static function getEditToken($pageid, $title)
80 // We could check for $title->userCan('edit') here,
81 // but that's too expensive for this purpose
83 if(!$wgUser->isAllowed('edit'))
86 // The edit token is always the same, let's exploit that
87 static $cachedEditToken = null;
88 if(!is_null($cachedEditToken))
89 return $cachedEditToken;
91 $cachedEditToken = $wgUser->editToken();
92 return $cachedEditToken;
95 public static function getDeleteToken($pageid, $title)
98 if(!$wgUser->isAllowed('delete'))
101 static $cachedDeleteToken = null;
102 if(!is_null($cachedDeleteToken))
103 return $cachedDeleteToken;
105 $cachedDeleteToken = $wgUser->editToken();
106 return $cachedDeleteToken;
109 public static function getProtectToken($pageid, $title)
112 if(!$wgUser->isAllowed('protect'))
115 static $cachedProtectToken = null;
116 if(!is_null($cachedProtectToken))
117 return $cachedProtectToken;
119 $cachedProtectToken = $wgUser->editToken();
120 return $cachedProtectToken;
123 public static function getMoveToken($pageid, $title)
126 if(!$wgUser->isAllowed('move'))
129 static $cachedMoveToken = null;
130 if(!is_null($cachedMoveToken))
131 return $cachedMoveToken;
133 $cachedMoveToken = $wgUser->editToken();
134 return $cachedMoveToken;
137 public static function getBlockToken($pageid, $title)
140 if(!$wgUser->isAllowed('block'))
143 static $cachedBlockToken = null;
144 if(!is_null($cachedBlockToken))
145 return $cachedBlockToken;
147 $cachedBlockToken = $wgUser->editToken();
148 return $cachedBlockToken;
151 public static function getUnblockToken($pageid, $title)
153 // Currently, this is exactly the same as the block token
154 return self
::getBlockToken($pageid, $title);
157 public static function getEmailToken($pageid, $title)
160 if(!$wgUser->canSendEmail() ||
$wgUser->isBlockedFromEmailUser())
163 static $cachedEmailToken = null;
164 if(!is_null($cachedEmailToken))
165 return $cachedEmailToken;
167 $cachedEmailToken = $wgUser->editToken();
168 return $cachedEmailToken;
171 public function execute() {
175 $params = $this->extractRequestParams();
176 $fld_protection = $fld_talkid = $fld_subjectid = $fld_url = $fld_readable = false;
177 if(!is_null($params['prop'])) {
178 $prop = array_flip($params['prop']);
179 $fld_protection = isset($prop['protection']);
180 $fld_talkid = isset($prop['talkid']);
181 $fld_subjectid = isset($prop['subjectid']);
182 $fld_url = isset($prop['url']);
183 $fld_readable = isset($prop['readable']);
186 $pageSet = $this->getPageSet();
187 $titles = $pageSet->getGoodTitles();
188 $missing = $pageSet->getMissingTitles();
189 $result = $this->getResult();
191 $pageRestrictions = $pageSet->getCustomField('page_restrictions');
192 $pageIsRedir = $pageSet->getCustomField('page_is_redirect');
193 $pageIsNew = $pageSet->getCustomField('page_is_new');
194 $pageCounter = $pageSet->getCustomField('page_counter');
195 $pageTouched = $pageSet->getCustomField('page_touched');
196 $pageLatest = $pageSet->getCustomField('page_latest');
197 $pageLength = $pageSet->getCustomField('page_len');
199 $db = $this->getDB();
200 if ($fld_protection && count($titles)) {
201 $this->addTables('page_restrictions');
202 $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade'));
203 $this->addWhereFld('pr_page', array_keys($titles));
205 $res = $this->select(__METHOD__
);
206 while($row = $db->fetchObject($res)) {
208 'type' => $row->pr_type
,
209 'level' => $row->pr_level
,
210 'expiry' => Block
::decodeExpiry( $row->pr_expiry
, TS_ISO_8601
)
214 $protections[$row->pr_page
][] = $a;
216 # Also check old restrictions
217 if($pageRestrictions[$row->pr_page
]) {
218 foreach(explode(':', trim($pageRestrictions[$pageid])) as $restrict) {
219 $temp = explode('=', trim($restrict));
220 if(count($temp) == 1) {
221 // old old format should be treated as edit/move restriction
222 $restriction = trim( $temp[0] );
223 if($restriction == '')
225 $protections[$row->pr_page
][] = array(
227 'level' => $restriction,
228 'expiry' => 'infinity',
230 $protections[$row->pr_page
][] = array(
232 'level' => $restriction,
233 'expiry' => 'infinity',
236 $restriction = trim( $temp[1] );
237 if($restriction == '')
239 $protections[$row->pr_page
][] = array(
241 'level' => $restriction,
242 'expiry' => 'infinity',
248 $db->freeResult($res);
251 foreach ($titles as $id => $title)
252 if ($title->getNamespace() == NS_FILE
)
254 // To avoid code duplication
255 $cascadeTypes = array(
258 'table' => 'templatelinks',
259 'ns' => 'tl_namespace',
260 'title' => 'tl_title',
261 'ids' => array_diff(array_keys($titles), $imageIds)
265 'table' => 'imagelinks',
272 foreach ($cascadeTypes as $type)
274 if (count($type['ids']) != 0) {
275 $this->resetQueryParams();
276 $this->addTables(array('page_restrictions', $type['table']));
277 $this->addTables('page', 'page_source');
278 $this->addTables('page', 'page_target');
279 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
280 'page_target.page_id AS page_target_id',
281 'page_source.page_namespace AS page_source_namespace',
282 'page_source.page_title AS page_source_title'));
283 $this->addWhere(array("{$type['prefix']}_from = pr_page",
284 'page_target.page_namespace = '.$type['ns'],
285 'page_target.page_title = '.$type['title'],
286 'page_source.page_id = pr_page'
288 $this->addWhereFld('pr_cascade', 1);
289 $this->addWhereFld('page_target.page_id', $type['ids']);
291 $res = $this->select(__METHOD__
);
292 while($row = $db->fetchObject($res)) {
293 $source = Title
::makeTitle($row->page_source_namespace
, $row->page_source_title
);
295 'type' => $row->pr_type
,
296 'level' => $row->pr_level
,
297 'expiry' => Block
::decodeExpiry( $row->pr_expiry
, TS_ISO_8601
),
298 'source' => $source->getPrefixedText()
300 $protections[$row->page_target_id
][] = $a;
302 $db->freeResult($res);
307 // We don't need to check for pt stuff if there are no nonexistent titles
308 if($fld_protection && count($missing))
310 $this->resetQueryParams();
311 // Construct a custom WHERE clause that matches all titles in $missing
312 $lb = new LinkBatch($missing);
313 $this->addTables('protected_titles');
314 $this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry'));
315 $this->addWhere($lb->constructSet('pt', $db));
316 $res = $this->select(__METHOD__
);
317 $prottitles = array();
318 while($row = $db->fetchObject($res)) {
319 $prottitles[$row->pt_namespace
][$row->pt_title
][] = array(
321 'level' => $row->pt_create_perm
,
322 'expiry' => Block
::decodeExpiry($row->pt_expiry
, TS_ISO_8601
)
325 $db->freeResult($res);
329 foreach ($missing as $title)
330 if ($title->getNamespace() == NS_FILE
)
331 $images[] = $title->getDBKey();
335 if (count($others) != 0) {
336 $lb = new LinkBatch($others);
337 $this->resetQueryParams();
338 $this->addTables(array('page_restrictions', 'page', 'templatelinks'));
339 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
340 'page_title', 'page_namespace',
341 'tl_title', 'tl_namespace'));
342 $this->addWhere($lb->constructSet('tl', $db));
343 $this->addWhere('pr_page = page_id');
344 $this->addWhere('pr_page = tl_from');
345 $this->addWhereFld('pr_cascade', 1);
347 $res = $this->select(__METHOD__
);
348 while($row = $db->fetchObject($res)) {
349 $source = Title
::makeTitle($row->page_namespace
, $row->page_title
);
351 'type' => $row->pr_type
,
352 'level' => $row->pr_level
,
353 'expiry' => Block
::decodeExpiry( $row->pr_expiry
, TS_ISO_8601
),
354 'source' => $source->getPrefixedText()
356 $prottitles[$row->tl_namespace
][$row->tl_title
][] = $a;
358 $db->freeResult($res);
361 if (count($images) != 0) {
362 $this->resetQueryParams();
363 $this->addTables(array('page_restrictions', 'page', 'imagelinks'));
364 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
365 'page_title', 'page_namespace', 'il_to'));
366 $this->addWhere('pr_page = page_id');
367 $this->addWhere('pr_page = il_from');
368 $this->addWhereFld('pr_cascade', 1);
369 $this->addWhereFld('il_to', $images);
371 $res = $this->select(__METHOD__
);
372 while($row = $db->fetchObject($res)) {
373 $source = Title
::makeTitle($row->page_namespace
, $row->page_title
);
375 'type' => $row->pr_type
,
376 'level' => $row->pr_level
,
377 'expiry' => Block
::decodeExpiry( $row->pr_expiry
, TS_ISO_8601
),
378 'source' => $source->getPrefixedText()
380 $prottitles[NS_FILE
][$row->il_to
][] = $a;
382 $db->freeResult($res);
386 // Run the talkid/subjectid query
387 if($fld_talkid ||
$fld_subjectid)
389 $talktitles = $subjecttitles =
390 $talkids = $subjectids = array();
391 $everything = array_merge($titles, $missing);
392 foreach($everything as $t)
394 if(MWNamespace
::isTalk($t->getNamespace()))
397 $subjecttitles[] = $t->getSubjectPage();
400 $talktitles[] = $t->getTalkPage();
402 if(count($talktitles) ||
count($subjecttitles))
404 // Construct a custom WHERE clause that matches
405 // all titles in $talktitles and $subjecttitles
406 $lb = new LinkBatch(array_merge($talktitles, $subjecttitles));
407 $this->resetQueryParams();
408 $this->addTables('page');
409 $this->addFields(array('page_title', 'page_namespace', 'page_id'));
410 $this->addWhere($lb->constructSet('page', $db));
411 $res = $this->select(__METHOD__
);
412 while($row = $db->fetchObject($res))
414 if(MWNamespace
::isTalk($row->page_namespace
))
415 $talkids[MWNamespace
::getSubject($row->page_namespace
)][$row->page_title
] = $row->page_id
;
417 $subjectids[MWNamespace
::getTalk($row->page_namespace
)][$row->page_title
] = $row->page_id
;
422 foreach ( $titles as $pageid => $title ) {
424 'touched' => wfTimestamp(TS_ISO_8601
, $pageTouched[$pageid]),
425 'lastrevid' => intval($pageLatest[$pageid]),
426 'counter' => intval($pageCounter[$pageid]),
427 'length' => intval($pageLength[$pageid]),
430 if ($pageIsRedir[$pageid])
431 $pageInfo['redirect'] = '';
433 if ($pageIsNew[$pageid])
434 $pageInfo['new'] = '';
436 if (!is_null($params['token'])) {
437 $tokenFunctions = $this->getTokenFunctions();
438 $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601
, time());
439 foreach($params['token'] as $t)
441 $val = call_user_func($tokenFunctions[$t], $pageid, $title);
443 $this->setWarning("Action '$t' is not allowed for the current user");
445 $pageInfo[$t . 'token'] = $val;
449 if($fld_protection) {
450 $pageInfo['protection'] = array();
451 if (isset($protections[$pageid])) {
452 $pageInfo['protection'] = $protections[$pageid];
453 $result->setIndexedTagName($pageInfo['protection'], 'pr');
456 if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()]))
457 $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()];
458 if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()]))
459 $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()];
461 $pageInfo['fullurl'] = $title->getFullURL();
462 $pageInfo['editurl'] = $title->getFullURL('action=edit');
465 if($title->userCanRead())
466 $pageInfo['readable'] = '';
468 $result->addValue(array (
471 ), $pageid, $pageInfo);
474 // Get properties for missing titles if requested
475 if(!is_null($params['token']) ||
$fld_protection ||
$fld_talkid ||
$fld_subjectid ||
476 $fld_url ||
$fld_readable)
478 $res = &$result->getData();
479 foreach($missing as $pageid => $title) {
480 if(!is_null($params['token']))
482 $tokenFunctions = $this->getTokenFunctions();
483 $res['query']['pages'][$pageid]['starttimestamp'] = wfTimestamp(TS_ISO_8601
, time());
484 foreach($params['token'] as $t)
486 $val = call_user_func($tokenFunctions[$t], $pageid, $title);
488 $this->setWarning("Action '$t' is not allowed for the current user");
490 $res['query']['pages'][$pageid][$t . 'token'] = $val;
495 // Apparently the XML formatting code doesn't like array(null)
496 // This is painful to fix, so we'll just work around it
497 if(isset($prottitles[$title->getNamespace()][$title->getDBkey()]))
498 $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()];
500 $res['query']['pages'][$pageid]['protection'] = array();
501 $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr');
503 if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()]))
504 $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()];
505 if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()]))
506 $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()];
508 $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL();
509 $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit');
512 if($title->userCanRead())
513 $res['query']['pages'][$pageid]['readable'] = '';
518 public function getAllowedParams() {
521 ApiBase
:: PARAM_DFLT
=> NULL,
522 ApiBase
:: PARAM_ISMULTI
=> true,
523 ApiBase
:: PARAM_TYPE
=> array (
531 ApiBase
:: PARAM_DFLT
=> NULL,
532 ApiBase
:: PARAM_ISMULTI
=> true,
533 ApiBase
:: PARAM_TYPE
=> array_keys($this->getTokenFunctions())
538 public function getParamDescription() {
541 'Which additional properties to get:',
542 ' "protection" - List the protection level of each page',
543 ' "talkid" - The page ID of the talk page for each non-talk page',
544 ' "subjectid" - The page ID of the parent page for each talk page'
546 'token' => 'Request a token to perform a data-modifying action on a page',
551 public function getDescription() {
552 return 'Get basic page information such as namespace, title, last touched date, ...';
555 protected function getExamples() {
557 'api.php?action=query&prop=info&titles=Main%20Page',
558 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
562 public function getVersion() {
563 return __CLASS__
. ': $Id$';