Use existing CSS classes for label and input elements in table for proper aligning
[lhc/web/wiklou.git] / includes / Revision.php
1 <?php
2 /**
3 * @todo document
4 */
5
6 /**
7 * @todo document
8 */
9 class Revision {
10 const DELETED_TEXT = 1;
11 const DELETED_COMMENT = 2;
12 const DELETED_USER = 4;
13 const DELETED_RESTRICTED = 8;
14
15 /**
16 * Load a page revision from a given revision ID number.
17 * Returns null if no such revision can be found.
18 *
19 * @param int $id
20 * @access public
21 * @static
22 */
23 public static function newFromId( $id ) {
24 return Revision::newFromConds(
25 array( 'page_id=rev_page',
26 'rev_id' => intval( $id ) ) );
27 }
28
29 /**
30 * Load either the current, or a specified, revision
31 * that's attached to a given title. If not attached
32 * to that title, will return null.
33 *
34 * @param Title $title
35 * @param int $id
36 * @return Revision
37 * @access public
38 * @static
39 */
40 public static function newFromTitle( &$title, $id = 0 ) {
41 if( $id ) {
42 $matchId = intval( $id );
43 } else {
44 $matchId = 'page_latest';
45 }
46 return Revision::newFromConds(
47 array( "rev_id=$matchId",
48 'page_id=rev_page',
49 'page_namespace' => $title->getNamespace(),
50 'page_title' => $title->getDBkey() ) );
51 }
52
53 /**
54 * Load a page revision from a given revision ID number.
55 * Returns null if no such revision can be found.
56 *
57 * @param Database $db
58 * @param int $id
59 * @access public
60 * @static
61 */
62 public static function loadFromId( &$db, $id ) {
63 return Revision::loadFromConds( $db,
64 array( 'page_id=rev_page',
65 'rev_id' => intval( $id ) ) );
66 }
67
68 /**
69 * Load either the current, or a specified, revision
70 * that's attached to a given page. If not attached
71 * to that page, will return null.
72 *
73 * @param Database $db
74 * @param int $pageid
75 * @param int $id
76 * @return Revision
77 * @access public
78 * @static
79 */
80 public static function loadFromPageId( $db, $pageid, $id = 0 ) {
81 $conds=array('page_id=rev_page','rev_page'=>intval( $pageid ), 'page_id'=>intval( $pageid ));
82 if( $id ) {
83 $conds['rev_id']=intval($id);
84 } else {
85 $conds[]='rev_id=page_latest';
86 }
87 return Revision::loadFromConds( $db, $conds );
88 }
89
90 /**
91 * Load either the current, or a specified, revision
92 * that's attached to a given page. If not attached
93 * to that page, will return null.
94 *
95 * @param Database $db
96 * @param Title $title
97 * @param int $id
98 * @return Revision
99 * @access public
100 * @static
101 */
102 public static function loadFromTitle( &$db, $title, $id = 0 ) {
103 if( $id ) {
104 $matchId = intval( $id );
105 } else {
106 $matchId = 'page_latest';
107 }
108 return Revision::loadFromConds(
109 $db,
110 array( "rev_id=$matchId",
111 'page_id=rev_page',
112 'page_namespace' => $title->getNamespace(),
113 'page_title' => $title->getDBkey() ) );
114 }
115
116 /**
117 * Load the revision for the given title with the given timestamp.
118 * WARNING: Timestamps may in some circumstances not be unique,
119 * so this isn't the best key to use.
120 *
121 * @param Database $db
122 * @param Title $title
123 * @param string $timestamp
124 * @return Revision
125 * @access public
126 * @static
127 */
128 public static function loadFromTimestamp( &$db, &$title, $timestamp ) {
129 return Revision::loadFromConds(
130 $db,
131 array( 'rev_timestamp' => $db->timestamp( $timestamp ),
132 'page_id=rev_page',
133 'page_namespace' => $title->getNamespace(),
134 'page_title' => $title->getDBkey() ) );
135 }
136
137 /**
138 * Given a set of conditions, fetch a revision.
139 *
140 * @param array $conditions
141 * @return Revision
142 * @access private
143 * @static
144 */
145 private static function newFromConds( $conditions ) {
146 $db = wfGetDB( DB_SLAVE );
147 $row = Revision::loadFromConds( $db, $conditions );
148 if( is_null( $row ) ) {
149 $dbw = wfGetDB( DB_MASTER );
150 $row = Revision::loadFromConds( $dbw, $conditions );
151 }
152 return $row;
153 }
154
155 /**
156 * Given a set of conditions, fetch a revision from
157 * the given database connection.
158 *
159 * @param Database $db
160 * @param array $conditions
161 * @return Revision
162 * @access private
163 * @static
164 */
165 private static function loadFromConds( $db, $conditions ) {
166 $res = Revision::fetchFromConds( $db, $conditions );
167 if( $res ) {
168 $row = $res->fetchObject();
169 $res->free();
170 if( $row ) {
171 $ret = new Revision( $row );
172 return $ret;
173 }
174 }
175 $ret = null;
176 return $ret;
177 }
178
179 /**
180 * Return a wrapper for a series of database rows to
181 * fetch all of a given page's revisions in turn.
182 * Each row can be fed to the constructor to get objects.
183 *
184 * @param Title $title
185 * @return ResultWrapper
186 * @access public
187 * @static
188 */
189 public static function fetchAllRevisions( &$title ) {
190 return Revision::fetchFromConds(
191 wfGetDB( DB_SLAVE ),
192 array( 'page_namespace' => $title->getNamespace(),
193 'page_title' => $title->getDBkey(),
194 'page_id=rev_page' ) );
195 }
196
197 /**
198 * Return a wrapper for a series of database rows to
199 * fetch all of a given page's revisions in turn.
200 * Each row can be fed to the constructor to get objects.
201 *
202 * @param Title $title
203 * @return ResultWrapper
204 * @access public
205 * @static
206 */
207 public static function fetchRevision( &$title ) {
208 return Revision::fetchFromConds(
209 wfGetDB( DB_SLAVE ),
210 array( 'rev_id=page_latest',
211 'page_namespace' => $title->getNamespace(),
212 'page_title' => $title->getDBkey(),
213 'page_id=rev_page' ) );
214 }
215
216 /**
217 * Given a set of conditions, return a ResultWrapper
218 * which will return matching database rows with the
219 * fields necessary to build Revision objects.
220 *
221 * @param Database $db
222 * @param array $conditions
223 * @return ResultWrapper
224 * @access private
225 * @static
226 */
227 private static function fetchFromConds( $db, $conditions ) {
228 $res = $db->select(
229 array( 'page', 'revision' ),
230 array( 'page_namespace',
231 'page_title',
232 'page_latest',
233 'rev_id',
234 'rev_page',
235 'rev_text_id',
236 'rev_comment',
237 'rev_user_text',
238 'rev_user',
239 'rev_minor_edit',
240 'rev_timestamp',
241 'rev_deleted',
242 'rev_len' ),
243 $conditions,
244 'Revision::fetchRow',
245 array( 'LIMIT' => 1 ) );
246 $ret = $db->resultObject( $res );
247 return $ret;
248 }
249
250 /**
251 * Return the list of revision fields that should be selected to create
252 * a new revision.
253 */
254 static function selectFields() {
255 return array(
256 'rev_id',
257 'rev_page',
258 'rev_text_id',
259 'rev_timestamp',
260 'rev_comment',
261 'rev_minor_edit',
262 'rev_user',
263 'rev_user_text,'.
264 'rev_deleted',
265 'rev_len'
266 );
267 }
268
269 /**
270 * @param object $row
271 * @access private
272 */
273 function Revision( $row ) {
274 if( is_object( $row ) ) {
275 $this->mId = intval( $row->rev_id );
276 $this->mPage = intval( $row->rev_page );
277 $this->mTextId = intval( $row->rev_text_id );
278 $this->mComment = $row->rev_comment;
279 $this->mUserText = $row->rev_user_text;
280 $this->mUser = intval( $row->rev_user );
281 $this->mMinorEdit = intval( $row->rev_minor_edit );
282 $this->mTimestamp = $row->rev_timestamp;
283 $this->mDeleted = intval( $row->rev_deleted );
284
285 if( !isset( $row->rev_len ) || is_null( $row->rev_len ) )
286 $this->mSize = null;
287 else
288 $this->mSize = intval( $row->rev_len );
289
290 if( isset( $row->page_latest ) ) {
291 $this->mCurrent = ( $row->rev_id == $row->page_latest );
292 $this->mTitle = Title::makeTitle( $row->page_namespace,
293 $row->page_title );
294 } else {
295 $this->mCurrent = false;
296 $this->mTitle = null;
297 }
298
299 // Lazy extraction...
300 $this->mText = null;
301 if( isset( $row->old_text ) ) {
302 $this->mTextRow = $row;
303 } else {
304 // 'text' table row entry will be lazy-loaded
305 $this->mTextRow = null;
306 }
307 } elseif( is_array( $row ) ) {
308 // Build a new revision to be saved...
309 global $wgUser;
310
311 $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
312 $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
313 $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
314 $this->mUserText = isset( $row['user_text'] ) ? strval( $row['user_text'] ) : $wgUser->getName();
315 $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
316 $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
317 $this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestamp( TS_MW );
318 $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
319 $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
320
321 // Enforce spacing trimming on supplied text
322 $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
323 $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
324 $this->mTextRow = null;
325
326 $this->mTitle = null; # Load on demand if needed
327 $this->mCurrent = false;
328 # If we still have no len_size, see it we have the text to figure it out
329 if ( !$this->mSize )
330 $this->mSize = is_null($this->mText) ? null : strlen($this->mText);
331 } else {
332 throw new MWException( 'Revision constructor passed invalid row format.' );
333 }
334 }
335
336 /**#@+
337 * @access public
338 */
339
340 /**
341 * @return int
342 */
343 function getId() {
344 return $this->mId;
345 }
346
347 /**
348 * @return int
349 */
350 function getTextId() {
351 return $this->mTextId;
352 }
353
354 /**
355 * Returns the length of the text in this revision, or null if unknown.
356 */
357 function getSize() {
358 return $this->mSize;
359 }
360
361 /**
362 * Returns the title of the page associated with this entry.
363 * @return Title
364 */
365 function getTitle() {
366 if( isset( $this->mTitle ) ) {
367 return $this->mTitle;
368 }
369 $dbr = wfGetDB( DB_SLAVE );
370 $row = $dbr->selectRow(
371 array( 'page', 'revision' ),
372 array( 'page_namespace', 'page_title' ),
373 array( 'page_id=rev_page',
374 'rev_id' => $this->mId ),
375 'Revision::getTitle' );
376 if( $row ) {
377 $this->mTitle = Title::makeTitle( $row->page_namespace,
378 $row->page_title );
379 }
380 return $this->mTitle;
381 }
382
383 /**
384 * Set the title of the revision
385 * @param Title $title
386 */
387 function setTitle( $title ) {
388 $this->mTitle = $title;
389 }
390
391 /**
392 * @return int
393 */
394 function getPage() {
395 return $this->mPage;
396 }
397
398 /**
399 * Fetch revision's user id if it's available to all users
400 * @return int
401 */
402 function getUser() {
403 if( $this->isDeleted( self::DELETED_USER ) ) {
404 return 0;
405 } else {
406 return $this->mUser;
407 }
408 }
409
410 /**
411 * Fetch revision's user id without regard for the current user's permissions
412 * @return string
413 */
414 function getRawUser() {
415 return $this->mUser;
416 }
417
418 /**
419 * Fetch revision's username if it's available to all users
420 * @return string
421 */
422 function getUserText() {
423 if( $this->isDeleted( self::DELETED_USER ) ) {
424 return "";
425 } else {
426 return $this->mUserText;
427 }
428 }
429
430 /**
431 * Fetch revision's username without regard for view restrictions
432 * @return string
433 */
434 function getRawUserText() {
435 return $this->mUserText;
436 }
437
438 /**
439 * Fetch revision comment if it's available to all users
440 * @return string
441 */
442 function getComment() {
443 if( $this->isDeleted( self::DELETED_COMMENT ) ) {
444 return "";
445 } else {
446 return $this->mComment;
447 }
448 }
449
450 /**
451 * Fetch revision comment without regard for the current user's permissions
452 * @return string
453 */
454 function getRawComment() {
455 return $this->mComment;
456 }
457
458 /**
459 * @return bool
460 */
461 function isMinor() {
462 return (bool)$this->mMinorEdit;
463 }
464
465 /**
466 * int $field one of DELETED_* bitfield constants
467 * @return bool
468 */
469 function isDeleted( $field ) {
470 return ($this->mDeleted & $field) == $field;
471 }
472
473 /**
474 * Fetch revision text if it's available to all users
475 * @return string
476 */
477 function getText() {
478 if( $this->isDeleted( self::DELETED_TEXT ) ) {
479 return "";
480 } else {
481 return $this->getRawText();
482 }
483 }
484
485 /**
486 * Fetch revision text without regard for view restrictions
487 * @return string
488 */
489 function getRawText() {
490 if( is_null( $this->mText ) ) {
491 // Revision text is immutable. Load on demand:
492 $this->mText = $this->loadText();
493 }
494 return $this->mText;
495 }
496
497 /**
498 * Fetch revision text if it's available to THIS user
499 * @return string
500 */
501 function revText() {
502 if( !$this->userCan( self::DELETED_TEXT ) ) {
503 return "";
504 } else {
505 return $this->getRawText();
506 }
507 }
508
509 /**
510 * @return string
511 */
512 function getTimestamp() {
513 return wfTimestamp(TS_MW, $this->mTimestamp);
514 }
515
516 /**
517 * @return bool
518 */
519 function isCurrent() {
520 return $this->mCurrent;
521 }
522
523 /**
524 * @return Revision
525 */
526 function getPrevious() {
527 $prev = $this->mTitle->getPreviousRevisionID( $this->mId );
528 if ( $prev ) {
529 return Revision::newFromTitle( $this->mTitle, $prev );
530 } else {
531 return null;
532 }
533 }
534
535 /**
536 * @return Revision
537 */
538 function getNext() {
539 $next = $this->mTitle->getNextRevisionID( $this->mId );
540 if ( $next ) {
541 return Revision::newFromTitle( $this->mTitle, $next );
542 } else {
543 return null;
544 }
545 }
546 /**#@-*/
547
548 /**
549 * Get revision text associated with an old or archive row
550 * $row is usually an object from wfFetchRow(), both the flags and the text
551 * field must be included
552 *
553 * @param integer $row Id of a row
554 * @param string $prefix table prefix (default 'old_')
555 * @return string $text|false the text requested
556 */
557 public static function getRevisionText( $row, $prefix = 'old_' ) {
558 wfProfileIn( __METHOD__ );
559
560 # Get data
561 $textField = $prefix . 'text';
562 $flagsField = $prefix . 'flags';
563
564 if( isset( $row->$flagsField ) ) {
565 $flags = explode( ',', $row->$flagsField );
566 } else {
567 $flags = array();
568 }
569
570 if( isset( $row->$textField ) ) {
571 $text = $row->$textField;
572 } else {
573 wfProfileOut( __METHOD__ );
574 return false;
575 }
576
577 # Use external methods for external objects, text in table is URL-only then
578 if ( in_array( 'external', $flags ) ) {
579 $url=$text;
580 @list(/* $proto */,$path)=explode('://',$url,2);
581 if ($path=="") {
582 wfProfileOut( __METHOD__ );
583 return false;
584 }
585 $text=ExternalStore::fetchFromURL($url);
586 }
587
588 // If the text was fetched without an error, convert it
589 if ( $text !== false ) {
590 if( in_array( 'gzip', $flags ) ) {
591 # Deal with optional compression of archived pages.
592 # This can be done periodically via maintenance/compressOld.php, and
593 # as pages are saved if $wgCompressRevisions is set.
594 $text = gzinflate( $text );
595 }
596
597 if( in_array( 'object', $flags ) ) {
598 # Generic compressed storage
599 $obj = unserialize( $text );
600 if ( !is_object( $obj ) ) {
601 // Invalid object
602 wfProfileOut( __METHOD__ );
603 return false;
604 }
605 $text = $obj->getText();
606 }
607
608 global $wgLegacyEncoding;
609 if( $wgLegacyEncoding && !in_array( 'utf-8', $flags ) ) {
610 # Old revisions kept around in a legacy encoding?
611 # Upconvert on demand.
612 global $wgInputEncoding, $wgContLang;
613 $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
614 }
615 }
616 wfProfileOut( __METHOD__ );
617 return $text;
618 }
619
620 /**
621 * If $wgCompressRevisions is enabled, we will compress data.
622 * The input string is modified in place.
623 * Return value is the flags field: contains 'gzip' if the
624 * data is compressed, and 'utf-8' if we're saving in UTF-8
625 * mode.
626 *
627 * @param mixed $text reference to a text
628 * @return string
629 */
630 static function compressRevisionText( &$text ) {
631 global $wgCompressRevisions;
632 $flags = array();
633
634 # Revisions not marked this way will be converted
635 # on load if $wgLegacyCharset is set in the future.
636 $flags[] = 'utf-8';
637
638 if( $wgCompressRevisions ) {
639 if( function_exists( 'gzdeflate' ) ) {
640 $text = gzdeflate( $text );
641 $flags[] = 'gzip';
642 } else {
643 wfDebug( "Revision::compressRevisionText() -- no zlib support, not compressing\n" );
644 }
645 }
646 return implode( ',', $flags );
647 }
648
649 /**
650 * Insert a new revision into the database, returning the new revision ID
651 * number on success and dies horribly on failure.
652 *
653 * @param Database $dbw
654 * @return int
655 */
656 function insertOn( &$dbw ) {
657 global $wgDefaultExternalStore;
658
659 wfProfileIn( __METHOD__ );
660
661 $data = $this->mText;
662 $flags = Revision::compressRevisionText( $data );
663
664 # Write to external storage if required
665 if ( $wgDefaultExternalStore ) {
666 if ( is_array( $wgDefaultExternalStore ) ) {
667 // Distribute storage across multiple clusters
668 $store = $wgDefaultExternalStore[mt_rand(0, count( $wgDefaultExternalStore ) - 1)];
669 } else {
670 $store = $wgDefaultExternalStore;
671 }
672 // Store and get the URL
673 $data = ExternalStore::insert( $store, $data );
674 if ( !$data ) {
675 # This should only happen in the case of a configuration error, where the external store is not valid
676 throw new MWException( "Unable to store text to external storage $store" );
677 }
678 if ( $flags ) {
679 $flags .= ',';
680 }
681 $flags .= 'external';
682 }
683
684 # Record the text (or external storage URL) to the text table
685 if( !isset( $this->mTextId ) ) {
686 $old_id = $dbw->nextSequenceValue( 'text_old_id_val' );
687 $dbw->insert( 'text',
688 array(
689 'old_id' => $old_id,
690 'old_text' => $data,
691 'old_flags' => $flags,
692 ), __METHOD__
693 );
694 $this->mTextId = $dbw->insertId();
695 }
696
697 # Record the edit in revisions
698 $rev_id = isset( $this->mId )
699 ? $this->mId
700 : $dbw->nextSequenceValue( 'rev_rev_id_val' );
701 $dbw->insert( 'revision',
702 array(
703 'rev_id' => $rev_id,
704 'rev_page' => $this->mPage,
705 'rev_text_id' => $this->mTextId,
706 'rev_comment' => $this->mComment,
707 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
708 'rev_user' => $this->mUser,
709 'rev_user_text' => $this->mUserText,
710 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
711 'rev_deleted' => $this->mDeleted,
712 'rev_len' => $this->mSize,
713 ), __METHOD__
714 );
715
716 $this->mId = !is_null($rev_id) ? $rev_id : $dbw->insertId();
717 wfProfileOut( __METHOD__ );
718 return $this->mId;
719 }
720
721 /**
722 * Lazy-load the revision's text.
723 * Currently hardcoded to the 'text' table storage engine.
724 *
725 * @return string
726 * @access private
727 */
728 function loadText() {
729 wfProfileIn( __METHOD__ );
730
731 // Caching may be beneficial for massive use of external storage
732 global $wgRevisionCacheExpiry, $wgMemc;
733 $key = wfMemcKey( 'revisiontext', 'textid', $this->getTextId() );
734 if( $wgRevisionCacheExpiry ) {
735 $text = $wgMemc->get( $key );
736 if( is_string( $text ) ) {
737 wfProfileOut( __METHOD__ );
738 return $text;
739 }
740 }
741
742 // If we kept data for lazy extraction, use it now...
743 if ( isset( $this->mTextRow ) ) {
744 $row = $this->mTextRow;
745 $this->mTextRow = null;
746 } else {
747 $row = null;
748 }
749
750 if( !$row ) {
751 // Text data is immutable; check slaves first.
752 $dbr = wfGetDB( DB_SLAVE );
753 $row = $dbr->selectRow( 'text',
754 array( 'old_text', 'old_flags' ),
755 array( 'old_id' => $this->getTextId() ),
756 __METHOD__ );
757 }
758
759 if( !$row ) {
760 // Possible slave lag!
761 $dbw = wfGetDB( DB_MASTER );
762 $row = $dbw->selectRow( 'text',
763 array( 'old_text', 'old_flags' ),
764 array( 'old_id' => $this->getTextId() ),
765 __METHOD__ );
766 }
767
768 $text = self::getRevisionText( $row );
769
770 if( $wgRevisionCacheExpiry ) {
771 $wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
772 }
773
774 wfProfileOut( __METHOD__ );
775
776 return $text;
777 }
778
779 /**
780 * Create a new null-revision for insertion into a page's
781 * history. This will not re-save the text, but simply refer
782 * to the text from the previous version.
783 *
784 * Such revisions can for instance identify page rename
785 * operations and other such meta-modifications.
786 *
787 * @param Database $dbw
788 * @param int $pageId ID number of the page to read from
789 * @param string $summary
790 * @param bool $minor
791 * @return Revision
792 */
793 public static function newNullRevision( &$dbw, $pageId, $summary, $minor ) {
794 wfProfileIn( __METHOD__ );
795
796 $current = $dbw->selectRow(
797 array( 'page', 'revision' ),
798 array( 'page_latest', 'rev_text_id' ),
799 array(
800 'page_id' => $pageId,
801 'page_latest=rev_id',
802 ),
803 __METHOD__ );
804
805 if( $current ) {
806 $revision = new Revision( array(
807 'page' => $pageId,
808 'comment' => $summary,
809 'minor_edit' => $minor,
810 'text_id' => $current->rev_text_id,
811 ) );
812 } else {
813 $revision = null;
814 }
815
816 wfProfileOut( __METHOD__ );
817 return $revision;
818 }
819
820 /**
821 * Determine if the current user is allowed to view a particular
822 * field of this revision, if it's marked as deleted.
823 * @param int $field one of self::DELETED_TEXT,
824 * self::DELETED_COMMENT,
825 * self::DELETED_USER
826 * @return bool
827 */
828 function userCan( $field ) {
829 if( ( $this->mDeleted & $field ) == $field ) {
830 global $wgUser;
831 $permission = ( $this->mDeleted & self::DELETED_RESTRICTED ) == self::DELETED_RESTRICTED
832 ? 'hiderevision'
833 : 'deleterevision';
834 wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
835 return $wgUser->isAllowed( $permission );
836 } else {
837 return true;
838 }
839 }
840
841
842 /**
843 * Get rev_timestamp from rev_id, without loading the rest of the row
844 * @param integer $id
845 */
846 static function getTimestampFromID( $id ) {
847 $dbr = wfGetDB( DB_SLAVE );
848 $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
849 array( 'rev_id' => $id ), __METHOD__ );
850 if ( $timestamp === false ) {
851 # Not in slave, try master
852 $dbw = wfGetDB( DB_MASTER );
853 $timestamp = $dbw->selectField( 'revision', 'rev_timestamp',
854 array( 'rev_id' => $id ), __METHOD__ );
855 }
856 return $timestamp;
857 }
858
859 static function countByPageId( $db, $id ) {
860 $row = $db->selectRow( 'revision', 'COUNT(*) AS revCount',
861 array( 'rev_page' => $id ), __METHOD__ );
862 if( $row ) {
863 return $row->revCount;
864 }
865 return 0;
866 }
867
868 static function countByTitle( $db, $title ) {
869 $id = $title->getArticleId();
870 if( $id ) {
871 return Revision::countByPageId( $db, $id );
872 }
873 return 0;
874 }
875 }
876
877 /**
878 * Aliases for backwards compatibility with 1.6
879 */
880 define( 'MW_REV_DELETED_TEXT', Revision::DELETED_TEXT );
881 define( 'MW_REV_DELETED_COMMENT', Revision::DELETED_COMMENT );
882 define( 'MW_REV_DELETED_USER', Revision::DELETED_USER );
883 define( 'MW_REV_DELETED_RESTRICTED', Revision::DELETED_RESTRICTED );
884
885
886