Merge "DatabaseMssql: Don't duplicate body of makeList()"
[mediawiki.git] / includes / api / ApiQueryRevisionsBase.php
blob281f8381cc82ea10b3db4b11bbab169716a09c3c
1 <?php
2 /**
5 * Created on Oct 3, 2014 as a split from ApiQueryRevisions
7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
24 * @file
27 /**
28 * A base class for functions common to producing a list of revisions.
30 * @ingroup API
32 abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
34 protected $limit, $diffto, $difftotext, $expandTemplates, $generateXML, $section,
35 $parseContent, $fetchContent, $contentFormat, $setParsedLimit = true;
37 protected $fld_ids = false, $fld_flags = false, $fld_timestamp = false,
38 $fld_size = false, $fld_sha1 = false, $fld_comment = false,
39 $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
40 $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
42 public function execute() {
43 $this->run();
46 public function executeGenerator( $resultPageSet ) {
47 $this->run( $resultPageSet );
50 /**
51 * @param ApiPageSet $resultPageSet
52 * @return void
54 abstract protected function run( ApiPageSet $resultPageSet = null );
56 /**
57 * Parse the parameters into the various instance fields.
59 * @param array $params
61 protected function parseParameters( $params ) {
62 if ( !is_null( $params['difftotext'] ) ) {
63 $this->difftotext = $params['difftotext'];
64 } elseif ( !is_null( $params['diffto'] ) ) {
65 if ( $params['diffto'] == 'cur' ) {
66 $params['diffto'] = 0;
68 if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
69 && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
70 ) {
71 $p = $this->getModulePrefix();
72 $this->dieUsage(
73 "{$p}diffto must be set to a non-negative number, \"prev\", \"next\" or \"cur\"",
74 'diffto'
77 // Check whether the revision exists and is readable,
78 // DifferenceEngine returns a rather ambiguous empty
79 // string if that's not the case
80 if ( $params['diffto'] != 0 ) {
81 $difftoRev = Revision::newFromId( $params['diffto'] );
82 if ( !$difftoRev ) {
83 $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
85 if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
86 $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
87 $params['diffto'] = null;
90 $this->diffto = $params['diffto'];
93 $prop = array_flip( $params['prop'] );
95 $this->fld_ids = isset( $prop['ids'] );
96 $this->fld_flags = isset( $prop['flags'] );
97 $this->fld_timestamp = isset( $prop['timestamp'] );
98 $this->fld_comment = isset( $prop['comment'] );
99 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
100 $this->fld_size = isset( $prop['size'] );
101 $this->fld_sha1 = isset( $prop['sha1'] );
102 $this->fld_content = isset( $prop['content'] );
103 $this->fld_contentmodel = isset( $prop['contentmodel'] );
104 $this->fld_userid = isset( $prop['userid'] );
105 $this->fld_user = isset( $prop['user'] );
106 $this->fld_tags = isset( $prop['tags'] );
108 if ( !empty( $params['contentformat'] ) ) {
109 $this->contentFormat = $params['contentformat'];
112 $this->limit = $params['limit'];
114 $this->fetchContent = $this->fld_content || !is_null( $this->diffto )
115 || !is_null( $this->difftotext );
117 $smallLimit = false;
118 if ( $this->fetchContent ) {
119 $smallLimit = true;
120 $this->expandTemplates = $params['expandtemplates'];
121 $this->generateXML = $params['generatexml'];
122 $this->parseContent = $params['parse'];
123 if ( $this->parseContent ) {
124 // Must manually initialize unset limit
125 if ( is_null( $this->limit ) ) {
126 $this->limit = 1;
129 if ( isset( $params['section'] ) ) {
130 $this->section = $params['section'];
131 } else {
132 $this->section = false;
136 $userMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
137 $botMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
138 if ( $this->limit == 'max' ) {
139 $this->limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
140 if ( $this->setParsedLimit ) {
141 $this->getResult()->setParsedLimit( $this->getModuleName(), $this->limit );
145 if ( is_null( $this->limit ) ) {
146 $this->limit = 10;
148 $this->validateLimit( 'limit', $this->limit, 1, $userMax, $botMax );
152 * Extract information from the Revision
154 * @param Revision $revision
155 * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set
156 * @return array
158 protected function extractRevisionInfo( Revision $revision, $row ) {
159 $title = $revision->getTitle();
160 $user = $this->getUser();
161 $vals = array();
162 $anyHidden = false;
164 if ( $this->fld_ids ) {
165 $vals['revid'] = intval( $revision->getId() );
166 if ( !is_null( $revision->getParentId() ) ) {
167 $vals['parentid'] = intval( $revision->getParentId() );
171 if ( $this->fld_flags && $revision->isMinor() ) {
172 $vals['minor'] = '';
175 if ( $this->fld_user || $this->fld_userid ) {
176 if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
177 $vals['userhidden'] = '';
178 $anyHidden = true;
180 if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
181 if ( $this->fld_user ) {
182 $vals['user'] = $revision->getUserText( Revision::RAW );
184 $userid = $revision->getUser( Revision::RAW );
185 if ( !$userid ) {
186 $vals['anon'] = '';
189 if ( $this->fld_userid ) {
190 $vals['userid'] = $userid;
195 if ( $this->fld_timestamp ) {
196 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
199 if ( $this->fld_size ) {
200 if ( !is_null( $revision->getSize() ) ) {
201 $vals['size'] = intval( $revision->getSize() );
202 } else {
203 $vals['size'] = 0;
207 if ( $this->fld_sha1 ) {
208 if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
209 $vals['sha1hidden'] = '';
210 $anyHidden = true;
212 if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
213 if ( $revision->getSha1() != '' ) {
214 $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
215 } else {
216 $vals['sha1'] = '';
221 if ( $this->fld_contentmodel ) {
222 $vals['contentmodel'] = $revision->getContentModel();
225 if ( $this->fld_comment || $this->fld_parsedcomment ) {
226 if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
227 $vals['commenthidden'] = '';
228 $anyHidden = true;
230 if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
231 $comment = $revision->getComment( Revision::RAW );
233 if ( $this->fld_comment ) {
234 $vals['comment'] = $comment;
237 if ( $this->fld_parsedcomment ) {
238 $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
243 if ( $this->fld_tags ) {
244 if ( $row->ts_tags ) {
245 $tags = explode( ',', $row->ts_tags );
246 $this->getResult()->setIndexedTagName( $tags, 'tag' );
247 $vals['tags'] = $tags;
248 } else {
249 $vals['tags'] = array();
253 $content = null;
254 global $wgParser;
255 if ( $this->fetchContent ) {
256 $content = $revision->getContent( Revision::FOR_THIS_USER, $this->getUser() );
257 // Expand templates after getting section content because
258 // template-added sections don't count and Parser::preprocess()
259 // will have less input
260 if ( $content && $this->section !== false ) {
261 $content = $content->getSection( $this->section, false );
262 if ( !$content ) {
263 $this->dieUsage(
264 "There is no section {$this->section} in r" . $revision->getId(),
265 'nosuchsection'
269 if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
270 $vals['texthidden'] = '';
271 $anyHidden = true;
272 } elseif ( !$content ) {
273 $vals['textmissing'] = '';
276 if ( $this->fld_content && $content ) {
277 $text = null;
279 if ( $this->generateXML ) {
280 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
281 $t = $content->getNativeData(); # note: don't set $text
283 $wgParser->startExternalParse(
284 $title,
285 ParserOptions::newFromContext( $this->getContext() ),
286 Parser::OT_PREPROCESS
288 $dom = $wgParser->preprocessToDom( $t );
289 if ( is_callable( array( $dom, 'saveXML' ) ) ) {
290 $xml = $dom->saveXML();
291 } else {
292 $xml = $dom->__toString();
294 $vals['parsetree'] = $xml;
295 } else {
296 $vals['badcontentformatforparsetree'] = '';
297 $this->setWarning( "Conversion to XML is supported for wikitext only, " .
298 $title->getPrefixedDBkey() .
299 " uses content model " . $content->getModel() );
303 if ( $this->expandTemplates && !$this->parseContent ) {
304 #XXX: implement template expansion for all content types in ContentHandler?
305 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
306 $text = $content->getNativeData();
308 $text = $wgParser->preprocess(
309 $text,
310 $title,
311 ParserOptions::newFromContext( $this->getContext() )
313 } else {
314 $this->setWarning( "Template expansion is supported for wikitext only, " .
315 $title->getPrefixedDBkey() .
316 " uses content model " . $content->getModel() );
317 $vals['badcontentformat'] = '';
318 $text = false;
321 if ( $this->parseContent ) {
322 $po = $content->getParserOutput(
323 $title,
324 $revision->getId(),
325 ParserOptions::newFromContext( $this->getContext() )
327 $text = $po->getText();
330 if ( $text === null ) {
331 $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
332 $model = $content->getModel();
334 if ( !$content->isSupportedFormat( $format ) ) {
335 $name = $title->getPrefixedDBkey();
336 $this->setWarning( "The requested format {$this->contentFormat} is not " .
337 "supported for content model $model used by $name" );
338 $vals['badcontentformat'] = '';
339 $text = false;
340 } else {
341 $text = $content->serialize( $format );
342 // always include format and model.
343 // Format is needed to deserialize, model is needed to interpret.
344 $vals['contentformat'] = $format;
345 $vals['contentmodel'] = $model;
349 if ( $text !== false ) {
350 ApiResult::setContent( $vals, $text );
354 if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
355 static $n = 0; // Number of uncached diffs we've had
357 if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
358 $vals['diff'] = array();
359 $context = new DerivativeContext( $this->getContext() );
360 $context->setTitle( $title );
361 $handler = $revision->getContentHandler();
363 if ( !is_null( $this->difftotext ) ) {
364 $model = $title->getContentModel();
366 if ( $this->contentFormat
367 && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
369 $name = $title->getPrefixedDBkey();
370 $this->setWarning( "The requested format {$this->contentFormat} is not " .
371 "supported for content model $model used by $name" );
372 $vals['diff']['badcontentformat'] = '';
373 $engine = null;
374 } else {
375 $difftocontent = ContentHandler::makeContent(
376 $this->difftotext,
377 $title,
378 $model,
379 $this->contentFormat
382 $engine = $handler->createDifferenceEngine( $context );
383 $engine->setContent( $content, $difftocontent );
385 } else {
386 $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
387 $vals['diff']['from'] = $engine->getOldid();
388 $vals['diff']['to'] = $engine->getNewid();
390 if ( $engine ) {
391 $difftext = $engine->getDiffBody();
392 ApiResult::setContent( $vals['diff'], $difftext );
393 if ( !$engine->wasCacheHit() ) {
394 $n++;
397 } else {
398 $vals['diff']['notcached'] = '';
402 if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
403 $vals['suppressed'] = '';
406 return $vals;
409 public function getCacheMode( $params ) {
410 if ( $this->userCanSeeRevDel() ) {
411 return 'private';
414 return 'public';
417 public function getAllowedParams() {
418 return array(
419 'prop' => array(
420 ApiBase::PARAM_ISMULTI => true,
421 ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
422 ApiBase::PARAM_TYPE => array(
423 'ids',
424 'flags',
425 'timestamp',
426 'user',
427 'userid',
428 'size',
429 'sha1',
430 'contentmodel',
431 'comment',
432 'parsedcomment',
433 'content',
434 'tags'
436 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop',
438 'limit' => array(
439 ApiBase::PARAM_TYPE => 'limit',
440 ApiBase::PARAM_MIN => 1,
441 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
442 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
443 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit',
445 'expandtemplates' => array(
446 ApiBase::PARAM_DFLT => false,
447 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates',
449 'generatexml' => array(
450 ApiBase::PARAM_DFLT => false,
451 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml',
453 'parse' => array(
454 ApiBase::PARAM_DFLT => false,
455 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse',
457 'section' => array(
458 ApiBase::PARAM_DFLT => null,
459 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section',
461 'diffto' => array(
462 ApiBase::PARAM_DFLT => null,
463 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto',
465 'difftotext' => array(
466 ApiBase::PARAM_DFLT => null,
467 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext',
469 'contentformat' => array(
470 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
471 ApiBase::PARAM_DFLT => null,
472 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat',