Index: trunk/extensions/WikiSync/WikiSyncExporter.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -83,7 +83,11 @@ |
84 | 84 | # call the parent to do the logging |
85 | 85 | # avoid bug in 1.15.4 Special:Import (new file page text without the file uploaded) |
86 | 86 | # PHP Fatal error: Call to a member function insertOn() on a non-object in E:\www\psychologos\includes\specials\SpecialImport.php on line 334 |
87 | | - if ( $title->getArticleId() !== 0 ) { |
| 87 | + // do not create informational null revisions |
| 88 | + // because they are placed on top of real user made revisions, |
| 89 | + // making the binary search algorithm used to compare local and remote revs to fail |
| 90 | + // TODO: change the binary search algorithm to two/three level hashes |
| 91 | + if ( WikiSyncSetup::$report_null_revisions && $title->getArticleId() !== 0 ) { |
88 | 92 | parent::reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ); |
89 | 93 | } |
90 | 94 | } |
Index: trunk/extensions/WikiSync/WikiSyncApi.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -46,7 +46,7 @@ |
47 | 47 | // we construct like ApiBase, however we also use SQL select building methods from ApiQueryBase |
48 | 48 | public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) { |
49 | 49 | // we call ApiBase only, ApiQueryBase is unsuitable to our needs |
50 | | - parent::__construct( $mainModule, $moduleName, $modulePrefix ); |
| 50 | + ApiBase::__construct( $mainModule, $moduleName, $modulePrefix = '' ); |
51 | 51 | $this->mDb = null; |
52 | 52 | $this->resetQueryParams(); |
53 | 53 | } |
— | — | @@ -245,9 +245,9 @@ |
246 | 246 | } |
247 | 247 | |
248 | 248 | public function execute() { |
249 | | - $db = $this->getDB(); |
250 | 249 | /* Get the parameters of the request. */ |
251 | 250 | $params = $this->extractRequestParams(); |
| 251 | + $db = $this->getDB(); |
252 | 252 | # next line is required, because getCustomPrinter() is not being executed from FauxRequest |
253 | 253 | $this->xmlDumpMode = $params['xmldump']; |
254 | 254 | |
— | — | @@ -259,19 +259,36 @@ |
260 | 260 | 'rev_user_text', |
261 | 261 | 'OCTET_LENGTH( old_text ) AS text_len' |
262 | 262 | ); |
263 | | - $this->addFields( $selectFields ); |
264 | 263 | $this->addTables( array( 'revision', 'text' ) ); |
265 | 264 | $this->addWhereRange( 'rev_id', $params['dir'], $params['startid'], $params['endid'] ); |
266 | 265 | $this->addOption( 'LIMIT', $params['limit'] + 1 ); |
| 266 | + if ( $params['exclude_user'] !== null ) { |
| 267 | + # we are not including revisions created by WikiSyncBot, which are created in |
| 268 | + # ImportReporter::reportPage to log the reason of import (set by constructors of |
| 269 | + # WikiSyncImportReporter / ImportReporter) |
| 270 | + # otherwise, fake reverse synchronizations might occur |
| 271 | + $this->addWhere( 'rev_user_text != ' . $db->addQuotes( $params['exclude_user'] ) ); |
| 272 | + } |
267 | 273 | $this->addJoinConds( |
268 | 274 | array( |
269 | 275 | 'text' => array( 'INNER JOIN', 'rev_text_id=old_id' ) |
270 | 276 | ) |
271 | 277 | ); |
| 278 | + if ( $params['pageinfo'] === true ) { |
| 279 | + $this->addTables( 'page' ); |
| 280 | + $this->addJoinConds( |
| 281 | + array( |
| 282 | + 'page' => array( 'INNER JOIN', 'page_id=rev_page' ), |
| 283 | + ) |
| 284 | + ); |
| 285 | + $selectFields = array_merge( $selectFields, |
| 286 | + array( 'page_namespace', 'page_title', 'page_latest' ) |
| 287 | + ); |
| 288 | + } |
| 289 | + $this->addFields( $selectFields ); |
272 | 290 | $dbres = $this->select(__METHOD__); |
273 | | - |
| 291 | + $limit = $params['limit']; |
274 | 292 | $result = $this->getResult(); |
275 | | - $limit = $params['limit']; |
276 | 293 | |
277 | 294 | if ( $this->xmlDumpMode ) { |
278 | 295 | # use default IIS / Apache execution time limit which much larger than default PHP limit |
— | — | @@ -280,19 +297,30 @@ |
281 | 298 | $db->dataSeek( $dbres, $count - 1 ); |
282 | 299 | $last_row = $db->fetchObject( $dbres ); |
283 | 300 | $db->dataSeek( $dbres, 0 ); |
284 | | - # I don't know how to remove last element of db result list without of re-sending the select |
285 | | - # do we really need rev_deleted ??? |
| 301 | + $this->addOption( 'LIMIT', $params['limit'] ); |
286 | 302 | $selectFields = array_merge( $selectFields, |
287 | | - array( 'page_id', 'page_namespace', 'page_title', 'page_restrictions', 'page_is_redirect', 'rev_user', 'rev_text_id', 'rev_deleted', 'rev_minor_edit', 'rev_comment', 'old_text' ) |
| 303 | + array( 'page_id', |
| 304 | + 'page_restrictions', |
| 305 | + 'page_is_redirect', |
| 306 | + 'rev_user', |
| 307 | + 'rev_text_id', |
| 308 | + 'rev_deleted', |
| 309 | + 'rev_minor_edit', |
| 310 | + 'rev_comment', |
| 311 | + 'old_text' ) |
288 | 312 | ); |
| 313 | + if ( $params['pageinfo'] !== true ) { |
| 314 | + $this->addTables( 'page' ); |
| 315 | + $this->addJoinConds( |
| 316 | + array( |
| 317 | + 'page' => array( 'INNER JOIN', 'page_id=rev_page' ), |
| 318 | + ) |
| 319 | + ); |
| 320 | + $selectFields = array_merge( $selectFields, |
| 321 | + array( 'page_namespace', 'page_title', 'page_latest' ) |
| 322 | + ); |
| 323 | + } |
289 | 324 | $this->addFields( $selectFields ); |
290 | | - $this->addTables( 'page' ); |
291 | | - $this->addOption( 'LIMIT', $params['limit'] ); |
292 | | - $this->addJoinConds( |
293 | | - array( |
294 | | - 'page' => array( 'INNER JOIN', 'page_id=rev_page' ), |
295 | | - ) |
296 | | - ); |
297 | 325 | $dbres = $this->select(__METHOD__); |
298 | 326 | if ( !$this->rawOutputMode ) { |
299 | 327 | while ( $row = $db->fetchObject( $dbres ) ) { |
— | — | @@ -351,7 +379,7 @@ |
352 | 380 | $vals['textlen'] = $row->text_len; |
353 | 381 | $vals['usertext'] = $row->rev_user_text; |
354 | 382 | if ( isset( $row->page_namespace ) ) { |
355 | | - $vals['namespace'] = $row->page_namespace; |
| 383 | + $vals['namespace'] = intval( $row->page_namespace ); |
356 | 384 | } |
357 | 385 | if ( isset( $row->page_title ) ) { |
358 | 386 | $vals['title'] = $row->page_title; |
— | — | @@ -359,6 +387,9 @@ |
360 | 388 | if ( isset( $row->page_is_redirect ) ) { |
361 | 389 | $vals['redirect'] = $row->page_is_redirect; |
362 | 390 | } |
| 391 | + if ( isset( $row->page_latest ) ) { |
| 392 | + $vals['latest'] = $row->page_latest; |
| 393 | + } |
363 | 394 | return $vals; |
364 | 395 | } |
365 | 396 | |
— | — | @@ -396,6 +427,10 @@ |
397 | 428 | ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, |
398 | 429 | ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 |
399 | 430 | ), |
| 431 | + 'exclude_user' => array( |
| 432 | + ApiBase :: PARAM_TYPE => 'user' |
| 433 | + ), |
| 434 | + 'pageinfo' => false, |
400 | 435 | 'xmldump' => false, |
401 | 436 | 'rawxml' => false |
402 | 437 | ); |
— | — | @@ -407,7 +442,9 @@ |
408 | 443 | 'endid' => 'stop revision enumeration on this revid (enum)', |
409 | 444 | 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)', |
410 | 445 | 'limit' => 'limit how many revisions will be returned (enum)', |
411 | | - 'xmldump' => 'return xml dump of selected revisions instead', |
| 446 | + 'exclude_user' => 'exclude revisions created by particular user', |
| 447 | + 'pageinfo' => 'return also information about pages associated with selected revisions', |
| 448 | + 'xmldump' => 'return also xml dump of selected revisions', |
412 | 449 | 'rawxml' => 'return xml dump as raw xml' |
413 | 450 | ); |
414 | 451 | } |
— | — | @@ -422,12 +459,18 @@ |
423 | 460 | 'api.php?action=revisionhistory', |
424 | 461 | 'The same as it would look in JSON (use without fm postfix)', |
425 | 462 | 'api.php?action=revisionhistory&format=jsonfm', |
| 463 | + 'The same with associated page info (use without fm postfix)', |
| 464 | + 'api.php?action=revisionhistory&pageinfo&format=jsonfm', |
| 465 | + 'Most recently created revisions with no revisions created by WikiSyncBot', |
| 466 | + 'api.php?action=revisionhistory&exclude_user=WikiSyncBot', |
426 | 467 | 'Get first results (with continuation) of revisions with id from 20000 to 19000', |
427 | 468 | 'api.php?action=revisionhistory&startid=20000&endid=19000', |
428 | 469 | 'Get first results (with continuation) of revisions with id values from 19000 to 20000 in reverse order', |
429 | 470 | 'api.php?action=revisionhistory&startid=19000&endid=20000&dir=newer', |
430 | 471 | 'Get xml dump of first results (with continuation) of revisions with id values from 19000 (wrap)', |
431 | 472 | 'api.php?action=revisionhistory&startid=19000&dir=newer&xmldump&format=jsonfm', |
| 473 | + 'Get xml dump of first results (with continuation) of revisions with id values from 19000 (wrap) with associated page info', |
| 474 | + 'api.php?action=revisionhistory&startid=19000&dir=newer&xmldump&format=jsonfm', |
432 | 475 | 'Get xml dump of first results (no continuation) of revisions with id values from 19000 (raw xml)', |
433 | 476 | 'api.php?action=revisionhistory&startid=19000&dir=newer&xmldump&rawxml', |
434 | 477 | 'Standard api xml export in nowrap mode (just for comparsion)', |
— | — | @@ -440,7 +483,7 @@ |
441 | 484 | } |
442 | 485 | } /* end of ApiRevisionHistory class */ |
443 | 486 | |
444 | | -class ApiGetFile extends ApiQueryBase { |
| 487 | +class ApiGetFile extends ApiBase { |
445 | 488 | |
446 | 489 | var $dbkey = ''; // a dbkey name of file |
447 | 490 | var $fpath; // a filesystem path to file |
— | — | @@ -604,3 +647,88 @@ |
605 | 648 | } |
606 | 649 | |
607 | 650 | } /* end of ApiGetFile class */ |
| 651 | + |
| 652 | +class ApiSyncImport extends ApiImport { |
| 653 | + |
| 654 | + public function __construct($main, $action) { |
| 655 | + parent :: __construct($main, $action); |
| 656 | + } |
| 657 | + |
| 658 | + public function execute() { |
| 659 | + global $wgUser; |
| 660 | + if ( !$wgUser->isAllowed( 'import' ) ) { |
| 661 | + $this->dieUsageMsg( array('cantimport') ); |
| 662 | + } |
| 663 | + $params = $this->extractRequestParams(); |
| 664 | + if ( !isset( $params['token'] ) ) { |
| 665 | + $this->dieUsageMsg( array('missingparam', 'token') ); |
| 666 | + } |
| 667 | + if ( !$wgUser->matchEditToken( $params['token'] ) ) { |
| 668 | + $this->dieUsageMsg( array('sessionfailure') ); |
| 669 | + } |
| 670 | + $source = null; |
| 671 | + if ( !$wgUser->isAllowed( 'importupload' ) ) { |
| 672 | + $this->dieUsageMsg( array('cantimport-upload') ); |
| 673 | + } |
| 674 | + $source = ImportStreamSource::newFromUpload( 'xml' ); |
| 675 | + if ( $source instanceof Status ) { |
| 676 | + if ( $source->isOK() ) { |
| 677 | + $source = $source->value; |
| 678 | + } else { |
| 679 | + $this->dieUsageMsg( array('import-unknownerror', $source->getWikiText() ) ); |
| 680 | + } |
| 681 | + } elseif ( $source instanceof WikiErrorMsg ) { |
| 682 | + $this->dieUsageMsg( array_merge( array($source->getMessageKey()), $source->getMessageArgs() ) ); |
| 683 | + } elseif ( WikiError::isError( $source ) ) { |
| 684 | + // This shouldn't happen |
| 685 | + $this->dieUsageMsg( array('import-unknownerror', $source->getMessage() ) ); |
| 686 | + } |
| 687 | + $importer = new WikiImporter( $source ); |
| 688 | + $reporter = new WikiSyncImportReporter( $importer, true, '', wfMsg( 'wikisync_log_imported_by' ) ); |
| 689 | + |
| 690 | + $result = $importer->doImport(); |
| 691 | + if ( $result instanceof WikiXmlError ) { |
| 692 | + $this->dieUsageMsg( array('import-xml-error', |
| 693 | + $result->mLine, |
| 694 | + $result->mColumn, |
| 695 | + $result->mByte . $result->mContext, |
| 696 | + xml_error_string($result->mXmlError) ) ); |
| 697 | + } elseif ( WikiError::isError( $result ) ) { |
| 698 | + // This shouldn't happen |
| 699 | + $this->dieUsageMsg( array('import-unknownerror', $result->getMessage() ) ); |
| 700 | + } |
| 701 | + $resultData = $reporter->getData(); |
| 702 | + $this->getResult()->setIndexedTagName( $resultData, 'page' ); |
| 703 | + $this->getResult()->addValue( null, $this->getModuleName(), $resultData ); |
| 704 | + } |
| 705 | + |
| 706 | + public function getAllowedParams() { |
| 707 | + global $wgImportSources; |
| 708 | + return array ( |
| 709 | + 'token' => null, |
| 710 | + 'xml' => null, |
| 711 | + ); |
| 712 | + } |
| 713 | + |
| 714 | + public function getParamDescription() { |
| 715 | + return array ( |
| 716 | + 'token' => 'Import token obtained through prop=info', |
| 717 | + 'xml' => 'Uploaded XML file', |
| 718 | + ); |
| 719 | + } |
| 720 | + |
| 721 | + public function getDescription() { |
| 722 | + return array ( |
| 723 | + 'Import a page from XML file, with suppressing of creation of informational null revisions' |
| 724 | + ); |
| 725 | + } |
| 726 | + |
| 727 | + protected function getExamples() { |
| 728 | + return array(); |
| 729 | + } |
| 730 | + |
| 731 | + public function getVersion() { |
| 732 | + return __CLASS__ . ': $Id$'; |
| 733 | + } |
| 734 | + |
| 735 | +} /* end of ApiSyncImport class */ |
Index: trunk/extensions/WikiSync/WikiSyncPage.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -61,7 +61,7 @@ |
62 | 62 | ), |
63 | 63 | array( '__tag'=>'tr', |
64 | 64 | array( '__tag'=>'td', wfMsgHtml( 'wikisync_remote_wiki_user' ) ), |
65 | | - array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'text', 'name'=>'remote_wiki_user', 'value'=>$remote_wiki_user ) ) |
| 65 | + array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'text', 'name'=>'remote_wiki_user', 'value'=>$remote_wiki_user, 'disabled'=>'' ) ) |
66 | 66 | ), |
67 | 67 | array( '__tag'=>'tr', |
68 | 68 | array( '__tag'=>'td', wfMsgHtml( 'wikisync_remote_wiki_pass' ) ), |
Index: trunk/extensions/WikiSync/WikiSyncClient.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -260,7 +260,7 @@ |
261 | 261 | return true; |
262 | 262 | } |
263 | 263 | |
264 | | - /* |
| 264 | + /** |
265 | 265 | * called via AJAX to perform remote login via the API |
266 | 266 | * @param $args[0] : remote wiki root |
267 | 267 | * @param $args[1] : remote wiki user |
— | — | @@ -347,22 +347,23 @@ |
348 | 348 | return $json_result->getResult( $response->login->result ); |
349 | 349 | } |
350 | 350 | |
351 | | - /* |
| 351 | + /** |
352 | 352 | * Access to local API |
353 | 353 | * @param $api_params string in JSON format {key:val} or PHP array ($key=>val) |
354 | 354 | * @return result of local API query |
355 | 355 | */ |
356 | 356 | static function localAPIwrap( $api_params ) { |
| 357 | + global $wgEnableWriteAPI; |
357 | 358 | if ( is_string( $api_params ) ) { |
358 | 359 | $api_params = FormatJson::decode( $api_params, true ); |
359 | 360 | } |
360 | 361 | $req = new FauxRequest( $api_params ); |
361 | | - $api = new ApiMain( $req ); |
| 362 | + $api = new ApiMain( $req, $wgEnableWriteAPI ); |
362 | 363 | $api->execute(); |
363 | 364 | return $api->getResultData(); |
364 | 365 | } |
365 | 366 | |
366 | | - /* |
| 367 | + /** |
367 | 368 | * called via AJAX to perform API request on local wiki (HTTP GET) |
368 | 369 | * @param $args[0] : API query parameters line in JSON format {'key':'val'} or PHP associative array |
369 | 370 | * @param $args[1] : optional, type of result: |
— | — | @@ -402,7 +403,7 @@ |
403 | 404 | return $json_result->getResult(); |
404 | 405 | } |
405 | 406 | |
406 | | - /* |
| 407 | + /** |
407 | 408 | * called via AJAX to perform API request on remote wiki (HTTP GET/POST) |
408 | 409 | * @param $args[0] : remote context in JSON format, keys |
409 | 410 | * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val } |
— | — | @@ -549,7 +550,8 @@ |
550 | 551 | } |
551 | 552 | fclose( $fp ); |
552 | 553 | if ( self::$directionToLocal ) { |
553 | | - # suppress "pear mail" smtp bugs in EmailNotification::actuallyNotifyOnPageChange() |
| 554 | + # suppress "pear mail" possible smtp fatal errors |
| 555 | + # in EmailNotification::actuallyNotifyOnPageChange() |
554 | 556 | $wgSMTP = false; |
555 | 557 | $wgEnableEmail = false; |
556 | 558 | $wgEnableUserEmail = false; |
— | — | @@ -565,25 +567,46 @@ |
566 | 568 | return $json_result->getResult( 'no_import_rights' ); |
567 | 569 | } |
568 | 570 | $source = ImportStreamSource::newFromFile( $fname ); |
569 | | - if ( !$source->isOK() ) { |
| 571 | + $err_msg = null; |
| 572 | + if ( $source instanceof Status ) { |
| 573 | + if ( $source->isOK() ) { |
| 574 | + $source = $source->value; |
| 575 | + } else { |
| 576 | + $err_msg = $source->getWikiText(); |
| 577 | + } |
| 578 | + } elseif ( $source instanceof WikiErrorMsg || WikiError::isError( $source ) ) { |
| 579 | + $err_msg = $source->getMessage(); |
| 580 | + } |
| 581 | + if ( $err_msg !== null ) { |
570 | 582 | @unlink( $fname ); |
571 | | - return $json_result->getResult( 'import', $source->getWikiText() ); |
| 583 | + return $json_result->getResult( 'import', $err_msg ); |
572 | 584 | } |
573 | | - $importer = new WikiImporter( $source->value ); |
574 | | - $reporter = new WikiSyncImportReporter( $importer, true, '', wfMsg( 'wikisync_log_imported_by' ) ); |
575 | | - try { |
576 | | - $importer->doImport(); |
577 | | - } catch ( MWException $e ) { |
578 | | - return $json_result->getResult( 'import', $e->getMessage() ); |
| 585 | + $importer = new WikiImporter( $source ); |
| 586 | + $reporter = new WikiSyncImportReporter( $importer, false, '', wfMsg( 'wikisync_log_imported_by' ) ); |
| 587 | + $result = $importer->doImport(); |
| 588 | + @fclose( $source->mHandle ); |
| 589 | + if ( !WikiSyncSetup::$debug_mode ) { |
| 590 | + @unlink( $fname ); |
579 | 591 | } |
580 | | - @fclose( $source->value->mHandle ); |
581 | | - @unlink( $fname ); |
| 592 | + if ( $result instanceof WikiXmlError ) { |
| 593 | + $r = |
| 594 | + array( |
| 595 | + 'line' => $result->mLine, |
| 596 | + 'column' => $result->mColumn, |
| 597 | + 'context' => $result->mByte . $result->mContext, |
| 598 | + 'xmlerror' => xml_error_string( $result->mXmlError ) |
| 599 | + ); |
| 600 | + $json_result->append( $r ); |
| 601 | + return $json_result->getResult( 'import', $result->getMessage() ); |
| 602 | + } elseif ( WikiError::isError( $result ) ) { |
| 603 | + return $json_result->getResult( 'import', $source->getMessage() ); |
| 604 | + } |
582 | 605 | $resultData = $reporter->getData(); |
583 | 606 | $json_result->setStatus( '1' ); // API success |
584 | 607 | return $json_result->getResult(); |
585 | 608 | } else { |
586 | 609 | $APIparams = array( |
587 | | - 'action' => 'import', |
| 610 | + 'action' => 'syncimport', |
588 | 611 | 'format' => 'json', |
589 | 612 | 'token' => $dstImportToken, |
590 | 613 | ); |
— | — | @@ -597,7 +620,7 @@ |
598 | 621 | } |
599 | 622 | } |
600 | 623 | |
601 | | - /* |
| 624 | + /** |
602 | 625 | * called via AJAX to perform synchronization of one XML chunk from source to destination wiki |
603 | 626 | * @param $args[0] : remote context in JSON format, keys |
604 | 627 | * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val } |
— | — | @@ -613,6 +636,7 @@ |
614 | 637 | $APIparams = array( |
615 | 638 | 'action' => 'revisionhistory', |
616 | 639 | 'format' => 'json', |
| 640 | + 'exclude_user' => WikiSyncSetup::$remote_wiki_user, |
617 | 641 | 'xmldump' => '', |
618 | 642 | 'dir' => 'newer', |
619 | 643 | 'startid' => $client_params['startid'], |
— | — | @@ -623,7 +647,7 @@ |
624 | 648 | $result['ws_msg'] = 'source: ' . $result['ws_msg'] . ' (' . __METHOD__ . ')'; |
625 | 649 | return FormatJson::encode( $result ); |
626 | 650 | } |
627 | | - # collect the file titles existed in current chunk's revisions |
| 651 | + # collect the file titles that exist in current chunk's revisions |
628 | 652 | $files = array(); |
629 | 653 | foreach ( $result['query']['revisionhistory'] as $entry ) { |
630 | 654 | if ( $entry['namespace'] == NS_FILE && $entry['redirect'] === '0' ) { |
— | — | @@ -658,7 +682,7 @@ |
659 | 683 | return array( 'titles'=>$titles, 'sha1'=>$sha1, 'sizes'=>$sizes, 'timestamps'=>$timestamps ); |
660 | 684 | } |
661 | 685 | |
662 | | - /* |
| 686 | + /** |
663 | 687 | * called via AJAX to compare source and destination list of files |
664 | 688 | * @param $args[0] : remote context in JSON format, keys |
665 | 689 | * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val } |
— | — | @@ -726,7 +750,7 @@ |
727 | 751 | return $wgTmpDirectory . '/' . $snoopy->logintoken . '_' . $snoopy->sessionid . '_' . $chunk_fname; |
728 | 752 | } |
729 | 753 | |
730 | | - /* |
| 754 | + /** |
731 | 755 | * called via AJAX to transfer one chunk of file from source to destination wiki |
732 | 756 | * @param $args[0] : remote context in JSON format, keys |
733 | 757 | * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val } |
— | — | @@ -833,8 +857,8 @@ |
834 | 858 | } |
835 | 859 | } |
836 | 860 | |
837 | | - /* |
838 | | - * called via AJAX to transfer one chunk of file from source to destination wiki |
| 861 | + /** |
| 862 | + * called via AJAX to store previousely uploaded temporary file into local repository |
839 | 863 | * @param $args[0] : remote context in JSON format, keys |
840 | 864 | * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val } |
841 | 865 | * @param $args[1] : client parameters line in JSON format {'key':'val'} |
— | — | @@ -871,7 +895,7 @@ |
872 | 896 | if ( !$status->isGood() ) { |
873 | 897 | return $json_result->getResult( 'upload', $status->getWikiText() ); |
874 | 898 | } |
875 | | - if ( !unlink( $chunk_fpath ) ) { |
| 899 | + if ( !@unlink( $chunk_fpath ) ) { |
876 | 900 | return $json_result->getResult( 'chunk_file', 'Cannot unlink temporary file ' . $chunk_fpath . ' in ' . __METHOD__ ); |
877 | 901 | } |
878 | 902 | // API success |
Index: trunk/extensions/WikiSync/WikiSync.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -108,10 +108,17 @@ |
109 | 109 | class WikiSyncSetup { |
110 | 110 | |
111 | 111 | const COOKIE_EXPIRE_TIME = 2592000; // 60 * 60 * 24 * 30; see also WikiSync.js, WikiSync.cookieExpireTime |
| 112 | + # please never change next value in between the synchronizations |
| 113 | + # otherwise null revisions created by ImportReporter::reportPage |
| 114 | + # will not be skipped, thus fake synchronizations might occur |
| 115 | + const WIKISYNC_BOT_NAME = 'WikiSyncBot'; |
112 | 116 | |
| 117 | + static $remote_wiki_user = self::WIKISYNC_BOT_NAME; |
| 118 | + static $report_null_revisions = false; |
113 | 119 | # {{{ changable in LocalSettings.php : |
| 120 | + # debug mode (do not erase temporary XML dump files) |
| 121 | + static $debug_mode = false; |
114 | 122 | static $remote_wiki_root = 'http://www.mediawiki.org/w'; |
115 | | - static $remote_wiki_user = ''; |
116 | 123 | static $proxy_address = ''; # 'http://10.0.0.78:3128'; |
117 | 124 | # which user groups can synchronize from remote to local |
118 | 125 | static $rtl_access_groups = array( 'user' ); |
— | — | @@ -126,7 +133,7 @@ |
127 | 134 | static $proxy_pass = ''; |
128 | 135 | # }}} |
129 | 136 | |
130 | | - static $version = '0.3.1'; // version of extension |
| 137 | + static $version = '0.3.2'; // version of extension |
131 | 138 | static $ExtDir; // filesys path with windows path fix |
132 | 139 | static $ScriptPath; // apache virtual path |
133 | 140 | |
— | — | @@ -181,11 +188,13 @@ |
182 | 189 | $wgAutoloadClasses['ApiWikiSync'] = |
183 | 190 | $wgAutoloadClasses['ApiRevisionHistory'] = |
184 | 191 | $wgAutoloadClasses['ApiFindSimilarRev'] = |
185 | | - $wgAutoloadClasses['ApiGetFile'] = self::$ExtDir . '/WikiSyncApi.php'; |
| 192 | + $wgAutoloadClasses['ApiGetFile'] = |
| 193 | + $wgAutoloadClasses['ApiSyncImport'] = self::$ExtDir . '/WikiSyncApi.php'; |
186 | 194 | |
187 | 195 | $wgAPIModules['revisionhistory'] = 'ApiRevisionHistory'; |
188 | 196 | $wgAPIModules['similarrev'] = 'ApiFindSimilarRev'; |
189 | 197 | $wgAPIModules['getfile'] = 'ApiGetFile'; |
| 198 | + $wgAPIModules['syncimport'] = 'ApiSyncImport'; |
190 | 199 | |
191 | 200 | $wgAjaxExportList[] = 'WikiSyncClient::remoteLogin'; |
192 | 201 | $wgAjaxExportList[] = 'WikiSyncClient::localAPIget'; |
— | — | @@ -273,15 +282,15 @@ |
274 | 283 | } |
275 | 284 | |
276 | 285 | static function checkUserMembership( $groups ) { |
277 | | - global $wgUser, $wgLang; |
278 | | - $ug = $wgUser->getEffectiveGroups(); |
279 | | - if ( !$wgUser->isAnon() && !in_array( 'user', $ug ) ) { |
| 286 | + global $wgLang; |
| 287 | + $ug = self::$user->getEffectiveGroups(); |
| 288 | + if ( !self::$user->isAnon() && !in_array( 'user', $ug ) ) { |
280 | 289 | $ug[] = 'user'; |
281 | 290 | } |
282 | 291 | if ( array_intersect( $groups, $ug ) ) { |
283 | 292 | return true; |
284 | 293 | } |
285 | | - return wfMsg( 'wikisync_api_result_noaccess', $wgLang->commaList( $groups ) ); |
| 294 | + return wfMsgExt( 'wikisync_api_result_noaccess', array( 'parsemag' ), $wgLang->commaList( $groups ), count( $groups ) ); |
286 | 295 | } |
287 | 296 | |
288 | 297 | /** |
— | — | @@ -294,12 +303,15 @@ |
295 | 304 | * @return true, when the current user has access to synchronization; |
296 | 305 | * string error message, when the current user has no access |
297 | 306 | */ |
298 | | - static function initUser( $direction = null) { |
| 307 | + static function initUser( $direction = null ) { |
299 | 308 | global $wgUser, $wgRequest; |
300 | 309 | self::$user = is_object( $wgUser ) ? $wgUser : new User(); |
301 | 310 | self::$response = $wgRequest->response(); |
| 311 | + self::$cookie_prefix = 'WikiSync_' . md5( self::$user->getName() ) . '_'; |
302 | 312 | wfLoadExtensionMessages( 'WikiSync' ); |
303 | | - self::$cookie_prefix = 'WikiSync_' . md5( self::$user->getName() ) . '_'; |
| 313 | + if ( self::$user->getName() !== self::WIKISYNC_BOT_NAME ) { |
| 314 | + return wfMsg( 'wikisync_unsupported_user', self::WIKISYNC_BOT_NAME ); |
| 315 | + } |
304 | 316 | if ( $direction === true ) { |
305 | 317 | return self::checkUserMembership( self::$rtl_access_groups ); |
306 | 318 | } elseif ( $direction === false ) { |
Index: trunk/extensions/WikiSync/WikiSyncQXML.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.3.1 |
| 32 | + * @version 0.3.2 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
Index: trunk/extensions/WikiSync/README |
— | — | @@ -1,4 +1,4 @@ |
2 | | -MediaWiki extension WikiSync, version 0.3.1 |
| 2 | +MediaWiki extension WikiSync, version 0.3.2 |
3 | 3 | |
4 | 4 | WikiSync allows an AJAX-based synchronization of revisions and files between |
5 | 5 | global wiki site and it's local mirror. Files download can optionally be disabled, |
Index: trunk/extensions/WikiSync/WikiSync.i18n.php |
— | — | @@ -35,9 +35,10 @@ |
36 | 36 | 'wikisync_apply_button' => 'Apply', |
37 | 37 | 'wikisync_log_imported_by' => 'Imported by [[Special:WikiSync|WikiSync]]', |
38 | 38 | 'wikisync_log_uploaded_by' => 'Uploaded by [[Special:WikiSync|WikiSync]]', |
| 39 | + 'wikisync_unsupported_user' => 'Only a special bot user $1 can perform wiki synchronizations. Please login as $1. Do not change $1 name in between the synchronizations, otherwise informational null revisions will not be properly skipped (see [http://www.mediawiki.org/wiki/Extension:WikiSync] for more info).', |
39 | 40 | 'wikisync_api_result_unknown_action' => 'Unknown API action', |
40 | 41 | 'wikisync_api_result_exception' => 'Exception occured in local API call', |
41 | | - 'wikisync_api_result_noaccess' => 'Only members of the following groups can perform this action: $1', |
| 42 | + 'wikisync_api_result_noaccess' => 'Only members of the following {{PLURAL:$2|group|groups}} can perform this action: $1', |
42 | 43 | 'wikisync_api_result_invalid_parameter' => 'Invalid value of parameter', |
43 | 44 | 'wikisync_api_result_http' => 'HTTP error while querying data from remote API', |
44 | 45 | 'wikisync_api_result_Unsupported' => 'Your version of MediaWiki is unsupported (less than 1.15)', |
— | — | @@ -1232,6 +1233,7 @@ |
1233 | 1234 | 'wikisync_apply_button' => 'Применить', |
1234 | 1235 | 'wikisync_log_imported_by' => 'Импортировано с помощью [[Special:WikiSync]]', |
1235 | 1236 | 'wikisync_log_uploaded_by' => 'Загружено с помощью [[Special:WikiSync]]', |
| 1237 | + 'wikisync_unsupported_user' => 'Только специальный бот под именем $1 может синхронизировать вики сайты. Пожалуйста зайдите как пользователь $1. Не изменяйте имя учетной записи $1 между синхронизациями, в противном случае информационные нулевые ревизии не будут правильно пропущены (см. [http://www.mediawiki.org/wiki/Extension:WikiSync] для более подробной информации).', |
1236 | 1238 | 'wikisync_api_result_unknown_action' => 'Неизвестное действие (action) API', |
1237 | 1239 | 'wikisync_api_result_exception' => 'Произошло исключение в местном API-вызове', |
1238 | 1240 | 'wikisync_api_result_noaccess' => 'Только пользователи, входящие в нижеперечисленные группы, могут выполнять указанное действие: ($1)', |
Index: trunk/extensions/WikiSync/INSTALL |
— | — | @@ -1,4 +1,4 @@ |
2 | | -MediaWiki extension WikiSync, version 0.3.1 |
| 2 | +MediaWiki extension WikiSync, version 0.3.2 |
3 | 3 | |
4 | 4 | * download the latest available version and extract it to your wiki extension directory. |
5 | 5 | * add the following line to LocalSettings.php |
Index: trunk/extensions/WikiSync/WikiSync.js |
— | — | @@ -27,7 +27,7 @@ |
28 | 28 | * * Add this line at the end of your LocalSettings.php file : |
29 | 29 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
30 | 30 | * |
31 | | - * @version 0.3.1 |
| 31 | + * @version 0.3.2 |
32 | 32 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
33 | 33 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
34 | 34 | * @addtogroup Extensions |
— | — | @@ -127,6 +127,9 @@ |
128 | 128 | srcWikiRoot : '', |
129 | 129 | dstWikiRoot : '', |
130 | 130 | |
| 131 | + // remote login form DOM node |
| 132 | + loginForm : null, |
| 133 | + |
131 | 134 | // revision ids of source wiki (dichotomy search) |
132 | 135 | // note that all of these should be numbers, while API/callback parameters should be string |
133 | 136 | srcFirstId : null, // very first revid |
— | — | @@ -153,7 +156,7 @@ |
154 | 157 | syncFiles : false, // true, when files should be synched as well |
155 | 158 | fileList : [], // list of files in format of {'title':,'size':,'timestamp':} |
156 | 159 | fileListIdx : 0, // current index of the list of files |
157 | | - // currently _accumulated_ size of all files in fileList (counted in chunks, file by fule) |
| 160 | + // currently _accumulated_ size of all files in fileList (counted in chunks, file by file) |
158 | 161 | fileListSize : 0, |
159 | 162 | // currently accumulated offset (which is also a current size) of currently transferred file |
160 | 163 | currFileOffset : 0, |
— | — | @@ -824,8 +827,8 @@ |
825 | 828 | case 'xml_chunk' : |
826 | 829 | var clientParams = { |
827 | 830 | 'startid' : parseInt( operation.startid ), |
828 | | - // please do not use larger value because it can cause memory exhaust errors and php timeouts |
829 | | - 'limit' : 10, |
| 831 | + // please do not use larger value because it may cause memory exhaust errors and php timeouts |
| 832 | + 'limit' : (typeof operation.limit === 'undefined') ? 10 : parseInt( operation.limit ), |
830 | 833 | 'dst_import_token' : this.dstImportToken |
831 | 834 | }; |
832 | 835 | var nextOp = { |
— | — | @@ -862,12 +865,12 @@ |
863 | 866 | return; |
864 | 867 | } |
865 | 868 | // try to synchronize next chunk |
866 | | - var params = { |
| 869 | + var nextOp = { |
867 | 870 | 'fname' : 'synchronize', |
868 | 871 | 'opcode' : 'xml_chunk', |
869 | 872 | 'startid' : '' + this.xmlContinueStartId |
870 | 873 | }; |
871 | | - this.synchronize( params ); |
| 874 | + this.synchronize( nextOp ); |
872 | 875 | return; |
873 | 876 | case 'success' : |
874 | 877 | this.filesPercents.setVisibility( false ); |
— | — | @@ -972,6 +975,7 @@ |
973 | 976 | case 'start' : |
974 | 977 | var APIparams = { |
975 | 978 | 'action' : 'revisionhistory', |
| 979 | + 'exclude_user' : 'WikiSyncBot', |
976 | 980 | 'format' : 'json', |
977 | 981 | 'dir' : 'older', |
978 | 982 | 'limit' : '1' |
— | — | @@ -1045,6 +1049,7 @@ |
1046 | 1050 | } |
1047 | 1051 | // disable all buttons |
1048 | 1052 | this.setButtons( false ); |
| 1053 | + this.syncFiles = this.loginForm.ws_sync_files.checked; |
1049 | 1054 | WikiSyncScheduler.reset(); |
1050 | 1055 | this.schedulerLogger.log( |
1051 | 1056 | this.formatMessage( |
— | — | @@ -1073,10 +1078,10 @@ |
1074 | 1079 | this.filesPercents.setVisibility( false ); |
1075 | 1080 | this.showIframe( '' ); |
1076 | 1081 | this.errorDefaultAction(); |
1077 | | - var loginForm = document.getElementById( 'remote_login_form' ); |
| 1082 | + this.loginForm = document.getElementById( 'remote_login_form' ); |
1078 | 1083 | // {{{ restore remote login / password cookies to login form, if any |
1079 | | - WikiSyncUtils.cookieToInput( 'ruser', loginForm.remote_wiki_user ); |
1080 | | - var rpass = WikiSyncUtils.cookieToInput( 'rpass', loginForm.remote_wiki_pass ); |
| 1084 | + WikiSyncUtils.cookieToInput( 'ruser', this.loginForm.remote_wiki_user ); |
| 1085 | + var rpass = WikiSyncUtils.cookieToInput( 'rpass', this.loginForm.remote_wiki_pass ); |
1081 | 1086 | // }}} |
1082 | 1087 | // {{{ restore scheduler cookies to scheduler form, if any |
1083 | 1088 | var schedulerForm = document.getElementById( 'scheduler_form' ); |
— | — | @@ -1085,9 +1090,9 @@ |
1086 | 1091 | WikiSyncUtils.cookieToInput( 'auto_sync_time_interval', schedulerForm.ws_auto_sync_time_interval ); |
1087 | 1092 | // }}} |
1088 | 1093 | if ( rpass !== null ) { |
1089 | | - loginForm.ws_store_password.checked = true; |
| 1094 | + this.loginForm.ws_store_password.checked = true; |
1090 | 1095 | // try to autologin |
1091 | | - this.submitRemoteLogin( loginForm ); |
| 1096 | + this.submitRemoteLogin( this.loginForm ); |
1092 | 1097 | } |
1093 | 1098 | WikiSyncScheduler.setup( schedulerForm ); |
1094 | 1099 | window.setTimeout( function() { WikiSyncScheduler.poll(); }, this.pollTimeout ); |