]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/api/ApiQueryInfo.php
MediaWiki 1.15.5
[autoinstallsdev/mediawiki.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         private $fld_protection = false, $fld_talkid = false,
39                 $fld_subjectid = false, $fld_url = false,
40                 $fld_readable = false;
41
42         public function __construct($query, $moduleName) {
43                 parent :: __construct($query, $moduleName, 'in');
44         }
45
46         public function requestExtraData($pageSet) {
47                 $pageSet->requestField('page_restrictions');
48                 $pageSet->requestField('page_is_redirect');
49                 $pageSet->requestField('page_is_new');
50                 $pageSet->requestField('page_counter');
51                 $pageSet->requestField('page_touched');
52                 $pageSet->requestField('page_latest');
53                 $pageSet->requestField('page_len');
54         }
55
56         /**
57          * Get an array mapping token names to their handler functions.
58          * The prototype for a token function is func($pageid, $title)
59          * it should return a token or false (permission denied)
60          * @return array(tokenname => function)
61          */
62         protected function getTokenFunctions() {
63                 // Don't call the hooks twice
64                 if(isset($this->tokenFunctions))
65                         return $this->tokenFunctions;
66
67                 // If we're in JSON callback mode, no tokens can be obtained
68                 if(!is_null($this->getMain()->getRequest()->getVal('callback')))
69                         return array();
70
71                 $this->tokenFunctions = array(
72                         'edit' => array( 'ApiQueryInfo', 'getEditToken' ),
73                         'delete' => array( 'ApiQueryInfo', 'getDeleteToken' ),
74                         'protect' => array( 'ApiQueryInfo', 'getProtectToken' ),
75                         'move' => array( 'ApiQueryInfo', 'getMoveToken' ),
76                         'block' => array( 'ApiQueryInfo', 'getBlockToken' ),
77                         'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
78                         'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
79                         'import' => array( 'ApiQueryInfo', 'getImportToken' ),
80                 );
81                 wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions));
82                 return $this->tokenFunctions;
83         }
84
85         public static function getEditToken($pageid, $title)
86         {
87                 // We could check for $title->userCan('edit') here,
88                 // but that's too expensive for this purpose
89                 // and would break caching
90                 global $wgUser;
91                 if(!$wgUser->isAllowed('edit'))
92                         return false;
93                 
94                 // The edit token is always the same, let's exploit that
95                 static $cachedEditToken = null;
96                 if(!is_null($cachedEditToken))
97                         return $cachedEditToken;
98
99                 $cachedEditToken = $wgUser->editToken();
100                 return $cachedEditToken;
101         }
102         
103         public static function getDeleteToken($pageid, $title)
104         {
105                 global $wgUser;
106                 if(!$wgUser->isAllowed('delete'))
107                         return false;                   
108
109                 static $cachedDeleteToken = null;
110                 if(!is_null($cachedDeleteToken))
111                         return $cachedDeleteToken;
112
113                 $cachedDeleteToken = $wgUser->editToken();
114                 return $cachedDeleteToken;
115         }
116
117         public static function getProtectToken($pageid, $title)
118         {
119                 global $wgUser;
120                 if(!$wgUser->isAllowed('protect'))
121                         return false;
122
123                 static $cachedProtectToken = null;
124                 if(!is_null($cachedProtectToken))
125                         return $cachedProtectToken;
126
127                 $cachedProtectToken = $wgUser->editToken();
128                 return $cachedProtectToken;
129         }
130
131         public static function getMoveToken($pageid, $title)
132         {
133                 global $wgUser;
134                 if(!$wgUser->isAllowed('move'))
135                         return false;
136
137                 static $cachedMoveToken = null;
138                 if(!is_null($cachedMoveToken))
139                         return $cachedMoveToken;
140
141                 $cachedMoveToken = $wgUser->editToken();
142                 return $cachedMoveToken;
143         }
144
145         public static function getBlockToken($pageid, $title)
146         {
147                 global $wgUser;
148                 if(!$wgUser->isAllowed('block'))
149                         return false;
150
151                 static $cachedBlockToken = null;
152                 if(!is_null($cachedBlockToken))
153                         return $cachedBlockToken;
154
155                 $cachedBlockToken = $wgUser->editToken();
156                 return $cachedBlockToken;
157         }
158
159         public static function getUnblockToken($pageid, $title)
160         {
161                 // Currently, this is exactly the same as the block token
162                 return self::getBlockToken($pageid, $title);
163         }
164
165         public static function getEmailToken($pageid, $title)
166         {
167                 global $wgUser;
168                 if(!$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser())
169                         return false;
170
171                 static $cachedEmailToken = null;
172                 if(!is_null($cachedEmailToken))
173                         return $cachedEmailToken;
174
175                 $cachedEmailToken = $wgUser->editToken();
176                 return $cachedEmailToken;
177         }
178         
179         public static function getImportToken($pageid, $title)
180         {
181                 global $wgUser;
182                 if(!$wgUser->isAllowed('import'))
183                         return false;
184
185                 static $cachedImportToken = null;
186                 if(!is_null($cachedImportToken))
187                         return $cachedImportToken;
188
189                 $cachedImportToken = $wgUser->editToken();
190                 return $cachedImportToken;
191         }
192
193         public function execute() {
194                 $this->params = $this->extractRequestParams();
195                 if(!is_null($this->params['prop'])) {
196                         $prop = array_flip($this->params['prop']);
197                         $this->fld_protection = isset($prop['protection']);
198                         $this->fld_talkid = isset($prop['talkid']);
199                         $this->fld_subjectid = isset($prop['subjectid']);
200                         $this->fld_url = isset($prop['url']);
201                         $this->fld_readable = isset($prop['readable']);
202                 }
203
204                 $pageSet = $this->getPageSet();
205                 $this->titles = $pageSet->getGoodTitles();
206                 $this->missing = $pageSet->getMissingTitles();
207                 $this->everything = $this->titles + $this->missing;
208                 $result = $this->getResult();
209
210                 uasort($this->everything, array('Title', 'compare'));
211                 if(!is_null($this->params['continue']))
212                 {
213                         // Throw away any titles we're gonna skip so they don't
214                         // clutter queries
215                         $cont = explode('|', $this->params['continue']);
216                         if(count($cont) != 2)
217                                 $this->dieUsage("Invalid continue param. You should pass the original " .
218                                                 "value returned by the previous query", "_badcontinue");
219                         $conttitle = Title::makeTitleSafe($cont[0], $cont[1]);
220                         foreach($this->everything as $pageid => $title)
221                         {
222                                 if(Title::compare($title, $conttitle) >= 0)
223                                         break;
224                                 unset($this->titles[$pageid]);
225                                 unset($this->missing[$pageid]);
226                                 unset($this->everything[$pageid]);
227                         }
228                 }
229
230                 $this->pageRestrictions = $pageSet->getCustomField('page_restrictions');
231                 $this->pageIsRedir = $pageSet->getCustomField('page_is_redirect');
232                 $this->pageIsNew = $pageSet->getCustomField('page_is_new');
233                 $this->pageCounter = $pageSet->getCustomField('page_counter');
234                 $this->pageTouched = $pageSet->getCustomField('page_touched');
235                 $this->pageLatest = $pageSet->getCustomField('page_latest');
236                 $this->pageLength = $pageSet->getCustomField('page_len');
237
238                 $db = $this->getDB();
239                 // Get protection info if requested
240                 if ($this->fld_protection)
241                         $this->getProtectionInfo();
242
243                 // Run the talkid/subjectid query if requested
244                 if($this->fld_talkid || $this->fld_subjectid)
245                         $this->getTSIDs();
246
247                 foreach($this->everything as $pageid => $title) {
248                         $pageInfo = $this->extractPageInfo($pageid, $title);
249                         $fit = $result->addValue(array (
250                                 'query',
251                                 'pages'
252                         ), $pageid, $pageInfo);
253                         if(!$fit)
254                         {
255                                 $this->setContinueEnumParameter('continue',
256                                                 $title->getNamespace() . '|' .
257                                                 $title->getText());
258                                 break;
259                         }
260                 }
261         }
262
263         /**
264          * Get a result array with information about a title
265          * @param $pageid int Page ID (negative for missing titles)
266          * @param $title Title object
267          * @return array
268          */
269         private function extractPageInfo($pageid, $title)
270         {
271                 $pageInfo = array();
272                 if($title->exists())
273                 {
274                         $pageInfo['touched'] = wfTimestamp(TS_ISO_8601, $this->pageTouched[$pageid]);
275                         $pageInfo['lastrevid'] = intval($this->pageLatest[$pageid]);
276                         $pageInfo['counter'] = intval($this->pageCounter[$pageid]);
277                         $pageInfo['length'] = intval($this->pageLength[$pageid]);
278                         if ($this->pageIsRedir[$pageid])
279                                 $pageInfo['redirect'] = '';
280                         if ($this->pageIsNew[$pageid])
281                                 $pageInfo['new'] = '';
282                 }
283
284                 if (!is_null($this->params['token'])) {
285                         $tokenFunctions = $this->getTokenFunctions();
286                         $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time());
287                         foreach($this->params['token'] as $t)
288                         {
289                                 $val = call_user_func($tokenFunctions[$t], $pageid, $title);
290                                 if($val === false)
291                                         $this->setWarning("Action '$t' is not allowed for the current user");
292                                 else
293                                         $pageInfo[$t . 'token'] = $val;
294                         }
295                 }
296
297                 if($this->fld_protection) {
298                         $pageInfo['protection'] = array();
299                         if (isset($this->protections[$title->getNamespace()][$title->getDBkey()])) 
300                                 $pageInfo['protection'] =
301                                         $this->protections[$title->getNamespace()][$title->getDBkey()];
302                         $this->getResult()->setIndexedTagName($pageInfo['protection'], 'pr');
303                 }
304                 if($this->fld_talkid && isset($this->talkids[$title->getNamespace()][$title->getDBKey()]))
305                         $pageInfo['talkid'] = $this->talkids[$title->getNamespace()][$title->getDBKey()];
306                 if($this->fld_subjectid && isset($this->subjectids[$title->getNamespace()][$title->getDBKey()]))
307                         $pageInfo['subjectid'] = $this->subjectids[$title->getNamespace()][$title->getDBKey()];
308                 if($this->fld_url) {
309                         $pageInfo['fullurl'] = $title->getFullURL();
310                         $pageInfo['editurl'] = $title->getFullURL('action=edit');
311                 }
312                 if($this->fld_readable)
313                         if($title->userCanRead())
314                                 $pageInfo['readable'] = '';
315                 return $pageInfo;
316         }
317
318         /**
319          * Get information about protections and put it in $protections
320          */
321         private function getProtectionInfo()
322         {
323                 $this->protections = array();
324                 $db = $this->getDB();
325
326                 // Get normal protections for existing titles
327                 if(count($this->titles))
328                 {
329                         $this->addTables(array('page_restrictions', 'page'));
330                         $this->addWhere('page_id=pr_page');
331                         $this->addFields(array('pr_page', 'pr_type', 'pr_level',
332                                         'pr_expiry', 'pr_cascade', 'page_namespace',
333                                         'page_title'));
334                         $this->addWhereFld('pr_page', array_keys($this->titles));
335
336                         $res = $this->select(__METHOD__);
337                         while($row = $db->fetchObject($res)) {
338                                 $a = array(
339                                         'type' => $row->pr_type,
340                                         'level' => $row->pr_level,
341                                         'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601)
342                                 );
343                                 if($row->pr_cascade)
344                                         $a['cascade'] = '';
345                                 $this->protections[$row->page_namespace][$row->page_title][] = $a;
346
347                                 # Also check old restrictions
348                                 if($this->pageRestrictions[$row->pr_page]) {
349                                         $restrictions = explode(':', trim($this->pageRestrictions[$row->pr_page]));
350                                         foreach($restrictions as $restrict) {
351                                                 $temp = explode('=', trim($restrict));
352                                                 if(count($temp) == 1) {
353                                                         // old old format should be treated as edit/move restriction
354                                                         $restriction = trim($temp[0]);
355
356                                                         if($restriction == '')
357                                                                 continue;
358                                                         $this->protections[$row->page_namespace][$row->page_title][] = array(
359                                                                 'type' => 'edit',
360                                                                 'level' => $restriction,
361                                                                 'expiry' => 'infinity',
362                                                         );
363                                                         $this->protections[$row->page_namespace][$row->page_title][] = array(
364                                                                 'type' => 'move',
365                                                                 'level' => $restriction,
366                                                                 'expiry' => 'infinity',
367                                                         );
368                                                 } else {
369                                                         $restriction = trim($temp[1]);
370                                                         if($restriction == '')
371                                                                 continue;
372                                                         $this->protections[$row->page_namespace][$row->page_title][] = array(
373                                                                 'type' => $temp[0],
374                                                                 'level' => $restriction,
375                                                                 'expiry' => 'infinity',
376                                                         );
377                                                 }
378                                         }
379                                 }
380                         }
381                         $db->freeResult($res);
382                 }
383
384                 // Get protections for missing titles
385                 if(count($this->missing))
386                 {
387                         $this->resetQueryParams();
388                         $lb = new LinkBatch($this->missing);
389                         $this->addTables('protected_titles');
390                         $this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry'));
391                         $this->addWhere($lb->constructSet('pt', $db));
392                         $res = $this->select(__METHOD__);
393                         while($row = $db->fetchObject($res)) {
394                                 $this->protections[$row->pt_namespace][$row->pt_title][] = array(
395                                         'type' => 'create',
396                                         'level' => $row->pt_create_perm,
397                                         'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601)
398                                 );
399                         }
400                         $db->freeResult($res);
401                 }
402
403                 // Cascading protections
404                 $images = $others = array();
405                 foreach ($this->everything as $title)
406                         if ($title->getNamespace() == NS_FILE)
407                                 $images[] = $title->getDBKey();
408                         else
409                                 $others[] = $title;
410
411                 if (count($others)) {
412                         // Non-images: check templatelinks
413                         $lb = new LinkBatch($others);
414                         $this->resetQueryParams();
415                         $this->addTables(array('page_restrictions', 'page', 'templatelinks'));
416                         $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
417                                         'page_title', 'page_namespace',
418                                         'tl_title', 'tl_namespace'));
419                         $this->addWhere($lb->constructSet('tl', $db));
420                         $this->addWhere('pr_page = page_id');
421                         $this->addWhere('pr_page = tl_from');
422                         $this->addWhereFld('pr_cascade', 1);
423
424                         $res = $this->select(__METHOD__);
425                         while($row = $db->fetchObject($res)) {
426                                 $source = Title::makeTitle($row->page_namespace, $row->page_title);
427                                 $this->protections[$row->tl_namespace][$row->tl_title][] = array(
428                                         'type' => $row->pr_type,
429                                         'level' => $row->pr_level,
430                                         'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601),
431                                         'source' => $source->getPrefixedText()
432                                 );
433                         }
434                         $db->freeResult($res);
435                 }
436
437                 if (count($images)) {
438                         // Images: check imagelinks
439                         $this->resetQueryParams();
440                         $this->addTables(array('page_restrictions', 'page', 'imagelinks'));
441                         $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 
442                                         'page_title', 'page_namespace', 'il_to'));
443                         $this->addWhere('pr_page = page_id');
444                         $this->addWhere('pr_page = il_from');
445                         $this->addWhereFld('pr_cascade', 1);
446                         $this->addWhereFld('il_to', $images);
447                         
448                         $res = $this->select(__METHOD__);
449                         while($row = $db->fetchObject($res)) {
450                                 $source = Title::makeTitle($row->page_namespace, $row->page_title);
451                                 $this->protections[NS_FILE][$row->il_to][] = array(
452                                         'type' => $row->pr_type,
453                                         'level' => $row->pr_level,
454                                         'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601),
455                                         'source' => $source->getPrefixedText()
456                                 );
457                         }
458                         $db->freeResult($res);
459                 }
460         }
461
462         /**
463          * Get talk page IDs (if requested) and subject page IDs (if requested)
464          * and put them in $talkids and $subjectids 
465          */
466         private function getTSIDs()
467         {
468                 $getTitles = $this->talkids = $this->subjectids = array();
469                 $db = $this->getDB();
470                 foreach($this->everything as $t)
471                 {
472                         if(MWNamespace::isTalk($t->getNamespace()))
473                         {
474                                 if($this->fld_subjectid)
475                                         $getTitles[] = $t->getSubjectPage();
476                         }
477                         else if($this->fld_talkid)
478                                 $getTitles[] = $t->getTalkPage();
479                 }
480                 if(!count($getTitles))
481                         return;
482                 
483                 // Construct a custom WHERE clause that matches
484                 // all titles in $getTitles
485                 $lb = new LinkBatch($getTitles);
486                 $this->resetQueryParams();
487                 $this->addTables('page');
488                 $this->addFields(array('page_title', 'page_namespace', 'page_id'));
489                 $this->addWhere($lb->constructSet('page', $db));
490                 $res = $this->select(__METHOD__);
491                 while($row = $db->fetchObject($res))
492                 {
493                         if(MWNamespace::isTalk($row->page_namespace))
494                                 $this->talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] =
495                                                 intval($row->page_id);
496                         else
497                                 $this->subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] =
498                                                 intval($row->page_id);
499                 }
500         }
501
502         public function getCacheMode( $params ) {
503                 $publicProps = array(
504                         'protection',
505                         'talkid',
506                         'subjectid',
507                         'url',
508                 );
509                 if ( !is_null( $params['prop'] ) ) {
510                         foreach ( $params['prop'] as $prop ) {
511                                 if ( !in_array( $prop, $publicProps ) ) {
512                                         return 'private';
513                                 }
514                         }
515                 }
516                 if ( !is_null( $params['token'] ) ) {
517                         return 'private';
518                 }
519                 return 'public';
520         }
521
522         public function getAllowedParams() {
523                 return array (
524                         'prop' => array (
525                                 ApiBase :: PARAM_DFLT => NULL,
526                                 ApiBase :: PARAM_ISMULTI => true,
527                                 ApiBase :: PARAM_TYPE => array (
528                                         'protection',
529                                         'talkid',
530                                         'subjectid',
531                                         'url',
532                                         'readable', # private
533                                         // If you add more properties here, please consider whether they 
534                                         // need to be added to getCacheMode()
535                                 )),
536                         'token' => array (
537                                 ApiBase :: PARAM_DFLT => NULL,
538                                 ApiBase :: PARAM_ISMULTI => true,
539                                 ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions())
540                         ),
541                         'continue' => null,
542                 );
543         }
544
545         public function getParamDescription() {
546                 return array (
547                         'prop' => array (
548                                 'Which additional properties to get:',
549                                 ' protection   - List the protection level of each page',
550                                 ' talkid       - The page ID of the talk page for each non-talk page',
551                                 ' subjectid    - The page ID of the parent page for each talk page'
552                         ),
553                         'token' => 'Request a token to perform a data-modifying action on a page',
554                         'continue' => 'When more results are available, use this to continue',
555                 );
556         }
557
558         public function getDescription() {
559                 return 'Get basic page information such as namespace, title, last touched date, ...';
560         }
561
562         protected function getExamples() {
563                 return array (
564                         'api.php?action=query&prop=info&titles=Main%20Page',
565                         'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
566                 );
567         }
568
569         public function getVersion() {
570                 return __CLASS__ . ': $Id: ApiQueryInfo.php 69986 2010-07-27 03:57:39Z tstarling $';
571         }
572 }