Fix fail from r69755, press save, actually do "LIMIT_SML2, LIMIT_BIG2 are in ApiBase...
[lhc/web/wiklou.git] / includes / Import.php
1 <?php
2 /**
3 * MediaWiki page data importer
4 * Copyright (C) 2003,2005 Brion Vibber <brion@pobox.com>
5 * http://www.mediawiki.org/
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 * @ingroup SpecialPage
24 */
25
26 /**
27 *
28 * @ingroup SpecialPage
29 */
30 class WikiRevision {
31 var $title = null;
32 var $id = 0;
33 var $timestamp = "20010115000000";
34 var $user = 0;
35 var $user_text = "";
36 var $text = "";
37 var $comment = "";
38 var $minor = false;
39 var $type = "";
40 var $action = "";
41 var $params = "";
42
43 function setTitle( $title ) {
44 if( is_object( $title ) ) {
45 $this->title = $title;
46 } elseif( is_null( $title ) ) {
47 throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." );
48 } else {
49 throw new MWException( "WikiRevision given non-object title in import." );
50 }
51 }
52
53 function setID( $id ) {
54 $this->id = $id;
55 }
56
57 function setTimestamp( $ts ) {
58 # 2003-08-05T18:30:02Z
59 $this->timestamp = wfTimestamp( TS_MW, $ts );
60 }
61
62 function setUsername( $user ) {
63 $this->user_text = $user;
64 }
65
66 function setUserIP( $ip ) {
67 $this->user_text = $ip;
68 }
69
70 function setText( $text ) {
71 $this->text = $text;
72 }
73
74 function setComment( $text ) {
75 $this->comment = $text;
76 }
77
78 function setMinor( $minor ) {
79 $this->minor = (bool)$minor;
80 }
81
82 function setSrc( $src ) {
83 $this->src = $src;
84 }
85
86 function setFilename( $filename ) {
87 $this->filename = $filename;
88 }
89
90 function setSize( $size ) {
91 $this->size = intval( $size );
92 }
93
94 function setType( $type ) {
95 $this->type = $type;
96 }
97
98 function setAction( $action ) {
99 $this->action = $action;
100 }
101
102 function setParams( $params ) {
103 $this->params = $params;
104 }
105
106 function getTitle() {
107 return $this->title;
108 }
109
110 function getID() {
111 return $this->id;
112 }
113
114 function getTimestamp() {
115 return $this->timestamp;
116 }
117
118 function getUser() {
119 return $this->user_text;
120 }
121
122 function getText() {
123 return $this->text;
124 }
125
126 function getComment() {
127 return $this->comment;
128 }
129
130 function getMinor() {
131 return $this->minor;
132 }
133
134 function getSrc() {
135 return $this->src;
136 }
137
138 function getFilename() {
139 return $this->filename;
140 }
141
142 function getSize() {
143 return $this->size;
144 }
145
146 function getType() {
147 return $this->type;
148 }
149
150 function getAction() {
151 return $this->action;
152 }
153
154 function getParams() {
155 return $this->params;
156 }
157
158 function importOldRevision() {
159 $dbw = wfGetDB( DB_MASTER );
160
161 # Sneak a single revision into place
162 $user = User::newFromName( $this->getUser() );
163 if( $user ) {
164 $userId = intval( $user->getId() );
165 $userText = $user->getName();
166 } else {
167 $userId = 0;
168 $userText = $this->getUser();
169 }
170
171 // avoid memory leak...?
172 $linkCache = LinkCache::singleton();
173 $linkCache->clear();
174
175 $article = new Article( $this->title );
176 $pageId = $article->getId();
177 if( $pageId == 0 ) {
178 # must create the page...
179 $pageId = $article->insertOn( $dbw );
180 $created = true;
181 } else {
182 $created = false;
183
184 $prior = $dbw->selectField( 'revision', '1',
185 array( 'rev_page' => $pageId,
186 'rev_timestamp' => $dbw->timestamp( $this->timestamp ),
187 'rev_user_text' => $userText,
188 'rev_comment' => $this->getComment() ),
189 __METHOD__
190 );
191 if( $prior ) {
192 // FIXME: this could fail slightly for multiple matches :P
193 wfDebug( __METHOD__ . ": skipping existing revision for [[" .
194 $this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
195 return false;
196 }
197 }
198
199 # FIXME: Use original rev_id optionally (better for backups)
200 # Insert the row
201 $revision = new Revision( array(
202 'page' => $pageId,
203 'text' => $this->getText(),
204 'comment' => $this->getComment(),
205 'user' => $userId,
206 'user_text' => $userText,
207 'timestamp' => $this->timestamp,
208 'minor_edit' => $this->minor,
209 ) );
210 $revId = $revision->insertOn( $dbw );
211 $changed = $article->updateIfNewerOn( $dbw, $revision );
212
213 # To be on the safe side...
214 $tempTitle = $GLOBALS['wgTitle'];
215 $GLOBALS['wgTitle'] = $this->title;
216
217 if( $created ) {
218 wfDebug( __METHOD__ . ": running onArticleCreate\n" );
219 Article::onArticleCreate( $this->title );
220
221 wfDebug( __METHOD__ . ": running create updates\n" );
222 $article->createUpdates( $revision );
223
224 } elseif( $changed ) {
225 wfDebug( __METHOD__ . ": running onArticleEdit\n" );
226 Article::onArticleEdit( $this->title );
227
228 wfDebug( __METHOD__ . ": running edit updates\n" );
229 $article->editUpdates(
230 $this->getText(),
231 $this->getComment(),
232 $this->minor,
233 $this->timestamp,
234 $revId );
235 }
236 $GLOBALS['wgTitle'] = $tempTitle;
237
238 return true;
239 }
240
241 function importLogItem() {
242 $dbw = wfGetDB( DB_MASTER );
243 # FIXME: this will not record autoblocks
244 if( !$this->getTitle() ) {
245 wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
246 $this->timestamp . "\n" );
247 return;
248 }
249 # Check if it exists already
250 // FIXME: use original log ID (better for backups)
251 $prior = $dbw->selectField( 'logging', '1',
252 array( 'log_type' => $this->getType(),
253 'log_action' => $this->getAction(),
254 'log_timestamp' => $dbw->timestamp( $this->timestamp ),
255 'log_namespace' => $this->getTitle()->getNamespace(),
256 'log_title' => $this->getTitle()->getDBkey(),
257 'log_comment' => $this->getComment(),
258 #'log_user_text' => $this->user_text,
259 'log_params' => $this->params ),
260 __METHOD__
261 );
262 // FIXME: this could fail slightly for multiple matches :P
263 if( $prior ) {
264 wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
265 $this->timestamp . "\n" );
266 return false;
267 }
268 $log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
269 $data = array(
270 'log_id' => $log_id,
271 'log_type' => $this->type,
272 'log_action' => $this->action,
273 'log_timestamp' => $dbw->timestamp( $this->timestamp ),
274 'log_user' => User::idFromName( $this->user_text ),
275 #'log_user_text' => $this->user_text,
276 'log_namespace' => $this->getTitle()->getNamespace(),
277 'log_title' => $this->getTitle()->getDBkey(),
278 'log_comment' => $this->getComment(),
279 'log_params' => $this->params
280 );
281 $dbw->insert( 'logging', $data, __METHOD__ );
282 }
283
284 function importUpload() {
285 wfDebug( __METHOD__ . ": STUB\n" );
286
287 /**
288 // from file revert...
289 $source = $this->file->getArchiveVirtualUrl( $this->oldimage );
290 $comment = $wgRequest->getText( 'wpComment' );
291 // TODO: Preserve file properties from database instead of reloading from file
292 $status = $this->file->upload( $source, $comment, $comment );
293 if( $status->isGood() ) {
294 */
295
296 /**
297 // from file upload...
298 $this->mLocalFile = wfLocalFile( $nt );
299 $this->mDestName = $this->mLocalFile->getName();
300 //....
301 $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText,
302 File::DELETE_SOURCE, $this->mFileProps );
303 if ( !$status->isGood() ) {
304 $resultDetails = array( 'internal' => $status->getWikiText() );
305 */
306
307 // @todo Fixme: upload() uses $wgUser, which is wrong here
308 // it may also create a page without our desire, also wrong potentially.
309 // and, it will record a *current* upload, but we might want an archive version here
310
311 $file = wfLocalFile( $this->getTitle() );
312 if( !$file ) {
313 var_dump( $file );
314 wfDebug( "IMPORT: Bad file. :(\n" );
315 return false;
316 }
317
318 $source = $this->downloadSource();
319 if( !$source ) {
320 wfDebug( "IMPORT: Could not fetch remote file. :(\n" );
321 return false;
322 }
323
324 $status = $file->upload( $source,
325 $this->getComment(),
326 $this->getComment(), // Initial page, if none present...
327 File::DELETE_SOURCE,
328 false, // props...
329 $this->getTimestamp() );
330
331 if( $status->isGood() ) {
332 // yay?
333 wfDebug( "IMPORT: is ok?\n" );
334 return true;
335 }
336
337 wfDebug( "IMPORT: is bad? " . $status->getXml() . "\n" );
338 return false;
339
340 }
341
342 function downloadSource() {
343 global $wgEnableUploads;
344 if( !$wgEnableUploads ) {
345 return false;
346 }
347
348 $tempo = tempnam( wfTempDir(), 'download' );
349 $f = fopen( $tempo, 'wb' );
350 if( !$f ) {
351 wfDebug( "IMPORT: couldn't write to temp file $tempo\n" );
352 return false;
353 }
354
355 // @todo Fixme!
356 $src = $this->getSrc();
357 $data = Http::get( $src );
358 if( !$data ) {
359 wfDebug( "IMPORT: couldn't fetch source $src\n" );
360 fclose( $f );
361 unlink( $tempo );
362 return false;
363 }
364
365 fwrite( $f, $data );
366 fclose( $f );
367
368 return $tempo;
369 }
370
371 }
372
373 /**
374 * @todo document (e.g. one-sentence class description).
375 * @ingroup SpecialPage
376 */
377 class ImportStringSource {
378 function __construct( $string ) {
379 $this->mString = $string;
380 $this->mRead = false;
381 }
382
383 function atEnd() {
384 return $this->mRead;
385 }
386
387 function readChunk() {
388 if( $this->atEnd() ) {
389 return false;
390 } else {
391 $this->mRead = true;
392 return $this->mString;
393 }
394 }
395 }
396
397 /**
398 * @todo document (e.g. one-sentence class description).
399 * @ingroup SpecialPage
400 */
401 class ImportStreamSource {
402 function __construct( $handle ) {
403 $this->mHandle = $handle;
404 }
405
406 function atEnd() {
407 return feof( $this->mHandle );
408 }
409
410 function readChunk() {
411 return fread( $this->mHandle, 32768 );
412 }
413
414 static function newFromFile( $filename ) {
415 $file = @fopen( $filename, 'rt' );
416 if( !$file ) {
417 return new WikiErrorMsg( "importcantopen" );
418 }
419 return new ImportStreamSource( $file );
420 }
421
422 static function newFromUpload( $fieldname = "xmlimport" ) {
423 $upload =& $_FILES[$fieldname];
424
425 if( !isset( $upload ) || !$upload['name'] ) {
426 return new WikiErrorMsg( 'importnofile' );
427 }
428 if( !empty( $upload['error'] ) ) {
429 switch($upload['error']){
430 case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini.
431 return new WikiErrorMsg( 'importuploaderrorsize' );
432 case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
433 return new WikiErrorMsg( 'importuploaderrorsize' );
434 case 3: # The uploaded file was only partially uploaded
435 return new WikiErrorMsg( 'importuploaderrorpartial' );
436 case 6: #Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.
437 return new WikiErrorMsg( 'importuploaderrortemp' );
438 # case else: # Currently impossible
439 }
440
441 }
442 $fname = $upload['tmp_name'];
443 if( is_uploaded_file( $fname ) ) {
444 return ImportStreamSource::newFromFile( $fname );
445 } else {
446 return new WikiErrorMsg( 'importnofile' );
447 }
448 }
449
450 static function newFromURL( $url, $method = 'GET' ) {
451 wfDebug( __METHOD__ . ": opening $url\n" );
452 # Use the standard HTTP fetch function; it times out
453 # quicker and sorts out user-agent problems which might
454 # otherwise prevent importing from large sites, such
455 # as the Wikimedia cluster, etc.
456 $data = Http::request( $method, $url );
457 if( $data !== false ) {
458 $file = tmpfile();
459 fwrite( $file, $data );
460 fflush( $file );
461 fseek( $file, 0 );
462 return new ImportStreamSource( $file );
463 } else {
464 return new WikiErrorMsg( 'importcantopen' );
465 }
466 }
467
468 public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
469 if( $page == '' ) {
470 return new WikiErrorMsg( 'import-noarticle' );
471 }
472 $link = Title::newFromText( "$interwiki:Special:Export/$page" );
473 if( is_null( $link ) || $link->getInterwiki() == '' ) {
474 return new WikiErrorMsg( 'importbadinterwiki' );
475 } else {
476 $params = array();
477 if ( $history ) $params['history'] = 1;
478 if ( $templates ) $params['templates'] = 1;
479 if ( $pageLinkDepth ) $params['pagelink-depth'] = $pageLinkDepth;
480 $url = $link->getFullUrl( $params );
481 # For interwikis, use POST to avoid redirects.
482 return ImportStreamSource::newFromURL( $url, "POST" );
483 }
484 }
485 }