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