merge ORACLE_WORK. sorry, this may break some parts of MySQL, i did not test extensi...
[lhc/web/wiklou.git] / includes / Revision.php
1 <?php
2 /**
3 * @package MediaWiki
4 * @todo document
5 */
6
7 /** */
8 require_once( 'Database.php' );
9 require_once( 'Article.php' );
10
11 /**
12 * @package MediaWiki
13 * @todo document
14 */
15 class Revision {
16 /**
17 * Load a page revision from a given revision ID number.
18 * Returns null if no such revision can be found.
19 *
20 * @param int $id
21 * @static
22 * @access public
23 */
24 function newFromId( $id ) {
25 return Revision::newFromConds(
26 array( 'page_id=rev_page',
27 'rev_id' => IntVal( $id ) ) );
28 return $ret;
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 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 either the current, or a specified, revision
57 * that's attached to a given page. If not attached
58 * to that page, will return null.
59 *
60 * @param Database $db
61 * @param int $pageid
62 * @param int $id
63 * @return Revision
64 * @access public
65 */
66 function loadFromPageId( &$db, $pageid, $id = 0 ) {
67 if( $id ) {
68 $matchId = IntVal( $id );
69 } else {
70 $matchId = 'page_latest';
71 }
72 $ret = Revision::loadFromConds(
73 $db,
74 array( "rev_id=$matchId",
75 'rev_page' => IntVal( $pageid ),
76 'page_id=rev_page' ) );
77 return $ret;
78 }
79
80 /**
81 * Load either the current, or a specified, revision
82 * that's attached to a given page. If not attached
83 * to that page, will return null.
84 *
85 * @param Database $db
86 * @param Title $title
87 * @param int $id
88 * @return Revision
89 * @access public
90 */
91 function loadFromTitle( &$db, $title, $id = 0 ) {
92 if( $id ) {
93 $matchId = IntVal( $id );
94 } else {
95 $matchId = 'page_latest';
96 }
97 return Revision::loadFromConds(
98 $db,
99 array( "rev_id=$matchId",
100 'page_id=rev_page',
101 'page_namespace' => $title->getNamespace(),
102 'page_title' => $title->getDbkey() ) );
103 }
104
105 /**
106 * Load the revision for the given title with the given timestamp.
107 * WARNING: Timestamps may in some circumstances not be unique,
108 * so this isn't the best key to use.
109 *
110 * @param Database $db
111 * @param Title $title
112 * @param string $timestamp
113 * @return Revision
114 * @access public
115 * @static
116 */
117 function loadFromTimestamp( &$db, &$title, $timestamp ) {
118 return Revision::loadFromConds(
119 $db,
120 array( 'rev_timestamp' => $db->timestamp( $timestamp ),
121 'page_id=rev_page',
122 'page_namespace' => $title->getNamespace(),
123 'page_title' => $title->getDbkey() ) );
124 }
125
126 /**
127 * Given a set of conditions, fetch a revision.
128 *
129 * @param array $conditions
130 * @return Revision
131 * @static
132 * @access private
133 */
134 function newFromConds( $conditions ) {
135 $db =& wfGetDB( DB_SLAVE );
136 $row = Revision::loadFromConds( $db, $conditions );
137 if( is_null( $row ) ) {
138 $dbw =& wfGetDB( DB_MASTER );
139 $row = Revision::loadFromConds( $dbw, $conditions );
140 }
141 return $row;
142 }
143
144 /**
145 * Given a set of conditions, fetch a revision from
146 * the given database connection.
147 *
148 * @param Database $db
149 * @param array $conditions
150 * @return Revision
151 * @static
152 * @access private
153 */
154 function loadFromConds( &$db, $conditions ) {
155 $res = Revision::fetchFromConds( $db, $conditions );
156 if( $res ) {
157 $row = $res->fetchObject();
158 $res->free();
159 if( $row ) {
160 $ret = new Revision( $row );
161 return $ret;
162 }
163 }
164 $ret = null;
165 return $ret;
166 }
167
168 /**
169 * Return a wrapper for a series of database rows to
170 * fetch all of a given page's revisions in turn.
171 * Each row can be fed to the constructor to get objects.
172 *
173 * @param Title $title
174 * @return ResultWrapper
175 * @static
176 * @access public
177 */
178 function fetchAllRevisions( &$title ) {
179 return Revision::fetchFromConds(
180 wfGetDB( DB_SLAVE ),
181 array( 'page_namespace' => $title->getNamespace(),
182 'page_title' => $title->getDbkey(),
183 'page_id=rev_page' ) );
184 }
185
186 /**
187 * Return a wrapper for a series of database rows to
188 * fetch all of a given page's revisions in turn.
189 * Each row can be fed to the constructor to get objects.
190 *
191 * @param Title $title
192 * @return ResultWrapper
193 * @static
194 * @access public
195 */
196 function fetchRevision( &$title ) {
197 return Revision::fetchFromConds(
198 wfGetDB( DB_SLAVE ),
199 array( 'rev_id=page_latest',
200 'page_namespace' => $title->getNamespace(),
201 'page_title' => $title->getDbkey(),
202 'page_id=rev_page' ) );
203 }
204
205 /**
206 * Given a set of conditions, return a ResultWrapper
207 * which will return matching database rows with the
208 * fields necessary to build Revision objects.
209 *
210 * @param Database $db
211 * @param array $conditions
212 * @return ResultWrapper
213 * @static
214 * @access private
215 */
216 function fetchFromConds( &$db, $conditions ) {
217 $res = $db->select(
218 array( 'page', 'revision' ),
219 array( 'page_namespace',
220 'page_title',
221 'page_latest',
222 'rev_id',
223 'rev_page',
224 'rev_text_id',
225 'rev_comment',
226 'rev_user_text',
227 'rev_user',
228 'rev_minor_edit',
229 'rev_timestamp',
230 'rev_deleted' ),
231 $conditions,
232 'Revision::fetchRow',
233 array( 'LIMIT' => 1 ) );
234 $ret = $db->resultObject( $res );
235 return $ret;
236 }
237
238 /**
239 * @param object $row
240 * @access private
241 */
242 function Revision( $row ) {
243 if( is_object( $row ) ) {
244 $this->mId = IntVal( $row->rev_id );
245 $this->mPage = IntVal( $row->rev_page );
246 $this->mTextId = IntVal( $row->rev_text_id );
247 $this->mComment = $row->rev_comment;
248 $this->mUserText = $row->rev_user_text;
249 $this->mUser = IntVal( $row->rev_user );
250 $this->mMinorEdit = IntVal( $row->rev_minor_edit );
251 $this->mTimestamp = $row->rev_timestamp;
252 $this->mDeleted = IntVal( $row->rev_deleted );
253
254 $this->mCurrent = ( $row->rev_id == $row->page_latest );
255 $this->mTitle = Title::makeTitle( $row->page_namespace,
256 $row->page_title );
257
258 if( isset( $row->old_text ) ) {
259 $this->mText = $this->getRevisionText( $row );
260 } else {
261 $this->mText = null;
262 }
263 } elseif( is_array( $row ) ) {
264 // Build a new revision to be saved...
265 global $wgUser;
266
267 $this->mId = isset( $row['id'] ) ? IntVal( $row['id'] ) : null;
268 $this->mPage = isset( $row['page'] ) ? IntVal( $row['page'] ) : null;
269 $this->mTextId = isset( $row['text_id'] ) ? IntVal( $row['text_id'] ) : null;
270 $this->mUserText = isset( $row['user_text'] ) ? StrVal( $row['user_text'] ) : $wgUser->getName();
271 $this->mUser = isset( $row['user'] ) ? IntVal( $row['user'] ) : $wgUser->getId();
272 $this->mMinorEdit = isset( $row['minor_edit'] ) ? IntVal( $row['minor_edit'] ) : 0;
273 $this->mTimestamp = isset( $row['timestamp'] ) ? StrVal( $row['timestamp'] ) : wfTimestamp( TS_MW );
274 $this->mDeleted = isset( $row['deleted'] ) ? IntVal( $row['deleted'] ) : 0;
275
276 // Enforce spacing trimming on supplied text
277 $this->mComment = isset( $row['comment'] ) ? trim( StrVal( $row['comment'] ) ) : null;
278 $this->mText = isset( $row['text'] ) ? rtrim( StrVal( $row['text'] ) ) : null;
279
280 $this->mTitle = null; # Load on demand if needed
281 $this->mCurrent = false;
282 } else {
283 wfDebugDieBacktrace( 'Revision constructor passed invalid row format.' );
284 }
285 }
286
287 /**#@+
288 * @access public
289 */
290
291 /**
292 * @return int
293 */
294 function getId() {
295 return $this->mId;
296 }
297
298 /**
299 * @return int
300 */
301 function getTextId() {
302 return $this->mTextId;
303 }
304
305 /**
306 * Returns the title of the page associated with this entry.
307 * @return Title
308 */
309 function getTitle() {
310 if( isset( $this->mTitle ) ) {
311 return $this->mTitle;
312 }
313 $dbr =& wfGetDB( DB_SLAVE );
314 $row = $dbr->selectRow(
315 array( 'page', 'revision' ),
316 array( 'page_namespace', 'page_title' ),
317 array( 'page_id=rev_page',
318 'rev_id' => $this->mId ),
319 'Revision::getTItle' );
320 if( $row ) {
321 $this->mTitle = Title::makeTitle( $row->page_namespace,
322 $row->page_title );
323 }
324 return $this->mTitle;
325 }
326
327 /**
328 * @return int
329 */
330 function getPage() {
331 return $this->mPage;
332 }
333
334 /**
335 * @return int
336 */
337 function getUser() {
338 return $this->mUser;
339 }
340
341 /**
342 * @return string
343 */
344 function getUserText() {
345 return $this->mUserText;
346 }
347
348 /**
349 * @return string
350 */
351 function getComment() {
352 return $this->mComment;
353 }
354
355 /**
356 * @return bool
357 */
358 function isMinor() {
359 return (bool)$this->mMinorEdit;
360 }
361
362 /**
363 * @return bool
364 */
365 function isDeleted() {
366 return (bool)$this->mDeleted;
367 }
368
369 /**
370 * @return string
371 */
372 function getText() {
373 if( is_null( $this->mText ) ) {
374 // Revision text is immutable. Load on demand:
375 $this->mText = $this->loadText();
376 }
377 return $this->mText;
378 }
379
380 /**
381 * @return string
382 */
383 function getTimestamp() {
384 return wfTimestamp(TS_MW, $this->mTimestamp);
385 }
386
387 /**
388 * @return bool
389 */
390 function isCurrent() {
391 return $this->mCurrent;
392 }
393
394 /**
395 * @return Revision
396 */
397 function getPrevious() {
398 $prev = $this->mTitle->getPreviousRevisionID( $this->mId );
399 return Revision::newFromTitle( $this->mTitle, $prev );
400 }
401
402 /**
403 * @return Revision
404 */
405 function getNext() {
406 $next = $this->mTitle->getNextRevisionID( $this->mId );
407 return Revision::newFromTitle( $this->mTitle, $next );
408 }
409 /**#@-*/
410
411 /**
412 * Get revision text associated with an old or archive row
413 * $row is usually an object from wfFetchRow(), both the flags and the text
414 * field must be included
415 * @static
416 * @param integer $row Id of a row
417 * @param string $prefix table prefix (default 'old_')
418 * @return string $text|false the text requested
419 */
420 function getRevisionText( $row, $prefix = 'old_' ) {
421 $fname = 'Revision::getRevisionText';
422 wfProfileIn( $fname );
423
424 # Get data
425 $textField = $prefix . 'text';
426 $flagsField = $prefix . 'flags';
427
428 if( isset( $row->$flagsField ) ) {
429 $flags = explode( ',', $row->$flagsField );
430 } else {
431 $flags = array();
432 }
433
434 if( isset( $row->$textField ) ) {
435 $text = $row->$textField;
436 } else {
437 wfProfileOut( $fname );
438 return false;
439 }
440
441 # Use external methods for external objects, text in table is URL-only then
442 if ( in_array( 'external', $flags ) ) {
443 $url=$text;
444 @list($proto,$path)=explode('://',$url,2);
445 if ($path=="") {
446 wfProfileOut( $fname );
447 return false;
448 }
449 require_once('ExternalStore.php');
450 $text=ExternalStore::fetchFromURL($url);
451 }
452
453 if( in_array( 'gzip', $flags ) ) {
454 # Deal with optional compression of archived pages.
455 # This can be done periodically via maintenance/compressOld.php, and
456 # as pages are saved if $wgCompressRevisions is set.
457 $text = gzinflate( $text );
458 }
459
460 if( in_array( 'object', $flags ) ) {
461 # Generic compressed storage
462 $obj = unserialize( $text );
463
464 # Bugger, corrupted my test database by double-serializing
465 if ( !is_object( $obj ) ) {
466 $obj = unserialize( $obj );
467 }
468
469 $text = $obj->getText();
470 }
471
472 global $wgLegacyEncoding;
473 if( $wgLegacyEncoding && !in_array( 'utf-8', $flags ) ) {
474 # Old revisions kept around in a legacy encoding?
475 # Upconvert on demand.
476 global $wgInputEncoding, $wgContLang;
477 $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
478 }
479 wfProfileOut( $fname );
480 return $text;
481 }
482
483 /**
484 * If $wgCompressRevisions is enabled, we will compress data.
485 * The input string is modified in place.
486 * Return value is the flags field: contains 'gzip' if the
487 * data is compressed, and 'utf-8' if we're saving in UTF-8
488 * mode.
489 *
490 * @static
491 * @param mixed $text reference to a text
492 * @return string
493 */
494 function compressRevisionText( &$text ) {
495 global $wgCompressRevisions;
496 $flags = array();
497
498 # Revisions not marked this way will be converted
499 # on load if $wgLegacyCharset is set in the future.
500 $flags[] = 'utf-8';
501
502 if( $wgCompressRevisions ) {
503 if( function_exists( 'gzdeflate' ) ) {
504 $text = gzdeflate( $text );
505 $flags[] = 'gzip';
506 } else {
507 wfDebug( "Revision::compressRevisionText() -- no zlib support, not compressing\n" );
508 }
509 }
510 return implode( ',', $flags );
511 }
512
513 /**
514 * Insert a new revision into the database, returning the new revision ID
515 * number on success and dies horribly on failure.
516 *
517 * @param Database $dbw
518 * @return int
519 */
520 function insertOn( &$dbw ) {
521 $fname = 'Revision::insertOn';
522 wfProfileIn( $fname );
523
524 $mungedText = $this->mText;
525 $flags = Revision::compressRevisionText( $mungedText );
526
527 # Record the text to the text table
528 if( !isset( $this->mTextId ) ) {
529 $old_id = $dbw->nextSequenceValue( 'text_old_id_val' );
530 $dbw->insert( 'text',
531 array(
532 'old_id' => $old_id,
533 'old_text' => $mungedText,
534 'old_flags' => $flags,
535 ), $fname
536 );
537 $this->mTextId = $dbw->insertId();
538 }
539
540 # Record the edit in revisions
541 $rev_id = isset( $this->mId )
542 ? $this->mId
543 : $dbw->nextSequenceValue( 'rev_rev_id_val' );
544 $dbw->insert( 'revision',
545 array(
546 'rev_id' => $rev_id,
547 'rev_page' => $this->mPage,
548 'rev_text_id' => $this->mTextId,
549 'rev_comment' => $this->mComment,
550 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
551 'rev_user' => $this->mUser,
552 'rev_user_text' => $this->mUserText,
553 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
554 'rev_deleted' => $this->mDeleted,
555 ), $fname
556 );
557
558 $this->mId = !is_null($rev_id) ? $rev_id : $dbw->insertId();
559
560 wfProfileOut( $fname );
561 return $this->mId;
562 }
563
564 /**
565 * Lazy-load the revision's text.
566 * Currently hardcoded to the 'text' table storage engine.
567 *
568 * @return string
569 * @access private
570 */
571 function loadText() {
572 $fname = 'Revision::loadText';
573 wfProfileIn( $fname );
574
575 $dbr =& wfGetDB( DB_SLAVE );
576 $row = $dbr->selectRow( 'text',
577 array( 'old_text', 'old_flags' ),
578 array( 'old_id' => $this->getTextId() ),
579 $fname);
580
581 if( !$row ) {
582 $dbw =& wfGetDB( DB_MASTER );
583 $row = $dbw->selectRow( 'text',
584 array( 'old_text', 'old_flags' ),
585 array( 'old_id' => $this->getTextId() ),
586 $fname);
587 }
588
589 $text = Revision::getRevisionText( $row );
590 wfProfileOut( $fname );
591
592 return $text;
593 }
594
595 /**
596 * Create a new null-revision for insertion into a page's
597 * history. This will not re-save the text, but simply refer
598 * to the text from the previous version.
599 *
600 * Such revisions can for instance identify page rename
601 * operations and other such meta-modifications.
602 *
603 * @param Database $dbw
604 * @param int $pageId ID number of the page to read from
605 * @param string $summary
606 * @param bool $minor
607 * @return Revision
608 */
609 function newNullRevision( &$dbw, $pageId, $summary, $minor ) {
610 $fname = 'Revision::newNullRevision';
611 wfProfileIn( $fname );
612
613 $current = $dbw->selectRow(
614 array( 'page', 'revision' ),
615 array( 'page_latest', 'rev_text_id' ),
616 array(
617 'page_id' => $pageId,
618 'page_latest=rev_id',
619 ),
620 $fname );
621
622 if( $current ) {
623 $revision = new Revision( array(
624 'page' => $pageId,
625 'comment' => $summary,
626 'minor_edit' => $minor,
627 'text_id' => $current->rev_text_id,
628 ) );
629 } else {
630 $revision = null;
631 }
632
633 wfProfileOut( $fname );
634 return $revision;
635 }
636
637 }
638 ?>