* Make undeletion (more or less) work with new schema
[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 'rev_id=old_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 */
41 function &newFromTitle( &$title, $id = 0 ) {
42 if( $id ) {
43 $matchId = IntVal( $id );
44 } else {
45 $matchId = 'page_latest';
46 }
47 return Revision::newFromConds(
48 array( "rev_id=$matchId",
49 'page_id=rev_page',
50 'page_namespace' => $title->getNamespace(),
51 'page_title' => $title->getDbkey(),
52 'rev_id=old_id' ) );
53 }
54
55 /**
56 * Given a set of conditions, fetch a revision.
57 *
58 * @param array $conditions
59 * @return Revision
60 * @static
61 * @access private
62 */
63 function &newFromConds( $conditions ) {
64 $res =& Revision::fetchFromConds( $conditions );
65 if( $res ) {
66 $row = $res->fetchObject();
67 $res->free();
68 if( $row ) {
69 return new Revision( $row );
70 }
71 }
72 return null;
73 }
74
75 /**
76 * Return a wrapper for a series of database rows to
77 * fetch all of a given page's revisions in turn.
78 * Each row can be fed to the constructor to get objects.
79 *
80 * @param Title $title
81 * @return ResultWrapper
82 * @static
83 * @access public
84 */
85 function &fetchAllRevisions( &$title ) {
86 return Revision::fetchFromConds(
87 array( 'page_namespace' => $title->getNamespace(),
88 'page_title' => $title->getDbkey(),
89 'page_id=rev_page',
90 'rev_id=old_id' ) );
91 }
92
93 /**
94 * Return a wrapper for a series of database rows to
95 * fetch all of a given page's revisions in turn.
96 * Each row can be fed to the constructor to get objects.
97 *
98 * @param Title $title
99 * @return ResultWrapper
100 * @static
101 * @access public
102 */
103 function &fetchRevision( &$title ) {
104 return Revision::fetchFromConds(
105 array( 'rev_id=page_latest',
106 'page_namespace' => $title->getNamespace(),
107 'page_title' => $title->getDbkey(),
108 'page_id=rev_page',
109 'rev_id=old_id' ) );
110 }
111 /**
112 * Given a set of conditions, return a ResultWrapper
113 * which will return matching database rows with the
114 * fields necessary to build Revision objects.
115 *
116 * @param array $conditions
117 * @return ResultWrapper
118 * @static
119 * @access private
120 */
121 function &fetchFromConds( $conditions ) {
122 $dbr =& wfGetDB( DB_SLAVE );
123 $res = $dbr->select(
124 array( 'page', 'revision', 'text' ),
125 array( 'page_namespace',
126 'page_title',
127 'page_latest',
128 'rev_id',
129 'rev_page',
130 'rev_comment',
131 'rev_user_text',
132 'rev_user',
133 'rev_minor_edit',
134 'rev_timestamp',
135 'old_flags',
136 'old_text' ),
137 $conditions,
138 'Revision::fetchRow' );
139 return $dbr->resultObject( $res );
140 }
141
142 /**
143 * @param object $row
144 * @access private
145 */
146 function Revision( $row ) {
147 if( is_object( $row ) ) {
148 $this->mId = IntVal( $row->rev_id );
149 $this->mPage = IntVal( $row->rev_page );
150 $this->mComment = $row->rev_comment;
151 $this->mUserText = $row->rev_user_text;
152 $this->mUser = IntVal( $row->rev_user );
153 $this->mMinorEdit = IntVal( $row->rev_minor_edit );
154 $this->mTimestamp = $row->rev_timestamp;
155
156 $this->mCurrent = ( $row->rev_id == $row->page_latest );
157 $this->mTitle = Title::makeTitle( $row->page_namespace,
158 $row->page_title );
159 $this->mText = $this->getRevisionText( $row );
160 } elseif( is_array( $row ) ) {
161 // Build a new revision to be saved...
162 global $wgUser;
163
164 $this->mId = isset( $row['id'] ) ? IntVal( $row['id'] ) : null;
165 $this->mPage = isset( $row['page'] ) ? IntVal( $row['page'] ) : null;
166 $this->mComment = isset( $row['comment'] ) ? StrVal( $row['comment'] ) : null;
167 $this->mUserText = isset( $row['user_text'] ) ? StrVal( $row['user_text'] ) : $wgUser->getName();
168 $this->mUser = isset( $row['user'] ) ? IntVal( $row['user'] ) : $wgUser->getId();
169 $this->mMinorEdit = isset( $row['minor_edit'] ) ? IntVal( $row['minor_edit'] ) : 0;
170 $this->mTimestamp = isset( $row['timestamp'] ) ? StrVal( $row['timestamp'] ) : wfTimestamp( TS_MW );
171 $this->mText = isset( $row['text'] ) ? StrVal( $row['text'] ) : '';
172
173 $this->mTitle = null; # Load on demand if needed
174 $this->mCurrent = false;
175 } else {
176 wfDebugDieBacktrace( 'Revision constructor passed invalid row format.' );
177 }
178 }
179
180 /**#@+
181 * @access public
182 */
183
184 /**
185 * @return int
186 */
187 function getId() {
188 return $this->mId;
189 }
190
191 /**
192 * Returns the title of the page associated with this entry.
193 * @return Title
194 */
195 function &getTitle() {
196 if( isset( $this->mTitle ) ) {
197 return $this->mTitle;
198 }
199 $dbr =& wfGetDB( DB_SLAVE );
200 $row = $dbr->selectRow(
201 array( 'page', 'revision' ),
202 array( 'page_namespace', 'page_title' ),
203 array( 'page_id=rev_page',
204 'rev_id' => $this->mId ),
205 'Revision::getTItle' );
206 if( $row ) {
207 $this->mTitle =& Title::makeTitle( $row->page_namespace,
208 $row->page_title );
209 }
210 return $this->mTitle;
211 }
212
213 /**
214 * @return int
215 */
216 function getUser() {
217 return $this->mUser;
218 }
219
220 /**
221 * @return string
222 */
223 function getUserText() {
224 return $this->mUserText;
225 }
226
227 /**
228 * @return string
229 */
230 function getComment() {
231 return $this->mComment;
232 }
233
234 /**
235 * @return bool
236 */
237 function isMinor() {
238 return (bool)$this->mMinorEdit;
239 }
240
241 /**
242 * @return string
243 */
244 function getText() {
245 return $this->mText;
246 }
247
248 /**
249 * @return string
250 */
251 function getTimestamp() {
252 return $this->mTimestamp;
253 }
254
255 /**
256 * @return bool
257 */
258 function isCurrent() {
259 return $this->mCurrent;
260 }
261
262 /**
263 * @return Revision
264 */
265 function &getPrevious() {
266 $prev = $this->mTitle->getPreviousRevisionID( $this->mId );
267 return Revision::newFromTitle( $this->mTitle, $prev );
268 }
269
270 /**
271 * @return Revision
272 */
273 function &getNext() {
274 $next = $this->mTitle->getNextRevisionID( $this->mId );
275 return Revision::newFromTitle( $this->mTitle, $next );
276 }
277 /**#@-*/
278
279 /**
280 * Get revision text associated with an old or archive row
281 * $row is usually an object from wfFetchRow(), both the flags and the text
282 * field must be included
283 * @static
284 * @param integer $row Id of a row
285 * @param string $prefix table prefix (default 'old_')
286 * @return string $text|false the text requested
287 */
288 function getRevisionText( $row, $prefix = 'old_' ) {
289 $fname = 'Revision::getRevisionText';
290 wfProfileIn( $fname );
291
292 # Get data
293 $textField = $prefix . 'text';
294 $flagsField = $prefix . 'flags';
295
296 if( isset( $row->$flagsField ) ) {
297 $flags = explode( ',', $row->$flagsField );
298 } else {
299 $flags = array();
300 }
301
302 if( isset( $row->$textField ) ) {
303 $text = $row->$textField;
304 } else {
305 wfProfileOut( $fname );
306 return false;
307 }
308
309 if( in_array( 'gzip', $flags ) ) {
310 # Deal with optional compression of archived pages.
311 # This can be done periodically via maintenance/compressOld.php, and
312 # as pages are saved if $wgCompressRevisions is set.
313 $text = gzinflate( $text );
314 }
315
316 if( in_array( 'object', $flags ) ) {
317 # Generic compressed storage
318 $obj = unserialize( $text );
319
320 # Bugger, corrupted my test database by double-serializing
321 if ( !is_object( $obj ) ) {
322 $obj = unserialize( $obj );
323 }
324
325 $text = $obj->getText();
326 }
327
328 global $wgLegacyEncoding;
329 if( $wgLegacyEncoding && !in_array( 'utf-8', $flags ) ) {
330 # Old revisions kept around in a legacy encoding?
331 # Upconvert on demand.
332 global $wgInputEncoding, $wgContLang;
333 $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
334 }
335 wfProfileOut( $fname );
336 return $text;
337 }
338
339 /**
340 * If $wgCompressRevisions is enabled, we will compress data.
341 * The input string is modified in place.
342 * Return value is the flags field: contains 'gzip' if the
343 * data is compressed, and 'utf-8' if we're saving in UTF-8
344 * mode.
345 *
346 * @static
347 * @param mixed $text reference to a text
348 * @return string
349 */
350 function compressRevisionText( &$text ) {
351 global $wgCompressRevisions, $wgUseLatin1;
352 $flags = array();
353 if( !$wgUseLatin1 ) {
354 # Revisions not marked this way will be converted
355 # on load if $wgLegacyCharset is set in the future.
356 $flags[] = 'utf-8';
357 }
358 if( $wgCompressRevisions ) {
359 if( function_exists( 'gzdeflate' ) ) {
360 $text = gzdeflate( $text );
361 $flags[] = 'gzip';
362 } else {
363 wfDebug( "Revision::compressRevisionText() -- no zlib support, not compressing\n" );
364 }
365 }
366 return implode( ',', $flags );
367 }
368
369 /**
370 * Insert a new revision into the database, returning the new revision ID
371 * number on success and dies horribly on failure.
372 *
373 * @param Database $dbw
374 * @return int
375 */
376 function insertOn( &$dbw ) {
377 $fname = 'Revision::insertOn';
378 wfProfileIn( $fname );
379
380 $mungedText = $this->mText;
381 $flags = Revision::compressRevisionText( $mungedText );
382
383 # Record the text to the text table
384 $old_id = isset( $this->mId )
385 ? $this->mId
386 : $dbw->nextSequenceValue( 'text_old_id_val' );
387 $dbw->insert( 'text',
388 array(
389 'old_id' => $old_id,
390 'old_text' => $mungedText,
391 'old_flags' => $flags,
392 ), $fname
393 );
394 $revisionId = $dbw->insertId();
395
396 # Record the edit in revisions
397 $dbw->insert( 'revision',
398 array(
399 'rev_id' => $revisionId,
400 'rev_page' => $this->mPage,
401 'rev_comment' => $this->mComment,
402 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
403 'rev_user' => $this->mUser,
404 'rev_user_text' => $this->mUserText,
405 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
406 ), $fname
407 );
408
409 $this->mId = $revisionId;
410
411 wfProfileOut( $fname );
412 return $revisionId;
413 }
414 }
415 ?>