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