Kill undefined variable warnings.
[lhc/web/wiklou.git] / includes / api / ApiQueryInfo.php
1 <?php
2
3 /*
4 * Created on Sep 25, 2006
5 *
6 * API for MediaWiki 1.8+
7 *
8 * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
9 *
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.
14 *
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.
19 *
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
24 */
25
26 if (!defined('MEDIAWIKI')) {
27 // Eclipse helper - will be ignored in production
28 require_once ('ApiQueryBase.php');
29 }
30
31 /**
32 * A query module to show basic page information.
33 *
34 * @ingroup API
35 */
36 class ApiQueryInfo extends ApiQueryBase {
37
38 public function __construct($query, $moduleName) {
39 parent :: __construct($query, $moduleName, 'in');
40 }
41
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');
50 }
51
52 protected function getTokenFunctions() {
53 // tokenname => function
54 // function prototype is func($pageid, $title)
55 // should return token or false
56
57 // Don't call the hooks twice
58 if(isset($this->tokenFunctions))
59 return $this->tokenFunctions;
60
61 // If we're in JSON callback mode, no tokens can be obtained
62 if(!is_null($this->getMain()->getRequest()->getVal('callback')))
63 return array();
64
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' ),
73 );
74 wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions));
75 return $this->tokenFunctions;
76 }
77
78 public static function getEditToken($pageid, $title)
79 {
80 // We could check for $title->userCan('edit') here,
81 // but that's too expensive for this purpose
82 global $wgUser;
83 if(!$wgUser->isAllowed('edit'))
84 return false;
85
86 // The edit token is always the same, let's exploit that
87 static $cachedEditToken = null;
88 if(!is_null($cachedEditToken))
89 return $cachedEditToken;
90
91 $cachedEditToken = $wgUser->editToken();
92 return $cachedEditToken;
93 }
94
95 public static function getDeleteToken($pageid, $title)
96 {
97 global $wgUser;
98 if(!$wgUser->isAllowed('delete'))
99 return false;
100
101 static $cachedDeleteToken = null;
102 if(!is_null($cachedDeleteToken))
103 return $cachedDeleteToken;
104
105 $cachedDeleteToken = $wgUser->editToken();
106 return $cachedDeleteToken;
107 }
108
109 public static function getProtectToken($pageid, $title)
110 {
111 global $wgUser;
112 if(!$wgUser->isAllowed('protect'))
113 return false;
114
115 static $cachedProtectToken = null;
116 if(!is_null($cachedProtectToken))
117 return $cachedProtectToken;
118
119 $cachedProtectToken = $wgUser->editToken();
120 return $cachedProtectToken;
121 }
122
123 public static function getMoveToken($pageid, $title)
124 {
125 global $wgUser;
126 if(!$wgUser->isAllowed('move'))
127 return false;
128
129 static $cachedMoveToken = null;
130 if(!is_null($cachedMoveToken))
131 return $cachedMoveToken;
132
133 $cachedMoveToken = $wgUser->editToken();
134 return $cachedMoveToken;
135 }
136
137 public static function getBlockToken($pageid, $title)
138 {
139 global $wgUser;
140 if(!$wgUser->isAllowed('block'))
141 return false;
142
143 static $cachedBlockToken = null;
144 if(!is_null($cachedBlockToken))
145 return $cachedBlockToken;
146
147 $cachedBlockToken = $wgUser->editToken();
148 return $cachedBlockToken;
149 }
150
151 public static function getUnblockToken($pageid, $title)
152 {
153 // Currently, this is exactly the same as the block token
154 return self::getBlockToken($pageid, $title);
155 }
156
157 public static function getEmailToken($pageid, $title)
158 {
159 global $wgUser;
160 if(!$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser())
161 return false;
162
163 static $cachedEmailToken = null;
164 if(!is_null($cachedEmailToken))
165 return $cachedEmailToken;
166
167 $cachedEmailToken = $wgUser->editToken();
168 return $cachedEmailToken;
169 }
170
171 public function execute() {
172
173 global $wgUser;
174
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']);
184 }
185
186 $pageSet = $this->getPageSet();
187 $titles = $pageSet->getGoodTitles();
188 $missing = $pageSet->getMissingTitles();
189 $result = $this->getResult();
190
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');
198
199 $db = $this->getDB();
200 if ($fld_protection && !empty($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));
204
205 $res = $this->select(__METHOD__);
206 while($row = $db->fetchObject($res)) {
207 $a = array(
208 'type' => $row->pr_type,
209 'level' => $row->pr_level,
210 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 )
211 );
212 if($row->pr_cascade)
213 $a['cascade'] = '';
214 $protections[$row->pr_page][] = $a;
215 }
216 $db->freeResult($res);
217
218 $imageIds = array();
219 foreach ($titles as $id => $title)
220 if ($title->getNamespace() == NS_IMAGE)
221 $imageIds[] = $id;
222 // To avoid code duplication
223 $cascadeTypes = array(
224 array(
225 'prefix' => 'tl',
226 'table' => 'templatelinks',
227 'ns' => 'tl_namespace',
228 'title' => 'tl_title',
229 'ids' => array_diff(array_keys($titles), $imageIds)
230 ),
231 array(
232 'prefix' => 'il',
233 'table' => 'imagelinks',
234 'ns' => NS_IMAGE,
235 'title' => 'il_to',
236 'ids' => $imageIds
237 )
238 );
239
240 foreach ($cascadeTypes as $type)
241 {
242 if (count($type['ids']) != 0) {
243 $this->resetQueryParams();
244 $this->addTables(array('page_restrictions', $type['table']));
245 $this->addTables('page', 'page_source');
246 $this->addTables('page', 'page_target');
247 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
248 'page_target.page_id AS page_target_id',
249 'page_source.page_namespace AS page_source_namespace',
250 'page_source.page_title AS page_source_title'));
251 $this->addWhere(array("{$type['prefix']}_from = pr_page",
252 'page_target.page_namespace = '.$type['ns'],
253 'page_target.page_title = '.$type['title'],
254 'page_source.page_id = pr_page'
255 ));
256 $this->addWhereFld('pr_cascade', 1);
257 $this->addWhereFld('page_target.page_id', $type['ids']);
258
259 $res = $this->select(__METHOD__);
260 while($row = $db->fetchObject($res)) {
261 $source = Title::makeTitle($row->page_source_namespace, $row->page_source_title);
262 $a = array(
263 'type' => $row->pr_type,
264 'level' => $row->pr_level,
265 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
266 'source' => $source->getPrefixedText()
267 );
268 $protections[$row->page_target_id][] = $a;
269 }
270 $db->freeResult($res);
271 }
272 }
273 }
274
275 // We don't need to check for pt stuff if there are no nonexistent titles
276 if($fld_protection && !empty($missing))
277 {
278 $this->resetQueryParams();
279 // Construct a custom WHERE clause that matches all titles in $missing
280 $lb = new LinkBatch($missing);
281 $this->addTables('protected_titles');
282 $this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry'));
283 $this->addWhere($lb->constructSet('pt', $db));
284 $res = $this->select(__METHOD__);
285 $prottitles = array();
286 while($row = $db->fetchObject($res)) {
287 $prottitles[$row->pt_namespace][$row->pt_title][] = array(
288 'type' => 'create',
289 'level' => $row->pt_create_perm,
290 'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601)
291 );
292 }
293 $db->freeResult($res);
294
295 $images = array();
296 $others = array();
297 foreach ($missing as $title)
298 if ($title->getNamespace() == NS_IMAGE)
299 $images[] = $title->getDbKey();
300 else
301 $others[] = $title;
302
303 if (count($others) != 0) {
304 $lb = new LinkBatch($others);
305 $this->resetQueryParams();
306 $this->addTables(array('page_restrictions', 'page', 'templatelinks'));
307 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
308 'page_title', 'page_namespace',
309 'tl_title', 'tl_namespace'));
310 $this->addWhere($lb->constructSet('tl', $db));
311 $this->addWhere('pr_page = page_id');
312 $this->addWhere('pr_page = tl_from');
313 $this->addWhereFld('pr_cascade', 1);
314
315 $res = $this->select(__METHOD__);
316 while($row = $db->fetchObject($res)) {
317 $source = Title::makeTitle($row->page_namespace, $row->page_title);
318 $a = array(
319 'type' => $row->pr_type,
320 'level' => $row->pr_level,
321 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
322 'source' => $source->getPrefixedText()
323 );
324 $prottitles[$row->tl_namespace][$row->tl_title][] = $a;
325 }
326 $db->freeResult($res);
327 }
328
329 if (count($images) != 0) {
330 $this->resetQueryParams();
331 $this->addTables(array('page_restrictions', 'page', 'imagelinks'));
332 $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
333 'page_title', 'page_namespace', 'il_to'));
334 $this->addWhere('pr_page = page_id');
335 $this->addWhere('pr_page = il_from');
336 $this->addWhereFld('pr_cascade', 1);
337 $this->addWhereFld('il_to', $images);
338
339 $res = $this->select(__METHOD__);
340 while($row = $db->fetchObject($res)) {
341 $source = Title::makeTitle($row->page_namespace, $row->page_title);
342 $a = array(
343 'type' => $row->pr_type,
344 'level' => $row->pr_level,
345 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
346 'source' => $source->getPrefixedText()
347 );
348 $prottitles[NS_IMAGE][$row->il_to][] = $a;
349 }
350 $db->freeResult($res);
351 }
352 }
353
354 // Run the talkid/subjectid query
355 if($fld_talkid || $fld_subjectid)
356 {
357 $talktitles = $subjecttitles =
358 $talkids = $subjectids = array();
359 $everything = array_merge($titles, $missing);
360 foreach($everything as $t)
361 {
362 if(MWNamespace::isTalk($t->getNamespace()))
363 {
364 if($fld_subjectid)
365 $subjecttitles[] = $t->getSubjectPage();
366 }
367 else if($fld_talkid)
368 $talktitles[] = $t->getTalkPage();
369 }
370 if(!empty($talktitles) || !empty($subjecttitles))
371 {
372 // Construct a custom WHERE clause that matches
373 // all titles in $talktitles and $subjecttitles
374 $lb = new LinkBatch(array_merge($talktitles, $subjecttitles));
375 $this->resetQueryParams();
376 $this->addTables('page');
377 $this->addFields(array('page_title', 'page_namespace', 'page_id'));
378 $this->addWhere($lb->constructSet('page', $db));
379 $res = $this->select(__METHOD__);
380 while($row = $db->fetchObject($res))
381 {
382 if(MWNamespace::isTalk($row->page_namespace))
383 $talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = $row->page_id;
384 else
385 $subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = $row->page_id;
386 }
387 }
388 }
389
390 foreach ( $titles as $pageid => $title ) {
391 $pageInfo = array (
392 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]),
393 'lastrevid' => intval($pageLatest[$pageid]),
394 'counter' => intval($pageCounter[$pageid]),
395 'length' => intval($pageLength[$pageid]),
396 );
397
398 if ($pageIsRedir[$pageid])
399 $pageInfo['redirect'] = '';
400
401 if ($pageIsNew[$pageid])
402 $pageInfo['new'] = '';
403
404 if (!is_null($params['token'])) {
405 $tokenFunctions = $this->getTokenFunctions();
406 foreach($params['token'] as $t)
407 {
408 $val = call_user_func($tokenFunctions[$t], $pageid, $title);
409 if($val === false)
410 $this->setWarning("Action '$t' is not allowed for the current user");
411 else
412 $pageInfo[$t . 'token'] = $val;
413 }
414 }
415
416 if($fld_protection) {
417 $pageInfo['protection'] = array();
418 if (isset($protections[$pageid])) {
419 $pageInfo['protection'] = $protections[$pageid];
420 $result->setIndexedTagName($pageInfo['protection'], 'pr');
421 }
422 # Also check old restrictions
423 if( $pageRestrictions[$pageid] ) {
424 foreach( explode( ':', trim( $pageRestrictions[$pageid] ) ) as $restrict ) {
425 $temp = explode( '=', trim( $restrict ) );
426 if(count($temp) == 1) {
427 // old old format should be treated as edit/move restriction
428 $restriction = trim( $temp[0] );
429 $pageInfo['protection'][] = array(
430 'type' => 'edit',
431 'level' => $restriction,
432 'expiry' => 'infinity',
433 );
434 $pageInfo['protection'][] = array(
435 'type' => 'move',
436 'level' => $restriction,
437 'expiry' => 'infinity',
438 );
439 } else {
440 $restriction = trim( $temp[1] );
441 $pageInfo['protection'][] = array(
442 'type' => $temp[0],
443 'level' => $restriction,
444 'expiry' => 'infinity',
445 );
446 }
447 }
448 $result->setIndexedTagName($pageInfo['protection'], 'pr');
449 }
450 }
451 if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()]))
452 $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()];
453 if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()]))
454 $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()];
455 if($fld_url) {
456 $pageInfo['fullurl'] = $title->getFullURL();
457 $pageInfo['editurl'] = $title->getFullURL('action=edit');
458 }
459 if($fld_readable)
460 if($title->userCanRead())
461 $pageInfo['readable'] = '';
462
463 $result->addValue(array (
464 'query',
465 'pages'
466 ), $pageid, $pageInfo);
467 }
468
469 // Get properties for missing titles if requested
470 if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid ||
471 $fld_url || $fld_readable)
472 {
473 $res = &$result->getData();
474 foreach($missing as $pageid => $title) {
475 if(!is_null($params['token']))
476 {
477 $tokenFunctions = $this->getTokenFunctions();
478 foreach($params['token'] as $t)
479 {
480 $val = call_user_func($tokenFunctions[$t], $pageid, $title);
481 if($val !== false)
482 $res['query']['pages'][$pageid][$t . 'token'] = $val;
483 }
484 }
485 if($fld_protection)
486 {
487 // Apparently the XML formatting code doesn't like array(null)
488 // This is painful to fix, so we'll just work around it
489 if(isset($prottitles[$title->getNamespace()][$title->getDBkey()]))
490 $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()];
491 else
492 $res['query']['pages'][$pageid]['protection'] = array();
493 $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr');
494 }
495 if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()]))
496 $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()];
497 if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()]))
498 $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()];
499 if($fld_url) {
500 $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL();
501 $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit');
502 }
503 if($fld_readable)
504 if($title->userCanRead())
505 $res['query']['pages'][$pageid]['readable'] = '';
506 }
507 }
508 }
509
510 public function getAllowedParams() {
511 return array (
512 'prop' => array (
513 ApiBase :: PARAM_DFLT => NULL,
514 ApiBase :: PARAM_ISMULTI => true,
515 ApiBase :: PARAM_TYPE => array (
516 'protection',
517 'talkid',
518 'subjectid',
519 'url',
520 'readable',
521 )),
522 'token' => array (
523 ApiBase :: PARAM_DFLT => NULL,
524 ApiBase :: PARAM_ISMULTI => true,
525 ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions())
526 )
527 );
528 }
529
530 public function getParamDescription() {
531 return array (
532 'prop' => array (
533 'Which additional properties to get:',
534 ' "protection" - List the protection level of each page',
535 ' "talkid" - The page ID of the talk page for each non-talk page',
536 ' "subjectid" - The page ID of the parent page for each talk page'
537 ),
538 'token' => 'Request a token to perform a data-modifying action on a page',
539 );
540 }
541
542
543 public function getDescription() {
544 return 'Get basic page information such as namespace, title, last touched date, ...';
545 }
546
547 protected function getExamples() {
548 return array (
549 'api.php?action=query&prop=info&titles=Main%20Page',
550 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
551 );
552 }
553
554 public function getVersion() {
555 return __CLASS__ . ': $Id$';
556 }
557 }